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
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))

View file

@ -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))

View file

@ -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
}
}
}

View file

@ -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 {

View file

@ -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">

View file

@ -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 {

View file

@ -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
}

View file

@ -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))
}