diff --git a/cmd/portable.go b/cmd/portable.go index ba598299..6f3d2a66 100644 --- a/cmd/portable.go +++ b/cmd/portable.go @@ -14,8 +14,8 @@ import ( "github.com/drakkan/sftpgo/v2/common" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/service" "github.com/drakkan/sftpgo/v2/sftpd" "github.com/drakkan/sftpgo/v2/version" diff --git a/common/common_test.go b/common/common_test.go index 3ff13aad..d543102a 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -19,8 +19,8 @@ import ( "golang.org/x/crypto/bcrypt" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/vfs" ) diff --git a/common/connection_test.go b/common/connection_test.go index 233bd868..bd74bc29 100644 --- a/common/connection_test.go +++ b/common/connection_test.go @@ -13,8 +13,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/vfs" ) diff --git a/common/protocol_test.go b/common/protocol_test.go index d78a3315..4c6ea296 100644 --- a/common/protocol_test.go +++ b/common/protocol_test.go @@ -34,10 +34,11 @@ import ( "github.com/drakkan/sftpgo/v2/dataprovider" "github.com/drakkan/sftpgo/v2/httpclient" "github.com/drakkan/sftpgo/v2/httpdtest" - "github.com/drakkan/sftpgo/v2/kms" + _ "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/mfa" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/vfs" ) diff --git a/common/transfer_test.go b/common/transfer_test.go index 765ee250..e01ef6ac 100644 --- a/common/transfer_test.go +++ b/common/transfer_test.go @@ -11,8 +11,8 @@ import ( "github.com/stretchr/testify/require" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/vfs" ) diff --git a/config/config.go b/config/config.go index 161ab5a0..60ef2cc7 100644 --- a/config/config.go +++ b/config/config.go @@ -16,9 +16,9 @@ import ( "github.com/drakkan/sftpgo/v2/ftpd" "github.com/drakkan/sftpgo/v2/httpclient" "github.com/drakkan/sftpgo/v2/httpd" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/mfa" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sdk/plugin" "github.com/drakkan/sftpgo/v2/sftpd" "github.com/drakkan/sftpgo/v2/smtp" diff --git a/config/config_test.go b/config/config_test.go index 78558f1a..44fca8da 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -17,8 +17,9 @@ import ( "github.com/drakkan/sftpgo/v2/ftpd" "github.com/drakkan/sftpgo/v2/httpclient" "github.com/drakkan/sftpgo/v2/httpd" - "github.com/drakkan/sftpgo/v2/kms" + _ "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/mfa" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sdk/plugin" "github.com/drakkan/sftpgo/v2/sftpd" "github.com/drakkan/sftpgo/v2/smtp" diff --git a/dataprovider/admin.go b/dataprovider/admin.go index 5042cc2f..07116e91 100644 --- a/dataprovider/admin.go +++ b/dataprovider/admin.go @@ -15,10 +15,10 @@ import ( passwordvalidator "github.com/wagslane/go-password-validator" "golang.org/x/crypto/bcrypt" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/mfa" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" ) diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index f3517da4..e8e003c0 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -46,11 +46,11 @@ import ( "golang.org/x/crypto/ssh" "github.com/drakkan/sftpgo/v2/httpclient" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/metric" "github.com/drakkan/sftpgo/v2/mfa" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sdk/plugin" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/vfs" diff --git a/dataprovider/user.go b/dataprovider/user.go index dc1e9ac1..3da3cb26 100644 --- a/dataprovider/user.go +++ b/dataprovider/user.go @@ -15,10 +15,10 @@ import ( "strings" "time" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/mfa" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/vfs" ) diff --git a/ftpd/cryptfs_test.go b/ftpd/cryptfs_test.go index 4667411c..c1627faf 100644 --- a/ftpd/cryptfs_test.go +++ b/ftpd/cryptfs_test.go @@ -18,8 +18,8 @@ import ( "github.com/drakkan/sftpgo/v2/common" "github.com/drakkan/sftpgo/v2/dataprovider" "github.com/drakkan/sftpgo/v2/httpdtest" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" ) func TestBasicFTPHandlingCryptFs(t *testing.T) { diff --git a/ftpd/ftpd_test.go b/ftpd/ftpd_test.go index 61d65fa2..a98e5f0e 100644 --- a/ftpd/ftpd_test.go +++ b/ftpd/ftpd_test.go @@ -32,10 +32,11 @@ import ( "github.com/drakkan/sftpgo/v2/dataprovider" "github.com/drakkan/sftpgo/v2/ftpd" "github.com/drakkan/sftpgo/v2/httpdtest" - "github.com/drakkan/sftpgo/v2/kms" + _ "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/mfa" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sftpd" "github.com/drakkan/sftpgo/v2/vfs" ) diff --git a/httpd/api_mfa.go b/httpd/api_mfa.go index 03ca8472..cad6616a 100644 --- a/httpd/api_mfa.go +++ b/httpd/api_mfa.go @@ -8,9 +8,9 @@ import ( "github.com/go-chi/render" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/mfa" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" ) diff --git a/httpd/api_user.go b/httpd/api_user.go index 68c27319..2731d399 100644 --- a/httpd/api_user.go +++ b/httpd/api_user.go @@ -10,8 +10,8 @@ import ( "github.com/drakkan/sftpgo/v2/common" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/smtp" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/vfs" diff --git a/httpd/httpd_test.go b/httpd/httpd_test.go index ce9f40f6..d03328fd 100644 --- a/httpd/httpd_test.go +++ b/httpd/httpd_test.go @@ -45,10 +45,11 @@ import ( "github.com/drakkan/sftpgo/v2/httpclient" "github.com/drakkan/sftpgo/v2/httpd" "github.com/drakkan/sftpgo/v2/httpdtest" - "github.com/drakkan/sftpgo/v2/kms" + _ "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/mfa" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sdk/plugin" "github.com/drakkan/sftpgo/v2/sftpd" "github.com/drakkan/sftpgo/v2/smtp" diff --git a/httpd/internal_test.go b/httpd/internal_test.go index 549fb96a..9ac631ad 100644 --- a/httpd/internal_test.go +++ b/httpd/internal_test.go @@ -33,8 +33,8 @@ import ( "github.com/drakkan/sftpgo/v2/common" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sdk/plugin" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/vfs" diff --git a/httpd/webadmin.go b/httpd/webadmin.go index e958a489..27081f8d 100644 --- a/httpd/webadmin.go +++ b/httpd/webadmin.go @@ -16,9 +16,9 @@ import ( "github.com/drakkan/sftpgo/v2/common" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/mfa" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/smtp" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/version" diff --git a/httpdtest/httpdtest.go b/httpdtest/httpdtest.go index dcabb3b1..e05b99b9 100644 --- a/httpdtest/httpdtest.go +++ b/httpdtest/httpdtest.go @@ -20,7 +20,7 @@ import ( "github.com/drakkan/sftpgo/v2/dataprovider" "github.com/drakkan/sftpgo/v2/httpclient" "github.com/drakkan/sftpgo/v2/httpd" - "github.com/drakkan/sftpgo/v2/kms" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/version" "github.com/drakkan/sftpgo/v2/vfs" diff --git a/kms/builtin.go b/kms/builtin.go index f842f844..4d900d64 100644 --- a/kms/builtin.go +++ b/kms/builtin.go @@ -6,18 +6,25 @@ import ( "crypto/rand" "crypto/sha256" "encoding/hex" + "errors" "io" + + sdkkms "github.com/drakkan/sftpgo/v2/sdk/kms" +) + +var ( + errMalformedCiphertext = errors.New("malformed ciphertext") ) type builtinSecret struct { - BaseSecret + sdkkms.BaseSecret } func init() { - RegisterSecretProvider(SchemeBuiltin, SecretStatusAES256GCM, newBuiltinSecret) + sdkkms.RegisterSecretProvider(sdkkms.SchemeBuiltin, sdkkms.SecretStatusAES256GCM, newBuiltinSecret) } -func newBuiltinSecret(base BaseSecret, url, masterKey string) SecretProvider { +func newBuiltinSecret(base sdkkms.BaseSecret, url, masterKey string) sdkkms.SecretProvider { return &builtinSecret{ BaseSecret: base, } @@ -28,7 +35,7 @@ func (s *builtinSecret) Name() string { } func (s *builtinSecret) IsEncrypted() bool { - return s.Status == SecretStatusAES256GCM + return s.Status == sdkkms.SecretStatusAES256GCM } func (s *builtinSecret) deriveKey(key []byte) []byte { @@ -44,10 +51,10 @@ func (s *builtinSecret) deriveKey(key []byte) []byte { func (s *builtinSecret) Encrypt() error { if s.Payload == "" { - return ErrInvalidSecret + return sdkkms.ErrInvalidSecret } switch s.Status { - case SecretStatusPlain: + case sdkkms.SecretStatusPlain: key := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, key); err != nil { return err @@ -71,16 +78,16 @@ func (s *builtinSecret) Encrypt() error { ciphertext := gcm.Seal(nonce, nonce, []byte(s.Payload), aad) s.Key = hex.EncodeToString(key) s.Payload = hex.EncodeToString(ciphertext) - s.Status = SecretStatusAES256GCM + s.Status = sdkkms.SecretStatusAES256GCM return nil default: - return ErrWrongSecretStatus + return sdkkms.ErrWrongSecretStatus } } func (s *builtinSecret) Decrypt() error { switch s.Status { - case SecretStatusAES256GCM: + case sdkkms.SecretStatusAES256GCM: encrypted, err := hex.DecodeString(s.Payload) if err != nil { return err @@ -110,18 +117,18 @@ func (s *builtinSecret) Decrypt() error { if err != nil { return err } - s.Status = SecretStatusPlain + s.Status = sdkkms.SecretStatusPlain s.Payload = string(plaintext) s.Key = "" s.AdditionalData = "" return nil default: - return ErrWrongSecretStatus + return sdkkms.ErrWrongSecretStatus } } -func (s *builtinSecret) Clone() SecretProvider { - baseSecret := BaseSecret{ +func (s *builtinSecret) Clone() sdkkms.SecretProvider { + baseSecret := sdkkms.BaseSecret{ Status: s.Status, Payload: s.Payload, Key: s.Key, diff --git a/kms/kms.go b/kms/kms.go index ebce5fda..39f18b05 100644 --- a/kms/kms.go +++ b/kms/kms.go @@ -1,446 +1,2 @@ -// Package kms provides Key Management Services support +// Package kms provides built-in Key Management Services support package kms - -import ( - "encoding/json" - "errors" - "os" - "strings" - "sync" - - "github.com/drakkan/sftpgo/v2/logger" - "github.com/drakkan/sftpgo/v2/util" -) - -// 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 - GetMode() int - SetKey(string) - SetAdditionalData(string) - SetStatus(SecretStatus) - Clone() SecretProvider -} - -const ( - logSender = "kms" -) - -// 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 Platform’s Key Management Service - // (GCP KMS) to keep information secret - SecretStatusGCP SecretStatus = "GCP" - // SecretStatusAWS means we use customer master keys from Amazon Web Service’s - // 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" - // SecretStatusAzureKeyVault means we use Azure KeyVault to keep information secret - SecretStatusAzureKeyVault SecretStatus = "AzureKeyVault" - // SecretStatusRedacted means the secret is redacted - SecretStatusRedacted SecretStatus = "Redacted" -) - -// Scheme defines the supported URL scheme -type Scheme = string - -// supported URL schemes -const ( - SchemeLocal Scheme = "local" - SchemeBuiltin Scheme = "builtin" - SchemeAWS Scheme = "awskms" - SchemeGCP Scheme = "gcpkms" - SchemeVaultTransit Scheme = "hashivault" - SchemeAzureKeyVault Scheme = "azurekeyvault" -) - -// 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"` - MasterKeyString string `json:"master_key" mapstructure:"master_key"` - masterKey string -} - -type registeredSecretProvider struct { - encryptedStatus SecretStatus - newFn func(base BaseSecret, url, masterKey string) SecretProvider -} - -var ( - // ErrWrongSecretStatus defines the error to return if the secret status is not appropriate - // for the request operation - ErrWrongSecretStatus = errors.New("wrong secret status") - // ErrInvalidSecret defines the error to return if a secret is not valid - ErrInvalidSecret = errors.New("invalid secret") - errMalformedCiphertext = errors.New("malformed ciphertext") - validSecretStatuses = []string{SecretStatusPlain, SecretStatusAES256GCM, SecretStatusSecretBox, - SecretStatusVaultTransit, SecretStatusAWS, SecretStatusGCP, SecretStatusRedacted} - config Configuration - secretProviders = make(map[string]registeredSecretProvider) -) - -// RegisterSecretProvider register a new secret provider -func RegisterSecretProvider(scheme string, encryptedStatus SecretStatus, fn func(base BaseSecret, url, masterKey string) SecretProvider) { - secretProviders[scheme] = registeredSecretProvider{ - encryptedStatus: encryptedStatus, - newFn: fn, - } -} - -// 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, "", "") -} - -// Initialize configures the KMS support -func (c *Configuration) Initialize() error { - if c.Secrets.MasterKeyString != "" { - c.Secrets.masterKey = c.Secrets.MasterKeyString - } - if c.Secrets.masterKey == "" && c.Secrets.MasterKeyPath != "" { - mKey, err := os.ReadFile(c.Secrets.MasterKeyPath) - if err != nil { - return err - } - c.Secrets.masterKey = strings.TrimSpace(string(mKey)) - } - config = *c - if config.Secrets.URL == "" { - config.Secrets.URL = SchemeLocal + "://" - } - for k, v := range secretProviders { - logger.Debug(logSender, "", "secret provider registered for scheme: %#v, encrypted status: %#v", - k, v.encryptedStatus) - } - 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 { - for k, v := range secretProviders { - if strings.HasPrefix(c.Secrets.URL, k) { - return v.newFn(base, c.Secrets.URL, c.Secrets.masterKey) - } - } - logger.Warn(logSender, "", "no secret provider registered for URL %v, fallback to local provider", c.Secrets.URL) - return NewLocalSecret(base, c.Secrets.URL, c.Secrets.masterKey) -} - -// Secret defines the struct used to store confidential data -type Secret struct { - sync.RWMutex - provider SecretProvider -} - -// MarshalJSON return the JSON encoding of the Secret object -func (s *Secret) MarshalJSON() ([]byte, error) { - s.RLock() - defer s.RUnlock() - - return json.Marshal(&BaseSecret{ - Status: s.provider.GetStatus(), - Payload: s.provider.GetPayload(), - Key: s.provider.GetKey(), - AdditionalData: s.provider.GetAdditionalData(), - Mode: s.provider.GetMode(), - }) -} - -// UnmarshalJSON parses the JSON-encoded data and stores the result -// in the Secret object -func (s *Secret) UnmarshalJSON(data []byte) error { - s.Lock() - defer s.Unlock() - - baseSecret := BaseSecret{} - err := json.Unmarshal(data, &baseSecret) - if err != nil { - return err - } - if baseSecret.isEmpty() { - s.provider = config.getSecretProvider(baseSecret) - return nil - } - - if baseSecret.Status == SecretStatusPlain || baseSecret.Status == SecretStatusRedacted { - s.provider = config.getSecretProvider(baseSecret) - return nil - } - - for _, v := range secretProviders { - if v.encryptedStatus == baseSecret.Status { - s.provider = v.newFn(baseSecret, config.Secrets.URL, config.Secrets.masterKey) - return nil - } - } - logger.Debug(logSender, "", "no provider registered for status %#v", baseSecret.Status) - - return ErrInvalidSecret -} - -// IsEqual returns true if all the secrets fields are equal -func (s *Secret) IsEqual(other *Secret) bool { - if s.GetStatus() != other.GetStatus() { - return false - } - if s.GetPayload() != other.GetPayload() { - return false - } - if s.GetKey() != other.GetKey() { - return false - } - if s.GetAdditionalData() != other.GetAdditionalData() { - return false - } - if s.GetMode() != other.GetMode() { - return false - } - return true -} - -// Clone returns a copy of the secret object -func (s *Secret) Clone() *Secret { - s.RLock() - defer s.RUnlock() - - return &Secret{ - provider: s.provider.Clone(), - } -} - -// 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 { - s.RLock() - defer s.RUnlock() - - return s.provider.IsEncrypted() -} - -// IsPlain returns true if the secret is in plain text -func (s *Secret) IsPlain() bool { - s.RLock() - defer s.RUnlock() - - return s.provider.GetStatus() == SecretStatusPlain -} - -// IsNotPlainAndNotEmpty returns true if the secret is not plain and not empty. -// This is an utility method, we update the secret for an existing user -// if it is empty or plain -func (s *Secret) IsNotPlainAndNotEmpty() bool { - s.RLock() - defer s.RUnlock() - - return !s.IsPlain() && !s.IsEmpty() -} - -// IsRedacted returns true if the secret is redacted -func (s *Secret) IsRedacted() bool { - s.RLock() - defer s.RUnlock() - - return s.provider.GetStatus() == SecretStatusRedacted -} - -// GetPayload returns the secret payload -func (s *Secret) GetPayload() string { - s.RLock() - defer s.RUnlock() - - return s.provider.GetPayload() -} - -// GetAdditionalData returns the secret additional data -func (s *Secret) GetAdditionalData() string { - s.RLock() - defer s.RUnlock() - - return s.provider.GetAdditionalData() -} - -// GetStatus returns the secret status -func (s *Secret) GetStatus() SecretStatus { - s.RLock() - defer s.RUnlock() - - return s.provider.GetStatus() -} - -// GetKey returns the secret key -func (s *Secret) GetKey() string { - s.RLock() - defer s.RUnlock() - - return s.provider.GetKey() -} - -// GetMode returns the secret mode -func (s *Secret) GetMode() int { - s.RLock() - defer s.RUnlock() - - return s.provider.GetMode() -} - -// SetAdditionalData sets the given additional data -func (s *Secret) SetAdditionalData(value string) { - s.Lock() - defer s.Unlock() - - s.provider.SetAdditionalData(value) -} - -// SetStatus sets the status for this secret -func (s *Secret) SetStatus(value SecretStatus) { - s.Lock() - defer s.Unlock() - - s.provider.SetStatus(value) -} - -// SetKey sets the key for this secret -func (s *Secret) SetKey(value string) { - s.Lock() - defer s.Unlock() - - s.provider.SetKey(value) -} - -// IsEmpty returns true if all fields are empty -func (s *Secret) IsEmpty() bool { - s.RLock() - defer s.RUnlock() - - 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 { - s.RLock() - defer s.RUnlock() - - 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 { - s.RLock() - defer s.RUnlock() - - if !util.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.Lock() - defer s.Unlock() - - s.provider.SetKey("") - s.provider.SetAdditionalData("") -} - -// Encrypt encrypts a plain text Secret object -func (s *Secret) Encrypt() error { - s.Lock() - defer s.Unlock() - - return s.provider.Encrypt() -} - -// Decrypt decrypts a Secret object -func (s *Secret) Decrypt() error { - s.Lock() - defer s.Unlock() - - return s.provider.Decrypt() -} - -// TryDecrypt decrypts a Secret object if encrypted. -// It returns a nil error if the object is not encrypted -func (s *Secret) TryDecrypt() error { - s.Lock() - defer s.Unlock() - - if s.provider.IsEncrypted() { - return s.provider.Decrypt() - } - return nil -} diff --git a/kms/local.go b/kms/local.go index a360b163..f710718f 100644 --- a/kms/local.go +++ b/kms/local.go @@ -9,19 +9,21 @@ import ( "gocloud.dev/secrets/localsecrets" "golang.org/x/crypto/hkdf" + + sdkkms "github.com/drakkan/sftpgo/v2/sdk/kms" ) func init() { - RegisterSecretProvider(SchemeLocal, SecretStatusSecretBox, NewLocalSecret) + sdkkms.RegisterSecretProvider(sdkkms.SchemeLocal, sdkkms.SecretStatusSecretBox, NewLocalSecret) } type localSecret struct { - BaseSecret + sdkkms.BaseSecret masterKey string } // NewLocalSecret returns a SecretProvider that use a locally provided symmetric key -func NewLocalSecret(base BaseSecret, url, masterKey string) SecretProvider { +func NewLocalSecret(base sdkkms.BaseSecret, url, masterKey string) sdkkms.SecretProvider { return &localSecret{ BaseSecret: base, masterKey: masterKey, @@ -33,15 +35,15 @@ func (s *localSecret) Name() string { } func (s *localSecret) IsEncrypted() bool { - return s.Status == SecretStatusSecretBox + return s.Status == sdkkms.SecretStatusSecretBox } func (s *localSecret) Encrypt() error { - if s.Status != SecretStatusPlain { - return ErrWrongSecretStatus + if s.Status != sdkkms.SecretStatusPlain { + return sdkkms.ErrWrongSecretStatus } if s.Payload == "" { - return ErrInvalidSecret + return sdkkms.ErrInvalidSecret } secretKey, err := localsecrets.NewRandomKey() if err != nil { @@ -60,14 +62,14 @@ func (s *localSecret) Encrypt() error { } s.Key = hex.EncodeToString(secretKey[:]) s.Payload = base64.StdEncoding.EncodeToString(ciphertext) - s.Status = SecretStatusSecretBox + s.Status = sdkkms.SecretStatusSecretBox s.Mode = s.getEncryptionMode() return nil } func (s *localSecret) Decrypt() error { if !s.IsEncrypted() { - return ErrWrongSecretStatus + return sdkkms.ErrWrongSecretStatus } encrypted, err := base64.StdEncoding.DecodeString(s.Payload) if err != nil { @@ -88,7 +90,7 @@ func (s *localSecret) Decrypt() error { if err != nil { return err } - s.Status = SecretStatusPlain + s.Status = sdkkms.SecretStatusPlain s.Payload = string(plaintext) s.Key = "" s.AdditionalData = "" @@ -129,8 +131,8 @@ func (s *localSecret) getEncryptionMode() int { return 1 } -func (s *localSecret) Clone() SecretProvider { - baseSecret := BaseSecret{ +func (s *localSecret) Clone() sdkkms.SecretProvider { + baseSecret := sdkkms.BaseSecret{ Status: s.Status, Payload: s.Payload, Key: s.Key, diff --git a/main.go b/main.go index 4bdac6e5..c7b7659a 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "go.uber.org/automaxprocs/maxprocs" "github.com/drakkan/sftpgo/v2/cmd" + _ "github.com/drakkan/sftpgo/v2/kms" ) func main() { diff --git a/sdk/filesystem.go b/sdk/filesystem.go index 229f2d08..6f75582e 100644 --- a/sdk/filesystem.go +++ b/sdk/filesystem.go @@ -1,6 +1,6 @@ package sdk -import "github.com/drakkan/sftpgo/v2/kms" +import "github.com/drakkan/sftpgo/v2/sdk/kms" // FilesystemProvider defines the supported storage filesystems type FilesystemProvider int diff --git a/kms/basesecret.go b/sdk/kms/basesecret.go similarity index 100% rename from kms/basesecret.go rename to sdk/kms/basesecret.go diff --git a/sdk/kms/kms.go b/sdk/kms/kms.go new file mode 100644 index 00000000..a186f93e --- /dev/null +++ b/sdk/kms/kms.go @@ -0,0 +1,446 @@ +// Package kms provides Key Management Services support +package kms + +import ( + "encoding/json" + "errors" + "os" + "strings" + "sync" + + "github.com/drakkan/sftpgo/v2/logger" + "github.com/drakkan/sftpgo/v2/util" +) + +// 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 + GetMode() int + SetKey(string) + SetAdditionalData(string) + SetStatus(SecretStatus) + Clone() SecretProvider +} + +const ( + logSender = "kms" +) + +// 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 Platform’s Key Management Service + // (GCP KMS) to keep information secret + SecretStatusGCP SecretStatus = "GCP" + // SecretStatusAWS means we use customer master keys from Amazon Web Service’s + // 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" + // SecretStatusAzureKeyVault means we use Azure KeyVault to keep information secret + SecretStatusAzureKeyVault SecretStatus = "AzureKeyVault" + // SecretStatusRedacted means the secret is redacted + SecretStatusRedacted SecretStatus = "Redacted" +) + +// Scheme defines the supported URL scheme +type Scheme = string + +// supported URL schemes +const ( + SchemeLocal Scheme = "local" + SchemeBuiltin Scheme = "builtin" + SchemeAWS Scheme = "awskms" + SchemeGCP Scheme = "gcpkms" + SchemeVaultTransit Scheme = "hashivault" + SchemeAzureKeyVault Scheme = "azurekeyvault" +) + +// 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"` + MasterKeyString string `json:"master_key" mapstructure:"master_key"` + masterKey string +} + +type registeredSecretProvider struct { + encryptedStatus SecretStatus + newFn func(base BaseSecret, url, masterKey string) SecretProvider +} + +var ( + // ErrWrongSecretStatus defines the error to return if the secret status is not appropriate + // for the request operation + ErrWrongSecretStatus = errors.New("wrong secret status") + // ErrInvalidSecret defines the error to return if a secret is not valid + ErrInvalidSecret = errors.New("invalid secret") + validSecretStatuses = []string{SecretStatusPlain, SecretStatusAES256GCM, SecretStatusSecretBox, + SecretStatusVaultTransit, SecretStatusAWS, SecretStatusGCP, SecretStatusRedacted} + config Configuration + secretProviders = make(map[string]registeredSecretProvider) +) + +// RegisterSecretProvider register a new secret provider +func RegisterSecretProvider(scheme string, encryptedStatus SecretStatus, fn func(base BaseSecret, url, masterKey string) SecretProvider) { + secretProviders[scheme] = registeredSecretProvider{ + encryptedStatus: encryptedStatus, + newFn: fn, + } +} + +// 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, "", "") +} + +// Initialize configures the KMS support +func (c *Configuration) Initialize() error { + if c.Secrets.MasterKeyString != "" { + c.Secrets.masterKey = c.Secrets.MasterKeyString + } + if c.Secrets.masterKey == "" && c.Secrets.MasterKeyPath != "" { + mKey, err := os.ReadFile(c.Secrets.MasterKeyPath) + if err != nil { + return err + } + c.Secrets.masterKey = strings.TrimSpace(string(mKey)) + } + config = *c + if config.Secrets.URL == "" { + config.Secrets.URL = SchemeLocal + "://" + } + for k, v := range secretProviders { + logger.Debug(logSender, "", "secret provider registered for scheme: %#v, encrypted status: %#v", + k, v.encryptedStatus) + } + 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 { + for k, v := range secretProviders { + if strings.HasPrefix(c.Secrets.URL, k) { + return v.newFn(base, c.Secrets.URL, c.Secrets.masterKey) + } + } + // we assume that SchemeLocal is always registered + logger.Warn(logSender, "", "no secret provider registered for URL %v, fallback to local provider", c.Secrets.URL) + return secretProviders[SchemeLocal].newFn(base, c.Secrets.URL, c.Secrets.masterKey) +} + +// Secret defines the struct used to store confidential data +type Secret struct { + sync.RWMutex + provider SecretProvider +} + +// MarshalJSON return the JSON encoding of the Secret object +func (s *Secret) MarshalJSON() ([]byte, error) { + s.RLock() + defer s.RUnlock() + + return json.Marshal(&BaseSecret{ + Status: s.provider.GetStatus(), + Payload: s.provider.GetPayload(), + Key: s.provider.GetKey(), + AdditionalData: s.provider.GetAdditionalData(), + Mode: s.provider.GetMode(), + }) +} + +// UnmarshalJSON parses the JSON-encoded data and stores the result +// in the Secret object +func (s *Secret) UnmarshalJSON(data []byte) error { + s.Lock() + defer s.Unlock() + + baseSecret := BaseSecret{} + err := json.Unmarshal(data, &baseSecret) + if err != nil { + return err + } + if baseSecret.isEmpty() { + s.provider = config.getSecretProvider(baseSecret) + return nil + } + + if baseSecret.Status == SecretStatusPlain || baseSecret.Status == SecretStatusRedacted { + s.provider = config.getSecretProvider(baseSecret) + return nil + } + + for _, v := range secretProviders { + if v.encryptedStatus == baseSecret.Status { + s.provider = v.newFn(baseSecret, config.Secrets.URL, config.Secrets.masterKey) + return nil + } + } + logger.Debug(logSender, "", "no provider registered for status %#v", baseSecret.Status) + + return ErrInvalidSecret +} + +// IsEqual returns true if all the secrets fields are equal +func (s *Secret) IsEqual(other *Secret) bool { + if s.GetStatus() != other.GetStatus() { + return false + } + if s.GetPayload() != other.GetPayload() { + return false + } + if s.GetKey() != other.GetKey() { + return false + } + if s.GetAdditionalData() != other.GetAdditionalData() { + return false + } + if s.GetMode() != other.GetMode() { + return false + } + return true +} + +// Clone returns a copy of the secret object +func (s *Secret) Clone() *Secret { + s.RLock() + defer s.RUnlock() + + return &Secret{ + provider: s.provider.Clone(), + } +} + +// 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 { + s.RLock() + defer s.RUnlock() + + return s.provider.IsEncrypted() +} + +// IsPlain returns true if the secret is in plain text +func (s *Secret) IsPlain() bool { + s.RLock() + defer s.RUnlock() + + return s.provider.GetStatus() == SecretStatusPlain +} + +// IsNotPlainAndNotEmpty returns true if the secret is not plain and not empty. +// This is an utility method, we update the secret for an existing user +// if it is empty or plain +func (s *Secret) IsNotPlainAndNotEmpty() bool { + s.RLock() + defer s.RUnlock() + + return !s.IsPlain() && !s.IsEmpty() +} + +// IsRedacted returns true if the secret is redacted +func (s *Secret) IsRedacted() bool { + s.RLock() + defer s.RUnlock() + + return s.provider.GetStatus() == SecretStatusRedacted +} + +// GetPayload returns the secret payload +func (s *Secret) GetPayload() string { + s.RLock() + defer s.RUnlock() + + return s.provider.GetPayload() +} + +// GetAdditionalData returns the secret additional data +func (s *Secret) GetAdditionalData() string { + s.RLock() + defer s.RUnlock() + + return s.provider.GetAdditionalData() +} + +// GetStatus returns the secret status +func (s *Secret) GetStatus() SecretStatus { + s.RLock() + defer s.RUnlock() + + return s.provider.GetStatus() +} + +// GetKey returns the secret key +func (s *Secret) GetKey() string { + s.RLock() + defer s.RUnlock() + + return s.provider.GetKey() +} + +// GetMode returns the secret mode +func (s *Secret) GetMode() int { + s.RLock() + defer s.RUnlock() + + return s.provider.GetMode() +} + +// SetAdditionalData sets the given additional data +func (s *Secret) SetAdditionalData(value string) { + s.Lock() + defer s.Unlock() + + s.provider.SetAdditionalData(value) +} + +// SetStatus sets the status for this secret +func (s *Secret) SetStatus(value SecretStatus) { + s.Lock() + defer s.Unlock() + + s.provider.SetStatus(value) +} + +// SetKey sets the key for this secret +func (s *Secret) SetKey(value string) { + s.Lock() + defer s.Unlock() + + s.provider.SetKey(value) +} + +// IsEmpty returns true if all fields are empty +func (s *Secret) IsEmpty() bool { + s.RLock() + defer s.RUnlock() + + 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 { + s.RLock() + defer s.RUnlock() + + 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 { + s.RLock() + defer s.RUnlock() + + if !util.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.Lock() + defer s.Unlock() + + s.provider.SetKey("") + s.provider.SetAdditionalData("") +} + +// Encrypt encrypts a plain text Secret object +func (s *Secret) Encrypt() error { + s.Lock() + defer s.Unlock() + + return s.provider.Encrypt() +} + +// Decrypt decrypts a Secret object +func (s *Secret) Decrypt() error { + s.Lock() + defer s.Unlock() + + return s.provider.Decrypt() +} + +// TryDecrypt decrypts a Secret object if encrypted. +// It returns a nil error if the object is not encrypted +func (s *Secret) TryDecrypt() error { + s.Lock() + defer s.Unlock() + + if s.provider.IsEncrypted() { + return s.provider.Decrypt() + } + return nil +} diff --git a/sdk/plugin/kms.go b/sdk/plugin/kms.go index 0b8319a5..6e80a51b 100644 --- a/sdk/plugin/kms.go +++ b/sdk/plugin/kms.go @@ -9,8 +9,8 @@ import ( "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" + "github.com/drakkan/sftpgo/v2/sdk/kms" kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms" "github.com/drakkan/sftpgo/v2/util" ) diff --git a/sdk/plugin/plugin.go b/sdk/plugin/plugin.go index a0b6b845..b1fe6569 100644 --- a/sdk/plugin/plugin.go +++ b/sdk/plugin/plugin.go @@ -11,8 +11,8 @@ import ( "github.com/hashicorp/go-hclog" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sdk/plugin/auth" "github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher" kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms" diff --git a/sdk/user.go b/sdk/user.go index ac62bbab..99c97e23 100644 --- a/sdk/user.go +++ b/sdk/user.go @@ -3,7 +3,7 @@ package sdk import ( "strings" - "github.com/drakkan/sftpgo/v2/kms" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" ) diff --git a/service/service_portable.go b/service/service_portable.go index 326d1bc0..8e801458 100644 --- a/service/service_portable.go +++ b/service/service_portable.go @@ -17,9 +17,9 @@ import ( "github.com/drakkan/sftpgo/v2/config" "github.com/drakkan/sftpgo/v2/dataprovider" "github.com/drakkan/sftpgo/v2/ftpd" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sftpd" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/version" diff --git a/sftpd/cryptfs_test.go b/sftpd/cryptfs_test.go index 6d8f2c84..aefaaf9b 100644 --- a/sftpd/cryptfs_test.go +++ b/sftpd/cryptfs_test.go @@ -15,8 +15,8 @@ import ( "github.com/drakkan/sftpgo/v2/dataprovider" "github.com/drakkan/sftpgo/v2/httpdtest" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/vfs" ) diff --git a/sftpd/internal_test.go b/sftpd/internal_test.go index 523b4091..6d5b57c7 100644 --- a/sftpd/internal_test.go +++ b/sftpd/internal_test.go @@ -20,8 +20,8 @@ import ( "github.com/drakkan/sftpgo/v2/common" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/vfs" ) diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index ab9b4d73..7a84a984 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -42,10 +42,11 @@ import ( "github.com/drakkan/sftpgo/v2/config" "github.com/drakkan/sftpgo/v2/dataprovider" "github.com/drakkan/sftpgo/v2/httpdtest" - "github.com/drakkan/sftpgo/v2/kms" + _ "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/mfa" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sftpd" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/vfs" diff --git a/vfs/filesystem.go b/vfs/filesystem.go index 0947a6c4..7f15c06e 100644 --- a/vfs/filesystem.go +++ b/vfs/filesystem.go @@ -3,8 +3,8 @@ package vfs import ( "fmt" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" ) diff --git a/vfs/gcsfs.go b/vfs/gcsfs.go index 5ce06ff6..6af87892 100644 --- a/vfs/gcsfs.go +++ b/vfs/gcsfs.go @@ -23,9 +23,9 @@ import ( "google.golang.org/api/iterator" "google.golang.org/api/option" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/metric" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sdk/plugin" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/version" diff --git a/vfs/sftpfs.go b/vfs/sftpfs.go index 3caee526..692c0d98 100644 --- a/vfs/sftpfs.go +++ b/vfs/sftpfs.go @@ -19,9 +19,9 @@ import ( "github.com/rs/xid" "golang.org/x/crypto/ssh" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/version" ) diff --git a/vfs/vfs.go b/vfs/vfs.go index fa177e0c..73807f04 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -16,9 +16,9 @@ import ( "github.com/eikenb/pipeat" "github.com/pkg/sftp" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sdk/plugin" "github.com/drakkan/sftpgo/v2/sdk/plugin/metadata" "github.com/drakkan/sftpgo/v2/util" diff --git a/webdavd/internal_test.go b/webdavd/internal_test.go index 326cc2de..2a2b099d 100644 --- a/webdavd/internal_test.go +++ b/webdavd/internal_test.go @@ -22,8 +22,8 @@ import ( "github.com/drakkan/sftpgo/v2/common" "github.com/drakkan/sftpgo/v2/dataprovider" - "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/util" "github.com/drakkan/sftpgo/v2/vfs" ) diff --git a/webdavd/webdavd_test.go b/webdavd/webdavd_test.go index 9c48e05f..4e37e50d 100644 --- a/webdavd/webdavd_test.go +++ b/webdavd/webdavd_test.go @@ -31,9 +31,10 @@ import ( "github.com/drakkan/sftpgo/v2/dataprovider" "github.com/drakkan/sftpgo/v2/httpclient" "github.com/drakkan/sftpgo/v2/httpdtest" - "github.com/drakkan/sftpgo/v2/kms" + _ "github.com/drakkan/sftpgo/v2/kms" "github.com/drakkan/sftpgo/v2/logger" "github.com/drakkan/sftpgo/v2/sdk" + "github.com/drakkan/sftpgo/v2/sdk/kms" "github.com/drakkan/sftpgo/v2/sftpd" "github.com/drakkan/sftpgo/v2/vfs" "github.com/drakkan/sftpgo/v2/webdavd"