Feat private key passphrase for sftpfs (#855)

Signed-off-by: Sunil Keswani <sunilke@zeta.tech>
This commit is contained in:
sunilke 2022-05-30 22:30:39 +05:30 committed by GitHub
parent f6b11c2d01
commit 84e3132ed1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 73 additions and 12 deletions

View file

@ -67,6 +67,7 @@ func updateFolder(w http.ResponseWriter, r *http.Request) {
currentCryptoPassphrase := folder.FsConfig.CryptConfig.Passphrase currentCryptoPassphrase := folder.FsConfig.CryptConfig.Passphrase
currentSFTPPassword := folder.FsConfig.SFTPConfig.Password currentSFTPPassword := folder.FsConfig.SFTPConfig.Password
currentSFTPKey := folder.FsConfig.SFTPConfig.PrivateKey currentSFTPKey := folder.FsConfig.SFTPConfig.PrivateKey
currentSFTPKeyPassphrase := folder.FsConfig.SFTPConfig.Passphrase
folder.FsConfig.S3Config = vfs.S3FsConfig{} folder.FsConfig.S3Config = vfs.S3FsConfig{}
folder.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{} folder.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
@ -82,7 +83,7 @@ func updateFolder(w http.ResponseWriter, r *http.Request) {
folder.Name = name folder.Name = name
folder.FsConfig.SetEmptySecretsIfNil() folder.FsConfig.SetEmptySecretsIfNil()
updateEncryptedSecrets(&folder.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl, currentGCSCredentials, 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)) err = dataprovider.UpdateFolder(&folder, users, groups, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
if err != nil { if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err)) sendAPIResponse(w, r, err, "", getRespStatus(err))

View file

@ -72,6 +72,7 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
currentCryptoPassphrase := group.UserSettings.FsConfig.CryptConfig.Passphrase currentCryptoPassphrase := group.UserSettings.FsConfig.CryptConfig.Passphrase
currentSFTPPassword := group.UserSettings.FsConfig.SFTPConfig.Password currentSFTPPassword := group.UserSettings.FsConfig.SFTPConfig.Password
currentSFTPKey := group.UserSettings.FsConfig.SFTPConfig.PrivateKey currentSFTPKey := group.UserSettings.FsConfig.SFTPConfig.PrivateKey
currentSFTPKeyPassphrase := group.UserSettings.FsConfig.SFTPConfig.Passphrase
group.UserSettings.FsConfig.S3Config = vfs.S3FsConfig{} group.UserSettings.FsConfig.S3Config = vfs.S3FsConfig{}
group.UserSettings.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{} group.UserSettings.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
@ -87,7 +88,7 @@ func updateGroup(w http.ResponseWriter, r *http.Request) {
group.Name = name group.Name = name
group.UserSettings.FsConfig.SetEmptySecretsIfNil() group.UserSettings.FsConfig.SetEmptySecretsIfNil()
updateEncryptedSecrets(&group.UserSettings.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl, 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)) err = dataprovider.UpdateGroup(&group, users, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
if err != nil { if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err)) sendAPIResponse(w, r, err, "", getRespStatus(err))

View file

@ -134,6 +134,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
currentCryptoPassphrase := user.FsConfig.CryptConfig.Passphrase currentCryptoPassphrase := user.FsConfig.CryptConfig.Passphrase
currentSFTPPassword := user.FsConfig.SFTPConfig.Password currentSFTPPassword := user.FsConfig.SFTPConfig.Password
currentSFTPKey := user.FsConfig.SFTPConfig.PrivateKey currentSFTPKey := user.FsConfig.SFTPConfig.PrivateKey
currentSFTPKeyPassphrase := user.FsConfig.SFTPConfig.Passphrase
user.Permissions = make(map[string][]string) user.Permissions = make(map[string][]string)
user.FsConfig.S3Config = vfs.S3FsConfig{} user.FsConfig.S3Config = vfs.S3FsConfig{}
@ -159,7 +160,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
user.Permissions = currentPermissions user.Permissions = currentPermissions
} }
updateEncryptedSecrets(&user.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl, 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)) err = dataprovider.UpdateUser(&user, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
if err != nil { if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err)) sendAPIResponse(w, r, err, "", getRespStatus(err))
@ -231,7 +232,7 @@ func disconnectUser(username string) {
} }
func updateEncryptedSecrets(fsConfig *vfs.Filesystem, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl, 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 // we use the new access secret if plain or empty, otherwise the old value
switch fsConfig.Provider { switch fsConfig.Provider {
case sdk.S3FilesystemProvider: case sdk.S3FilesystemProvider:
@ -262,5 +263,8 @@ func updateEncryptedSecrets(fsConfig *vfs.Filesystem, currentS3AccessSecret, cur
if fsConfig.SFTPConfig.PrivateKey.IsNotPlainAndNotEmpty() { if fsConfig.SFTPConfig.PrivateKey.IsNotPlainAndNotEmpty() {
fsConfig.SFTPConfig.PrivateKey = currentSFTPKey fsConfig.SFTPConfig.PrivateKey = currentSFTPKey
} }
if fsConfig.SFTPConfig.Passphrase.IsNotPlainAndNotEmpty() {
fsConfig.SFTPConfig.Passphrase = currentSFTPKeyPassphrase
}
} }
} }

View file

@ -1219,6 +1219,7 @@ func getSFTPConfig(r *http.Request) (vfs.SFTPFsConfig, error) {
config.Username = r.Form.Get("sftp_username") config.Username = r.Form.Get("sftp_username")
config.Password = getSecretFromFormField(r, "sftp_password") config.Password = getSecretFromFormField(r, "sftp_password")
config.PrivateKey = getSecretFromFormField(r, "sftp_private_key") config.PrivateKey = getSecretFromFormField(r, "sftp_private_key")
config.Passphrase = getSecretFromFormField(r, "sftp_passphrase")
fingerprintsFormValue := r.Form.Get("sftp_fingerprints") fingerprintsFormValue := r.Form.Get("sftp_fingerprints")
config.Fingerprints = getSliceFromDelimitedValues(fingerprintsFormValue, "\n") config.Fingerprints = getSliceFromDelimitedValues(fingerprintsFormValue, "\n")
config.Prefix = r.Form.Get("sftp_prefix") 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, 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.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{ updatedUser = getUserFromTemplate(updatedUser, userTemplateFields{
Username: updatedUser.Username, Username: updatedUser.Username,
@ -2336,7 +2337,7 @@ func (s *httpdServer) handleWebUpdateFolderPost(w http.ResponseWriter, r *http.R
updatedFolder.FsConfig.SetEmptySecretsIfNil() updatedFolder.FsConfig.SetEmptySecretsIfNil()
updateEncryptedSecrets(&updatedFolder.FsConfig, folder.FsConfig.S3Config.AccessSecret, folder.FsConfig.AzBlobConfig.AccountKey, 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.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) 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, updateEncryptedSecrets(&updatedGroup.UserSettings.FsConfig, group.UserSettings.FsConfig.S3Config.AccessSecret,
group.UserSettings.FsConfig.AzBlobConfig.AccountKey, group.UserSettings.FsConfig.AzBlobConfig.SASURL, group.UserSettings.FsConfig.AzBlobConfig.AccountKey, group.UserSettings.FsConfig.AzBlobConfig.SASURL,
group.UserSettings.FsConfig.GCSConfig.Credentials, group.UserSettings.FsConfig.CryptConfig.Passphrase, 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) err = dataprovider.UpdateGroup(&updatedGroup, group.Users, claims.Username, ipAddr)
if err != nil { if err != nil {

View file

@ -424,6 +424,17 @@
</div> </div>
</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"> <div class="form-group row fsconfig fsconfig-sftpfs">
<label for="idSFTPFingerprints" class="col-sm-2 col-form-label">Fingerprints</label> <label for="idSFTPFingerprints" class="col-sm-2 col-form-label">Fingerprints</label>
<div class="col-sm-10"> <div class="col-sm-10">

View file

@ -26,6 +26,7 @@ func (f *Filesystem) SetEmptySecrets() {
f.CryptConfig.Passphrase = kms.NewEmptySecret() f.CryptConfig.Passphrase = kms.NewEmptySecret()
f.SFTPConfig.Password = kms.NewEmptySecret() f.SFTPConfig.Password = kms.NewEmptySecret()
f.SFTPConfig.PrivateKey = kms.NewEmptySecret() f.SFTPConfig.PrivateKey = kms.NewEmptySecret()
f.SFTPConfig.Passphrase = kms.NewEmptySecret()
} }
// SetEmptySecretsIfNil sets the secrets to empty if nil // SetEmptySecretsIfNil sets the secrets to empty if nil
@ -51,6 +52,9 @@ func (f *Filesystem) SetEmptySecretsIfNil() {
if f.SFTPConfig.PrivateKey == nil { if f.SFTPConfig.PrivateKey == nil {
f.SFTPConfig.PrivateKey = kms.NewEmptySecret() f.SFTPConfig.PrivateKey = kms.NewEmptySecret()
} }
if f.SFTPConfig.Passphrase == nil {
f.SFTPConfig.Passphrase = kms.NewEmptySecret()
}
} }
// SetNilSecretsIfEmpty set the secrets to nil if empty. // 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() { if f.SFTPConfig.PrivateKey != nil && f.SFTPConfig.PrivateKey.IsEmpty() {
f.SFTPConfig.PrivateKey = nil 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 // IsEqual returns true if the fs is equal to other
@ -191,6 +198,9 @@ func (f *Filesystem) HasRedactedSecret() bool {
if f.SFTPConfig.PrivateKey.IsRedacted() { if f.SFTPConfig.PrivateKey.IsRedacted() {
return true return true
} }
if f.SFTPConfig.Passphrase.IsRedacted() {
return true
}
} }
return false return false
@ -276,6 +286,7 @@ func (f *Filesystem) GetACopy() Filesystem {
}, },
Password: f.SFTPConfig.Password.Clone(), Password: f.SFTPConfig.Password.Clone(),
PrivateKey: f.SFTPConfig.PrivateKey.Clone(), PrivateKey: f.SFTPConfig.PrivateKey.Clone(),
Passphrase: f.SFTPConfig.Passphrase.Clone(),
}, },
} }
if len(f.SFTPConfig.Fingerprints) > 0 { if len(f.SFTPConfig.Fingerprints) > 0 {

View file

@ -168,6 +168,9 @@ func (v *BaseVirtualFolder) HasRedactedSecret() bool {
if v.FsConfig.SFTPConfig.PrivateKey.IsRedacted() { if v.FsConfig.SFTPConfig.PrivateKey.IsRedacted() {
return true return true
} }
if v.FsConfig.SFTPConfig.Passphrase.IsRedacted() {
return true
}
} }
return false return false
} }

View file

@ -41,6 +41,7 @@ type SFTPFsConfig struct {
Password *kms.Secret `json:"password,omitempty"` Password *kms.Secret `json:"password,omitempty"`
PrivateKey *kms.Secret `json:"private_key,omitempty"` PrivateKey *kms.Secret `json:"private_key,omitempty"`
forbiddenSelfUsernames []string `json:"-"` forbiddenSelfUsernames []string `json:"-"`
Passphrase *kms.Secret `json:"passphrase,omitempty"`
} }
// HideConfidentialData hides confidential data // HideConfidentialData hides confidential data
@ -51,6 +52,9 @@ func (c *SFTPFsConfig) HideConfidentialData() {
if c.PrivateKey != nil { if c.PrivateKey != nil {
c.PrivateKey.Hide() c.PrivateKey.Hide()
} }
if c.Passphrase != nil {
c.Passphrase.Hide()
}
} }
func (c *SFTPFsConfig) isEqual(other *SFTPFsConfig) bool { func (c *SFTPFsConfig) isEqual(other *SFTPFsConfig) bool {
@ -82,6 +86,9 @@ func (c *SFTPFsConfig) isEqual(other *SFTPFsConfig) bool {
if !c.Password.IsEqual(other.Password) { if !c.Password.IsEqual(other.Password) {
return false return false
} }
if !c.Passphrase.IsEqual(other.Passphrase) {
return false
}
return c.PrivateKey.IsEqual(other.PrivateKey) return c.PrivateKey.IsEqual(other.PrivateKey)
} }
@ -92,6 +99,9 @@ func (c *SFTPFsConfig) setEmptyCredentialsIfNil() {
if c.PrivateKey == nil { if c.PrivateKey == nil {
c.PrivateKey = kms.NewEmptySecret() c.PrivateKey = kms.NewEmptySecret()
} }
if c.Passphrase == nil {
c.Passphrase = kms.NewEmptySecret()
}
} }
// validate returns an error if the configuration is not valid // 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)) 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 return nil
} }
@ -195,6 +211,11 @@ func NewSFTPFs(connectionID, mountPath, localTempDir string, forbiddenSelfUserna
return nil, err return nil, err
} }
} }
if !config.Passphrase.IsEmpty() {
if err := config.Passphrase.TryDecrypt(); err != nil {
return nil, err
}
}
config.forbiddenSelfUsernames = forbiddenSelfUsernames config.forbiddenSelfUsernames = forbiddenSelfUsernames
sftpFs := &SFTPFs{ sftpFs := &SFTPFs{
connectionID: connectionID, connectionID: connectionID,
@ -778,11 +799,19 @@ func (fs *SFTPFs) createConnection() error {
ClientVersion: fmt.Sprintf("SSH-2.0-SFTPGo_%v", version.Get().Version), ClientVersion: fmt.Sprintf("SSH-2.0-SFTPGo_%v", version.Get().Version),
} }
if fs.config.PrivateKey.GetPayload() != "" { if fs.config.PrivateKey.GetPayload() != "" {
signer, err := ssh.ParsePrivateKey([]byte(fs.config.PrivateKey.GetPayload())) var signer ssh.Signer
if err != nil { if fs.config.Passphrase.GetPayload() != "" {
fsLog(fs, logger.LevelError, "unable to parse the provided private key: %v", err) signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(fs.config.PrivateKey.GetPayload()), []byte(fs.config.Passphrase.GetPayload()))
fs.err <- err if err != nil {
return err 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)) clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
} }