add KMS support

Fixes #226
This commit is contained in:
Nicola Murino 2020-11-30 21:46:34 +01:00
parent af0c9b76c4
commit 634b723b5d
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
46 changed files with 1582 additions and 536 deletions

View file

@ -14,6 +14,7 @@ import (
"github.com/drakkan/sftpgo/common" "github.com/drakkan/sftpgo/common"
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/service" "github.com/drakkan/sftpgo/service"
"github.com/drakkan/sftpgo/sftpd" "github.com/drakkan/sftpgo/sftpd"
"github.com/drakkan/sftpgo/version" "github.com/drakkan/sftpgo/version"
@ -143,13 +144,10 @@ Please take a look at the usage below to customize the serving parameters`,
FsConfig: dataprovider.Filesystem{ FsConfig: dataprovider.Filesystem{
Provider: dataprovider.FilesystemProvider(portableFsProvider), Provider: dataprovider.FilesystemProvider(portableFsProvider),
S3Config: vfs.S3FsConfig{ S3Config: vfs.S3FsConfig{
Bucket: portableS3Bucket, Bucket: portableS3Bucket,
Region: portableS3Region, Region: portableS3Region,
AccessKey: portableS3AccessKey, AccessKey: portableS3AccessKey,
AccessSecret: vfs.Secret{ AccessSecret: kms.NewPlainSecret(portableS3AccessSecret),
Status: vfs.SecretStatusPlain,
Payload: portableS3AccessSecret,
},
Endpoint: portableS3Endpoint, Endpoint: portableS3Endpoint,
StorageClass: portableS3StorageClass, StorageClass: portableS3StorageClass,
KeyPrefix: portableS3KeyPrefix, KeyPrefix: portableS3KeyPrefix,
@ -157,22 +155,16 @@ Please take a look at the usage below to customize the serving parameters`,
UploadConcurrency: portableS3ULConcurrency, UploadConcurrency: portableS3ULConcurrency,
}, },
GCSConfig: vfs.GCSFsConfig{ GCSConfig: vfs.GCSFsConfig{
Bucket: portableGCSBucket, Bucket: portableGCSBucket,
Credentials: vfs.Secret{ Credentials: kms.NewPlainSecret(string(portableGCSCredentials)),
Status: vfs.SecretStatusPlain,
Payload: string(portableGCSCredentials),
},
AutomaticCredentials: portableGCSAutoCredentials, AutomaticCredentials: portableGCSAutoCredentials,
StorageClass: portableGCSStorageClass, StorageClass: portableGCSStorageClass,
KeyPrefix: portableGCSKeyPrefix, KeyPrefix: portableGCSKeyPrefix,
}, },
AzBlobConfig: vfs.AzBlobFsConfig{ AzBlobConfig: vfs.AzBlobFsConfig{
Container: portableAzContainer, Container: portableAzContainer,
AccountName: portableAzAccountName, AccountName: portableAzAccountName,
AccountKey: vfs.Secret{ AccountKey: kms.NewPlainSecret(portableAzAccountKey),
Status: vfs.SecretStatusPlain,
Payload: portableAzAccountKey,
},
Endpoint: portableAzEndpoint, Endpoint: portableAzEndpoint,
AccessTier: portableAzAccessTier, AccessTier: portableAzAccessTier,
SASURL: portableAzSASURL, SASURL: portableAzSASURL,

View file

@ -83,6 +83,11 @@ Command-line flags should be specified in the Subsystem declaration.
} }
httpConfig := config.GetHTTPConfig() httpConfig := config.GetHTTPConfig()
httpConfig.Initialize(configDir) httpConfig.Initialize(configDir)
kmsConfig := config.GetKMSConfig()
if err := kmsConfig.Initialize(); err != nil {
logger.Error(logSender, connectionID, "unable to initialize KMS: %v", err)
os.Exit(1)
}
user, err := dataprovider.UserExists(username) user, err := dataprovider.UserExists(username)
if err == nil { if err == nil {
if user.HomeDir != filepath.Clean(homedir) && !preserveHomeDir { if user.HomeDir != filepath.Clean(homedir) && !preserveHomeDir {

View file

@ -12,6 +12,7 @@ import (
"github.com/drakkan/sftpgo/ftpd" "github.com/drakkan/sftpgo/ftpd"
"github.com/drakkan/sftpgo/httpclient" "github.com/drakkan/sftpgo/httpclient"
"github.com/drakkan/sftpgo/httpd" "github.com/drakkan/sftpgo/httpd"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/sftpd" "github.com/drakkan/sftpgo/sftpd"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
@ -43,6 +44,7 @@ type globalConfig struct {
ProviderConf dataprovider.Config `json:"data_provider" mapstructure:"data_provider"` ProviderConf dataprovider.Config `json:"data_provider" mapstructure:"data_provider"`
HTTPDConfig httpd.Conf `json:"httpd" mapstructure:"httpd"` HTTPDConfig httpd.Conf `json:"httpd" mapstructure:"httpd"`
HTTPConfig httpclient.Config `json:"http" mapstructure:"http"` HTTPConfig httpclient.Config `json:"http" mapstructure:"http"`
KMSConfig kms.Configuration `json:"kms" mapstructure:"kms"`
} }
func init() { func init() {
@ -164,6 +166,12 @@ func init() {
CACertificates: nil, CACertificates: nil,
SkipTLSVerify: false, SkipTLSVerify: false,
}, },
KMSConfig: kms.Configuration{
Secrets: kms.Secrets{
URL: "",
MasterKeyPath: "",
},
},
} }
viper.SetEnvPrefix(configEnvPrefix) viper.SetEnvPrefix(configEnvPrefix)
@ -240,6 +248,16 @@ func GetHTTPConfig() httpclient.Config {
return globalConf.HTTPConfig return globalConf.HTTPConfig
} }
// GetKMSConfig returns the KMS configuration
func GetKMSConfig() kms.Configuration {
return globalConf.KMSConfig
}
// SetKMSConfig sets the kms configuration
func SetKMSConfig(config kms.Configuration) {
globalConf.KMSConfig = config
}
// HasServicesToStart returns true if the config defines at least a service to start. // HasServicesToStart returns true if the config defines at least a service to start.
// Supported services are SFTP, FTP and WebDAV // Supported services are SFTP, FTP and WebDAV
func HasServicesToStart() bool { func HasServicesToStart() bool {
@ -456,4 +474,6 @@ func setViperDefaults() {
viper.SetDefault("http.timeout", globalConf.HTTPConfig.Timeout) viper.SetDefault("http.timeout", globalConf.HTTPConfig.Timeout)
viper.SetDefault("http.ca_certificates", globalConf.HTTPConfig.CACertificates) viper.SetDefault("http.ca_certificates", globalConf.HTTPConfig.CACertificates)
viper.SetDefault("http.skip_tls_verify", globalConf.HTTPConfig.SkipTLSVerify) viper.SetDefault("http.skip_tls_verify", globalConf.HTTPConfig.SkipTLSVerify)
viper.SetDefault("kms.secrets.url", globalConf.KMSConfig.Secrets.URL)
viper.SetDefault("kms.secrets.master_key_path", globalConf.KMSConfig.Secrets.MasterKeyPath)
} }

View file

@ -279,6 +279,12 @@ func TestSetGetConfig(t *testing.T) {
config.SetWebDAVDConfig(webDavConf) config.SetWebDAVDConfig(webDavConf)
assert.Equal(t, webDavConf.CertificateFile, config.GetWebDAVDConfig().CertificateFile) assert.Equal(t, webDavConf.CertificateFile, config.GetWebDAVDConfig().CertificateFile)
assert.Equal(t, webDavConf.CertificateKeyFile, config.GetWebDAVDConfig().CertificateKeyFile) assert.Equal(t, webDavConf.CertificateKeyFile, config.GetWebDAVDConfig().CertificateKeyFile)
kmsConf := config.GetKMSConfig()
kmsConf.Secrets.MasterKeyPath = "apath"
kmsConf.Secrets.URL = "aurl"
config.SetKMSConfig(kmsConf)
assert.Equal(t, kmsConf.Secrets.MasterKeyPath, config.GetKMSConfig().Secrets.MasterKeyPath)
assert.Equal(t, kmsConf.Secrets.URL, config.GetKMSConfig().Secrets.URL)
} }
func TestServiceToStart(t *testing.T) { func TestServiceToStart(t *testing.T) {
@ -313,11 +319,15 @@ func TestConfigFromEnv(t *testing.T) {
os.Setenv("SFTPGO_DATA_PROVIDER__PASSWORD_HASHING__ARGON2_OPTIONS__ITERATIONS", "41") os.Setenv("SFTPGO_DATA_PROVIDER__PASSWORD_HASHING__ARGON2_OPTIONS__ITERATIONS", "41")
os.Setenv("SFTPGO_DATA_PROVIDER__POOL_SIZE", "10") os.Setenv("SFTPGO_DATA_PROVIDER__POOL_SIZE", "10")
os.Setenv("SFTPGO_DATA_PROVIDER__ACTIONS__EXECUTE_ON", "add") os.Setenv("SFTPGO_DATA_PROVIDER__ACTIONS__EXECUTE_ON", "add")
os.Setenv("SFTPGO_KMS__SECRETS__URL", "local")
os.Setenv("SFTPGO_KMS__SECRETS__MASTER_KEY_PATH", "path")
t.Cleanup(func() { t.Cleanup(func() {
os.Unsetenv("SFTPGO_SFTPD__BIND_ADDRESS") os.Unsetenv("SFTPGO_SFTPD__BIND_ADDRESS")
os.Unsetenv("SFTPGO_DATA_PROVIDER__PASSWORD_HASHING__ARGON2_OPTIONS__ITERATIONS") os.Unsetenv("SFTPGO_DATA_PROVIDER__PASSWORD_HASHING__ARGON2_OPTIONS__ITERATIONS")
os.Unsetenv("SFTPGO_DATA_PROVIDER__POOL_SIZE") os.Unsetenv("SFTPGO_DATA_PROVIDER__POOL_SIZE")
os.Unsetenv("SFTPGO_DATA_PROVIDER__ACTIONS__EXECUTE_ON") os.Unsetenv("SFTPGO_DATA_PROVIDER__ACTIONS__EXECUTE_ON")
os.Unsetenv("SFTPGO_KMS__SECRETS__URL")
os.Unsetenv("SFTPGO_KMS__SECRETS__MASTER_KEY_PATH")
}) })
err := config.LoadConfig(".", "invalid config") err := config.LoadConfig(".", "invalid config")
assert.NoError(t, err) assert.NoError(t, err)
@ -328,4 +338,7 @@ func TestConfigFromEnv(t *testing.T) {
assert.Equal(t, 10, dataProviderConf.PoolSize) assert.Equal(t, 10, dataProviderConf.PoolSize)
assert.Len(t, dataProviderConf.Actions.ExecuteOn, 1) assert.Len(t, dataProviderConf.Actions.ExecuteOn, 1)
assert.Contains(t, dataProviderConf.Actions.ExecuteOn, "add") assert.Contains(t, dataProviderConf.Actions.ExecuteOn, "add")
kmsConfig := config.GetKMSConfig()
assert.Equal(t, "local", kmsConfig.Secrets.URL)
assert.Equal(t, "path", kmsConfig.Secrets.MasterKeyPath)
} }

View file

@ -787,6 +787,7 @@ func joinUserAndFolders(u []byte, foldersBucket *bolt.Bucket) (User, error) {
} }
user.VirtualFolders = folders user.VirtualFolders = folders
} }
user.SetEmptySecretsIfNil()
return user, err return user, err
} }

View file

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
@ -129,6 +130,7 @@ func createUserFromV4(u compatUserV4, fsConfig Filesystem) User {
Filters: u.Filters, Filters: u.Filters,
} }
user.FsConfig = fsConfig user.FsConfig = fsConfig
user.SetEmptySecretsIfNil()
return user return user
} }
@ -160,12 +162,11 @@ func convertUserToV4(u User, fsConfig compatFilesystemV4) compatUserV4 {
return user return user
} }
func getCGSCredentialsFromV4(config compatGCSFsConfigV4) (vfs.Secret, error) { func getCGSCredentialsFromV4(config compatGCSFsConfigV4) (*kms.Secret, error) {
var secret vfs.Secret secret := kms.NewEmptySecret()
var err error var err error
if len(config.Credentials) > 0 { if len(config.Credentials) > 0 {
secret.Status = vfs.SecretStatusPlain secret = kms.NewPlainSecret(string(config.Credentials))
secret.Payload = string(config.Credentials)
return secret, nil return secret, nil
} }
if config.CredentialFile != "" { if config.CredentialFile != "" {
@ -173,14 +174,16 @@ func getCGSCredentialsFromV4(config compatGCSFsConfigV4) (vfs.Secret, error) {
if err != nil { if err != nil {
return secret, err return secret, err
} }
secret.Status = vfs.SecretStatusPlain secret = kms.NewPlainSecret(string(creds))
secret.Payload = string(creds)
return secret, nil return secret, nil
} }
return secret, err return secret, err
} }
func getCGSCredentialsFromV6(config vfs.GCSFsConfig, username string) (string, error) { func getCGSCredentialsFromV6(config vfs.GCSFsConfig, username string) (string, error) {
if config.Credentials == nil {
config.Credentials = kms.NewEmptySecret()
}
if config.Credentials.IsEmpty() { if config.Credentials.IsEmpty() {
config.CredentialFile = filepath.Join(credentialsDirPath, fmt.Sprintf("%v_gcs_credentials.json", config.CredentialFile = filepath.Join(credentialsDirPath, fmt.Sprintf("%v_gcs_credentials.json",
username)) username))
@ -199,7 +202,7 @@ func getCGSCredentialsFromV6(config vfs.GCSFsConfig, username string) (string, e
return "", err return "", err
} }
// in V4 GCS credentials were not encrypted // in V4 GCS credentials were not encrypted
return config.Credentials.Payload, nil return config.Credentials.GetPayload(), nil
} }
return "", nil return "", nil
} }
@ -229,7 +232,7 @@ func convertFsConfigToV4(fs Filesystem, username string) (compatFilesystemV4, er
if err != nil { if err != nil {
return fsV4, err return fsV4, err
} }
secretV4, err := utils.EncryptData(fs.S3Config.AccessSecret.Payload) secretV4, err := utils.EncryptData(fs.S3Config.AccessSecret.GetPayload())
if err != nil { if err != nil {
return fsV4, err return fsV4, err
} }
@ -253,7 +256,7 @@ func convertFsConfigToV4(fs Filesystem, username string) (compatFilesystemV4, er
if err != nil { if err != nil {
return fsV4, err return fsV4, err
} }
secretV4, err := utils.EncryptData(fs.AzBlobConfig.AccountKey.Payload) secretV4, err := utils.EncryptData(fs.AzBlobConfig.AccountKey.GetPayload())
if err != nil { if err != nil {
return fsV4, err return fsV4, err
} }
@ -292,14 +295,14 @@ func convertFsConfigFromV4(compatFs compatFilesystemV4, username string) (Filesy
KeyPrefix: compatFs.S3Config.KeyPrefix, KeyPrefix: compatFs.S3Config.KeyPrefix,
Region: compatFs.S3Config.Region, Region: compatFs.S3Config.Region,
AccessKey: compatFs.S3Config.AccessKey, AccessKey: compatFs.S3Config.AccessKey,
AccessSecret: vfs.Secret{}, AccessSecret: kms.NewEmptySecret(),
Endpoint: compatFs.S3Config.Endpoint, Endpoint: compatFs.S3Config.Endpoint,
StorageClass: compatFs.S3Config.StorageClass, StorageClass: compatFs.S3Config.StorageClass,
UploadPartSize: compatFs.S3Config.UploadPartSize, UploadPartSize: compatFs.S3Config.UploadPartSize,
UploadConcurrency: compatFs.S3Config.UploadConcurrency, UploadConcurrency: compatFs.S3Config.UploadConcurrency,
} }
if compatFs.S3Config.AccessSecret != "" { if compatFs.S3Config.AccessSecret != "" {
secret, err := vfs.GetSecretFromCompatString(compatFs.S3Config.AccessSecret) secret, err := kms.GetSecretFromCompatString(compatFs.S3Config.AccessSecret)
if err != nil { if err != nil {
providerLog(logger.LevelError, "unable to convert v4 filesystem for user %#v: %v", username, err) providerLog(logger.LevelError, "unable to convert v4 filesystem for user %#v: %v", username, err)
return fsConfig, err return fsConfig, err
@ -310,7 +313,7 @@ func convertFsConfigFromV4(compatFs compatFilesystemV4, username string) (Filesy
fsConfig.AzBlobConfig = vfs.AzBlobFsConfig{ fsConfig.AzBlobConfig = vfs.AzBlobFsConfig{
Container: compatFs.AzBlobConfig.Container, Container: compatFs.AzBlobConfig.Container,
AccountName: compatFs.AzBlobConfig.AccountName, AccountName: compatFs.AzBlobConfig.AccountName,
AccountKey: vfs.Secret{}, AccountKey: kms.NewEmptySecret(),
Endpoint: compatFs.AzBlobConfig.Endpoint, Endpoint: compatFs.AzBlobConfig.Endpoint,
SASURL: compatFs.AzBlobConfig.SASURL, SASURL: compatFs.AzBlobConfig.SASURL,
KeyPrefix: compatFs.AzBlobConfig.KeyPrefix, KeyPrefix: compatFs.AzBlobConfig.KeyPrefix,
@ -320,7 +323,7 @@ func convertFsConfigFromV4(compatFs compatFilesystemV4, username string) (Filesy
AccessTier: compatFs.AzBlobConfig.AccessTier, AccessTier: compatFs.AzBlobConfig.AccessTier,
} }
if compatFs.AzBlobConfig.AccountKey != "" { if compatFs.AzBlobConfig.AccountKey != "" {
secret, err := vfs.GetSecretFromCompatString(compatFs.AzBlobConfig.AccountKey) secret, err := kms.GetSecretFromCompatString(compatFs.AzBlobConfig.AccountKey)
if err != nil { if err != nil {
providerLog(logger.LevelError, "unable to convert v4 filesystem for user %#v: %v", username, err) providerLog(logger.LevelError, "unable to convert v4 filesystem for user %#v: %v", username, err)
return fsConfig, err return fsConfig, err

View file

@ -42,6 +42,7 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"github.com/drakkan/sftpgo/httpclient" "github.com/drakkan/sftpgo/httpclient"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/metrics" "github.com/drakkan/sftpgo/metrics"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
@ -124,6 +125,7 @@ var (
sqlTableFoldersMapping = "folders_mapping" sqlTableFoldersMapping = "folders_mapping"
sqlTableSchemaVersion = "schema_version" sqlTableSchemaVersion = "schema_version"
argon2Params *argon2id.Params argon2Params *argon2id.Params
lastLoginMinDelay = 10 * time.Minute
) )
type schemaVersion struct { type schemaVersion struct {
@ -577,7 +579,12 @@ func UpdateLastLogin(user User) error {
if config.ManageUsers == 0 { if config.ManageUsers == 0 {
return &MethodDisabledError{err: manageUsersDisabledError} return &MethodDisabledError{err: manageUsersDisabledError}
} }
return provider.updateLastLogin(user.Username) lastLogin := utils.GetTimeFromMsecSinceEpoch(user.LastLogin)
diff := -time.Until(lastLogin)
if diff < 0 || diff > lastLoginMinDelay {
return provider.updateLastLogin(user.Username)
}
return nil
} }
// UpdateUserQuota updates the quota for the given SFTP user adding filesAdd and sizeAdd. // UpdateUserQuota updates the quota for the given SFTP user adding filesAdd and sizeAdd.
@ -1099,12 +1106,12 @@ func saveGCSCredentials(user *User) error {
if user.FsConfig.Provider != GCSFilesystemProvider { if user.FsConfig.Provider != GCSFilesystemProvider {
return nil return nil
} }
if user.FsConfig.GCSConfig.Credentials.Payload == "" { if user.FsConfig.GCSConfig.Credentials.GetPayload() == "" {
return nil return nil
} }
if config.PreferDatabaseCredentials { if config.PreferDatabaseCredentials {
if user.FsConfig.GCSConfig.Credentials.IsPlain() { if user.FsConfig.GCSConfig.Credentials.IsPlain() {
user.FsConfig.GCSConfig.Credentials.AdditionalData = user.Username user.FsConfig.GCSConfig.Credentials.SetAdditionalData(user.Username)
err := user.FsConfig.GCSConfig.Credentials.Encrypt() err := user.FsConfig.GCSConfig.Credentials.Encrypt()
if err != nil { if err != nil {
return err return err
@ -1113,7 +1120,7 @@ func saveGCSCredentials(user *User) error {
return nil return nil
} }
if user.FsConfig.GCSConfig.Credentials.IsPlain() { if user.FsConfig.GCSConfig.Credentials.IsPlain() {
user.FsConfig.GCSConfig.Credentials.AdditionalData = user.Username user.FsConfig.GCSConfig.Credentials.SetAdditionalData(user.Username)
err := user.FsConfig.GCSConfig.Credentials.Encrypt() err := user.FsConfig.GCSConfig.Credentials.Encrypt()
if err != nil { if err != nil {
return &ValidationError{err: fmt.Sprintf("could not encrypt GCS credentials: %v", err)} return &ValidationError{err: fmt.Sprintf("could not encrypt GCS credentials: %v", err)}
@ -1132,7 +1139,7 @@ func saveGCSCredentials(user *User) error {
if err != nil { if err != nil {
return &ValidationError{err: fmt.Sprintf("could not save GCS credentials: %v", err)} return &ValidationError{err: fmt.Sprintf("could not save GCS credentials: %v", err)}
} }
user.FsConfig.GCSConfig.Credentials = vfs.Secret{} user.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
return nil return nil
} }
@ -1143,7 +1150,7 @@ func validateFilesystemConfig(user *User) error {
return &ValidationError{err: fmt.Sprintf("could not validate s3config: %v", err)} return &ValidationError{err: fmt.Sprintf("could not validate s3config: %v", err)}
} }
if user.FsConfig.S3Config.AccessSecret.IsPlain() { if user.FsConfig.S3Config.AccessSecret.IsPlain() {
user.FsConfig.S3Config.AccessSecret.AdditionalData = user.Username user.FsConfig.S3Config.AccessSecret.SetAdditionalData(user.Username)
err = user.FsConfig.S3Config.AccessSecret.Encrypt() err = user.FsConfig.S3Config.AccessSecret.Encrypt()
if err != nil { if err != nil {
return &ValidationError{err: fmt.Sprintf("could not encrypt s3 access secret: %v", err)} return &ValidationError{err: fmt.Sprintf("could not encrypt s3 access secret: %v", err)}
@ -1166,7 +1173,7 @@ func validateFilesystemConfig(user *User) error {
return &ValidationError{err: fmt.Sprintf("could not validate Azure Blob config: %v", err)} return &ValidationError{err: fmt.Sprintf("could not validate Azure Blob config: %v", err)}
} }
if user.FsConfig.AzBlobConfig.AccountKey.IsPlain() { if user.FsConfig.AzBlobConfig.AccountKey.IsPlain() {
user.FsConfig.AzBlobConfig.AccountKey.AdditionalData = user.Username user.FsConfig.AzBlobConfig.AccountKey.SetAdditionalData(user.Username)
err = user.FsConfig.AzBlobConfig.AccountKey.Encrypt() err = user.FsConfig.AzBlobConfig.AccountKey.Encrypt()
if err != nil { if err != nil {
return &ValidationError{err: fmt.Sprintf("could not encrypt Azure blob account key: %v", err)} return &ValidationError{err: fmt.Sprintf("could not encrypt Azure blob account key: %v", err)}
@ -1220,6 +1227,7 @@ func validateFolder(folder *vfs.BaseVirtualFolder) error {
} }
func validateUser(user *User) error { func validateUser(user *User) error {
user.SetEmptySecretsIfNil()
buildUserHomeDir(user) buildUserHomeDir(user)
if err := validateBaseParams(user); err != nil { if err := validateBaseParams(user); err != nil {
return err return err
@ -2131,7 +2139,7 @@ func CacheWebDAVUser(cachedUser *CachedUser, maxSize int) {
} }
} }
if len(cachedUser.User.Username) > 0 { if cachedUser.User.Username != "" {
webDAVUsersCache.Store(cachedUser.User.Username, cachedUser) webDAVUsersCache.Store(cachedUser.User.Username, cachedUser)
} }
} }
@ -2143,7 +2151,7 @@ func GetCachedWebDAVUser(username string) (interface{}, bool) {
// RemoveCachedWebDAVUser removes a cached WebDAV user // RemoveCachedWebDAVUser removes a cached WebDAV user
func RemoveCachedWebDAVUser(username string) { func RemoveCachedWebDAVUser(username string) {
if len(username) > 0 { if username != "" {
webDAVUsersCache.Delete(username) webDAVUsersCache.Delete(username)
} }
} }

View file

@ -264,7 +264,8 @@ func (p MemoryProvider) dumpUsers() ([]User, error) {
return users, errMemoryProviderClosed return users, errMemoryProviderClosed
} }
for _, username := range p.dbHandle.usernames { for _, username := range p.dbHandle.usernames {
user := p.dbHandle.users[username] u := p.dbHandle.users[username]
user := u.getACopy()
err = addCredentialsToUser(&user) err = addCredentialsToUser(&user)
if err != nil { if err != nil {
return users, err return users, err
@ -315,7 +316,8 @@ func (p MemoryProvider) getUsers(limit int, offset int, order string, username s
if itNum <= offset { if itNum <= offset {
continue continue
} }
user := p.dbHandle.users[username] u := p.dbHandle.users[username]
user := u.getACopy()
user.HideConfidentialData() user.HideConfidentialData()
users = append(users, user) users = append(users, user)
if len(users) >= limit { if len(users) >= limit {
@ -329,7 +331,8 @@ func (p MemoryProvider) getUsers(limit int, offset int, order string, username s
continue continue
} }
username := p.dbHandle.usernames[i] username := p.dbHandle.usernames[i]
user := p.dbHandle.users[username] u := p.dbHandle.users[username]
user := u.getACopy()
user.HideConfidentialData() user.HideConfidentialData()
users = append(users, user) users = append(users, user)
if len(users) >= limit { if len(users) >= limit {

View file

@ -59,7 +59,12 @@ func initializeMySQLProvider() error {
providerLog(logger.LevelDebug, "mysql database handle created, connection string: %#v, pool size: %v", providerLog(logger.LevelDebug, "mysql database handle created, connection string: %#v, pool size: %v",
getMySQLConnectionString(true), config.PoolSize) getMySQLConnectionString(true), config.PoolSize)
dbHandle.SetMaxOpenConns(config.PoolSize) dbHandle.SetMaxOpenConns(config.PoolSize)
dbHandle.SetConnMaxLifetime(1800 * time.Second) if config.PoolSize > 0 {
dbHandle.SetMaxIdleConns(config.PoolSize)
} else {
dbHandle.SetMaxIdleConns(2)
}
dbHandle.SetConnMaxLifetime(240 * time.Second)
provider = MySQLProvider{dbHandle: dbHandle} provider = MySQLProvider{dbHandle: dbHandle}
} else { } else {
providerLog(logger.LevelWarn, "error creating mysql database handler, connection string: %#v, error: %v", providerLog(logger.LevelWarn, "error creating mysql database handler, connection string: %#v, error: %v",

View file

@ -7,6 +7,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"strings" "strings"
"time"
// we import lib/pq here to be able to disable PostgreSQL support using a build tag // we import lib/pq here to be able to disable PostgreSQL support using a build tag
_ "github.com/lib/pq" _ "github.com/lib/pq"
@ -58,6 +59,12 @@ func initializePGSQLProvider() error {
providerLog(logger.LevelDebug, "postgres database handle created, connection string: %#v, pool size: %v", providerLog(logger.LevelDebug, "postgres database handle created, connection string: %#v, pool size: %v",
getPGSQLConnectionString(true), config.PoolSize) getPGSQLConnectionString(true), config.PoolSize)
dbHandle.SetMaxOpenConns(config.PoolSize) dbHandle.SetMaxOpenConns(config.PoolSize)
if config.PoolSize > 0 {
dbHandle.SetMaxIdleConns(config.PoolSize)
} else {
dbHandle.SetMaxIdleConns(2)
}
dbHandle.SetConnMaxLifetime(240 * time.Second)
provider = PGSQLProvider{dbHandle: dbHandle} provider = PGSQLProvider{dbHandle: dbHandle}
} else { } else {
providerLog(logger.LevelWarn, "error creating postgres database handler, connection string: %#v, error: %v", providerLog(logger.LevelWarn, "error creating postgres database handler, connection string: %#v, error: %v",

View file

@ -446,6 +446,7 @@ func getUserFromDbRow(row *sql.Row, rows *sql.Rows) (User, error) {
if additionalInfo.Valid { if additionalInfo.Valid {
user.AdditionalInfo = additionalInfo.String user.AdditionalInfo = additionalInfo.String
} }
user.SetEmptySecretsIfNil()
return user, err return user, err
} }

View file

@ -14,6 +14,7 @@ import (
"golang.org/x/net/webdav" "golang.org/x/net/webdav"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
@ -768,7 +769,21 @@ func (u User) GetDeniedIPAsString() string {
return result return result
} }
// SetEmptySecretsIfNil sets the secrets to empty if nil
func (u *User) SetEmptySecretsIfNil() {
if u.FsConfig.S3Config.AccessSecret == nil {
u.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
}
if u.FsConfig.GCSConfig.Credentials == nil {
u.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
}
if u.FsConfig.AzBlobConfig.AccountKey == nil {
u.FsConfig.AzBlobConfig.AccountKey = kms.NewEmptySecret()
}
}
func (u *User) getACopy() User { func (u *User) getACopy() User {
u.SetEmptySecretsIfNil()
pubKeys := make([]string, len(u.PublicKeys)) pubKeys := make([]string, len(u.PublicKeys))
copy(pubKeys, u.PublicKeys) copy(pubKeys, u.PublicKeys)
virtualFolders := make([]vfs.VirtualFolder, len(u.VirtualFolders)) virtualFolders := make([]vfs.VirtualFolder, len(u.VirtualFolders))
@ -799,7 +814,7 @@ func (u *User) getACopy() User {
Bucket: u.FsConfig.S3Config.Bucket, Bucket: u.FsConfig.S3Config.Bucket,
Region: u.FsConfig.S3Config.Region, Region: u.FsConfig.S3Config.Region,
AccessKey: u.FsConfig.S3Config.AccessKey, AccessKey: u.FsConfig.S3Config.AccessKey,
AccessSecret: u.FsConfig.S3Config.AccessSecret, AccessSecret: u.FsConfig.S3Config.AccessSecret.Clone(),
Endpoint: u.FsConfig.S3Config.Endpoint, Endpoint: u.FsConfig.S3Config.Endpoint,
StorageClass: u.FsConfig.S3Config.StorageClass, StorageClass: u.FsConfig.S3Config.StorageClass,
KeyPrefix: u.FsConfig.S3Config.KeyPrefix, KeyPrefix: u.FsConfig.S3Config.KeyPrefix,
@ -809,7 +824,7 @@ func (u *User) getACopy() User {
GCSConfig: vfs.GCSFsConfig{ GCSConfig: vfs.GCSFsConfig{
Bucket: u.FsConfig.GCSConfig.Bucket, Bucket: u.FsConfig.GCSConfig.Bucket,
CredentialFile: u.FsConfig.GCSConfig.CredentialFile, CredentialFile: u.FsConfig.GCSConfig.CredentialFile,
Credentials: u.FsConfig.GCSConfig.Credentials, Credentials: u.FsConfig.GCSConfig.Credentials.Clone(),
AutomaticCredentials: u.FsConfig.GCSConfig.AutomaticCredentials, AutomaticCredentials: u.FsConfig.GCSConfig.AutomaticCredentials,
StorageClass: u.FsConfig.GCSConfig.StorageClass, StorageClass: u.FsConfig.GCSConfig.StorageClass,
KeyPrefix: u.FsConfig.GCSConfig.KeyPrefix, KeyPrefix: u.FsConfig.GCSConfig.KeyPrefix,
@ -817,7 +832,7 @@ func (u *User) getACopy() User {
AzBlobConfig: vfs.AzBlobFsConfig{ AzBlobConfig: vfs.AzBlobFsConfig{
Container: u.FsConfig.AzBlobConfig.Container, Container: u.FsConfig.AzBlobConfig.Container,
AccountName: u.FsConfig.AzBlobConfig.AccountName, AccountName: u.FsConfig.AzBlobConfig.AccountName,
AccountKey: u.FsConfig.AzBlobConfig.AccountKey, AccountKey: u.FsConfig.AzBlobConfig.AccountKey.Clone(),
Endpoint: u.FsConfig.AzBlobConfig.Endpoint, Endpoint: u.FsConfig.AzBlobConfig.Endpoint,
SASURL: u.FsConfig.AzBlobConfig.SASURL, SASURL: u.FsConfig.AzBlobConfig.SASURL,
KeyPrefix: u.FsConfig.AzBlobConfig.KeyPrefix, KeyPrefix: u.FsConfig.AzBlobConfig.KeyPrefix,

View file

@ -28,6 +28,7 @@ For each account, the following properties can be configured:
- `chtimes` changing file or directory access and modification time is allowed - `chtimes` changing file or directory access and modification time is allowed
- `upload_bandwidth` maximum upload bandwidth as KB/s, 0 means unlimited. - `upload_bandwidth` maximum upload bandwidth as KB/s, 0 means unlimited.
- `download_bandwidth` maximum download bandwidth as KB/s, 0 means unlimited. - `download_bandwidth` maximum download bandwidth as KB/s, 0 means unlimited.
- `last_login` last user login as unix timestamp in milliseconds. It is saved at most once every 10 minutes
- `allowed_ip`, List of IP/Mask allowed to login. Any IP address not contained in this list cannot login. IP/Mask must be in CIDR notation as defined in RFC 4632 and RFC 4291, for example "192.0.2.0/24" or "2001:db8::/32" - `allowed_ip`, List of IP/Mask allowed to login. Any IP address not contained in this list cannot login. IP/Mask must be in CIDR notation as defined in RFC 4632 and RFC 4291, for example "192.0.2.0/24" or "2001:db8::/32"
- `denied_ip`, List of IP/Mask not allowed to login. If an IP address is both allowed and denied then login will be denied - `denied_ip`, List of IP/Mask not allowed to login. If an IP address is both allowed and denied then login will be denied
- `max_upload_file_size`, max allowed size, as bytes, for a single file upload. The upload will be aborted if/when the size of the file being sent exceeds this limit. 0 means unlimited. This restriction does not apply for SSH system commands such as `git` and `rsync` - `max_upload_file_size`, max allowed size, as bytes, for a single file upload. The upload will be aborted if/when the size of the file being sent exceeds this limit. 0 means unlimited. This restriction does not apply for SSH system commands such as `git` and `rsync`
@ -53,20 +54,20 @@ For each account, the following properties can be configured:
- `s3_bucket`, required for S3 filesystem - `s3_bucket`, required for S3 filesystem
- `s3_region`, required for S3 filesystem. Must match the region for your bucket. You can find here the list of available [AWS regions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions). For example if your bucket is at `Frankfurt` you have to set the region to `eu-central-1` - `s3_region`, required for S3 filesystem. Must match the region for your bucket. You can find here the list of available [AWS regions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions). For example if your bucket is at `Frankfurt` you have to set the region to `eu-central-1`
- `s3_access_key` - `s3_access_key`
- `s3_access_secret`, if provided it is stored encrypted (AES-256-GCM). You can leave access key and access secret blank to use credentials from environment - `s3_access_secret`, if provided it is stored encrypted based on kms configuration. You can leave access key and access secret blank to use credentials from environment
- `s3_endpoint`, specifies a S3 endpoint (server) different from AWS. It is not required if you are connecting to AWS - `s3_endpoint`, specifies a S3 endpoint (server) different from AWS. It is not required if you are connecting to AWS
- `s3_storage_class`, leave blank to use the default or specify a valid AWS [storage class](https://docs.aws.amazon.com/AmazonS3/latest/dev/storage-class-intro.html) - `s3_storage_class`, leave blank to use the default or specify a valid AWS [storage class](https://docs.aws.amazon.com/AmazonS3/latest/dev/storage-class-intro.html)
- `s3_key_prefix`, allows to restrict access to the folder identified by this prefix and its contents - `s3_key_prefix`, allows to restrict access to the folder identified by this prefix and its contents
- `s3_upload_part_size`, the buffer size for multipart uploads (MB). Zero means the default (5 MB). Minimum is 5 - `s3_upload_part_size`, the buffer size for multipart uploads (MB). Zero means the default (5 MB). Minimum is 5
- `s3_upload_concurrency` how many parts are uploaded in parallel - `s3_upload_concurrency` how many parts are uploaded in parallel
- `gcs_bucket`, required for GCS filesystem - `gcs_bucket`, required for GCS filesystem
- `gcs_credentials`, Google Cloud Storage JSON credentials base64 encoded - `gcs_credentials`, Google Cloud Storage JSON credentials base64 encoded. Credentials are stored encrypted based on kms configuration
- `gcs_automatic_credentials`, integer. Set to 1 to use Application Default Credentials strategy or set to 0 to use explicit credentials via `gcs_credentials` - `gcs_automatic_credentials`, integer. Set to 1 to use Application Default Credentials strategy or set to 0 to use explicit credentials via `gcs_credentials`
- `gcs_storage_class` - `gcs_storage_class`
- `gcs_key_prefix`, allows to restrict access to the folder identified by this prefix and its contents - `gcs_key_prefix`, allows to restrict access to the folder identified by this prefix and its contents
- `az_container`, Azure Blob Storage container - `az_container`, Azure Blob Storage container
- `az_account_name`, Azure account name. leave blank to use SAS URL - `az_account_name`, Azure account name. leave blank to use SAS URL
- `az_account_key`, Azure account key. leave blank to use SAS URL. If provided it is stored encrypted (AES-256-GCM) - `az_account_key`, Azure account key. leave blank to use SAS URL. If provided it is stored encrypted based on kms configuration
- `az_sas_url`, Azure shared access signature URL - `az_sas_url`, Azure shared access signature URL
- `az_endpoint`, Default is "blob.core.windows.net". If you use the emulator the endpoint must include the protocol, for example "http://127.0.0.1:10000" - `az_endpoint`, Default is "blob.core.windows.net". If you use the emulator the endpoint must include the protocol, for example "http://127.0.0.1:10000"
- `az_upload_part_size`, the buffer size for multipart uploads (MB). Zero means the default (4 MB) - `az_upload_part_size`, the buffer size for multipart uploads (MB). Zero means the default (4 MB)

View file

@ -163,6 +163,10 @@ The configuration file contains the following sections:
- `timeout`, integer. Timeout specifies a time limit, in seconds, for requests. - `timeout`, integer. Timeout specifies a time limit, in seconds, for requests.
- `ca_certificates`, list of strings. List of paths to extra CA certificates to trust. The paths can be absolute or relative to the config dir. Adding trusted CA certificates is a convenient way to use self-signed certificates without defeating the purpose of using TLS. - `ca_certificates`, list of strings. List of paths to extra CA certificates to trust. The paths can be absolute or relative to the config dir. Adding trusted CA certificates is a convenient way to use self-signed certificates without defeating the purpose of using TLS.
- `skip_tls_verify`, boolean. if enabled the HTTP client accepts any TLS certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to man-in-the-middle attacks. This should be used only for testing. - `skip_tls_verify`, boolean. if enabled the HTTP client accepts any TLS certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to man-in-the-middle attacks. This should be used only for testing.
- **kms**, configuration for the Key Management Service, more details can be found [here](./kms.md)
- `secrets`
- `url`
- `master_key_path`
A full example showing the default config (in JSON format) can be found [here](../sftpgo.json). A full example showing the default config (in JSON format) can be found [here](../sftpgo.json).

65
docs/kms.md Normal file
View file

@ -0,0 +1,65 @@
# Key Management Services
SFTPGo stores sensitive data such as Cloud accounts credentials. This data are stored as ciphertext and only loaded to RAM in plaintext when needed.
## Supported Services for encryption and decryption
The `secrets` section of the `kms` configuration allows to configure how to encrypt and decrypt sensitive data. The following configuration parameters are available:
- `url` defines the URI to the KMS service
- `master_key_path` defines the absolute path to a file containing the master encryption key. This could be, for example, a docker secrets or a file protected with filesystem level permissions.
We use [Go CDK](https://gocloud.dev/howto/secrets/) to access several key management services in a portable way.
### Local provider
If the `url` is empty SFTPGo uses local encryption for keeping secrets. Internally, it uses the [NaCl secret box](https://godoc.org/golang.org/x/crypto/nacl/secretbox) algorithm to perform encryption and authentication.
We first generate a random key, then the per-object encryption key is derived from this random key in the following way:
1. a master key is provided: the encryption key is derived using the HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as defined in [RFC 5869](http://tools.ietf.org/html/rfc5869)
2. no master key is provided: the encryption key is derived as simple hash of the random key. This is the default configuration.
For compatibility with SFTPGo versions 1.2.x and before we also support encryption based on `AES-256-GCM`. The data encrypted with this algorithm will never use the master key to keep backward compatibility.
### Google Cloud Key Management Service
To use keys from Google Cloud Platforms [Key Management Service](https://cloud.google.com/kms/) (GCP KMS) you have to use `gcpkms` as URL scheme like this:
```shell
gcpkms://projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]
```
SFTPGo will use Application Default Credentials. See [here](https://cloud.google.com/docs/authentication/production) for alternatives such as environment variables.
The URL host+path are used as the key resource ID; see [here](https://cloud.google.com/kms/docs/object-hierarchy#key) for more details.
If a master key is provided we first encrypt the plaintext data using the local provider and then we encrypt the resulting payload using the Cloud provider and store this ciphertext.
### AWS Key Management Service
To use customer master keys from Amazon Web Services [Key Management Service](https://aws.amazon.com/kms/) (AWS KMS) you have to use `awskms` as URL scheme. You can use the keys ID, alias, or Amazon Resource Name (ARN) to identify the key. You should specify the region query parameter to ensure your application connects to the correct region.
Here are some examples:
- By ID: `awskms://1234abcd-12ab-34cd-56ef-1234567890ab?region=us-east-1`
- By alias: `awskms://alias/ExampleAlias?region=us-east-1`
- By ARN: `arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34bc-56ef-1234567890ab?region=us-east-1`
SFTPGo will use the default AWS session. See [AWS Session](https://docs.aws.amazon.com/sdk-for-go/api/aws/session/) to learn about authentication alternatives such as environment variables.
If a master key is provided we first encrypt the plaintext data using the local provider and then we encrypt the resulting payload using the Cloud provider and store this ciphertext.
### HashiCorp Vault
To use the [transit secrets engine](https://www.vaultproject.io/docs/secrets/transit/index.html) in [Vault](https://www.vaultproject.io/) you have to use `hashivault` as URL scheme like this: `hashivault://mykey`.
The Vault server endpoint and authentication token are specified using the environment variables `VAULT_SERVER_URL` and `VAULT_SERVER_TOKEN`, respectively.
If a master key is provided we first encrypt the plaintext data using the local provider and then we encrypt the resulting payload using Vault and store this ciphertext.
### Notes
- The KMS configuration is global.
- If you set a master key you will be unable to decrypt the data without this key and the SFTPGo users that need the data as plain text will be unable to login.
- You can start using the local provider and then switch to an external one but you can't switch between external providers and still be able to decrypt the data encrypted using the previous provider.

View file

@ -29,7 +29,7 @@ The response is something like this:
{"message":"User created successfully.","user":{"id":xxxxxxxx},"success":true} {"message":"User created successfully.","user":{"id":xxxxxxxx},"success":true}
``` ```
Save the user id somewhere and add a reference to the matching SFTPGo account. Save the user id somewhere and add a reference to the matching SFTPGo account. You could also store this ID in the `additional_info` SFTPGo user field.
After this step you can use the Authy app installed on your phone to generate TOTP codes. After this step you can use the Authy app installed on your phone to generate TOTP codes.

View file

@ -60,8 +60,8 @@ Output:
"s3config": { "s3config": {
"access_key": "accesskey", "access_key": "accesskey",
"access_secret": { "access_secret": {
"payload": "ac46cec75466ba77e47f536436783b729ca5bbbb53252fda0de51f785a6da11ffb03", "payload": "dcd07e64a5ef5ede37b978198ca396ea9aee92453208ee2fee6f25407e47bf2119ba8edf2e81f91999bd5386c1a7",
"status": "AES-256-GCM" "status": "Secretbox"
}, },
"bucket": "test", "bucket": "test",
"endpoint": "http://127.0.0.1:9000", "endpoint": "http://127.0.0.1:9000",
@ -178,9 +178,16 @@ Output:
"download_bandwidth": 80, "download_bandwidth": 80,
"expiration_date": 0, "expiration_date": 0,
"filesystem": { "filesystem": {
"gcsconfig": {}, "azblobconfig": {
"account_key": {}
},
"gcsconfig": {
"credentials": {}
},
"provider": 0, "provider": 0,
"s3config": {} "s3config": {
"access_secret": {}
}
}, },
"filters": { "filters": {
"denied_ip": [ "denied_ip": [
@ -253,9 +260,16 @@ Output:
"download_bandwidth": 80, "download_bandwidth": 80,
"expiration_date": 0, "expiration_date": 0,
"filesystem": { "filesystem": {
"gcsconfig": {}, "azblobconfig": {
"account_key": {}
},
"gcsconfig": {
"credentials": {}
},
"provider": 0, "provider": 0,
"s3config": {} "s3config": {
"access_secret": {}
}
}, },
"filters": { "filters": {
"denied_ip": [ "denied_ip": [

View file

@ -26,6 +26,7 @@ import (
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/ftpd" "github.com/drakkan/sftpgo/ftpd"
"github.com/drakkan/sftpgo/httpd" "github.com/drakkan/sftpgo/httpd"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
) )
@ -131,6 +132,13 @@ func TestMain(m *testing.M) {
httpConfig := config.GetHTTPConfig() httpConfig := config.GetHTTPConfig()
httpConfig.Initialize(configDir) httpConfig.Initialize(configDir)
kmsConfig := config.GetKMSConfig()
err = kmsConfig.Initialize()
if err != nil {
logger.ErrorToConsole("error initializing kms: %v", err)
os.Exit(1)
}
httpdConf := config.GetHTTPDConfig() httpdConf := config.GetHTTPDConfig()
httpdConf.BindPort = 8079 httpdConf.BindPort = 8079
httpd.SetBaseURLAndCredentials("http://127.0.0.1:8079", "", "") httpd.SetBaseURLAndCredentials("http://127.0.0.1:8079", "", "")
@ -876,10 +884,7 @@ func TestLoginWithDatabaseCredentials(t *testing.T) {
u := getTestUser() u := getTestUser()
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test" u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = vfs.Secret{ u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret(`{ "type": "service_account" }`)
Status: vfs.SecretStatusPlain,
Payload: `{ "type": "service_account" }`,
}
providerConf := config.GetProviderConf() providerConf := config.GetProviderConf()
providerConf.PreferDatabaseCredentials = true providerConf.PreferDatabaseCredentials = true
@ -900,10 +905,10 @@ func TestLoginWithDatabaseCredentials(t *testing.T) {
user, _, err := httpd.AddUser(u, http.StatusOK) user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.GCSConfig.Credentials.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.GCSConfig.Credentials.GetStatus())
assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.Payload) assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.GetPayload())
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.AdditionalData) assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetAdditionalData())
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.Key) assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetKey())
assert.NoFileExists(t, credentialsFile) assert.NoFileExists(t, credentialsFile)
@ -928,10 +933,7 @@ func TestLoginInvalidFs(t *testing.T) {
u := getTestUser() u := getTestUser()
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test" u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = vfs.Secret{ u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
Status: vfs.SecretStatusPlain,
Payload: "invalid JSON for credentials",
}
user, _, err := httpd.AddUser(u, http.StatusOK) user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)

3
go.mod
View file

@ -23,6 +23,7 @@ require (
github.com/magiconair/properties v1.8.4 // indirect github.com/magiconair/properties v1.8.4 // indirect
github.com/mattn/go-sqlite3 v1.14.5 github.com/mattn/go-sqlite3 v1.14.5
github.com/miekg/dns v1.1.35 // indirect github.com/miekg/dns v1.1.35 // indirect
github.com/minio/sha256-simd v0.1.1
github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/otiai10/copy v1.2.0 github.com/otiai10/copy v1.2.0
github.com/pelletier/go-toml v1.8.1 // indirect github.com/pelletier/go-toml v1.8.1 // indirect
@ -43,6 +44,8 @@ require (
github.com/studio-b12/gowebdav v0.0.0-20200929080739-bdacfab94796 github.com/studio-b12/gowebdav v0.0.0-20200929080739-bdacfab94796
go.etcd.io/bbolt v1.3.5 go.etcd.io/bbolt v1.3.5
go.uber.org/automaxprocs v1.3.0 go.uber.org/automaxprocs v1.3.0
gocloud.dev v0.20.0
gocloud.dev/secrets/hashivault v0.20.0
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 // indirect golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 // indirect

163
go.sum
View file

@ -1,16 +1,21 @@
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.55.0/go.mod h1:ZHmoY+/lIMNkN2+fBmuTiqZ4inFhvQad8ft7MT8IV5Y=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.58.0/go.mod h1:W+9FnSUw6nhVwXlFcp1eL+krq5+HQUJeUogSeJZZiWg=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko=
@ -25,6 +30,7 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/firestore v1.2.0/go.mod h1:iISCjWnTpnoJT1R287xRdjvQHJrxQOpeah4phb5D3h0=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -33,23 +39,61 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.9.0/go.mod h1:m+/etGaqZbylxaNT876QGXqEHp4PR2Rq5GMqICWb9bU=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4= cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4=
cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho=
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-amqp-common-go/v3 v3.0.0/go.mod h1:SY08giD/XbhTz07tJdpw1SoxQXHPN30+DI3Z04SYqyg=
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-sdk-for-go v37.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-service-bus-go v0.10.1/go.mod h1:E/FOceuKAFUfpbIJDKWz/May6guE+eGibfGT6q+n1to=
github.com/Azure/azure-storage-blob-go v0.9.0/go.mod h1:8UBPbiOhrMQ4pLPi3gA1tXnpjrS76UYE/fo5A40vf4g=
github.com/Azure/azure-storage-blob-go v0.11.0 h1:WCTHKKNkHlzm7lzUNXRSD11784LwJqdrxnwWJxsJQHg= github.com/Azure/azure-storage-blob-go v0.11.0 h1:WCTHKKNkHlzm7lzUNXRSD11784LwJqdrxnwWJxsJQHg=
github.com/Azure/azure-storage-blob-go v0.11.0/go.mod h1:A0u4VjtpgZJ7Y7um/+ix2DHBuEKFC6sEIlj0xc13a4Q= github.com/Azure/azure-storage-blob-go v0.11.0/go.mod h1:A0u4VjtpgZJ7Y7um/+ix2DHBuEKFC6sEIlj0xc13a4Q=
github.com/Azure/go-amqp v0.12.6/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG138DPo=
github.com/Azure/go-amqp v0.12.7/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG138DPo=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest v0.9.3 h1:OZEIaBbMdUE/Js+BQKlpO81XlISgipr6yDJ+PSwsgi4=
github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/adal v0.9.2 h1:Aze/GQeAN1RRbGmnUJvUj+tFGBzFdIg3293/A9rbxC4=
github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw= github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
@ -70,7 +114,10 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.31.13/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.35.30 h1:ZT+70Tw1ar5U2bL81ZyIvcLorxlD1UoxoIgjsEkismY= github.com/aws/aws-sdk-go v1.35.30 h1:ZT+70Tw1ar5U2bL81ZyIvcLorxlD1UoxoIgjsEkismY=
github.com/aws/aws-sdk-go v1.35.30/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go v1.35.30/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
@ -83,6 +130,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@ -113,8 +161,11 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/drakkan/crypto v0.0.0-20201118124913-1ba5185435c1 h1:a+m/8QzsuNhWTVBprULP341v5EZZeKLleVSDcOiAS0c= github.com/drakkan/crypto v0.0.0-20201118124913-1ba5185435c1 h1:a+m/8QzsuNhWTVBprULP341v5EZZeKLleVSDcOiAS0c=
github.com/drakkan/crypto v0.0.0-20201118124913-1ba5185435c1/go.mod h1:HCh3rfXxsHzqOEbzc/nqz6WnUhb7Nv19n/o64V0Zmbg= github.com/drakkan/crypto v0.0.0-20201118124913-1ba5185435c1/go.mod h1:HCh3rfXxsHzqOEbzc/nqz6WnUhb7Nv19n/o64V0Zmbg=
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA= github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA=
@ -134,8 +185,10 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fclairamb/ftpserverlib v0.9.1-0.20201105003045-1edd6bf7ae53 h1:veX6+jZG1119HXbWAbU2tQ99Zz5BSaFf7tfLgjLjZGI= github.com/fclairamb/ftpserverlib v0.9.1-0.20201105003045-1edd6bf7ae53 h1:veX6+jZG1119HXbWAbU2tQ99Zz5BSaFf7tfLgjLjZGI=
github.com/fclairamb/ftpserverlib v0.9.1-0.20201105003045-1edd6bf7ae53/go.mod h1:sMPjxPuoVwwoV87gdPkyTb0dVofmCKpVZCQ3rMVokjc= github.com/fclairamb/ftpserverlib v0.9.1-0.20201105003045-1edd6bf7ae53/go.mod h1:sMPjxPuoVwwoV87gdPkyTb0dVofmCKpVZCQ3rMVokjc=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -151,18 +204,24 @@ github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -197,6 +256,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -206,10 +267,18 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-replayers/grpcreplay v0.1.0 h1:eNb1y9rZFmY4ax45uEEECSa8fsxGRU+8Bil52ASAwic=
github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk=
github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE=
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -217,19 +286,26 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.4.0 h1:kXcsA/rIGzJImVqPdhfnr6q0xsS9gU0515q1EPpJ9fE=
github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww=
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@ -247,16 +323,29 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -267,16 +356,27 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/vault/api v1.0.2 h1:/V9fULvLwt58vme/6Rkt/p/GtlresQv+Z9E6dgdANhs=
github.com/hashicorp/vault/api v1.0.2/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE=
github.com/hashicorp/vault/sdk v0.1.8 h1:pfF3KwA1yPlfpmcumNsFM4uo91WMasX5gTuIkItu9r0=
github.com/hashicorp/vault/sdk v0.1.8/go.mod h1:tHZfc6St71twLizWNHvnnbiGFo1aq0eD2jGPLtP8kAU=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -284,7 +384,9 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
@ -297,8 +399,10 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
@ -308,6 +412,8 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY= github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@ -325,15 +431,20 @@ github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@ -347,6 +458,7 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
@ -367,17 +479,21 @@ github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pires/go-proxyproto v0.3.2 h1:E5ig1h9SFGne7IWVY6yRu3UCzyAFkQIukXHMkdFUOCA= github.com/pires/go-proxyproto v0.3.2 h1:E5ig1h9SFGne7IWVY6yRu3UCzyAFkQIukXHMkdFUOCA=
github.com/pires/go-proxyproto v0.3.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pires/go-proxyproto v0.3.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
@ -438,14 +554,20 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY= github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
@ -474,6 +596,7 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -500,6 +623,7 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -519,6 +643,10 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
gocloud.dev v0.20.0 h1:mbEKMfnyPV7W1Rj35R1xXfjszs9dXkwSOq2KoFr25g8=
gocloud.dev v0.20.0/go.mod h1:+Y/RpSXrJthIOM8uFNzWp6MRu9pFPNFEEZrQMxpkfIc=
gocloud.dev/secrets/hashivault v0.20.0 h1:919urzRWksXrZNNqqlHvek9IE6lWvYM4m8dvFqXq4HU=
gocloud.dev/secrets/hashivault v0.20.0/go.mod h1:2nNlZ76i4JlT9qrPVKbINB01L8BGgv4wmq2Cqyz22dA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -540,6 +668,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@ -548,9 +677,11 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -565,6 +696,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -574,8 +706,10 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -600,11 +734,13 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -614,10 +750,12 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7 h1:s330+6z/Ko3J0o6rvOcwXe5nzs7UT9tLKHoOXYn6uE0= golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7 h1:s330+6z/Ko3J0o6rvOcwXe5nzs7UT9tLKHoOXYn6uE0=
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
@ -625,6 +763,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -635,6 +774,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@ -667,10 +807,15 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200317043434-63da46f3035e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200601175630-2caf76543d99/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200606014950-c42cb6316fb6/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200608174601-1b747fd94509/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@ -680,13 +825,16 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u
golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201118030313-598b068a9102 h1:kr6Ik/EJgxdTSLX+rSiDounHdHWMBu9Ks/ghr2hWNpo=
golang.org/x/tools v0.0.0-20201118030313-598b068a9102/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201118030313-598b068a9102/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@ -699,6 +847,7 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
@ -713,12 +862,15 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
@ -735,11 +887,15 @@ google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200317114155-1f3552e48f24/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200325114520-5b2d0af7952b/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200608115520-7c474a2e3482/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@ -751,8 +907,10 @@ google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201117123952-62d171c70ae1 h1:EVow1AaDgdoMjdO64/fntn4+RGTVor8YE/mkmIYsqFM= google.golang.org/genproto v0.0.0-20201117123952-62d171c70ae1 h1:EVow1AaDgdoMjdO64/fntn4+RGTVor8YE/mkmIYsqFM=
google.golang.org/genproto v0.0.0-20201117123952-62d171c70ae1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201117123952-62d171c70ae1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -784,11 +942,14 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/dutchcoders/goftp.v1 v1.0.0-20170301105846-ed59a591ce14 h1:tHqNpm9sPaE6BSuMLXBzgTwukQLdBEt4OYU2coQjEQQ=
gopkg.in/dutchcoders/goftp.v1 v1.0.0-20170301105846-ed59a591ce14/go.mod h1:nzmlZQ+UqB5+55CRTV/dOaiK8OrPl6Co96Ob8lH4Wxw= gopkg.in/dutchcoders/goftp.v1 v1.0.0-20170301105846-ed59a591ce14/go.mod h1:nzmlZQ+UqB5+55CRTV/dOaiK8OrPl6Co96Ob8lH4Wxw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@ -801,6 +962,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

View file

@ -11,6 +11,7 @@ import (
"github.com/drakkan/sftpgo/common" "github.com/drakkan/sftpgo/common"
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
) )
@ -82,6 +83,7 @@ func addUser(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", http.StatusBadRequest) sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return return
} }
user.SetEmptySecretsIfNil()
switch user.FsConfig.Provider { switch user.FsConfig.Provider {
case dataprovider.S3FilesystemProvider: case dataprovider.S3FilesystemProvider:
if user.FsConfig.S3Config.AccessSecret.IsRedacted() { if user.FsConfig.S3Config.AccessSecret.IsRedacted() {
@ -136,9 +138,9 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
return return
} }
currentPermissions := user.Permissions currentPermissions := user.Permissions
var currentS3AccessSecret vfs.Secret var currentS3AccessSecret *kms.Secret
var currentAzAccountKey vfs.Secret var currentAzAccountKey *kms.Secret
var currentGCSCredentials vfs.Secret var currentGCSCredentials *kms.Secret
if user.FsConfig.Provider == dataprovider.S3FilesystemProvider { if user.FsConfig.Provider == dataprovider.S3FilesystemProvider {
currentS3AccessSecret = user.FsConfig.S3Config.AccessSecret currentS3AccessSecret = user.FsConfig.S3Config.AccessSecret
} }
@ -157,6 +159,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", http.StatusBadRequest) sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return return
} }
user.SetEmptySecretsIfNil()
// we use new Permissions if passed otherwise the old ones // we use new Permissions if passed otherwise the old ones
if len(user.Permissions) == 0 { if len(user.Permissions) == 0 {
user.Permissions = currentPermissions user.Permissions = currentPermissions
@ -207,7 +210,7 @@ func disconnectUser(username string) {
} }
} }
func updateEncryptedSecrets(user *dataprovider.User, currentS3AccessSecret, currentAzAccountKey, currentGCSCredentials vfs.Secret) { func updateEncryptedSecrets(user *dataprovider.User, currentS3AccessSecret, currentAzAccountKey, currentGCSCredentials *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
if user.FsConfig.Provider == dataprovider.S3FilesystemProvider { if user.FsConfig.Provider == dataprovider.S3FilesystemProvider {
if !user.FsConfig.S3Config.AccessSecret.IsPlain() && !user.FsConfig.S3Config.AccessSecret.IsEmpty() { if !user.FsConfig.S3Config.AccessSecret.IsPlain() && !user.FsConfig.S3Config.AccessSecret.IsEmpty() {

View file

@ -21,6 +21,7 @@ import (
"github.com/drakkan/sftpgo/common" "github.com/drakkan/sftpgo/common"
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/httpclient" "github.com/drakkan/sftpgo/httpclient"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
"github.com/drakkan/sftpgo/version" "github.com/drakkan/sftpgo/version"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
@ -710,19 +711,41 @@ func compareAzBlobConfig(expected *dataprovider.User, actual *dataprovider.User)
return nil return nil
} }
func checkEncryptedSecret(expected, actual vfs.Secret) error { func areSecretEquals(expected, actual *kms.Secret) bool {
if expected == nil && actual == nil {
return true
}
if expected != nil && expected.IsEmpty() && actual == nil {
return true
}
if actual != nil && actual.IsEmpty() && expected == nil {
return true
}
return false
}
func checkEncryptedSecret(expected, actual *kms.Secret) error {
if areSecretEquals(expected, actual) {
return nil
}
if expected == nil && actual != nil && !actual.IsEmpty() {
return errors.New("secret mismatch")
}
if actual == nil && expected != nil && !expected.IsEmpty() {
return errors.New("secret mismatch")
}
if expected.IsPlain() && actual.IsEncrypted() { if expected.IsPlain() && actual.IsEncrypted() {
if actual.Payload == "" { if actual.GetPayload() == "" {
return errors.New("invalid secret payload") return errors.New("invalid secret payload")
} }
if actual.AdditionalData != "" { if actual.GetAdditionalData() != "" {
return errors.New("invalid secret additional data") return errors.New("invalid secret additional data")
} }
if actual.Key != "" { if actual.GetKey() != "" {
return errors.New("invalid secret key") return errors.New("invalid secret key")
} }
} else { } else {
if expected.Status != actual.Status || expected.Payload != actual.Payload { if expected.GetStatus() != actual.GetStatus() || expected.GetPayload() != actual.GetPayload() {
return errors.New("secret mismatch") return errors.New("secret mismatch")
} }
} }

View file

@ -32,6 +32,7 @@ import (
"github.com/drakkan/sftpgo/config" "github.com/drakkan/sftpgo/config"
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/httpd" "github.com/drakkan/sftpgo/httpd"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
@ -140,6 +141,12 @@ func TestMain(m *testing.M) {
httpConfig := config.GetHTTPConfig() httpConfig := config.GetHTTPConfig()
httpConfig.Initialize(configDir) httpConfig.Initialize(configDir)
kmsConfig := config.GetKMSConfig()
err = kmsConfig.Initialize()
if err != nil {
logger.ErrorToConsole("error initializing kms: %v", err)
os.Exit(1)
}
httpdConf := config.GetHTTPDConfig() httpdConf := config.GetHTTPDConfig()
@ -191,7 +198,7 @@ func TestMain(m *testing.M) {
defer testServer.Close() defer testServer.Close()
exitCode := m.Run() exitCode := m.Run()
//os.Remove(logfilePath) //nolint:errcheck os.Remove(logfilePath) //nolint:errcheck
os.RemoveAll(backupsPath) //nolint:errcheck os.RemoveAll(backupsPath) //nolint:errcheck
os.RemoveAll(credentialsPath) //nolint:errcheck os.RemoveAll(credentialsPath) //nolint:errcheck
os.Remove(certPath) //nolint:errcheck os.Remove(certPath) //nolint:errcheck
@ -439,14 +446,13 @@ func TestAddUserInvalidFsConfig(t *testing.T) {
u.FsConfig.S3Config.Bucket = "testbucket" u.FsConfig.S3Config.Bucket = "testbucket"
u.FsConfig.S3Config.Region = "eu-west-1" u.FsConfig.S3Config.Region = "eu-west-1"
u.FsConfig.S3Config.AccessKey = "access-key" u.FsConfig.S3Config.AccessKey = "access-key"
u.FsConfig.S3Config.AccessSecret.Payload = "access-secret" u.FsConfig.S3Config.AccessSecret = kms.NewSecret(kms.SecretStatusRedacted, "access-secret", "", "")
u.FsConfig.S3Config.AccessSecret.Status = vfs.SecretStatusRedacted
u.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?a=b" u.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?a=b"
u.FsConfig.S3Config.StorageClass = "Standard" //nolint:goconst u.FsConfig.S3Config.StorageClass = "Standard" //nolint:goconst
u.FsConfig.S3Config.KeyPrefix = "/adir/subdir/" u.FsConfig.S3Config.KeyPrefix = "/adir/subdir/"
_, _, err = httpd.AddUser(u, http.StatusBadRequest) _, _, err = httpd.AddUser(u, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
u.FsConfig.S3Config.AccessSecret.Status = vfs.SecretStatusPlain u.FsConfig.S3Config.AccessSecret.SetStatus(kms.SecretStatusPlain)
_, _, err = httpd.AddUser(u, http.StatusBadRequest) _, _, err = httpd.AddUser(u, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
u.FsConfig.S3Config.KeyPrefix = "" u.FsConfig.S3Config.KeyPrefix = ""
@ -468,20 +474,18 @@ func TestAddUserInvalidFsConfig(t *testing.T) {
u.FsConfig.GCSConfig.Bucket = "abucket" u.FsConfig.GCSConfig.Bucket = "abucket"
u.FsConfig.GCSConfig.StorageClass = "Standard" u.FsConfig.GCSConfig.StorageClass = "Standard"
u.FsConfig.GCSConfig.KeyPrefix = "/somedir/subdir/" u.FsConfig.GCSConfig.KeyPrefix = "/somedir/subdir/"
u.FsConfig.GCSConfig.Credentials.Payload = "test" //nolint:goconst u.FsConfig.GCSConfig.Credentials = kms.NewSecret(kms.SecretStatusRedacted, "test", "", "") //nolint:goconst
u.FsConfig.GCSConfig.Credentials.Status = vfs.SecretStatusRedacted
_, _, err = httpd.AddUser(u, http.StatusBadRequest) _, _, err = httpd.AddUser(u, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
u.FsConfig.GCSConfig.Credentials.Status = vfs.SecretStatusPlain u.FsConfig.GCSConfig.Credentials.SetStatus(kms.SecretStatusPlain)
_, _, err = httpd.AddUser(u, http.StatusBadRequest) _, _, err = httpd.AddUser(u, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
u.FsConfig.GCSConfig.KeyPrefix = "somedir/subdir/" //nolint:goconst u.FsConfig.GCSConfig.KeyPrefix = "somedir/subdir/" //nolint:goconst
u.FsConfig.GCSConfig.Credentials = vfs.Secret{} u.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
u.FsConfig.GCSConfig.AutomaticCredentials = 0 u.FsConfig.GCSConfig.AutomaticCredentials = 0
_, _, err = httpd.AddUser(u, http.StatusBadRequest) _, _, err = httpd.AddUser(u, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
u.FsConfig.GCSConfig.Credentials.Payload = "invalid" u.FsConfig.GCSConfig.Credentials = kms.NewSecret(kms.SecretStatusSecretBox, "invalid", "", "")
u.FsConfig.GCSConfig.Credentials.Status = vfs.SecretStatusAES256GCM
_, _, err = httpd.AddUser(u, http.StatusBadRequest) _, _, err = httpd.AddUser(u, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
@ -497,12 +501,11 @@ func TestAddUserInvalidFsConfig(t *testing.T) {
u.FsConfig.AzBlobConfig.Container = "container" u.FsConfig.AzBlobConfig.Container = "container"
_, _, err = httpd.AddUser(u, http.StatusBadRequest) _, _, err = httpd.AddUser(u, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
u.FsConfig.AzBlobConfig.AccountKey.Payload = "key" u.FsConfig.AzBlobConfig.AccountKey = kms.NewSecret(kms.SecretStatusRedacted, "key", "", "")
u.FsConfig.AzBlobConfig.AccountKey.Status = vfs.SecretStatusRedacted
u.FsConfig.AzBlobConfig.KeyPrefix = "/amedir/subdir/" u.FsConfig.AzBlobConfig.KeyPrefix = "/amedir/subdir/"
_, _, err = httpd.AddUser(u, http.StatusBadRequest) _, _, err = httpd.AddUser(u, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
u.FsConfig.AzBlobConfig.AccountKey.Status = vfs.SecretStatusPlain u.FsConfig.AzBlobConfig.AccountKey.SetStatus(kms.SecretStatusPlain)
_, _, err = httpd.AddUser(u, http.StatusBadRequest) _, _, err = httpd.AddUser(u, http.StatusBadRequest)
assert.NoError(t, err) assert.NoError(t, err)
u.FsConfig.AzBlobConfig.KeyPrefix = "amedir/subdir/" u.FsConfig.AzBlobConfig.KeyPrefix = "amedir/subdir/"
@ -1013,35 +1016,31 @@ func TestUserS3Config(t *testing.T) {
user.FsConfig.S3Config.Bucket = "test" //nolint:goconst user.FsConfig.S3Config.Bucket = "test" //nolint:goconst
user.FsConfig.S3Config.Region = "us-east-1" //nolint:goconst user.FsConfig.S3Config.Region = "us-east-1" //nolint:goconst
user.FsConfig.S3Config.AccessKey = "Server-Access-Key" user.FsConfig.S3Config.AccessKey = "Server-Access-Key"
user.FsConfig.S3Config.AccessSecret.Payload = "Server-Access-Secret" user.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("Server-Access-Secret")
user.FsConfig.S3Config.AccessSecret.Status = vfs.SecretStatusPlain
user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000" user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000"
user.FsConfig.S3Config.UploadPartSize = 8 user.FsConfig.S3Config.UploadPartSize = 8
user, body, err := httpd.UpdateUser(user, http.StatusOK, "") user, body, err := httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err, string(body)) assert.NoError(t, err, string(body))
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.S3Config.AccessSecret.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.S3Config.AccessSecret.GetStatus())
assert.NotEmpty(t, user.FsConfig.S3Config.AccessSecret.Payload) assert.NotEmpty(t, user.FsConfig.S3Config.AccessSecret.GetPayload())
assert.Empty(t, user.FsConfig.S3Config.AccessSecret.AdditionalData) assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetAdditionalData())
assert.Empty(t, user.FsConfig.S3Config.AccessSecret.Key) assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetKey())
_, err = httpd.RemoveUser(user, http.StatusOK) _, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
user.Password = defaultPassword user.Password = defaultPassword
user.ID = 0 user.ID = 0
secret := vfs.Secret{ secret := kms.NewSecret(kms.SecretStatusSecretBox, "Server-Access-Secret", "", "")
Payload: "Server-Access-Secret",
Status: vfs.SecretStatusAES256GCM,
}
user.FsConfig.S3Config.AccessSecret = secret user.FsConfig.S3Config.AccessSecret = secret
_, _, err = httpd.AddUser(user, http.StatusOK) _, _, err = httpd.AddUser(user, http.StatusOK)
assert.Error(t, err) assert.Error(t, err)
user.FsConfig.S3Config.AccessSecret.Status = vfs.SecretStatusPlain user.FsConfig.S3Config.AccessSecret.SetStatus(kms.SecretStatusPlain)
user, _, err = httpd.AddUser(user, http.StatusOK) user, _, err = httpd.AddUser(user, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
initialSecretPayload := user.FsConfig.S3Config.AccessSecret.Payload initialSecretPayload := user.FsConfig.S3Config.AccessSecret.GetPayload()
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.S3Config.AccessSecret.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.S3Config.AccessSecret.GetStatus())
assert.NotEmpty(t, initialSecretPayload) assert.NotEmpty(t, initialSecretPayload)
assert.Empty(t, user.FsConfig.S3Config.AccessSecret.AdditionalData) assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetAdditionalData())
assert.Empty(t, user.FsConfig.S3Config.AccessSecret.Key) assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetKey())
user.FsConfig.Provider = dataprovider.S3FilesystemProvider user.FsConfig.Provider = dataprovider.S3FilesystemProvider
user.FsConfig.S3Config.Bucket = "test-bucket" user.FsConfig.S3Config.Bucket = "test-bucket"
user.FsConfig.S3Config.Region = "us-east-1" //nolint:goconst user.FsConfig.S3Config.Region = "us-east-1" //nolint:goconst
@ -1051,16 +1050,16 @@ func TestUserS3Config(t *testing.T) {
user.FsConfig.S3Config.UploadConcurrency = 5 user.FsConfig.S3Config.UploadConcurrency = 5
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.S3Config.AccessSecret.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.S3Config.AccessSecret.GetStatus())
assert.Equal(t, initialSecretPayload, user.FsConfig.S3Config.AccessSecret.Payload) assert.Equal(t, initialSecretPayload, user.FsConfig.S3Config.AccessSecret.GetPayload())
assert.Empty(t, user.FsConfig.S3Config.AccessSecret.AdditionalData) assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetAdditionalData())
assert.Empty(t, user.FsConfig.S3Config.AccessSecret.Key) assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetKey())
// test user without access key and access secret (shared config state) // test user without access key and access secret (shared config state)
user.FsConfig.Provider = dataprovider.S3FilesystemProvider user.FsConfig.Provider = dataprovider.S3FilesystemProvider
user.FsConfig.S3Config.Bucket = "testbucket" user.FsConfig.S3Config.Bucket = "testbucket"
user.FsConfig.S3Config.Region = "us-east-1" user.FsConfig.S3Config.Region = "us-east-1"
user.FsConfig.S3Config.AccessKey = "" user.FsConfig.S3Config.AccessKey = ""
user.FsConfig.S3Config.AccessSecret = vfs.Secret{} user.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
user.FsConfig.S3Config.Endpoint = "" user.FsConfig.S3Config.Endpoint = ""
user.FsConfig.S3Config.KeyPrefix = "somedir/subdir" user.FsConfig.S3Config.KeyPrefix = "somedir/subdir"
user.FsConfig.S3Config.UploadPartSize = 6 user.FsConfig.S3Config.UploadPartSize = 6
@ -1089,49 +1088,46 @@ func TestUserGCSConfig(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
user.FsConfig.Provider = dataprovider.GCSFilesystemProvider user.FsConfig.Provider = dataprovider.GCSFilesystemProvider
user.FsConfig.GCSConfig.Bucket = "test" user.FsConfig.GCSConfig.Bucket = "test"
user.FsConfig.GCSConfig.Credentials.Payload = "fake credentials" //nolint:goconst user.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("fake credentials") //nolint:goconst
user.FsConfig.GCSConfig.Credentials.Status = vfs.SecretStatusPlain
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
credentialFile := filepath.Join(credentialsPath, fmt.Sprintf("%v_gcs_credentials.json", user.Username)) credentialFile := filepath.Join(credentialsPath, fmt.Sprintf("%v_gcs_credentials.json", user.Username))
assert.FileExists(t, credentialFile) assert.FileExists(t, credentialFile)
creds, err := ioutil.ReadFile(credentialFile) creds, err := ioutil.ReadFile(credentialFile)
assert.NoError(t, err) assert.NoError(t, err)
secret := &vfs.Secret{} secret := kms.NewEmptySecret()
err = json.Unmarshal(creds, secret) err = json.Unmarshal(creds, secret)
assert.NoError(t, err) assert.NoError(t, err)
err = secret.Decrypt() err = secret.Decrypt()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "fake credentials", secret.Payload) assert.Equal(t, "fake credentials", secret.GetPayload())
user.FsConfig.GCSConfig.Credentials.Payload = "fake encrypted credentials" user.FsConfig.GCSConfig.Credentials = kms.NewSecret(kms.SecretStatusSecretBox, "fake encrypted credentials", "", "")
user.FsConfig.GCSConfig.Credentials.Status = vfs.SecretStatusAES256GCM
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
assert.FileExists(t, credentialFile) assert.FileExists(t, credentialFile)
creds, err = ioutil.ReadFile(credentialFile) creds, err = ioutil.ReadFile(credentialFile)
assert.NoError(t, err) assert.NoError(t, err)
secret = &vfs.Secret{} secret = kms.NewEmptySecret()
err = json.Unmarshal(creds, secret) err = json.Unmarshal(creds, secret)
assert.NoError(t, err) assert.NoError(t, err)
err = secret.Decrypt() err = secret.Decrypt()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "fake credentials", secret.Payload) assert.Equal(t, "fake credentials", secret.GetPayload())
_, err = httpd.RemoveUser(user, http.StatusOK) _, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
user.Password = defaultPassword user.Password = defaultPassword
user.ID = 0 user.ID = 0
user.FsConfig.GCSConfig.Credentials.Payload = "fake credentials" user.FsConfig.GCSConfig.Credentials = kms.NewSecret(kms.SecretStatusSecretBox, "fake credentials", "", "")
user.FsConfig.GCSConfig.Credentials.Status = vfs.SecretStatusAES256GCM
_, _, err = httpd.AddUser(user, http.StatusOK) _, _, err = httpd.AddUser(user, http.StatusOK)
assert.Error(t, err) assert.Error(t, err)
user.FsConfig.GCSConfig.Credentials.Status = vfs.SecretStatusPlain user.FsConfig.GCSConfig.Credentials.SetStatus(kms.SecretStatusPlain)
user, body, err := httpd.AddUser(user, http.StatusOK) user, body, err := httpd.AddUser(user, http.StatusOK)
assert.NoError(t, err, string(body)) assert.NoError(t, err, string(body))
err = os.RemoveAll(credentialsPath) err = os.RemoveAll(credentialsPath)
assert.NoError(t, err) assert.NoError(t, err)
err = os.MkdirAll(credentialsPath, 0700) err = os.MkdirAll(credentialsPath, 0700)
assert.NoError(t, err) assert.NoError(t, err)
user.FsConfig.GCSConfig.Credentials = vfs.Secret{} user.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
user.FsConfig.GCSConfig.AutomaticCredentials = 1 user.FsConfig.GCSConfig.AutomaticCredentials = 1
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
@ -1141,8 +1137,7 @@ func TestUserGCSConfig(t *testing.T) {
user.FsConfig.S3Config.Bucket = "test1" user.FsConfig.S3Config.Bucket = "test1"
user.FsConfig.S3Config.Region = "us-east-1" user.FsConfig.S3Config.Region = "us-east-1"
user.FsConfig.S3Config.AccessKey = "Server-Access-Key1" user.FsConfig.S3Config.AccessKey = "Server-Access-Key1"
user.FsConfig.S3Config.AccessSecret.Payload = "secret" user.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("secret")
user.FsConfig.S3Config.AccessSecret.Status = vfs.SecretStatusPlain
user.FsConfig.S3Config.Endpoint = "http://localhost:9000" user.FsConfig.S3Config.Endpoint = "http://localhost:9000"
user.FsConfig.S3Config.KeyPrefix = "somedir/subdir" user.FsConfig.S3Config.KeyPrefix = "somedir/subdir"
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
@ -1150,8 +1145,7 @@ func TestUserGCSConfig(t *testing.T) {
user.FsConfig.S3Config = vfs.S3FsConfig{} user.FsConfig.S3Config = vfs.S3FsConfig{}
user.FsConfig.Provider = dataprovider.GCSFilesystemProvider user.FsConfig.Provider = dataprovider.GCSFilesystemProvider
user.FsConfig.GCSConfig.Bucket = "test1" user.FsConfig.GCSConfig.Bucket = "test1"
user.FsConfig.GCSConfig.Credentials.Payload = "fake credentials" user.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("fake credentials")
user.FsConfig.GCSConfig.Credentials.Status = vfs.SecretStatusPlain
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
@ -1165,49 +1159,42 @@ func TestUserAzureBlobConfig(t *testing.T) {
user.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider user.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider
user.FsConfig.AzBlobConfig.Container = "test" user.FsConfig.AzBlobConfig.Container = "test"
user.FsConfig.AzBlobConfig.AccountName = "Server-Account-Name" user.FsConfig.AzBlobConfig.AccountName = "Server-Account-Name"
user.FsConfig.AzBlobConfig.AccountKey.Payload = "Server-Account-Key" user.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret("Server-Account-Key")
user.FsConfig.AzBlobConfig.AccountKey.Status = vfs.SecretStatusPlain
user.FsConfig.AzBlobConfig.Endpoint = "http://127.0.0.1:9000" user.FsConfig.AzBlobConfig.Endpoint = "http://127.0.0.1:9000"
user.FsConfig.AzBlobConfig.UploadPartSize = 8 user.FsConfig.AzBlobConfig.UploadPartSize = 8
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
initialPayload := user.FsConfig.AzBlobConfig.AccountKey.Payload initialPayload := user.FsConfig.AzBlobConfig.AccountKey.GetPayload()
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.AzBlobConfig.AccountKey.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.AccountKey.GetStatus())
assert.NotEmpty(t, initialPayload) assert.NotEmpty(t, initialPayload)
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.AdditionalData) assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.Key) assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetKey())
user.FsConfig.AzBlobConfig.AccountKey.Status = vfs.SecretStatusAES256GCM user.FsConfig.AzBlobConfig.AccountKey.SetStatus(kms.SecretStatusSecretBox)
user.FsConfig.AzBlobConfig.AccountKey.AdditionalData = "data" user.FsConfig.AzBlobConfig.AccountKey.SetAdditionalData("data")
user.FsConfig.AzBlobConfig.AccountKey.Key = "fake key" user.FsConfig.AzBlobConfig.AccountKey.SetKey("fake key")
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.AzBlobConfig.AccountKey.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.AccountKey.GetStatus())
assert.Equal(t, initialPayload, user.FsConfig.AzBlobConfig.AccountKey.Payload) assert.Equal(t, initialPayload, user.FsConfig.AzBlobConfig.AccountKey.GetPayload())
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.AdditionalData) assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.Key) assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetKey())
_, err = httpd.RemoveUser(user, http.StatusOK) _, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
user.Password = defaultPassword user.Password = defaultPassword
user.ID = 0 user.ID = 0
secret := vfs.Secret{ secret := kms.NewSecret(kms.SecretStatusSecretBox, "Server-Account-Key", "", "")
Payload: "Server-Account-Key",
Status: vfs.SecretStatusAES256GCM,
}
user.FsConfig.AzBlobConfig.AccountKey = secret user.FsConfig.AzBlobConfig.AccountKey = secret
_, _, err = httpd.AddUser(user, http.StatusOK) _, _, err = httpd.AddUser(user, http.StatusOK)
assert.Error(t, err) assert.Error(t, err)
user.FsConfig.AzBlobConfig.AccountKey = vfs.Secret{ user.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret("Server-Account-Key-Test")
Payload: "Server-Account-Key-Test",
Status: vfs.SecretStatusPlain,
}
user, _, err = httpd.AddUser(user, http.StatusOK) user, _, err = httpd.AddUser(user, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
initialPayload = user.FsConfig.AzBlobConfig.AccountKey.Payload initialPayload = user.FsConfig.AzBlobConfig.AccountKey.GetPayload()
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.AzBlobConfig.AccountKey.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.AccountKey.GetStatus())
assert.NotEmpty(t, initialPayload) assert.NotEmpty(t, initialPayload)
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.AdditionalData) assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.Key) assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetKey())
user.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider user.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider
user.FsConfig.AzBlobConfig.Container = "test-container" user.FsConfig.AzBlobConfig.Container = "test-container"
user.FsConfig.AzBlobConfig.Endpoint = "http://localhost:9001" user.FsConfig.AzBlobConfig.Endpoint = "http://localhost:9001"
@ -1215,16 +1202,16 @@ func TestUserAzureBlobConfig(t *testing.T) {
user.FsConfig.AzBlobConfig.UploadConcurrency = 5 user.FsConfig.AzBlobConfig.UploadConcurrency = 5
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.AzBlobConfig.AccountKey.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.AccountKey.GetStatus())
assert.NotEmpty(t, initialPayload) assert.NotEmpty(t, initialPayload)
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.AdditionalData) assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.Key) assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetKey())
// test user without access key and access secret (sas) // test user without access key and access secret (sas)
user.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider user.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider
user.FsConfig.AzBlobConfig.SASURL = "https://myaccount.blob.core.windows.net/pictures/profile.jpg?sv=2012-02-12&st=2009-02-09&se=2009-02-10&sr=c&sp=r&si=YWJjZGVmZw%3d%3d&sig=dD80ihBh5jfNpymO5Hg1IdiJIEvHcJpCMiCMnN%2fRnbI%3d" user.FsConfig.AzBlobConfig.SASURL = "https://myaccount.blob.core.windows.net/pictures/profile.jpg?sv=2012-02-12&st=2009-02-09&se=2009-02-10&sr=c&sp=r&si=YWJjZGVmZw%3d%3d&sig=dD80ihBh5jfNpymO5Hg1IdiJIEvHcJpCMiCMnN%2fRnbI%3d"
user.FsConfig.AzBlobConfig.KeyPrefix = "somedir/subdir" user.FsConfig.AzBlobConfig.KeyPrefix = "somedir/subdir"
user.FsConfig.AzBlobConfig.AccountName = "" user.FsConfig.AzBlobConfig.AccountName = ""
user.FsConfig.AzBlobConfig.AccountKey = vfs.Secret{} user.FsConfig.AzBlobConfig.AccountKey = kms.NewEmptySecret()
user.FsConfig.AzBlobConfig.UploadPartSize = 6 user.FsConfig.AzBlobConfig.UploadPartSize = 6
user.FsConfig.AzBlobConfig.UploadConcurrency = 4 user.FsConfig.AzBlobConfig.UploadConcurrency = 4
user, _, err = httpd.UpdateUser(user, http.StatusOK, "") user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
@ -1260,8 +1247,7 @@ func TestUserHiddenFields(t *testing.T) {
u1.FsConfig.S3Config.Bucket = "test" u1.FsConfig.S3Config.Bucket = "test"
u1.FsConfig.S3Config.Region = "us-east-1" u1.FsConfig.S3Config.Region = "us-east-1"
u1.FsConfig.S3Config.AccessKey = "S3-Access-Key" u1.FsConfig.S3Config.AccessKey = "S3-Access-Key"
u1.FsConfig.S3Config.AccessSecret.Payload = "S3-Access-Secret" u1.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("S3-Access-Secret")
u1.FsConfig.S3Config.AccessSecret.Status = vfs.SecretStatusPlain
user1, _, err := httpd.AddUser(u1, http.StatusOK) user1, _, err := httpd.AddUser(u1, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
@ -1269,8 +1255,7 @@ func TestUserHiddenFields(t *testing.T) {
u2.Username = usernames[1] u2.Username = usernames[1]
u2.FsConfig.Provider = dataprovider.GCSFilesystemProvider u2.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u2.FsConfig.GCSConfig.Bucket = "test" u2.FsConfig.GCSConfig.Bucket = "test"
u2.FsConfig.GCSConfig.Credentials.Payload = "fake credentials" u2.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("fake credentials")
u2.FsConfig.GCSConfig.Credentials.Status = vfs.SecretStatusPlain
user2, _, err := httpd.AddUser(u2, http.StatusOK) user2, _, err := httpd.AddUser(u2, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
@ -1279,8 +1264,7 @@ func TestUserHiddenFields(t *testing.T) {
u3.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider u3.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider
u3.FsConfig.AzBlobConfig.Container = "test" u3.FsConfig.AzBlobConfig.Container = "test"
u3.FsConfig.AzBlobConfig.AccountName = "Server-Account-Name" u3.FsConfig.AzBlobConfig.AccountName = "Server-Account-Name"
u3.FsConfig.AzBlobConfig.AccountKey.Payload = "Server-Account-Key" u3.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret("Server-Account-Key")
u3.FsConfig.AzBlobConfig.AccountKey.Status = vfs.SecretStatusPlain
user3, _, err := httpd.AddUser(u3, http.StatusOK) user3, _, err := httpd.AddUser(u3, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
@ -1298,69 +1282,69 @@ func TestUserHiddenFields(t *testing.T) {
user1, _, err = httpd.GetUserByID(user1.ID, http.StatusOK) user1, _, err = httpd.GetUserByID(user1.ID, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, user1.Password) assert.Empty(t, user1.Password)
assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.Key) assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.GetKey())
assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.AdditionalData) assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.GetAdditionalData())
assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.Status) assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetStatus())
assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.Payload) assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetPayload())
user2, _, err = httpd.GetUserByID(user2.ID, http.StatusOK) user2, _, err = httpd.GetUserByID(user2.ID, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, user2.Password) assert.Empty(t, user2.Password)
assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.Key) assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetKey())
assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.AdditionalData) assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetAdditionalData())
assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.Status) assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetStatus())
assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.Payload) assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetPayload())
user3, _, err = httpd.GetUserByID(user3.ID, http.StatusOK) user3, _, err = httpd.GetUserByID(user3.ID, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, user3.Password) assert.Empty(t, user3.Password)
assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.Key) assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetKey())
assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.AdditionalData) assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.Status) assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetStatus())
assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.Payload) assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetPayload())
// finally check that we have all the data inside the data provider // finally check that we have all the data inside the data provider
user1, err = dataprovider.GetUserByID(user1.ID) user1, err = dataprovider.GetUserByID(user1.ID)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, user1.Password) assert.NotEmpty(t, user1.Password)
assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.Key) assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetKey())
assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.AdditionalData) assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetAdditionalData())
assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.Status) assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetStatus())
assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.Payload) assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetPayload())
err = user1.FsConfig.S3Config.AccessSecret.Decrypt() err = user1.FsConfig.S3Config.AccessSecret.Decrypt()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusPlain, user1.FsConfig.S3Config.AccessSecret.Status) assert.Equal(t, kms.SecretStatusPlain, user1.FsConfig.S3Config.AccessSecret.GetStatus())
assert.Equal(t, u1.FsConfig.S3Config.AccessSecret.Payload, user1.FsConfig.S3Config.AccessSecret.Payload) assert.Equal(t, u1.FsConfig.S3Config.AccessSecret.GetPayload(), user1.FsConfig.S3Config.AccessSecret.GetPayload())
assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.Key) assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.GetKey())
assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.AdditionalData) assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.GetAdditionalData())
user2, err = dataprovider.GetUserByID(user2.ID) user2, err = dataprovider.GetUserByID(user2.ID)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, user2.Password) assert.NotEmpty(t, user2.Password)
assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.Key) assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetKey())
assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.AdditionalData) assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetAdditionalData())
assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.Status) assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetStatus())
assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.Payload) assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetPayload())
err = user2.FsConfig.GCSConfig.Credentials.Decrypt() err = user2.FsConfig.GCSConfig.Credentials.Decrypt()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusPlain, user2.FsConfig.GCSConfig.Credentials.Status) assert.Equal(t, kms.SecretStatusPlain, user2.FsConfig.GCSConfig.Credentials.GetStatus())
assert.Equal(t, u2.FsConfig.GCSConfig.Credentials.Payload, user2.FsConfig.GCSConfig.Credentials.Payload) assert.Equal(t, u2.FsConfig.GCSConfig.Credentials.GetPayload(), user2.FsConfig.GCSConfig.Credentials.GetPayload())
assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.Key) assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetKey())
assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.AdditionalData) assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetAdditionalData())
user3, err = dataprovider.GetUserByID(user3.ID) user3, err = dataprovider.GetUserByID(user3.ID)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, user3.Password) assert.NotEmpty(t, user3.Password)
assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.Key) assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetKey())
assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.AdditionalData) assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.Status) assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetStatus())
assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.Payload) assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetPayload())
err = user3.FsConfig.AzBlobConfig.AccountKey.Decrypt() err = user3.FsConfig.AzBlobConfig.AccountKey.Decrypt()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusPlain, user3.FsConfig.AzBlobConfig.AccountKey.Status) assert.Equal(t, kms.SecretStatusPlain, user3.FsConfig.AzBlobConfig.AccountKey.GetStatus())
assert.Equal(t, u3.FsConfig.AzBlobConfig.AccountKey.Payload, user3.FsConfig.AzBlobConfig.AccountKey.Payload) assert.Equal(t, u3.FsConfig.AzBlobConfig.AccountKey.GetPayload(), user3.FsConfig.AzBlobConfig.AccountKey.GetPayload())
assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.Key) assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetKey())
assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.AdditionalData) assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
_, err = httpd.RemoveUser(user1, http.StatusOK) _, err = httpd.RemoveUser(user1, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
@ -1382,31 +1366,28 @@ func TestUserHiddenFields(t *testing.T) {
} }
func TestSecretObject(t *testing.T) { func TestSecretObject(t *testing.T) {
s := vfs.Secret{ s := kms.NewPlainSecret("test data")
Status: vfs.SecretStatusPlain, s.SetAdditionalData("username")
Payload: "test data",
AdditionalData: "username",
}
require.True(t, s.IsValid()) require.True(t, s.IsValid())
err := s.Encrypt() err := s.Encrypt()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, vfs.SecretStatusAES256GCM, s.Status) require.Equal(t, kms.SecretStatusSecretBox, s.GetStatus())
require.NotEmpty(t, s.Payload) require.NotEmpty(t, s.GetPayload())
require.NotEmpty(t, s.Key) require.NotEmpty(t, s.GetKey())
require.True(t, s.IsValid()) require.True(t, s.IsValid())
err = s.Decrypt() err = s.Decrypt()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, vfs.SecretStatusPlain, s.Status) require.Equal(t, kms.SecretStatusPlain, s.GetStatus())
require.Equal(t, "test data", s.Payload) require.Equal(t, "test data", s.GetPayload())
require.Empty(t, s.Key) require.Empty(t, s.GetKey())
oldFormat := "$aes$5b97e3a3324a2f53e2357483383367c0$0ed3132b584742ab217866219da633266782b69b13e50ebc6ddfb7c4fbf2f2a414c6d5f813" oldFormat := "$aes$5b97e3a3324a2f53e2357483383367c0$0ed3132b584742ab217866219da633266782b69b13e50ebc6ddfb7c4fbf2f2a414c6d5f813"
s, err = vfs.GetSecretFromCompatString(oldFormat) s, err = kms.GetSecretFromCompatString(oldFormat)
require.NoError(t, err) require.NoError(t, err)
require.True(t, s.IsValid()) require.True(t, s.IsValid())
require.Equal(t, vfs.SecretStatusPlain, s.Status) require.Equal(t, kms.SecretStatusPlain, s.GetStatus())
require.Equal(t, "test data", s.Payload) require.Equal(t, "test data", s.GetPayload())
require.Empty(t, s.Key) require.Empty(t, s.GetKey())
} }
func TestUpdateUserNoCredentials(t *testing.T) { func TestUpdateUserNoCredentials(t *testing.T) {
@ -3003,8 +2984,7 @@ func TestWebUserS3Mock(t *testing.T) {
user.FsConfig.S3Config.Bucket = "test" user.FsConfig.S3Config.Bucket = "test"
user.FsConfig.S3Config.Region = "eu-west-1" user.FsConfig.S3Config.Region = "eu-west-1"
user.FsConfig.S3Config.AccessKey = "access-key" user.FsConfig.S3Config.AccessKey = "access-key"
user.FsConfig.S3Config.AccessSecret.Payload = "access-secret" user.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("access-secret")
user.FsConfig.S3Config.AccessSecret.Status = vfs.SecretStatusPlain
user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?a=b" user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?a=b"
user.FsConfig.S3Config.StorageClass = "Standard" user.FsConfig.S3Config.StorageClass = "Standard"
user.FsConfig.S3Config.KeyPrefix = "somedir/subdir/" user.FsConfig.S3Config.KeyPrefix = "somedir/subdir/"
@ -3030,7 +3010,7 @@ func TestWebUserS3Mock(t *testing.T) {
form.Set("s3_bucket", user.FsConfig.S3Config.Bucket) form.Set("s3_bucket", user.FsConfig.S3Config.Bucket)
form.Set("s3_region", user.FsConfig.S3Config.Region) form.Set("s3_region", user.FsConfig.S3Config.Region)
form.Set("s3_access_key", user.FsConfig.S3Config.AccessKey) form.Set("s3_access_key", user.FsConfig.S3Config.AccessKey)
form.Set("s3_access_secret", user.FsConfig.S3Config.AccessSecret.Payload) form.Set("s3_access_secret", user.FsConfig.S3Config.AccessSecret.GetPayload())
form.Set("s3_storage_class", user.FsConfig.S3Config.StorageClass) form.Set("s3_storage_class", user.FsConfig.S3Config.StorageClass)
form.Set("s3_endpoint", user.FsConfig.S3Config.Endpoint) form.Set("s3_endpoint", user.FsConfig.S3Config.Endpoint)
form.Set("s3_key_prefix", user.FsConfig.S3Config.KeyPrefix) form.Set("s3_key_prefix", user.FsConfig.S3Config.KeyPrefix)
@ -3077,10 +3057,10 @@ func TestWebUserS3Mock(t *testing.T) {
assert.Equal(t, updateUser.FsConfig.S3Config.UploadPartSize, user.FsConfig.S3Config.UploadPartSize) assert.Equal(t, updateUser.FsConfig.S3Config.UploadPartSize, user.FsConfig.S3Config.UploadPartSize)
assert.Equal(t, updateUser.FsConfig.S3Config.UploadConcurrency, user.FsConfig.S3Config.UploadConcurrency) assert.Equal(t, updateUser.FsConfig.S3Config.UploadConcurrency, user.FsConfig.S3Config.UploadConcurrency)
assert.Equal(t, 2, len(updateUser.Filters.FileExtensions)) assert.Equal(t, 2, len(updateUser.Filters.FileExtensions))
assert.Equal(t, vfs.SecretStatusAES256GCM, updateUser.FsConfig.S3Config.AccessSecret.Status) assert.Equal(t, kms.SecretStatusSecretBox, updateUser.FsConfig.S3Config.AccessSecret.GetStatus())
assert.NotEmpty(t, updateUser.FsConfig.S3Config.AccessSecret.Payload) assert.NotEmpty(t, updateUser.FsConfig.S3Config.AccessSecret.GetPayload())
assert.Empty(t, updateUser.FsConfig.S3Config.AccessSecret.Key) assert.Empty(t, updateUser.FsConfig.S3Config.AccessSecret.GetKey())
assert.Empty(t, updateUser.FsConfig.S3Config.AccessSecret.AdditionalData) assert.Empty(t, updateUser.FsConfig.S3Config.AccessSecret.GetAdditionalData())
// now check that a redacted password is not saved // now check that a redacted password is not saved
form.Set("s3_access_secret", "[**redacted**] ") form.Set("s3_access_secret", "[**redacted**] ")
b, contentType, _ = getMultipartFormData(form, "", "") b, contentType, _ = getMultipartFormData(form, "", "")
@ -3096,10 +3076,10 @@ func TestWebUserS3Mock(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, len(users)) assert.Equal(t, 1, len(users))
lastUpdatedUser := users[0] lastUpdatedUser := users[0]
assert.Equal(t, vfs.SecretStatusAES256GCM, lastUpdatedUser.FsConfig.S3Config.AccessSecret.Status) assert.Equal(t, kms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.S3Config.AccessSecret.GetStatus())
assert.Equal(t, updateUser.FsConfig.S3Config.AccessSecret.Payload, lastUpdatedUser.FsConfig.S3Config.AccessSecret.Payload) assert.Equal(t, updateUser.FsConfig.S3Config.AccessSecret.GetPayload(), lastUpdatedUser.FsConfig.S3Config.AccessSecret.GetPayload())
assert.Empty(t, lastUpdatedUser.FsConfig.S3Config.AccessSecret.Key) assert.Empty(t, lastUpdatedUser.FsConfig.S3Config.AccessSecret.GetKey())
assert.Empty(t, lastUpdatedUser.FsConfig.S3Config.AccessSecret.AdditionalData) assert.Empty(t, lastUpdatedUser.FsConfig.S3Config.AccessSecret.GetAdditionalData())
// now clear credentials // now clear credentials
form.Set("s3_access_key", "") form.Set("s3_access_key", "")
form.Set("s3_access_secret", "") form.Set("s3_access_secret", "")
@ -3222,8 +3202,7 @@ func TestWebUserAzureBlobMock(t *testing.T) {
user.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider user.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider
user.FsConfig.AzBlobConfig.Container = "container" user.FsConfig.AzBlobConfig.Container = "container"
user.FsConfig.AzBlobConfig.AccountName = "aname" user.FsConfig.AzBlobConfig.AccountName = "aname"
user.FsConfig.AzBlobConfig.AccountKey.Payload = "access-skey" user.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret("access-skey")
user.FsConfig.AzBlobConfig.AccountKey.Status = vfs.SecretStatusPlain
user.FsConfig.AzBlobConfig.Endpoint = "http://127.0.0.1:9000/path?b=c" user.FsConfig.AzBlobConfig.Endpoint = "http://127.0.0.1:9000/path?b=c"
user.FsConfig.AzBlobConfig.KeyPrefix = "somedir/subdir/" user.FsConfig.AzBlobConfig.KeyPrefix = "somedir/subdir/"
user.FsConfig.AzBlobConfig.UploadPartSize = 5 user.FsConfig.AzBlobConfig.UploadPartSize = 5
@ -3248,7 +3227,7 @@ func TestWebUserAzureBlobMock(t *testing.T) {
form.Set("fs_provider", "3") form.Set("fs_provider", "3")
form.Set("az_container", user.FsConfig.AzBlobConfig.Container) form.Set("az_container", user.FsConfig.AzBlobConfig.Container)
form.Set("az_account_name", user.FsConfig.AzBlobConfig.AccountName) form.Set("az_account_name", user.FsConfig.AzBlobConfig.AccountName)
form.Set("az_account_key", user.FsConfig.AzBlobConfig.AccountKey.Payload) form.Set("az_account_key", user.FsConfig.AzBlobConfig.AccountKey.GetPayload())
form.Set("az_sas_url", user.FsConfig.AzBlobConfig.SASURL) form.Set("az_sas_url", user.FsConfig.AzBlobConfig.SASURL)
form.Set("az_endpoint", user.FsConfig.AzBlobConfig.Endpoint) form.Set("az_endpoint", user.FsConfig.AzBlobConfig.Endpoint)
form.Set("az_key_prefix", user.FsConfig.AzBlobConfig.KeyPrefix) form.Set("az_key_prefix", user.FsConfig.AzBlobConfig.KeyPrefix)
@ -3295,10 +3274,10 @@ func TestWebUserAzureBlobMock(t *testing.T) {
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadPartSize, user.FsConfig.AzBlobConfig.UploadPartSize) assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadPartSize, user.FsConfig.AzBlobConfig.UploadPartSize)
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadConcurrency, user.FsConfig.AzBlobConfig.UploadConcurrency) assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadConcurrency, user.FsConfig.AzBlobConfig.UploadConcurrency)
assert.Equal(t, 2, len(updateUser.Filters.FileExtensions)) assert.Equal(t, 2, len(updateUser.Filters.FileExtensions))
assert.Equal(t, vfs.SecretStatusAES256GCM, updateUser.FsConfig.AzBlobConfig.AccountKey.Status) assert.Equal(t, kms.SecretStatusSecretBox, updateUser.FsConfig.AzBlobConfig.AccountKey.GetStatus())
assert.NotEmpty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.Payload) assert.NotEmpty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetPayload())
assert.Empty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.Key) assert.Empty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetKey())
assert.Empty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.AdditionalData) assert.Empty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
// now check that a redacted password is not saved // now check that a redacted password is not saved
form.Set("az_account_key", "[**redacted**] ") form.Set("az_account_key", "[**redacted**] ")
b, contentType, _ = getMultipartFormData(form, "", "") b, contentType, _ = getMultipartFormData(form, "", "")
@ -3314,10 +3293,10 @@ func TestWebUserAzureBlobMock(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, len(users)) assert.Equal(t, 1, len(users))
lastUpdatedUser := users[0] lastUpdatedUser := users[0]
assert.Equal(t, vfs.SecretStatusAES256GCM, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.Status) assert.Equal(t, kms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetStatus())
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.AccountKey.Payload, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.Payload) assert.Equal(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetPayload(), lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetPayload())
assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.Key) assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetKey())
assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.AdditionalData) assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr.Code) checkResponseCode(t, http.StatusOK, rr.Code)
@ -3546,3 +3525,14 @@ func getMultipartFormData(values url.Values, fileFieldName, filePath string) (by
err := w.Close() err := w.Close()
return b, w.FormDataContentType(), err return b, w.FormDataContentType(), err
} }
func BenchmarkSecretDecryption(b *testing.B) {
s := kms.NewPlainSecret("test data")
s.SetAdditionalData("username")
err := s.Encrypt()
require.NoError(b, err)
for i := 0; i < b.N; i++ {
err = s.Clone().Decrypt()
require.NoError(b, err)
}
}

View file

@ -19,6 +19,7 @@ import (
"github.com/drakkan/sftpgo/common" "github.com/drakkan/sftpgo/common"
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
) )
@ -318,8 +319,11 @@ func TestCompareUserFields(t *testing.T) {
} }
func TestCompareUserFsConfig(t *testing.T) { func TestCompareUserFsConfig(t *testing.T) {
secretString := "access secret"
expected := &dataprovider.User{} expected := &dataprovider.User{}
actual := &dataprovider.User{} actual := &dataprovider.User{}
expected.SetEmptySecretsIfNil()
actual.SetEmptySecretsIfNil()
expected.FsConfig.Provider = dataprovider.S3FilesystemProvider expected.FsConfig.Provider = dataprovider.S3FilesystemProvider
err := compareUserFsConfig(expected, actual) err := compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
@ -336,36 +340,36 @@ func TestCompareUserFsConfig(t *testing.T) {
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
expected.FsConfig.S3Config.AccessKey = "" expected.FsConfig.S3Config.AccessKey = ""
actual.FsConfig.S3Config.AccessSecret.Payload = "access secret" actual.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret(secretString)
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
secret, _ := utils.EncryptData("access secret") secret, err := utils.EncryptData(secretString)
actual.FsConfig.S3Config.AccessSecret.Payload = "" assert.NoError(t, err)
expected.FsConfig.S3Config.AccessSecret.Payload = secret actual.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
kmsSecret, err := kms.GetSecretFromCompatString(secret)
assert.NoError(t, err)
expected.FsConfig.S3Config.AccessSecret = kmsSecret
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
expected.FsConfig.S3Config.AccessSecret.Payload = "test" expected.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret(secretString)
actual.FsConfig.S3Config.AccessSecret.Payload = "" actual.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
expected.FsConfig.S3Config.AccessSecret.Status = vfs.SecretStatusPlain expected.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret(secretString)
actual.FsConfig.S3Config.AccessSecret.Status = vfs.SecretStatusAES256GCM actual.FsConfig.S3Config.AccessSecret = kms.NewSecret(kms.SecretStatusSecretBox, "", "", "")
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
actual.FsConfig.S3Config.AccessSecret.Payload = "payload" actual.FsConfig.S3Config.AccessSecret = kms.NewSecret(kms.SecretStatusSecretBox, secretString, "", "data")
actual.FsConfig.S3Config.AccessSecret.AdditionalData = "data"
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
actual.FsConfig.S3Config.AccessSecret.AdditionalData = "" actual.FsConfig.S3Config.AccessSecret = kms.NewSecret(kms.SecretStatusSecretBox, secretString, "key", "")
actual.FsConfig.S3Config.AccessSecret.Key = "key"
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
expected.FsConfig.S3Config.AccessSecret.Status = "" expected.FsConfig.S3Config.AccessSecret = nil
expected.FsConfig.S3Config.AccessSecret.Payload = "" err = compareUserFsConfig(expected, actual)
actual.FsConfig.S3Config.AccessSecret.Status = "" assert.Error(t, err)
actual.FsConfig.S3Config.AccessSecret.Payload = "" expected.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
actual.FsConfig.S3Config.AccessSecret.AdditionalData = "" actual.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
actual.FsConfig.S3Config.AccessSecret.Key = ""
expected.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/" expected.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/"
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
@ -419,10 +423,10 @@ func TestCompareUserAzureConfig(t *testing.T) {
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
expected.FsConfig.AzBlobConfig.AccountName = "" expected.FsConfig.AzBlobConfig.AccountName = ""
expected.FsConfig.AzBlobConfig.AccountKey.Payload = "akey" expected.FsConfig.AzBlobConfig.AccountKey = kms.NewSecret(kms.SecretStatusAWS, "payload", "", "")
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)
expected.FsConfig.AzBlobConfig.AccountKey.Payload = "" expected.FsConfig.AzBlobConfig.AccountKey = kms.NewEmptySecret()
expected.FsConfig.AzBlobConfig.Endpoint = "endpt" expected.FsConfig.AzBlobConfig.Endpoint = "endpt"
err = compareUserFsConfig(expected, actual) err = compareUserFsConfig(expected, actual)
assert.Error(t, err) assert.Error(t, err)

View file

@ -2,7 +2,7 @@ openapi: 3.0.3
info: info:
title: SFTPGo title: SFTPGo
description: 'SFTPGo REST API' description: 'SFTPGo REST API'
version: 2.1.1 version: 2.1.2
servers: servers:
- url: /api/v1 - url: /api/v1
@ -965,6 +965,10 @@ components:
enum: enum:
- Plain - Plain
- AES-256-GCM - AES-256-GCM
- Secretbox
- GCP
- AWS
- VaultTransit
- Redacted - Redacted
description: Set to "Plain" to add or update an existing secret, set to "Redacted" to preserve the existing value description: Set to "Plain" to add or update an existing secret, set to "Redacted" to preserve the existing value
payload: payload:
@ -1234,7 +1238,7 @@ components:
last_login: last_login:
type: integer type: integer
format: int64 format: int64
description: Last user login as unix timestamp in milliseconds description: Last user login as unix timestamp in milliseconds. It is saved at most once every 10 minutes
filters: filters:
$ref: '#/components/schemas/UserFilters' $ref: '#/components/schemas/UserFilters'
filesystem: filesystem:

View file

@ -16,6 +16,7 @@ import (
"github.com/drakkan/sftpgo/common" "github.com/drakkan/sftpgo/common"
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
"github.com/drakkan/sftpgo/version" "github.com/drakkan/sftpgo/version"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
@ -205,6 +206,7 @@ func renderNotFoundPage(w http.ResponseWriter, err error) {
} }
func renderAddUserPage(w http.ResponseWriter, user dataprovider.User, error string) { func renderAddUserPage(w http.ResponseWriter, user dataprovider.User, error string) {
user.SetEmptySecretsIfNil()
data := userPage{ data := userPage{
basePage: getBasePageData("Add a new user", webUserPath), basePage: getBasePageData("Add a new user", webUserPath),
IsAdd: true, IsAdd: true,
@ -222,6 +224,7 @@ func renderAddUserPage(w http.ResponseWriter, user dataprovider.User, error stri
} }
func renderUpdateUserPage(w http.ResponseWriter, user dataprovider.User, error string) { func renderUpdateUserPage(w http.ResponseWriter, user dataprovider.User, error string) {
user.SetEmptySecretsIfNil()
data := userPage{ data := userPage{
basePage: getBasePageData("Update user", fmt.Sprintf("%v/%v", webUserPath, user.ID)), basePage: getBasePageData("Update user", fmt.Sprintf("%v/%v", webUserPath, user.ID)),
IsAdd: false, IsAdd: false,
@ -430,16 +433,13 @@ func getFiltersFromUserPostFields(r *http.Request) dataprovider.UserFilters {
return filters return filters
} }
func getSecretFromFormField(r *http.Request, field string) vfs.Secret { func getSecretFromFormField(r *http.Request, field string) *kms.Secret {
secret := vfs.Secret{ secret := kms.NewPlainSecret(r.Form.Get(field))
Payload: r.Form.Get(field), if strings.TrimSpace(secret.GetPayload()) == redactedSecret {
Status: vfs.SecretStatusPlain, secret.SetStatus(kms.SecretStatusRedacted)
} }
if strings.TrimSpace(secret.Payload) == redactedSecret { if strings.TrimSpace(secret.GetPayload()) == "" {
secret.Status = vfs.SecretStatusRedacted secret.SetStatus("")
}
if strings.TrimSpace(secret.Payload) == "" {
secret.Status = ""
} }
return secret return secret
} }
@ -492,10 +492,7 @@ func getFsConfigFromUserPostFields(r *http.Request) (dataprovider.Filesystem, er
} }
return fs, err return fs, err
} }
fs.GCSConfig.Credentials = vfs.Secret{ fs.GCSConfig.Credentials = kms.NewPlainSecret(string(fileBytes))
Status: vfs.SecretStatusPlain,
Payload: string(fileBytes),
}
fs.GCSConfig.AutomaticCredentials = 0 fs.GCSConfig.AutomaticCredentials = 0
} else if fs.Provider == dataprovider.AzureBlobFilesystemProvider { } else if fs.Provider == dataprovider.AzureBlobFilesystemProvider {
fs.AzBlobConfig.Container = r.Form.Get("az_container") fs.AzBlobConfig.Container = r.Form.Get("az_container")
@ -680,6 +677,7 @@ func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
return return
} }
updatedUser.ID = user.ID updatedUser.ID = user.ID
updatedUser.SetEmptySecretsIfNil()
if len(updatedUser.Password) == 0 { if len(updatedUser.Password) == 0 {
updatedUser.Password = user.Password updatedUser.Password = user.Password
} }

42
kms/aws.go Normal file
View file

@ -0,0 +1,42 @@
package kms
const (
awsProviderName = "AWS"
)
type awsSecret struct {
baseGCloudSecret
}
func newAWSSecret(base baseSecret, url, masterKey string) SecretProvider {
return &awsSecret{
baseGCloudSecret{
baseSecret: base,
url: url,
masterKey: masterKey,
},
}
}
func (s *awsSecret) Name() string {
return awsProviderName
}
func (s *awsSecret) IsEncrypted() bool {
return s.Status == SecretStatusAWS
}
func (s *awsSecret) Encrypt() error {
if err := s.baseGCloudSecret.Encrypt(); err != nil {
return err
}
s.Status = SecretStatusAWS
return nil
}
func (s *awsSecret) Decrypt() error {
if !s.IsEncrypted() {
return errWrongSecretStatus
}
return s.baseGCloudSecret.Decrypt()
}

99
kms/basegocloud.go Normal file
View file

@ -0,0 +1,99 @@
package kms
import (
"context"
"encoding/base64"
"time"
"gocloud.dev/secrets"
// import awskms package
_ "gocloud.dev/secrets/awskms"
// import gcpkms package
_ "gocloud.dev/secrets/gcpkms"
// import hashivault package
_ "gocloud.dev/secrets/hashivault"
)
type baseGCloudSecret struct {
baseSecret
masterKey string
url string
}
func (s *baseGCloudSecret) Encrypt() error {
if s.Status != SecretStatusPlain {
return errWrongSecretStatus
}
if s.Payload == "" {
return errInvalidSecret
}
payload := s.Payload
key := ""
if s.masterKey != "" {
localSecret := newLocalSecret(s.baseSecret, s.masterKey)
err := localSecret.Encrypt()
if err != nil {
return err
}
payload = localSecret.GetPayload()
key = localSecret.GetKey()
}
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(defaultTimeout))
defer cancelFn()
keeper, err := secrets.OpenKeeper(ctx, s.url)
if err != nil {
return err
}
defer keeper.Close()
ciphertext, err := keeper.Encrypt(context.Background(), []byte(payload))
if err != nil {
return err
}
s.Payload = base64.StdEncoding.EncodeToString(ciphertext)
s.Key = key
return nil
}
func (s *baseGCloudSecret) Decrypt() error {
encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
if err != nil {
return err
}
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(defaultTimeout))
defer cancelFn()
keeper, err := secrets.OpenKeeper(ctx, s.url)
if err != nil {
return err
}
defer keeper.Close()
plaintext, err := keeper.Decrypt(context.Background(), encrypted)
if err != nil {
return err
}
payload := string(plaintext)
if s.Key != "" {
baseSecret := baseSecret{
Status: SecretStatusSecretBox,
Payload: string(plaintext),
Key: s.Key,
AdditionalData: s.AdditionalData,
}
localSecret := newLocalSecret(baseSecret, s.masterKey)
err = localSecret.Decrypt()
if err != nil {
return err
}
payload = localSecret.GetPayload()
}
s.Status = SecretStatusPlain
s.Payload = payload
s.Key = ""
s.AdditionalData = ""
return nil
}

53
kms/basesecret.go Normal file
View file

@ -0,0 +1,53 @@
package kms
// baseSecret defines the base struct shared among all the secret providers
type baseSecret struct {
Status SecretStatus `json:"status,omitempty"`
Payload string `json:"payload,omitempty"`
Key string `json:"key,omitempty"`
AdditionalData string `json:"additional_data,omitempty"`
}
func (s *baseSecret) GetStatus() SecretStatus {
return s.Status
}
func (s *baseSecret) GetPayload() string {
return s.Payload
}
func (s *baseSecret) GetKey() string {
return s.Key
}
func (s *baseSecret) GetAdditionalData() string {
return s.AdditionalData
}
func (s *baseSecret) SetKey(value string) {
s.Key = value
}
func (s *baseSecret) SetAdditionalData(value string) {
s.AdditionalData = value
}
func (s *baseSecret) SetStatus(value SecretStatus) {
s.Status = value
}
func (s *baseSecret) isEmpty() bool {
if s.Status != "" {
return false
}
if s.Payload != "" {
return false
}
if s.Key != "" {
return false
}
if s.AdditionalData != "" {
return false
}
return true
}

122
kms/builtin.go Normal file
View file

@ -0,0 +1,122 @@
package kms
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"io"
"github.com/minio/sha256-simd"
)
const (
builtinProviderName = "Builtin"
)
type builtinSecret struct {
baseSecret
}
func newBuiltinSecret(base baseSecret) SecretProvider {
return &builtinSecret{
baseSecret: base,
}
}
func (s *builtinSecret) Name() string {
return builtinProviderName
}
func (s *builtinSecret) IsEncrypted() bool {
return s.Status == SecretStatusAES256GCM
}
func (s *builtinSecret) deriveKey(key []byte) []byte {
var combined []byte
combined = append(combined, key...)
if s.AdditionalData != "" {
combined = append(combined, []byte(s.AdditionalData)...)
}
combined = append(combined, key...)
hash := sha256.Sum256(combined)
return hash[:]
}
func (s *builtinSecret) Encrypt() error {
if s.Payload == "" {
return errInvalidSecret
}
switch s.Status {
case SecretStatusPlain:
key := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return err
}
block, err := aes.NewCipher(s.deriveKey(key))
if err != nil {
return err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return err
}
var aad []byte
if s.AdditionalData != "" {
aad = []byte(s.AdditionalData)
}
ciphertext := gcm.Seal(nonce, nonce, []byte(s.Payload), aad)
s.Key = hex.EncodeToString(key)
s.Payload = hex.EncodeToString(ciphertext)
s.Status = SecretStatusAES256GCM
return nil
default:
return errWrongSecretStatus
}
}
func (s *builtinSecret) Decrypt() error {
switch s.Status {
case SecretStatusAES256GCM:
encrypted, err := hex.DecodeString(s.Payload)
if err != nil {
return err
}
key, err := hex.DecodeString(s.Key)
if err != nil {
return err
}
block, err := aes.NewCipher(s.deriveKey(key))
if err != nil {
return err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
nonceSize := gcm.NonceSize()
if len(encrypted) < nonceSize {
return errMalformedCiphertext
}
nonce, ciphertext := encrypted[:nonceSize], encrypted[nonceSize:]
var aad []byte
if s.AdditionalData != "" {
aad = []byte(s.AdditionalData)
}
plaintext, err := gcm.Open(nil, nonce, ciphertext, aad)
if err != nil {
return err
}
s.Status = SecretStatusPlain
s.Payload = string(plaintext)
s.Key = ""
s.AdditionalData = ""
return nil
default:
return errWrongSecretStatus
}
}

42
kms/gcp.go Normal file
View file

@ -0,0 +1,42 @@
package kms
const (
gcpProviderName = "GCP"
)
type gcpSecret struct {
baseGCloudSecret
}
func newGCPSecret(base baseSecret, url, masterKey string) SecretProvider {
return &gcpSecret{
baseGCloudSecret{
baseSecret: base,
url: url,
masterKey: masterKey,
},
}
}
func (s *gcpSecret) Name() string {
return gcpProviderName
}
func (s *gcpSecret) IsEncrypted() bool {
return s.Status == SecretStatusGCP
}
func (s *gcpSecret) Encrypt() error {
if err := s.baseGCloudSecret.Encrypt(); err != nil {
return err
}
s.Status = SecretStatusGCP
return nil
}
func (s *gcpSecret) Decrypt() error {
if !s.IsEncrypted() {
return errWrongSecretStatus
}
return s.baseGCloudSecret.Decrypt()
}

327
kms/kms.go Normal file
View file

@ -0,0 +1,327 @@
package kms
import (
"encoding/json"
"errors"
"io/ioutil"
"strings"
"time"
"github.com/drakkan/sftpgo/utils"
)
// SecretProvider defines the interface for a KMS secrets provider
type SecretProvider interface {
Name() string
Encrypt() error
Decrypt() error
IsEncrypted() bool
GetStatus() SecretStatus
GetPayload() string
GetKey() string
GetAdditionalData() string
SetKey(string)
SetAdditionalData(string)
SetStatus(SecretStatus)
}
// SecretStatus defines the statuses of a Secret object
type SecretStatus = string
const (
// SecretStatusPlain means the secret is in plain text and must be encrypted
SecretStatusPlain SecretStatus = "Plain"
// SecretStatusAES256GCM means the secret is encrypted using AES-256-GCM
SecretStatusAES256GCM SecretStatus = "AES-256-GCM"
// SecretStatusSecretBox means the secret is encrypted using a locally provided symmetric key
SecretStatusSecretBox SecretStatus = "Secretbox"
// SecretStatusGCP means we use keys from Google Cloud Platforms Key Management Service
// (GCP KMS) to keep information secret
SecretStatusGCP SecretStatus = "GCP"
// SecretStatusAWS means we use customer master keys from Amazon Web Services
// Key Management Service (AWS KMS) to keep information secret
SecretStatusAWS SecretStatus = "AWS"
// SecretStatusVaultTransit means we use the transit secrets engine in Vault
// to keep information secret
SecretStatusVaultTransit SecretStatus = "VaultTransit"
// SecretStatusRedacted means the secret is redacted
SecretStatusRedacted SecretStatus = "Redacted"
)
// Configuration defines the KMS configuration
type Configuration struct {
Secrets Secrets `json:"secrets" mapstructure:"secrets"`
}
// Secrets define the KMS configuration for encryption/decryption
type Secrets struct {
URL string `json:"url" mapstructure:"url"`
MasterKeyPath string `json:"master_key_path" mapstructure:"master_key_path"`
masterKey string
}
var (
errWrongSecretStatus = errors.New("wrong secret status")
errMalformedCiphertext = errors.New("malformed ciphertext")
errInvalidSecret = errors.New("invalid secret")
validSecretStatuses = []string{SecretStatusPlain, SecretStatusAES256GCM, SecretStatusSecretBox,
SecretStatusVaultTransit, SecretStatusAWS, SecretStatusGCP, SecretStatusRedacted}
config Configuration
defaultTimeout = 10 * time.Second
)
// NewSecret builds a new Secret using the provided arguments
func NewSecret(status SecretStatus, payload, key, data string) *Secret {
return config.newSecret(status, payload, key, data)
}
// NewEmptySecret returns an empty secret
func NewEmptySecret() *Secret {
return NewSecret("", "", "", "")
}
// NewPlainSecret stores the give payload in a plain text secret
func NewPlainSecret(payload string) *Secret {
return NewSecret(SecretStatusPlain, payload, "", "")
}
// GetSecretFromCompatString returns a secret from the previous format
func GetSecretFromCompatString(secret string) (*Secret, error) {
plain, err := utils.DecryptData(secret)
if err != nil {
return &Secret{}, errMalformedCiphertext
}
return NewSecret(SecretStatusPlain, plain, "", ""), nil
}
// Initialize configures the KMS support
func (c *Configuration) Initialize() error {
if c.Secrets.MasterKeyPath != "" {
mKey, err := ioutil.ReadFile(c.Secrets.MasterKeyPath)
if err != nil {
return err
}
c.Secrets.masterKey = strings.TrimSpace(string(mKey))
}
config = *c
return nil
}
func (c *Configuration) newSecret(status SecretStatus, payload, key, data string) *Secret {
base := baseSecret{
Status: status,
Key: key,
Payload: payload,
AdditionalData: data,
}
return &Secret{
provider: c.getSecretProvider(base),
}
}
func (c *Configuration) getSecretProvider(base baseSecret) SecretProvider {
if strings.HasPrefix(c.Secrets.URL, "hashivault://") {
return newVaultSecret(base, c.Secrets.URL, c.Secrets.masterKey)
}
if strings.HasPrefix(c.Secrets.URL, "awskms://") {
return newAWSSecret(base, c.Secrets.URL, c.Secrets.masterKey)
}
if strings.HasPrefix(c.Secrets.URL, "gcpkms://") {
return newGCPSecret(base, c.Secrets.URL, c.Secrets.masterKey)
}
return newLocalSecret(base, c.Secrets.masterKey)
}
// Secret defines the struct used to store confidential data
type Secret struct {
provider SecretProvider
}
// MarshalJSON return the JSON encoding of the Secret object
func (s *Secret) MarshalJSON() ([]byte, error) {
return json.Marshal(&baseSecret{
Status: s.provider.GetStatus(),
Payload: s.provider.GetPayload(),
Key: s.provider.GetKey(),
AdditionalData: s.provider.GetAdditionalData(),
})
}
// UnmarshalJSON parses the JSON-encoded data and stores the result
// in the Secret object
func (s *Secret) UnmarshalJSON(data []byte) error {
baseSecret := baseSecret{}
err := json.Unmarshal(data, &baseSecret)
if err != nil {
return err
}
if baseSecret.isEmpty() {
s.provider = config.getSecretProvider(baseSecret)
return nil
}
switch baseSecret.Status {
case SecretStatusAES256GCM:
s.provider = newBuiltinSecret(baseSecret)
case SecretStatusSecretBox:
s.provider = newLocalSecret(baseSecret, config.Secrets.masterKey)
case SecretStatusVaultTransit:
s.provider = newVaultSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey)
case SecretStatusAWS:
s.provider = newAWSSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey)
case SecretStatusGCP:
s.provider = newGCPSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey)
case SecretStatusPlain, SecretStatusRedacted:
s.provider = config.getSecretProvider(baseSecret)
default:
return errInvalidSecret
}
return nil
}
// Clone returns a copy of the secret object
func (s *Secret) Clone() *Secret {
baseSecret := baseSecret{
Status: s.provider.GetStatus(),
Payload: s.provider.GetPayload(),
Key: s.provider.GetKey(),
AdditionalData: s.provider.GetAdditionalData(),
}
switch s.provider.Name() {
case builtinProviderName:
return &Secret{
provider: newBuiltinSecret(baseSecret),
}
case awsProviderName:
return &Secret{
provider: newAWSSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey),
}
case gcpProviderName:
return &Secret{
provider: newGCPSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey),
}
case localProviderName:
return &Secret{
provider: newLocalSecret(baseSecret, config.Secrets.masterKey),
}
case vaultProviderName:
return &Secret{
provider: newVaultSecret(baseSecret, config.Secrets.URL, config.Secrets.masterKey),
}
}
return NewSecret(s.GetStatus(), s.GetPayload(), s.GetKey(), s.GetAdditionalData())
}
// IsEncrypted returns true if the secret is encrypted
// This isn't a pointer receiver because we don't want to pass
// a pointer to html template
func (s *Secret) IsEncrypted() bool {
return s.provider.IsEncrypted()
}
// IsPlain returns true if the secret is in plain text
func (s *Secret) IsPlain() bool {
return s.provider.GetStatus() == SecretStatusPlain
}
// IsRedacted returns true if the secret is redacted
func (s *Secret) IsRedacted() bool {
return s.provider.GetStatus() == SecretStatusRedacted
}
// GetPayload returns the secret payload
func (s *Secret) GetPayload() string {
return s.provider.GetPayload()
}
// GetAdditionalData returns the secret additional data
func (s *Secret) GetAdditionalData() string {
return s.provider.GetAdditionalData()
}
// GetStatus returns the secret status
func (s *Secret) GetStatus() SecretStatus {
return s.provider.GetStatus()
}
// GetKey returns the secret key
func (s *Secret) GetKey() string {
return s.provider.GetKey()
}
// SetAdditionalData sets the given additional data
func (s *Secret) SetAdditionalData(value string) {
s.provider.SetAdditionalData(value)
}
// SetStatus sets the status for this secret
func (s *Secret) SetStatus(value SecretStatus) {
s.provider.SetStatus(value)
}
// SetKey sets the key for this secret
func (s *Secret) SetKey(value string) {
s.provider.SetKey(value)
}
// IsEmpty returns true if all fields are empty
func (s *Secret) IsEmpty() bool {
if s.provider.GetStatus() != "" {
return false
}
if s.provider.GetPayload() != "" {
return false
}
if s.provider.GetKey() != "" {
return false
}
if s.provider.GetAdditionalData() != "" {
return false
}
return true
}
// IsValid returns true if the secret is not empty and valid
func (s *Secret) IsValid() bool {
if !s.IsValidInput() {
return false
}
switch s.provider.GetStatus() {
case SecretStatusAES256GCM, SecretStatusSecretBox:
if len(s.provider.GetKey()) != 64 {
return false
}
case SecretStatusAWS, SecretStatusGCP, SecretStatusVaultTransit:
key := s.provider.GetKey()
if key != "" && len(key) != 64 {
return false
}
}
return true
}
// IsValidInput returns true if the secret is a valid user input
func (s *Secret) IsValidInput() bool {
if !utils.IsStringInSlice(s.provider.GetStatus(), validSecretStatuses) {
return false
}
if s.provider.GetPayload() == "" {
return false
}
return true
}
// Hide hides info to decrypt data
func (s *Secret) Hide() {
s.provider.SetKey("")
s.provider.SetAdditionalData("")
}
// Encrypt encrypts a plain text Secret object
func (s *Secret) Encrypt() error {
return s.provider.Encrypt()
}
// Decrypt decrypts a Secret object
func (s *Secret) Decrypt() error {
return s.provider.Decrypt()
}

120
kms/local.go Normal file
View file

@ -0,0 +1,120 @@
package kms
import (
"context"
"encoding/base64"
"encoding/hex"
"io"
"github.com/minio/sha256-simd"
"gocloud.dev/secrets/localsecrets"
"golang.org/x/crypto/hkdf"
)
const (
localProviderName = "Local"
)
type localSecret struct {
baseSecret
masterKey string
}
func newLocalSecret(base baseSecret, masterKey string) SecretProvider {
return &localSecret{
baseSecret: base,
masterKey: masterKey,
}
}
func (s *localSecret) Name() string {
return localProviderName
}
func (s *localSecret) IsEncrypted() bool {
return s.Status == SecretStatusSecretBox
}
func (s *localSecret) Encrypt() error {
if s.Status != SecretStatusPlain {
return errWrongSecretStatus
}
if s.Payload == "" {
return errInvalidSecret
}
secretKey, err := localsecrets.NewRandomKey()
if err != nil {
return err
}
key, err := s.deriveKey(secretKey[:])
if err != nil {
return err
}
keeper := localsecrets.NewKeeper(key)
defer keeper.Close()
ciphertext, err := keeper.Encrypt(context.Background(), []byte(s.Payload))
if err != nil {
return err
}
s.Key = hex.EncodeToString(secretKey[:])
s.Payload = base64.StdEncoding.EncodeToString(ciphertext)
s.Status = SecretStatusSecretBox
return nil
}
func (s *localSecret) Decrypt() error {
if !s.IsEncrypted() {
return errWrongSecretStatus
}
encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
if err != nil {
return err
}
secretKey, err := hex.DecodeString(s.Key)
if err != nil {
return err
}
key, err := s.deriveKey(secretKey[:])
if err != nil {
return err
}
keeper := localsecrets.NewKeeper(key)
defer keeper.Close()
plaintext, err := keeper.Decrypt(context.Background(), encrypted)
if err != nil {
return err
}
s.Status = SecretStatusPlain
s.Payload = string(plaintext)
s.Key = ""
s.AdditionalData = ""
return nil
}
func (s *localSecret) deriveKey(key []byte) ([32]byte, error) {
var masterKey []byte
if s.masterKey == "" {
var combined []byte
combined = append(combined, key...)
if s.AdditionalData != "" {
combined = append(combined, []byte(s.AdditionalData)...)
}
combined = append(combined, key...)
hash := sha256.Sum256(combined)
masterKey = hash[:]
} else {
masterKey = []byte(s.masterKey)
}
var derivedKey [32]byte
var info []byte
if s.AdditionalData != "" {
info = []byte(s.AdditionalData)
}
kdf := hkdf.New(sha256.New, masterKey, key, info)
if _, err := io.ReadFull(kdf, derivedKey[:]); err != nil {
return derivedKey, err
}
return derivedKey, nil
}

42
kms/vault.go Normal file
View file

@ -0,0 +1,42 @@
package kms
const (
vaultProviderName = "VaultTransit"
)
type vaultSecret struct {
baseGCloudSecret
}
func newVaultSecret(base baseSecret, url, masterKey string) SecretProvider {
return &vaultSecret{
baseGCloudSecret{
baseSecret: base,
url: url,
masterKey: masterKey,
},
}
}
func (s *vaultSecret) Name() string {
return vaultProviderName
}
func (s *vaultSecret) IsEncrypted() bool {
return s.Status == SecretStatusVaultTransit
}
func (s *vaultSecret) Encrypt() error {
if err := s.baseGCloudSecret.Encrypt(); err != nil {
return err
}
s.Status = SecretStatusVaultTransit
return nil
}
func (s *vaultSecret) Decrypt() error {
if !s.IsEncrypted() {
return errWrongSecretStatus
}
return s.baseGCloudSecret.Decrypt()
}

View file

@ -109,6 +109,13 @@ func (s *Service) Start() error {
httpConfig := config.GetHTTPConfig() httpConfig := config.GetHTTPConfig()
httpConfig.Initialize(s.ConfigDir) httpConfig.Initialize(s.ConfigDir)
kmsConfig := config.GetKMSConfig()
err = kmsConfig.Initialize()
if err != nil {
logger.Error(logSender, "", "unable to initialize KMS: %v", err)
logger.ErrorToConsole("unable to initialize KMS: %v", err)
os.Exit(1)
}
s.startServices() s.startServices()

View file

@ -15,6 +15,7 @@ import (
"github.com/drakkan/sftpgo/config" "github.com/drakkan/sftpgo/config"
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/sftpd" "github.com/drakkan/sftpgo/sftpd"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
@ -32,6 +33,11 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS
if err != nil { if err != nil {
fmt.Printf("error loading configuration file: %v using defaults\n", err) fmt.Printf("error loading configuration file: %v using defaults\n", err)
} }
kmsConfig := config.GetKMSConfig()
err = kmsConfig.Initialize()
if err != nil {
return err
}
printablePassword := s.configurePortableUser() printablePassword := s.configurePortableUser()
dataProviderConf := config.GetProviderConf() dataProviderConf := config.GetProviderConf()
dataProviderConf.Driver = dataprovider.MemoryDataProviderName dataProviderConf.Driver = dataprovider.MemoryDataProviderName
@ -237,5 +243,23 @@ func (s *Service) configurePortableUser() string {
s.PortableUser.Password = b.String() s.PortableUser.Password = b.String()
printablePassword = s.PortableUser.Password printablePassword = s.PortableUser.Password
} }
// we created the user before to initialize the KMS so we need to create the secret here
switch s.PortableUser.FsConfig.Provider {
case dataprovider.S3FilesystemProvider:
payload := s.PortableUser.FsConfig.S3Config.AccessSecret.GetPayload()
if payload != "" {
s.PortableUser.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret(payload)
}
case dataprovider.GCSFilesystemProvider:
payload := s.PortableUser.FsConfig.GCSConfig.Credentials.GetPayload()
if payload != "" {
s.PortableUser.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret(payload)
}
case dataprovider.AzureBlobFilesystemProvider:
payload := s.PortableUser.FsConfig.AzBlobConfig.AccountKey.GetPayload()
if payload != "" {
s.PortableUser.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret(payload)
}
}
return printablePassword return printablePassword
} }

View file

@ -41,6 +41,7 @@ import (
"github.com/drakkan/sftpgo/config" "github.com/drakkan/sftpgo/config"
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/httpd" "github.com/drakkan/sftpgo/httpd"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
@ -177,6 +178,12 @@ func TestMain(m *testing.M) {
httpConfig := config.GetHTTPConfig() httpConfig := config.GetHTTPConfig()
httpConfig.Initialize(configDir) httpConfig.Initialize(configDir)
kmsConfig := config.GetKMSConfig()
err = kmsConfig.Initialize()
if err != nil {
logger.ErrorToConsole("error initializing kms: %v", err)
os.Exit(1)
}
sftpdConf := config.GetSFTPDConfig() sftpdConf := config.GetSFTPDConfig()
httpdConf := config.GetHTTPDConfig() httpdConf := config.GetHTTPDConfig()
@ -1312,10 +1319,7 @@ func TestLoginWithDatabaseCredentials(t *testing.T) {
u := getTestUser(usePubKey) u := getTestUser(usePubKey)
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "testbucket" u.FsConfig.GCSConfig.Bucket = "testbucket"
u.FsConfig.GCSConfig.Credentials = vfs.Secret{ u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret(`{ "type": "service_account" }`)
Status: vfs.SecretStatusPlain,
Payload: `{ "type": "service_account" }`,
}
providerConf := config.GetProviderConf() providerConf := config.GetProviderConf()
providerConf.PreferDatabaseCredentials = true providerConf.PreferDatabaseCredentials = true
@ -1336,10 +1340,10 @@ func TestLoginWithDatabaseCredentials(t *testing.T) {
user, _, err := httpd.AddUser(u, http.StatusOK) user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.GCSConfig.Credentials.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.GCSConfig.Credentials.GetStatus())
assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.Payload) assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.GetPayload())
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.AdditionalData) assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetAdditionalData())
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.Key) assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetKey())
assert.NoFileExists(t, credentialsFile) assert.NoFileExists(t, credentialsFile)
@ -1364,10 +1368,7 @@ func TestLoginInvalidFs(t *testing.T) {
u := getTestUser(usePubKey) u := getTestUser(usePubKey)
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test" u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = vfs.Secret{ u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
Status: vfs.SecretStatusPlain,
Payload: "invalid JSON for credentials",
}
user, _, err := httpd.AddUser(u, http.StatusOK) user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -122,5 +122,11 @@
"timeout": 20, "timeout": 20,
"ca_certificates": [], "ca_certificates": [],
"skip_tls_verify": false "skip_tls_verify": false
},
"kms": {
"secrets": {
"url": "",
"master_key_path": ""
}
} }
} }

View file

@ -337,7 +337,7 @@
<label for="idS3AccessSecret" class="col-sm-2 col-form-label">Access Secret</label> <label for="idS3AccessSecret" class="col-sm-2 col-form-label">Access Secret</label>
<div class="col-sm-3"> <div class="col-sm-3">
<input type="text" class="form-control" id="idS3AccessSecret" name="s3_access_secret" placeholder="" <input type="text" class="form-control" id="idS3AccessSecret" name="s3_access_secret" placeholder=""
value="{{if .IsS3SecretEnc}}{{.RedactedSecret}}{{else}}{{.User.FsConfig.S3Config.AccessSecret.Payload}}{{end}}" maxlength="1000"> value="{{if .IsS3SecretEnc}}{{.RedactedSecret}}{{else}}{{.User.FsConfig.S3Config.AccessSecret.GetPayload}}{{end}}" maxlength="1000">
</div> </div>
</div> </div>
@ -448,7 +448,7 @@
<label for="idAzAccountKey" class="col-sm-2 col-form-label">Account Key</label> <label for="idAzAccountKey" class="col-sm-2 col-form-label">Account Key</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="idAzAccountKey" name="az_account_key" placeholder="" <input type="text" class="form-control" id="idAzAccountKey" name="az_account_key" placeholder=""
value="{{if .IsAzSecretEnc}}{{.RedactedSecret}}{{else}}{{.User.FsConfig.AzBlobConfig.AccountKey.Payload}}{{end}}" maxlength="1000"> value="{{if .IsAzSecretEnc}}{{.RedactedSecret}}{{else}}{{.User.FsConfig.AzBlobConfig.AccountKey.GetPayload}}{{end}}" maxlength="1000">
</div> </div>
</div> </div>

View file

@ -104,7 +104,7 @@ func NewAzBlobFs(connectionID, localTempDir string, config AzBlobFsConfig) (Fs,
return fs, nil return fs, nil
} }
credential, err := azblob.NewSharedKeyCredential(fs.config.AccountName, fs.config.AccountKey.Payload) credential, err := azblob.NewSharedKeyCredential(fs.config.AccountName, fs.config.AccountKey.GetPayload())
if err != nil { if err != nil {
return fs, fmt.Errorf("invalid credentials: %v", err) return fs, fmt.Errorf("invalid credentials: %v", err)
} }

View file

@ -23,6 +23,7 @@ import (
"google.golang.org/api/iterator" "google.golang.org/api/iterator"
"google.golang.org/api/option" "google.golang.org/api/option"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/metrics" "github.com/drakkan/sftpgo/metrics"
"github.com/drakkan/sftpgo/version" "github.com/drakkan/sftpgo/version"
@ -62,19 +63,21 @@ func NewGCSFs(connectionID, localTempDir string, config GCSFsConfig) (Fs, error)
ctx := context.Background() ctx := context.Background()
if fs.config.AutomaticCredentials > 0 { if fs.config.AutomaticCredentials > 0 {
fs.svc, err = storage.NewClient(ctx) fs.svc, err = storage.NewClient(ctx)
} else if fs.config.Credentials.IsEncrypted() { } else if !fs.config.Credentials.IsEmpty() {
err = fs.config.Credentials.Decrypt() if fs.config.Credentials.IsEncrypted() {
if err != nil { err = fs.config.Credentials.Decrypt()
return fs, err if err != nil {
return fs, err
}
} }
fs.svc, err = storage.NewClient(ctx, option.WithCredentialsJSON([]byte(fs.config.Credentials.Payload))) fs.svc, err = storage.NewClient(ctx, option.WithCredentialsJSON([]byte(fs.config.Credentials.GetPayload())))
} else { } else {
var creds []byte var creds []byte
creds, err = ioutil.ReadFile(fs.config.CredentialFile) creds, err = ioutil.ReadFile(fs.config.CredentialFile)
if err != nil { if err != nil {
return fs, err return fs, err
} }
secret := &Secret{} secret := kms.NewEmptySecret()
err = json.Unmarshal(creds, secret) err = json.Unmarshal(creds, secret)
if err != nil { if err != nil {
return fs, err return fs, err
@ -83,7 +86,7 @@ func NewGCSFs(connectionID, localTempDir string, config GCSFsConfig) (Fs, error)
if err != nil { if err != nil {
return fs, err return fs, err
} }
fs.svc, err = storage.NewClient(ctx, option.WithCredentialsJSON([]byte(secret.Payload))) fs.svc, err = storage.NewClient(ctx, option.WithCredentialsJSON([]byte(secret.GetPayload())))
} }
return fs, err return fs, err
} }

View file

@ -60,12 +60,14 @@ func NewS3Fs(connectionID, localTempDir string, config S3FsConfig) (Fs, error) {
awsConfig.WithRegion(fs.config.Region) awsConfig.WithRegion(fs.config.Region)
} }
if fs.config.AccessSecret.IsEncrypted() { if !fs.config.AccessSecret.IsEmpty() {
err := fs.config.AccessSecret.Decrypt() if fs.config.AccessSecret.IsEncrypted() {
if err != nil { err := fs.config.AccessSecret.Decrypt()
return fs, err if err != nil {
return fs, err
}
} }
awsConfig.Credentials = credentials.NewStaticCredentials(fs.config.AccessKey, fs.config.AccessSecret.Payload, "") awsConfig.Credentials = credentials.NewStaticCredentials(fs.config.AccessKey, fs.config.AccessSecret.GetPayload(), "")
} }
if fs.config.Endpoint != "" { if fs.config.Endpoint != "" {

View file

@ -1,209 +0,0 @@
package vfs
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"github.com/drakkan/sftpgo/utils"
)
// SecretStatus defines the statuses of a Secret object
type SecretStatus = string
const (
// SecretStatusPlain means the secret is in plain text and must be encrypted
SecretStatusPlain SecretStatus = "Plain"
// SecretStatusAES256GCM means the secret is encrypted using AES-256-GCM
SecretStatusAES256GCM SecretStatus = "AES-256-GCM"
// SecretStatusRedacted means the secret is redacted
SecretStatusRedacted SecretStatus = "Redacted"
)
var (
errWrongSecretStatus = errors.New("wrong secret status")
errMalformedCiphertext = errors.New("malformed ciphertext")
errInvalidSecret = errors.New("invalid secret")
validSecretStatuses = []string{SecretStatusPlain, SecretStatusAES256GCM, SecretStatusRedacted}
)
// Secret defines the struct used to store confidential data
type Secret struct {
Status SecretStatus `json:"status,omitempty"`
Payload string `json:"payload,omitempty"`
Key string `json:"key,omitempty"`
AdditionalData string `json:"additional_data,omitempty"`
}
// GetSecretFromCompatString returns a secret from the previous format
func GetSecretFromCompatString(secret string) (Secret, error) {
s := Secret{}
plain, err := utils.DecryptData(secret)
if err != nil {
return s, errMalformedCiphertext
}
s.Status = SecretStatusPlain
s.Payload = plain
return s, nil
}
// IsEncrypted returns true if the secret is encrypted
// This isn't a pointer receiver because we don't want to pass
// a pointer to html template
func (s *Secret) IsEncrypted() bool {
return s.Status == SecretStatusAES256GCM
}
// IsPlain returns true if the secret is in plain text
func (s *Secret) IsPlain() bool {
return s.Status == SecretStatusPlain
}
// IsRedacted returns true if the secret is redacted
func (s *Secret) IsRedacted() bool {
return s.Status == SecretStatusRedacted
}
// IsEmpty returns true if all fields are empty
func (s *Secret) IsEmpty() bool {
if s.Status != "" {
return false
}
if s.Payload != "" {
return false
}
if s.Key != "" {
return false
}
if s.AdditionalData != "" {
return false
}
return true
}
// IsValid returns true if the secret is not empty and valid
func (s *Secret) IsValid() bool {
if !s.IsValidInput() {
return false
}
if s.Status == SecretStatusAES256GCM {
if len(s.Key) != 64 {
return false
}
}
return true
}
// IsValidInput returns true if the secret is a valid user input
func (s *Secret) IsValidInput() bool {
if !utils.IsStringInSlice(s.Status, validSecretStatuses) {
return false
}
if s.Payload == "" {
return false
}
return true
}
// Hide hides info to decrypt data
func (s *Secret) Hide() {
s.Key = ""
s.AdditionalData = ""
}
// deriveKey is a weak method of deriving a key but it is still better than using the key as it is.
// We should use a KMS in future
func (s *Secret) deriveKey(key []byte) []byte {
var combined []byte
combined = append(combined, key...)
if s.AdditionalData != "" {
combined = append(combined, []byte(s.AdditionalData)...)
}
combined = append(combined, key...)
hash := sha256.Sum256(combined)
return hash[:]
}
// Encrypt encrypts a plain text Secret object
func (s *Secret) Encrypt() error {
if s.Payload == "" {
return errInvalidSecret
}
switch s.Status {
case SecretStatusPlain:
key := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return err
}
block, err := aes.NewCipher(s.deriveKey(key))
if err != nil {
return err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return err
}
var aad []byte
if s.AdditionalData != "" {
aad = []byte(s.AdditionalData)
}
ciphertext := gcm.Seal(nonce, nonce, []byte(s.Payload), aad)
s.Key = hex.EncodeToString(key)
s.Payload = hex.EncodeToString(ciphertext)
s.Status = SecretStatusAES256GCM
return nil
default:
return errWrongSecretStatus
}
}
// Decrypt decrypts a Secret object
func (s *Secret) Decrypt() error {
switch s.Status {
case SecretStatusAES256GCM:
encrypted, err := hex.DecodeString(s.Payload)
if err != nil {
return err
}
key, err := hex.DecodeString(s.Key)
if err != nil {
return err
}
block, err := aes.NewCipher(s.deriveKey(key))
if err != nil {
return err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
nonceSize := gcm.NonceSize()
if len(encrypted) < nonceSize {
return errMalformedCiphertext
}
nonce, ciphertext := encrypted[:nonceSize], encrypted[nonceSize:]
var aad []byte
if s.AdditionalData != "" {
aad = []byte(s.AdditionalData)
}
plaintext, err := gcm.Open(nil, nonce, ciphertext, aad)
if err != nil {
return err
}
s.Status = SecretStatusPlain
s.Payload = string(plaintext)
s.Key = ""
s.AdditionalData = ""
return nil
default:
return errWrongSecretStatus
}
}

View file

@ -15,6 +15,7 @@ import (
"github.com/eikenb/pipeat" "github.com/eikenb/pipeat"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
) )
@ -110,12 +111,12 @@ type S3FsConfig struct {
// folder. The prefix, if not empty, must not start with "/" and must // folder. The prefix, if not empty, must not start with "/" and must
// end with "/". // end with "/".
// If empty the whole bucket contents will be available // If empty the whole bucket contents will be available
KeyPrefix string `json:"key_prefix,omitempty"` KeyPrefix string `json:"key_prefix,omitempty"`
Region string `json:"region,omitempty"` Region string `json:"region,omitempty"`
AccessKey string `json:"access_key,omitempty"` AccessKey string `json:"access_key,omitempty"`
AccessSecret Secret `json:"access_secret,omitempty"` AccessSecret *kms.Secret `json:"access_secret,omitempty"`
Endpoint string `json:"endpoint,omitempty"` Endpoint string `json:"endpoint,omitempty"`
StorageClass string `json:"storage_class,omitempty"` StorageClass string `json:"storage_class,omitempty"`
// The buffer size (in MB) to use for multipart uploads. The minimum allowed part size is 5MB, // The buffer size (in MB) to use for multipart uploads. The minimum allowed part size is 5MB,
// and if this value is set to zero, the default value (5MB) for the AWS SDK will be used. // and if this value is set to zero, the default value (5MB) for the AWS SDK will be used.
// The minimum allowed value is 5. // The minimum allowed value is 5.
@ -137,9 +138,9 @@ type GCSFsConfig struct {
// folder. The prefix, if not empty, must not start with "/" and must // folder. The prefix, if not empty, must not start with "/" and must
// end with "/". // end with "/".
// If empty the whole bucket contents will be available // If empty the whole bucket contents will be available
KeyPrefix string `json:"key_prefix,omitempty"` KeyPrefix string `json:"key_prefix,omitempty"`
CredentialFile string `json:"-"` CredentialFile string `json:"-"`
Credentials Secret `json:"credentials,omitempty"` Credentials *kms.Secret `json:"credentials,omitempty"`
// 0 explicit, 1 automatic // 0 explicit, 1 automatic
AutomaticCredentials int `json:"automatic_credentials,omitempty"` AutomaticCredentials int `json:"automatic_credentials,omitempty"`
StorageClass string `json:"storage_class,omitempty"` StorageClass string `json:"storage_class,omitempty"`
@ -151,8 +152,8 @@ type AzBlobFsConfig struct {
// Storage Account Name, leave blank to use SAS URL // Storage Account Name, leave blank to use SAS URL
AccountName string `json:"account_name,omitempty"` AccountName string `json:"account_name,omitempty"`
// Storage Account Key leave blank to use SAS URL. // Storage Account Key leave blank to use SAS URL.
// The access key is stored encrypted (AES-256-GCM) // The access key is stored encrypted based on the kms configuration
AccountKey Secret `json:"account_key,omitempty"` AccountKey *kms.Secret `json:"account_key,omitempty"`
// Optional endpoint. Default is "blob.core.windows.net". // Optional endpoint. Default is "blob.core.windows.net".
// If you use the emulator the endpoint must include the protocol, // If you use the emulator the endpoint must include the protocol,
// for example "http://127.0.0.1:10000" // for example "http://127.0.0.1:10000"
@ -254,6 +255,9 @@ func checkS3Credentials(config *S3FsConfig) error {
// ValidateS3FsConfig returns nil if the specified s3 config is valid, otherwise an error // ValidateS3FsConfig returns nil if the specified s3 config is valid, otherwise an error
func ValidateS3FsConfig(config *S3FsConfig) error { func ValidateS3FsConfig(config *S3FsConfig) error {
if config.AccessSecret == nil {
config.AccessSecret = kms.NewEmptySecret()
}
if config.Bucket == "" { if config.Bucket == "" {
return errors.New("bucket cannot be empty") return errors.New("bucket cannot be empty")
} }
@ -283,6 +287,9 @@ func ValidateS3FsConfig(config *S3FsConfig) error {
// ValidateGCSFsConfig returns nil if the specified GCS config is valid, otherwise an error // ValidateGCSFsConfig returns nil if the specified GCS config is valid, otherwise an error
func ValidateGCSFsConfig(config *GCSFsConfig, credentialsFilePath string) error { func ValidateGCSFsConfig(config *GCSFsConfig, credentialsFilePath string) error {
if config.Credentials == nil {
config.Credentials = kms.NewEmptySecret()
}
if config.Bucket == "" { if config.Bucket == "" {
return errors.New("bucket cannot be empty") return errors.New("bucket cannot be empty")
} }
@ -310,8 +317,21 @@ func ValidateGCSFsConfig(config *GCSFsConfig, credentialsFilePath string) error
return nil return nil
} }
func checkAzCredentials(config *AzBlobFsConfig) error {
if config.AccountName == "" || !config.AccountKey.IsValidInput() {
return errors.New("credentials cannot be empty or invalid")
}
if config.AccountKey.IsEncrypted() && !config.AccountKey.IsValid() {
return errors.New("invalid encrypted account_key")
}
return nil
}
// ValidateAzBlobFsConfig returns nil if the specified Azure Blob config is valid, otherwise an error // ValidateAzBlobFsConfig returns nil if the specified Azure Blob config is valid, otherwise an error
func ValidateAzBlobFsConfig(config *AzBlobFsConfig) error { func ValidateAzBlobFsConfig(config *AzBlobFsConfig) error {
if config.AccountKey == nil {
config.AccountKey = kms.NewEmptySecret()
}
if config.SASURL != "" { if config.SASURL != "" {
_, err := url.Parse(config.SASURL) _, err := url.Parse(config.SASURL)
return err return err
@ -319,11 +339,8 @@ func ValidateAzBlobFsConfig(config *AzBlobFsConfig) error {
if config.Container == "" { if config.Container == "" {
return errors.New("container cannot be empty") return errors.New("container cannot be empty")
} }
if config.AccountName == "" || !config.AccountKey.IsValidInput() { if err := checkAzCredentials(config); err != nil {
return errors.New("credentials cannot be empty or invalid") return err
}
if config.AccountKey.IsEncrypted() && !config.AccountKey.IsValid() {
return errors.New("invalid encrypted account_key")
} }
if config.KeyPrefix != "" { if config.KeyPrefix != "" {
if strings.HasPrefix(config.KeyPrefix, "/") { if strings.HasPrefix(config.KeyPrefix, "/") {

View file

@ -136,10 +136,10 @@ func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer common.Connections.Remove(connection.GetID()) defer common.Connections.Remove(connection.GetID())
if !isCached { if !isCached {
// we update last login and check for home directory only if the user is not cached // we check the home directory only if the user is not cached
connection.Fs.CheckRootPath(connection.GetUsername(), user.GetUID(), user.GetGID()) connection.Fs.CheckRootPath(connection.GetUsername(), user.GetUID(), user.GetGID())
dataprovider.UpdateLastLogin(user) //nolint:errcheck
} }
dataprovider.UpdateLastLogin(user) //nolint:errcheck
prefix := path.Join("/", user.Username) prefix := path.Join("/", user.Username)
// see RFC4918, section 9.4 // see RFC4918, section 9.4

View file

@ -27,6 +27,7 @@ import (
"github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/httpclient" "github.com/drakkan/sftpgo/httpclient"
"github.com/drakkan/sftpgo/httpd" "github.com/drakkan/sftpgo/httpd"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/vfs" "github.com/drakkan/sftpgo/vfs"
"github.com/drakkan/sftpgo/webdavd" "github.com/drakkan/sftpgo/webdavd"
@ -125,6 +126,12 @@ func TestMain(m *testing.M) {
httpConfig := config.GetHTTPConfig() httpConfig := config.GetHTTPConfig()
httpConfig.Initialize(configDir) httpConfig.Initialize(configDir)
kmsConfig := config.GetKMSConfig()
err = kmsConfig.Initialize()
if err != nil {
logger.ErrorToConsole("error initializing kms: %v", err)
os.Exit(1)
}
httpdConf := config.GetHTTPDConfig() httpdConf := config.GetHTTPDConfig()
httpdConf.BindPort = 8078 httpdConf.BindPort = 8078
@ -861,10 +868,7 @@ func TestLoginWithDatabaseCredentials(t *testing.T) {
u := getTestUser() u := getTestUser()
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test" u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = vfs.Secret{ u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret(`{ "type": "service_account" }`)
Status: vfs.SecretStatusPlain,
Payload: `{ "type": "service_account" }`,
}
providerConf := config.GetProviderConf() providerConf := config.GetProviderConf()
providerConf.PreferDatabaseCredentials = true providerConf.PreferDatabaseCredentials = true
@ -885,10 +889,10 @@ func TestLoginWithDatabaseCredentials(t *testing.T) {
user, _, err := httpd.AddUser(u, http.StatusOK) user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, vfs.SecretStatusAES256GCM, user.FsConfig.GCSConfig.Credentials.Status) assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.GCSConfig.Credentials.GetStatus())
assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.Payload) assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.GetPayload())
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.AdditionalData) assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetAdditionalData())
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.Key) assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetKey())
assert.NoFileExists(t, credentialsFile) assert.NoFileExists(t, credentialsFile)
@ -912,10 +916,7 @@ func TestLoginInvalidFs(t *testing.T) {
u := getTestUser() u := getTestUser()
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test" u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = vfs.Secret{ u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
Status: vfs.SecretStatusPlain,
Payload: "invalid JSON for credentials",
}
user, _, err := httpd.AddUser(u, http.StatusOK) user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)