mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 09:00:27 +00:00
Feat private key passphrase for sftpfs (#855)
Signed-off-by: Sunil Keswani <sunilke@zeta.tech>
This commit is contained in:
parent
f6b11c2d01
commit
84e3132ed1
8 changed files with 73 additions and 12 deletions
|
@ -67,6 +67,7 @@ func updateFolder(w http.ResponseWriter, r *http.Request) {
|
|||
currentCryptoPassphrase := folder.FsConfig.CryptConfig.Passphrase
|
||||
currentSFTPPassword := folder.FsConfig.SFTPConfig.Password
|
||||
currentSFTPKey := folder.FsConfig.SFTPConfig.PrivateKey
|
||||
currentSFTPKeyPassphrase := folder.FsConfig.SFTPConfig.Passphrase
|
||||
|
||||
folder.FsConfig.S3Config = vfs.S3FsConfig{}
|
||||
folder.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
|
||||
|
@ -82,7 +83,7 @@ func updateFolder(w http.ResponseWriter, r *http.Request) {
|
|||
folder.Name = name
|
||||
folder.FsConfig.SetEmptySecretsIfNil()
|
||||
updateEncryptedSecrets(&folder.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl, currentGCSCredentials,
|
||||
currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey)
|
||||
currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey, currentSFTPKeyPassphrase)
|
||||
err = dataprovider.UpdateFolder(&folder, users, groups, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
|
|
|
@ -72,6 +72,7 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
|
|||
currentCryptoPassphrase := group.UserSettings.FsConfig.CryptConfig.Passphrase
|
||||
currentSFTPPassword := group.UserSettings.FsConfig.SFTPConfig.Password
|
||||
currentSFTPKey := group.UserSettings.FsConfig.SFTPConfig.PrivateKey
|
||||
currentSFTPKeyPassphrase := group.UserSettings.FsConfig.SFTPConfig.Passphrase
|
||||
|
||||
group.UserSettings.FsConfig.S3Config = vfs.S3FsConfig{}
|
||||
group.UserSettings.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
|
||||
|
@ -87,7 +88,7 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
|
|||
group.Name = name
|
||||
group.UserSettings.FsConfig.SetEmptySecretsIfNil()
|
||||
updateEncryptedSecrets(&group.UserSettings.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl,
|
||||
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey)
|
||||
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey, currentSFTPKeyPassphrase)
|
||||
err = dataprovider.UpdateGroup(&group, users, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
|
|
|
@ -134,6 +134,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
|||
currentCryptoPassphrase := user.FsConfig.CryptConfig.Passphrase
|
||||
currentSFTPPassword := user.FsConfig.SFTPConfig.Password
|
||||
currentSFTPKey := user.FsConfig.SFTPConfig.PrivateKey
|
||||
currentSFTPKeyPassphrase := user.FsConfig.SFTPConfig.Passphrase
|
||||
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.FsConfig.S3Config = vfs.S3FsConfig{}
|
||||
|
@ -159,7 +160,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
|||
user.Permissions = currentPermissions
|
||||
}
|
||||
updateEncryptedSecrets(&user.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl,
|
||||
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey)
|
||||
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey, currentSFTPKeyPassphrase)
|
||||
err = dataprovider.UpdateUser(&user, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
|
@ -231,7 +232,7 @@ func disconnectUser(username string) {
|
|||
}
|
||||
|
||||
func updateEncryptedSecrets(fsConfig *vfs.Filesystem, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl,
|
||||
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey *kms.Secret) {
|
||||
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey *kms.Secret, currentSFTPKeyPassphrase *kms.Secret) {
|
||||
// we use the new access secret if plain or empty, otherwise the old value
|
||||
switch fsConfig.Provider {
|
||||
case sdk.S3FilesystemProvider:
|
||||
|
@ -262,5 +263,8 @@ func updateEncryptedSecrets(fsConfig *vfs.Filesystem, currentS3AccessSecret, cur
|
|||
if fsConfig.SFTPConfig.PrivateKey.IsNotPlainAndNotEmpty() {
|
||||
fsConfig.SFTPConfig.PrivateKey = currentSFTPKey
|
||||
}
|
||||
if fsConfig.SFTPConfig.Passphrase.IsNotPlainAndNotEmpty() {
|
||||
fsConfig.SFTPConfig.Passphrase = currentSFTPKeyPassphrase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1219,6 +1219,7 @@ func getSFTPConfig(r *http.Request) (vfs.SFTPFsConfig, error) {
|
|||
config.Username = r.Form.Get("sftp_username")
|
||||
config.Password = getSecretFromFormField(r, "sftp_password")
|
||||
config.PrivateKey = getSecretFromFormField(r, "sftp_private_key")
|
||||
config.Passphrase = getSecretFromFormField(r, "sftp_passphrase")
|
||||
fingerprintsFormValue := r.Form.Get("sftp_fingerprints")
|
||||
config.Fingerprints = getSliceFromDelimitedValues(fingerprintsFormValue, "\n")
|
||||
config.Prefix = r.Form.Get("sftp_prefix")
|
||||
|
@ -2202,7 +2203,7 @@ func (s *httpdServer) handleWebUpdateUserPost(w http.ResponseWriter, r *http.Req
|
|||
}
|
||||
updateEncryptedSecrets(&updatedUser.FsConfig, user.FsConfig.S3Config.AccessSecret, user.FsConfig.AzBlobConfig.AccountKey,
|
||||
user.FsConfig.AzBlobConfig.SASURL, user.FsConfig.GCSConfig.Credentials, user.FsConfig.CryptConfig.Passphrase,
|
||||
user.FsConfig.SFTPConfig.Password, user.FsConfig.SFTPConfig.PrivateKey)
|
||||
user.FsConfig.SFTPConfig.Password, user.FsConfig.SFTPConfig.PrivateKey, user.FsConfig.SFTPConfig.Passphrase)
|
||||
|
||||
updatedUser = getUserFromTemplate(updatedUser, userTemplateFields{
|
||||
Username: updatedUser.Username,
|
||||
|
@ -2336,7 +2337,7 @@ func (s *httpdServer) handleWebUpdateFolderPost(w http.ResponseWriter, r *http.R
|
|||
updatedFolder.FsConfig.SetEmptySecretsIfNil()
|
||||
updateEncryptedSecrets(&updatedFolder.FsConfig, folder.FsConfig.S3Config.AccessSecret, folder.FsConfig.AzBlobConfig.AccountKey,
|
||||
folder.FsConfig.AzBlobConfig.SASURL, folder.FsConfig.GCSConfig.Credentials, folder.FsConfig.CryptConfig.Passphrase,
|
||||
folder.FsConfig.SFTPConfig.Password, folder.FsConfig.SFTPConfig.PrivateKey)
|
||||
folder.FsConfig.SFTPConfig.Password, folder.FsConfig.SFTPConfig.PrivateKey, folder.FsConfig.SFTPConfig.Passphrase)
|
||||
|
||||
updatedFolder = getFolderFromTemplate(updatedFolder, updatedFolder.Name)
|
||||
|
||||
|
@ -2500,7 +2501,7 @@ func (s *httpdServer) handleWebUpdateGroupPost(w http.ResponseWriter, r *http.Re
|
|||
updateEncryptedSecrets(&updatedGroup.UserSettings.FsConfig, group.UserSettings.FsConfig.S3Config.AccessSecret,
|
||||
group.UserSettings.FsConfig.AzBlobConfig.AccountKey, group.UserSettings.FsConfig.AzBlobConfig.SASURL,
|
||||
group.UserSettings.FsConfig.GCSConfig.Credentials, group.UserSettings.FsConfig.CryptConfig.Passphrase,
|
||||
group.UserSettings.FsConfig.SFTPConfig.Password, group.UserSettings.FsConfig.SFTPConfig.PrivateKey)
|
||||
group.UserSettings.FsConfig.SFTPConfig.Password, group.UserSettings.FsConfig.SFTPConfig.PrivateKey, group.UserSettings.FsConfig.SFTPConfig.Passphrase)
|
||||
|
||||
err = dataprovider.UpdateGroup(&updatedGroup, group.Users, claims.Username, ipAddr)
|
||||
if err != nil {
|
||||
|
|
|
@ -424,6 +424,17 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row fsconfig fsconfig-sftpfs">
|
||||
<label for="idSFTPPassphrase" class="col-sm-2 col-form-label">Passphrase</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idSFTPPassphrase" name="sftp_passphrase" placeholder=""
|
||||
value="{{if .SFTPConfig.Passphrase.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.SFTPConfig.Passphrase.GetPayload}}{{end}}" maxlength="255" aria-describedby="SFTPPassphraseHelpBlock">
|
||||
<small id="SFTPPassphraseHelpBlock" class="form-text text-muted">
|
||||
A passphrase is a word or phrase that protects private key files,its used in case of encrypted private keys.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row fsconfig fsconfig-sftpfs">
|
||||
<label for="idSFTPFingerprints" class="col-sm-2 col-form-label">Fingerprints</label>
|
||||
<div class="col-sm-10">
|
||||
|
|
|
@ -26,6 +26,7 @@ func (f *Filesystem) SetEmptySecrets() {
|
|||
f.CryptConfig.Passphrase = kms.NewEmptySecret()
|
||||
f.SFTPConfig.Password = kms.NewEmptySecret()
|
||||
f.SFTPConfig.PrivateKey = kms.NewEmptySecret()
|
||||
f.SFTPConfig.Passphrase = kms.NewEmptySecret()
|
||||
}
|
||||
|
||||
// SetEmptySecretsIfNil sets the secrets to empty if nil
|
||||
|
@ -51,6 +52,9 @@ func (f *Filesystem) SetEmptySecretsIfNil() {
|
|||
if f.SFTPConfig.PrivateKey == nil {
|
||||
f.SFTPConfig.PrivateKey = kms.NewEmptySecret()
|
||||
}
|
||||
if f.SFTPConfig.Passphrase == nil {
|
||||
f.SFTPConfig.Passphrase = kms.NewEmptySecret()
|
||||
}
|
||||
}
|
||||
|
||||
// SetNilSecretsIfEmpty set the secrets to nil if empty.
|
||||
|
@ -78,6 +82,9 @@ func (f *Filesystem) SetNilSecretsIfEmpty() {
|
|||
if f.SFTPConfig.PrivateKey != nil && f.SFTPConfig.PrivateKey.IsEmpty() {
|
||||
f.SFTPConfig.PrivateKey = nil
|
||||
}
|
||||
if f.SFTPConfig.Passphrase != nil && f.SFTPConfig.Passphrase.IsEmpty() {
|
||||
f.SFTPConfig.Passphrase = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsEqual returns true if the fs is equal to other
|
||||
|
@ -191,6 +198,9 @@ func (f *Filesystem) HasRedactedSecret() bool {
|
|||
if f.SFTPConfig.PrivateKey.IsRedacted() {
|
||||
return true
|
||||
}
|
||||
if f.SFTPConfig.Passphrase.IsRedacted() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -276,6 +286,7 @@ func (f *Filesystem) GetACopy() Filesystem {
|
|||
},
|
||||
Password: f.SFTPConfig.Password.Clone(),
|
||||
PrivateKey: f.SFTPConfig.PrivateKey.Clone(),
|
||||
Passphrase: f.SFTPConfig.Passphrase.Clone(),
|
||||
},
|
||||
}
|
||||
if len(f.SFTPConfig.Fingerprints) > 0 {
|
||||
|
|
|
@ -168,6 +168,9 @@ func (v *BaseVirtualFolder) HasRedactedSecret() bool {
|
|||
if v.FsConfig.SFTPConfig.PrivateKey.IsRedacted() {
|
||||
return true
|
||||
}
|
||||
if v.FsConfig.SFTPConfig.Passphrase.IsRedacted() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ type SFTPFsConfig struct {
|
|||
Password *kms.Secret `json:"password,omitempty"`
|
||||
PrivateKey *kms.Secret `json:"private_key,omitempty"`
|
||||
forbiddenSelfUsernames []string `json:"-"`
|
||||
Passphrase *kms.Secret `json:"passphrase,omitempty"`
|
||||
}
|
||||
|
||||
// HideConfidentialData hides confidential data
|
||||
|
@ -51,6 +52,9 @@ func (c *SFTPFsConfig) HideConfidentialData() {
|
|||
if c.PrivateKey != nil {
|
||||
c.PrivateKey.Hide()
|
||||
}
|
||||
if c.Passphrase != nil {
|
||||
c.Passphrase.Hide()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SFTPFsConfig) isEqual(other *SFTPFsConfig) bool {
|
||||
|
@ -82,6 +86,9 @@ func (c *SFTPFsConfig) isEqual(other *SFTPFsConfig) bool {
|
|||
if !c.Password.IsEqual(other.Password) {
|
||||
return false
|
||||
}
|
||||
if !c.Passphrase.IsEqual(other.Passphrase) {
|
||||
return false
|
||||
}
|
||||
return c.PrivateKey.IsEqual(other.PrivateKey)
|
||||
}
|
||||
|
||||
|
@ -92,6 +99,9 @@ func (c *SFTPFsConfig) setEmptyCredentialsIfNil() {
|
|||
if c.PrivateKey == nil {
|
||||
c.PrivateKey = kms.NewEmptySecret()
|
||||
}
|
||||
if c.Passphrase == nil {
|
||||
c.Passphrase = kms.NewEmptySecret()
|
||||
}
|
||||
}
|
||||
|
||||
// validate returns an error if the configuration is not valid
|
||||
|
@ -157,6 +167,12 @@ func (c *SFTPFsConfig) ValidateAndEncryptCredentials(additionalData string) erro
|
|||
return util.NewValidationError(fmt.Sprintf("could not encrypt SFTP fs private key: %v", err))
|
||||
}
|
||||
}
|
||||
if c.Passphrase.IsPlain() {
|
||||
c.Passphrase.SetAdditionalData(additionalData)
|
||||
if err := c.Passphrase.Encrypt(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -195,6 +211,11 @@ func NewSFTPFs(connectionID, mountPath, localTempDir string, forbiddenSelfUserna
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
if !config.Passphrase.IsEmpty() {
|
||||
if err := config.Passphrase.TryDecrypt(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
config.forbiddenSelfUsernames = forbiddenSelfUsernames
|
||||
sftpFs := &SFTPFs{
|
||||
connectionID: connectionID,
|
||||
|
@ -778,11 +799,19 @@ func (fs *SFTPFs) createConnection() error {
|
|||
ClientVersion: fmt.Sprintf("SSH-2.0-SFTPGo_%v", version.Get().Version),
|
||||
}
|
||||
if fs.config.PrivateKey.GetPayload() != "" {
|
||||
signer, err := ssh.ParsePrivateKey([]byte(fs.config.PrivateKey.GetPayload()))
|
||||
if err != nil {
|
||||
fsLog(fs, logger.LevelError, "unable to parse the provided private key: %v", err)
|
||||
fs.err <- err
|
||||
return err
|
||||
var signer ssh.Signer
|
||||
if fs.config.Passphrase.GetPayload() != "" {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(fs.config.PrivateKey.GetPayload()), []byte(fs.config.Passphrase.GetPayload()))
|
||||
if err != nil {
|
||||
fs.err <- err
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
signer, err = ssh.ParsePrivateKey([]byte(fs.config.PrivateKey.GetPayload()))
|
||||
if err != nil {
|
||||
fs.err <- err
|
||||
return err
|
||||
}
|
||||
}
|
||||
clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue