From e87807282bc502af12f02b8c88689e9fe7f98ab3 Mon Sep 17 00:00:00 2001 From: "Josh Mahowald (Contractor)" Date: Tue, 17 Oct 2017 23:49:20 -0500 Subject: [PATCH 1/2] Adding in parameter store with encryption --- cmd/root.go | 4 +- cmd/util.go | 11 ++++ pkg/kv/aws_param/aws_param.go | 89 ++++++++++++++++++++++++++++++ pkg/kv/aws_param/aws_param_test.go | 68 +++++++++++++++++++++++ 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 pkg/kv/aws_param/aws_param.go create mode 100644 pkg/kv/aws_param/aws_param_test.go diff --git a/cmd/root.go b/cmd/root.go index 3ead337a..42693e3a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,6 +17,8 @@ const cfgSecretThreshold = "secret-threshold" const cfgMode = "mode" const cfgModeValueAWSKMSSSM = "aws-kms-ssm" +const cfgModeValueAWSParameterStore = "aws-param" + const cfgModeValueGoogleCloudKMSGCS = "google-cloud-kms-gcs" const cfgGoogleCloudKMSProject = "google-cloud-kms-project" @@ -75,7 +77,7 @@ func init() { configStringVar( cfgMode, cfgModeValueGoogleCloudKMSGCS, - fmt.Sprintf("Select the mode to use '%s' => Google Cloud Storage with encryption using Google KMS; '%s' => AWS SSM parameter store using AWS KMS encryption", cfgModeValueGoogleCloudKMSGCS, cfgModeValueAWSKMSSSM), + fmt.Sprintf("Select the mode to use '%s' => Google Cloud Storage with encryption using Google KMS; '%s' => AWS SSM parameter store using AWS KMS encryption; '%s' => AWS Parameter Store with encryption", cfgModeValueGoogleCloudKMSGCS, cfgModeValueAWSKMSSSM, cfgModeValueAWSParameterStore), ) // Secret config diff --git a/cmd/util.go b/cmd/util.go index ee4c9b58..b796444b 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -8,6 +8,7 @@ import ( "github.com/jetstack-experimental/vault-unsealer/pkg/kv" "github.com/jetstack-experimental/vault-unsealer/pkg/kv/aws_kms" "github.com/jetstack-experimental/vault-unsealer/pkg/kv/aws_ssm" + "github.com/jetstack-experimental/vault-unsealer/pkg/kv/aws_param" "github.com/jetstack-experimental/vault-unsealer/pkg/kv/cloudkms" "github.com/jetstack-experimental/vault-unsealer/pkg/kv/gcs" @@ -70,5 +71,15 @@ func kvStoreForConfig(cfg *viper.Viper) (kv.Service, error) { return kms, nil } + if cfg.GetString(cfgMode) == cfgModeValueAWSParameterStore { + ssm, err := aws_param.New(cfg.GetString(cfgAWSKMSKeyID), cfg.GetString(cfgAWSSSMKeyPrefix)) + if err != nil { + return nil, fmt.Errorf("error creating AWS Parameter Store", err.Error()) + } + + return ssm, nil + } + + return nil, fmt.Errorf("Unsupported backend mode: '%s'", cfg.GetString(cfgMode)) } diff --git a/pkg/kv/aws_param/aws_param.go b/pkg/kv/aws_param/aws_param.go new file mode 100644 index 00000000..3d3dba37 --- /dev/null +++ b/pkg/kv/aws_param/aws_param.go @@ -0,0 +1,89 @@ +package aws_param + +import ( + "encoding/base64" + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ssm" + + "github.com/jetstack-experimental/vault-unsealer/pkg/kv" +) + +type awsSSM struct { + ssmService *ssm.SSM + kmsKeyId string + keyPrefix string +} + +var _ kv.Service = &awsSSM{} +var withDecryption = true; +func NewWithSession(sess *session.Session, kmsKeyId, keyPrefix string) (*awsSSM, error) { + return &awsSSM{ + ssmService: ssm.New(sess), + kmsKeyId: kmsKeyId, + keyPrefix: keyPrefix, + }, nil +} + +func New(kmsKeyId, keyPrefix string) (*awsSSM, error) { + sess, err := session.NewSession() + if err != nil { + return nil, err + } + + return NewWithSession(sess, kmsKeyId, keyPrefix) +} + +func newTrue() *bool { + b := true + return &b +} + + +func (a *awsSSM) Get(key string) ([]byte, error) { + out, err := a.ssmService.GetParameters(&ssm.GetParametersInput{ + Names: []*string{ + aws.String(a.name(key)), + }, + WithDecryption: &withDecryption, + }) + if err != nil { + return []byte{}, err + } + + if len(out.Parameters) < 1 { + return []byte{}, kv.NewNotFoundError("key '%s' not found") + } + + return base64.StdEncoding.DecodeString(*out.Parameters[0].Value) +} + +func (a *awsSSM) name(key string) string { + return fmt.Sprintf("%s%s", a.keyPrefix, key) +} + +func (a *awsSSM) Set(key string, val []byte) error { + _, err := a.ssmService.PutParameter(&ssm.PutParameterInput{ + Description: aws.String("vault-unsealer"), + Name: aws.String(a.name(key)), + Overwrite: aws.Bool(true), + Value: aws.String(base64.StdEncoding.EncodeToString(val)), + Type: aws.String("SecureString"), + KeyId: aws.String(a.kmsKeyId), + }) + return err +} + +func (a *awsSSM) Delete(key string) error { + _, err := a.ssmService.DeleteParameter(&ssm.DeleteParameterInput{ + Name: aws.String(a.name(key)), + }) + return err +} + +func (g *awsSSM) Test(key string) error { + // TODO: Implement a test if a Set is likely to work, AWS doesn't seemt to provide a dry-run on the parameter store + return nil +} diff --git a/pkg/kv/aws_param/aws_param_test.go b/pkg/kv/aws_param/aws_param_test.go new file mode 100644 index 00000000..7ddb1057 --- /dev/null +++ b/pkg/kv/aws_param/aws_param_test.go @@ -0,0 +1,68 @@ +package aws_param + +import ( + "os" + "testing" + + "github.com/jetstack-experimental/vault-unsealer/pkg/kv" +) + +func TestAWSIntegration(t *testing.T) { + + keyID := os.Getenv("AWS_KMS_KEY_ID") + region := os.Getenv("AWS_REGION") + + if keyID == "" { + t.Skip("Skip AWS integration tests: not environment variable 'AWS_KMS_KEY_ID' specified") + } + + if region == "" { + t.Skip("Skip AWS integration tests: not environment variable 'AWS_REGION' specified") + } + + payloadKey := "test123" + payloadValue := "payload123" + + a, err := New(keyID, "test-integration-") + if err != nil { + t.Errorf("Unexpected error creating SSM kv: %s", err) + } + + // graceful set (in case it's already existing) + err = a.Set(payloadKey, []byte(payloadValue)) + if err != nil { + t.Errorf("Unexpected error storing value in SSM kv: %s", err) + } + + // this should also work and overwrite a key + err = a.Set(payloadKey, []byte(payloadValue)) + if err != nil { + t.Errorf("Unexpected error storing value in SSM kv: %s", err) + } + + // this deletes the key + err = a.Delete(payloadKey) + if err != nil { + t.Errorf("Unexpected error storing value in SSM kv: %s", err) + } + + _, err = a.Get(payloadKey) + if _, ok := err.(*kv.NotFoundError); !ok { + t.Errorf("Expected an kv.NotFoundError for a non existing key") + } + + err = a.Set(payloadKey, []byte(payloadValue)) + if err != nil { + t.Errorf("Unexpected error storing value in SSM kv: %s", err) + } + + out, err := a.Get("test123") + if err != nil { + t.Errorf("Unexpected error storing value in SSM kv: %s", err) + } + + if exp, act := payloadValue, string(out); exp != act { + t.Errorf("Unexpected decrypt output: exp=%s act=%s", exp, act) + } + +} From 9e5fb594dd813fe46a01c30e02ccc11e905ac019 Mon Sep 17 00:00:00 2001 From: "Josh Mahowald (Contractor)" Date: Wed, 18 Oct 2017 08:18:08 -0500 Subject: [PATCH 2/2] No base64 encoding necessary, and that confuses those who would interact directly with parameter store --- pkg/kv/aws_param/aws_param.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/kv/aws_param/aws_param.go b/pkg/kv/aws_param/aws_param.go index 3d3dba37..ddfad149 100644 --- a/pkg/kv/aws_param/aws_param.go +++ b/pkg/kv/aws_param/aws_param.go @@ -1,7 +1,6 @@ package aws_param import ( - "encoding/base64" "fmt" "github.com/aws/aws-sdk-go/aws" @@ -57,7 +56,7 @@ func (a *awsSSM) Get(key string) ([]byte, error) { return []byte{}, kv.NewNotFoundError("key '%s' not found") } - return base64.StdEncoding.DecodeString(*out.Parameters[0].Value) + return []byte(*out.Parameters[0].Value), nil } func (a *awsSSM) name(key string) string { @@ -69,9 +68,9 @@ func (a *awsSSM) Set(key string, val []byte) error { Description: aws.String("vault-unsealer"), Name: aws.String(a.name(key)), Overwrite: aws.Bool(true), - Value: aws.String(base64.StdEncoding.EncodeToString(val)), + Value: aws.String(string(val)), Type: aws.String("SecureString"), - KeyId: aws.String(a.kmsKeyId), + // KeyId: aws.String(a.kmsKeyId), }) return err }