From 338301955ff0f682e2d8ac60ece13e76d8dc0c33 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sat, 17 Jul 2021 13:08:05 +0200 Subject: [PATCH] move cloud KMS providers to an external plugin --- docs/kms.md | 38 +------------ kms/aws/aws.go | 67 ----------------------- kms/aws/aws_disabled.go | 11 ---- kms/gcp/gcp.go | 67 ----------------------- kms/gcp/gcp_disabled.go | 11 ---- kms/gocloud/gocloud.go | 105 ------------------------------------ kms/kms.go | 1 + kms/vault/vault.go | 67 ----------------------- kms/vault/vault_disabled.go | 11 ---- main.go | 3 -- version/version.go | 4 +- 11 files changed, 5 insertions(+), 380 deletions(-) delete mode 100644 kms/aws/aws.go delete mode 100644 kms/aws/aws_disabled.go delete mode 100644 kms/gcp/gcp.go delete mode 100644 kms/gcp/gcp_disabled.go delete mode 100644 kms/gocloud/gocloud.go delete mode 100644 kms/vault/vault.go delete mode 100644 kms/vault/vault_disabled.go diff --git a/docs/kms.md b/docs/kms.md index e0285e0c..f2c53ae8 100644 --- a/docs/kms.md +++ b/docs/kms.md @@ -9,8 +9,6 @@ The `secrets` section of the `kms` configuration allows to configure how to encr - `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://pkg.go.dev/golang.org/x/crypto/nacl/secretbox) algorithm to perform encryption and authentication. @@ -22,41 +20,9 @@ We first generate a random key, then the per-object encryption key is derived fr 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 +### Cloud providers -To use keys from Google Cloud Platform’s [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 Service’s [Key Management Service](https://aws.amazon.com/kms/) (AWS KMS) you have to use `awskms` as URL scheme. You can use the key’s 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. +Several cloud providers are supported using the [sftpgo-plugin-kms](https://github.com/sftpgo/sftpgo-plugin-kms). ### Notes diff --git a/kms/aws/aws.go b/kms/aws/aws.go deleted file mode 100644 index a9baaffa..00000000 --- a/kms/aws/aws.go +++ /dev/null @@ -1,67 +0,0 @@ -// +build !noawskms - -package aws - -import ( - // we import awskms here to be able to disable AWS KMS support using a build tag - _ "gocloud.dev/secrets/awskms" - - "github.com/drakkan/sftpgo/v2/kms" - "github.com/drakkan/sftpgo/v2/kms/gocloud" - "github.com/drakkan/sftpgo/v2/version" -) - -const encryptedStatus = kms.SecretStatusAWS - -type awsSecret struct { - gocloud.Secret -} - -func init() { - version.AddFeature("+awskms") - kms.RegisterSecretProvider(kms.SchemeAWS, encryptedStatus, newAWSSecret) -} - -func newAWSSecret(base kms.BaseSecret, url, masterKey string) kms.SecretProvider { - return &awsSecret{ - gocloud.Secret{ - BaseSecret: base, - URL: url, - MasterKey: masterKey, - }, - } -} - -func (s *awsSecret) Name() string { - return "AWS" -} - -func (s *awsSecret) IsEncrypted() bool { - return s.Status == encryptedStatus -} - -func (s *awsSecret) Encrypt() error { - if err := s.Secret.Encrypt(); err != nil { - return err - } - s.Status = encryptedStatus - return nil -} - -func (s *awsSecret) Decrypt() error { - if !s.IsEncrypted() { - return kms.ErrWrongSecretStatus - } - return s.Secret.Decrypt() -} - -func (s *awsSecret) Clone() kms.SecretProvider { - baseSecret := kms.BaseSecret{ - Status: s.Status, - Payload: s.Payload, - Key: s.Key, - AdditionalData: s.AdditionalData, - Mode: s.Mode, - } - return newAWSSecret(baseSecret, s.URL, s.MasterKey) -} diff --git a/kms/aws/aws_disabled.go b/kms/aws/aws_disabled.go deleted file mode 100644 index 65caa5f8..00000000 --- a/kms/aws/aws_disabled.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build noawskms - -package aws - -import ( - "github.com/drakkan/sftpgo/v2/version" -) - -func init() { - version.AddFeature("-awskms") -} diff --git a/kms/gcp/gcp.go b/kms/gcp/gcp.go deleted file mode 100644 index 61ddc0ce..00000000 --- a/kms/gcp/gcp.go +++ /dev/null @@ -1,67 +0,0 @@ -// +build !nogcpkms - -package gcp - -import ( - // we import gcpkms here to be able to disable GCP KMS support using a build tag - _ "gocloud.dev/secrets/gcpkms" - - "github.com/drakkan/sftpgo/v2/kms" - "github.com/drakkan/sftpgo/v2/kms/gocloud" - "github.com/drakkan/sftpgo/v2/version" -) - -const encryptedStatus = kms.SecretStatusGCP - -type gcpSecret struct { - gocloud.Secret -} - -func init() { - version.AddFeature("+gcpkms") - kms.RegisterSecretProvider(kms.SchemeGCP, encryptedStatus, newGCPSecret) -} - -func newGCPSecret(base kms.BaseSecret, url, masterKey string) kms.SecretProvider { - return &gcpSecret{ - gocloud.Secret{ - BaseSecret: base, - URL: url, - MasterKey: masterKey, - }, - } -} - -func (s *gcpSecret) Name() string { - return "GCP" -} - -func (s *gcpSecret) IsEncrypted() bool { - return s.Status == encryptedStatus -} - -func (s *gcpSecret) Encrypt() error { - if err := s.Secret.Encrypt(); err != nil { - return err - } - s.Status = encryptedStatus - return nil -} - -func (s *gcpSecret) Decrypt() error { - if !s.IsEncrypted() { - return kms.ErrWrongSecretStatus - } - return s.Secret.Decrypt() -} - -func (s *gcpSecret) Clone() kms.SecretProvider { - baseSecret := kms.BaseSecret{ - Status: s.Status, - Payload: s.Payload, - Key: s.Key, - AdditionalData: s.AdditionalData, - Mode: s.Mode, - } - return newGCPSecret(baseSecret, s.URL, s.MasterKey) -} diff --git a/kms/gcp/gcp_disabled.go b/kms/gcp/gcp_disabled.go deleted file mode 100644 index bb9c62bc..00000000 --- a/kms/gcp/gcp_disabled.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build nogcpkms - -package gcp - -import ( - "github.com/drakkan/sftpgo/v2/version" -) - -func init() { - version.AddFeature("-gcpkms") -} diff --git a/kms/gocloud/gocloud.go b/kms/gocloud/gocloud.go deleted file mode 100644 index 11511a6f..00000000 --- a/kms/gocloud/gocloud.go +++ /dev/null @@ -1,105 +0,0 @@ -package gocloud - -import ( - "context" - "encoding/base64" - "time" - - "gocloud.dev/secrets" - - "github.com/drakkan/sftpgo/v2/kms" -) - -const ( - defaultTimeout = 10 * time.Second -) - -// Secret defines common methods for go-cloud based kms -type Secret struct { - kms.BaseSecret - MasterKey string - URL string -} - -func (s *Secret) Encrypt() error { - if s.Status != kms.SecretStatusPlain { - return kms.ErrWrongSecretStatus - } - if s.Payload == "" { - return kms.ErrInvalidSecret - } - - payload := s.Payload - key := "" - mode := 0 - if s.MasterKey != "" { - localSecret := kms.NewLocalSecret(s.BaseSecret, "", s.MasterKey) - err := localSecret.Encrypt() - if err != nil { - return err - } - payload = localSecret.GetPayload() - key = localSecret.GetKey() - mode = localSecret.GetMode() - } - - 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 - s.Mode = mode - return nil -} - -func (s *Secret) 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 := kms.BaseSecret{ - Status: kms.SecretStatusSecretBox, - Payload: payload, - Key: s.Key, - AdditionalData: s.AdditionalData, - Mode: s.Mode, - } - localSecret := kms.NewLocalSecret(baseSecret, "", s.MasterKey) - err = localSecret.Decrypt() - if err != nil { - return err - } - payload = localSecret.GetPayload() - } - s.Status = kms.SecretStatusPlain - s.Payload = payload - s.Key = "" - s.AdditionalData = "" - s.Mode = 0 - return nil -} diff --git a/kms/kms.go b/kms/kms.go index dff231e7..40d054c9 100644 --- a/kms/kms.go +++ b/kms/kms.go @@ -171,6 +171,7 @@ func (c *Configuration) getSecretProvider(base BaseSecret) SecretProvider { 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) } diff --git a/kms/vault/vault.go b/kms/vault/vault.go deleted file mode 100644 index cb5f895e..00000000 --- a/kms/vault/vault.go +++ /dev/null @@ -1,67 +0,0 @@ -// +build !novaultkms - -package vault - -import ( - // we import hashivault here to be able to disable Vault support using a build tag - _ "gocloud.dev/secrets/hashivault" - - "github.com/drakkan/sftpgo/v2/kms" - "github.com/drakkan/sftpgo/v2/kms/gocloud" - "github.com/drakkan/sftpgo/v2/version" -) - -const encryptedStatus = kms.SecretStatusVaultTransit - -type vaultSecret struct { - gocloud.Secret -} - -func init() { - version.AddFeature("+vaultkms") - kms.RegisterSecretProvider(kms.SchemeVaultTransit, encryptedStatus, newVaultSecret) -} - -func newVaultSecret(base kms.BaseSecret, url, masterKey string) kms.SecretProvider { - return &vaultSecret{ - gocloud.Secret{ - BaseSecret: base, - URL: url, - MasterKey: masterKey, - }, - } -} - -func (s *vaultSecret) Name() string { - return "VaultTransit" -} - -func (s *vaultSecret) IsEncrypted() bool { - return s.Status == encryptedStatus -} - -func (s *vaultSecret) Encrypt() error { - if err := s.Secret.Encrypt(); err != nil { - return err - } - s.Status = encryptedStatus - return nil -} - -func (s *vaultSecret) Decrypt() error { - if !s.IsEncrypted() { - return kms.ErrWrongSecretStatus - } - return s.Secret.Decrypt() -} - -func (s *vaultSecret) Clone() kms.SecretProvider { - baseSecret := kms.BaseSecret{ - Status: s.Status, - Payload: s.Payload, - Key: s.Key, - AdditionalData: s.AdditionalData, - Mode: s.Mode, - } - return newVaultSecret(baseSecret, s.URL, s.MasterKey) -} diff --git a/kms/vault/vault_disabled.go b/kms/vault/vault_disabled.go deleted file mode 100644 index 050dd204..00000000 --- a/kms/vault/vault_disabled.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build novaultkms - -package vault - -import ( - "github.com/drakkan/sftpgo/v2/version" -) - -func init() { - version.AddFeature("-vaultkms") -} diff --git a/main.go b/main.go index df3472c6..4bdac6e5 100644 --- a/main.go +++ b/main.go @@ -11,9 +11,6 @@ import ( "go.uber.org/automaxprocs/maxprocs" "github.com/drakkan/sftpgo/v2/cmd" - _ "github.com/drakkan/sftpgo/v2/kms/aws" - _ "github.com/drakkan/sftpgo/v2/kms/gcp" - _ "github.com/drakkan/sftpgo/v2/kms/vault" ) func main() { diff --git a/version/version.go b/version/version.go index 4f9e1ecb..676211d6 100644 --- a/version/version.go +++ b/version/version.go @@ -22,11 +22,11 @@ type Info struct { func GetAsString() string { var sb strings.Builder sb.WriteString(info.Version) - if len(info.CommitHash) > 0 { + if info.CommitHash != "" { sb.WriteString("-") sb.WriteString(info.CommitHash) } - if len(info.BuildDate) > 0 { + if info.BuildDate != "" { sb.WriteString("-") sb.WriteString(info.BuildDate) }