From 53aded259616ee61bfdbd821beec20e778a1eef7 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Thu, 28 Apr 2022 03:14:58 +0530 Subject: [PATCH 1/7] add support for AWS KMS credentials using .spec.decryption Signed-off-by: Sanskar Jaiswal --- controllers/kustomization_decryptor.go | 13 + controllers/kustomization_decryptor_test.go | 23 ++ go.mod | 2 +- internal/sops/awskms/keysource.go | 284 ++++++++++++++++++++ internal/sops/awskms/keysource_test.go | 47 ++++ internal/sops/keyservice/options.go | 9 + internal/sops/keyservice/server.go | 57 ++++ internal/sops/keyservice/server_test.go | 51 ++++ internal/sops/keyservice/utils_test.go | 9 + 9 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 internal/sops/awskms/keysource.go create mode 100644 internal/sops/awskms/keysource_test.go diff --git a/controllers/kustomization_decryptor.go b/controllers/kustomization_decryptor.go index c7d2b5cc..0706d0c7 100644 --- a/controllers/kustomization_decryptor.go +++ b/controllers/kustomization_decryptor.go @@ -47,6 +47,7 @@ import ( kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" "github.com/fluxcd/kustomize-controller/internal/sops/age" + "github.com/fluxcd/kustomize-controller/internal/sops/awskms" "github.com/fluxcd/kustomize-controller/internal/sops/azkv" intkeyservice "github.com/fluxcd/kustomize-controller/internal/sops/keyservice" "github.com/fluxcd/kustomize-controller/internal/sops/pgp" @@ -64,6 +65,9 @@ const ( // DecryptionVaultTokenFileName is the name of the file containing the // Hashicorp Vault token. DecryptionVaultTokenFileName = "sops.vault-token" + // DecryptionVaultTokenFileName is the name of the file containing the + // AWS KMS credentials + DecryptionAWSKmsFile = "sops.aws-kms" // DecryptionAzureAuthFile is the name of the file containing the Azure // credentials. DecryptionAzureAuthFile = "sops.azure-kv" @@ -129,6 +133,9 @@ type KustomizeDecryptor struct { // vaultToken is the Hashicorp Vault token used to authenticate towards // any Vault server. vaultToken string + // awsCreds is the AWS credentials object used to authenticate towards + // any AWS KMS. + awsCreds *awskms.Creds // azureToken is the Azure credential token used to authenticate towards // any Azure Key Vault. azureToken *azkv.Token @@ -220,6 +227,12 @@ func (d *KustomizeDecryptor) ImportKeys(ctx context.Context) error { token = strings.Trim(strings.TrimSpace(token), "\n") d.vaultToken = token } + case filepath.Ext(DecryptionAWSKmsFile): + if name == DecryptionAWSKmsFile { + if d.awsCreds, err = awskms.LoadAwsKmsCredsFromYaml(value); err != nil { + return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err) + } + } case filepath.Ext(DecryptionAzureAuthFile): // Make sure we have the absolute name if name == DecryptionAzureAuthFile { diff --git a/controllers/kustomization_decryptor_test.go b/controllers/kustomization_decryptor_test.go index 5ceff12d..d9b2cfe6 100644 --- a/controllers/kustomization_decryptor_test.go +++ b/controllers/kustomization_decryptor_test.go @@ -387,6 +387,29 @@ func TestKustomizeDecryptor_ImportKeys(t *testing.T) { g.Expect(decryptor.vaultToken).To(Equal("some-hcvault-token")) }, }, + { + name: "AWS KMS credentials", + decryption: &kustomizev1.Decryption{ + Provider: provider, + SecretRef: &meta.LocalObjectReference{ + Name: "awskms-secret", + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "awskms-secret", + Namespace: provider, + }, + Data: map[string][]byte{ + DecryptionAWSKmsFile: []byte(`aws_access_key_id: test-id +aws_secret_access_key: test-secret +aws_session_token: test-token`), + }, + }, + inspectFunc: func(g *GomegaWithT, decryptor *KustomizeDecryptor) { + g.Expect(decryptor.awsCreds).ToNot(BeNil()) + }, + }, { name: "Azure Key Vault token", decryption: &kustomizev1.Decryption{ diff --git a/go.mod b/go.mod index bbf2d6e6..0e4b2802 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.4.0 + github.com/aws/aws-sdk-go v1.37.18 github.com/cyphar/filepath-securejoin v0.2.3 github.com/dimchansky/utfbom v1.1.1 github.com/drone/envsubst v1.0.3 @@ -82,7 +83,6 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/armon/go-metrics v0.3.10 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/aws/aws-sdk-go v1.43.43 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect diff --git a/internal/sops/awskms/keysource.go b/internal/sops/awskms/keysource.go new file mode 100644 index 00000000..d0009155 --- /dev/null +++ b/internal/sops/awskms/keysource.go @@ -0,0 +1,284 @@ +package awskms + +import ( + "encoding/base64" + "fmt" + "os" + "regexp" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/kms" + "github.com/aws/aws-sdk-go/service/sts" + "sigs.k8s.io/yaml" +) + +const ( + arnRegex = `^arn:aws[\w-]*:kms:(.+):[0-9]+:(key|alias)/.+$` + stsSessionRegex = "[^a-zA-Z0-9=,.@-]+" +) + +// MasterKey is a AWS KMS key used to encrypt and decrypt sops' data key. +type MasterKey struct { + Arn string + Role string + EncryptedKey string + CreationDate time.Time + EncryptionContext map[string]*string + credentials *credentials.Credentials +} + +type Creds struct { + credentials *credentials.Credentials +} + +func NewCreds(credentials *credentials.Credentials) *Creds { + return &Creds{ + credentials: credentials, + } +} + +func LoadAwsKmsCredsFromYaml(b []byte) (*Creds, error) { + credInfo := struct { + AccessKeyID string `json:"aws_access_key_id"` + SecretAccessKey string `json:"aws_secret_access_key"` + SessionToken string `json:"aws_session_token"` + }{} + if err := yaml.Unmarshal(b, &credInfo); err != nil { + return nil, fmt.Errorf("failed to unmarshal AWS credentials file: %w", err) + } + creds := credentials.NewStaticCredentials(credInfo.AccessKeyID, credInfo.SecretAccessKey, credInfo.SessionToken) + return &Creds{ + credentials: creds, + }, nil +} + +func (c Creds) ApplyToMasterKey(key *MasterKey) { + key.credentials = c.credentials +} + +// EncryptedDataKey returns the encrypted data key this master key holds +func (key *MasterKey) EncryptedDataKey() []byte { + return []byte(key.EncryptedKey) +} + +// SetEncryptedDataKey sets the encrypted data key for this master key +func (key *MasterKey) SetEncryptedDataKey(enc []byte) { + key.EncryptedKey = string(enc) +} + +// Encrypt takes a sops data key, encrypts it with KMS and stores the result in the EncryptedKey field +func (key *MasterKey) Encrypt(dataKey []byte) error { + sess, err := key.createSession() + if err != nil { + return fmt.Errorf("failed to create session: %w", err) + } + kmsSvc := kms.New(sess) + out, err := kmsSvc.Encrypt(&kms.EncryptInput{Plaintext: dataKey, KeyId: &key.Arn, EncryptionContext: key.EncryptionContext}) + if err != nil { + return fmt.Errorf("failed to encrypt sops data key with AWS KMS: %w", err) + } + key.EncryptedKey = base64.StdEncoding.EncodeToString(out.CiphertextBlob) + return nil +} + +// EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet +func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error { + if key.EncryptedKey == "" { + return key.Encrypt(dataKey) + } + return nil +} + +// Decrypt decrypts the EncryptedKey field with AWS KMS and returns the result. +func (key *MasterKey) Decrypt() ([]byte, error) { + k, err := base64.StdEncoding.DecodeString(key.EncryptedKey) + if err != nil { + return nil, fmt.Errorf("error base64-decoding encrypted data key: %s", err) + } + sess, err := key.createSession() + if err != nil { + return nil, fmt.Errorf("error creating AWS session: %w", err) + } + kmsSvc := kms.New(sess) + decrypted, err := kmsSvc.Decrypt(&kms.DecryptInput{CiphertextBlob: k, EncryptionContext: key.EncryptionContext}) + if err != nil { + return nil, fmt.Errorf("failed to decrypt sops data key with AWS KMS: %w", err) + } + return decrypted.Plaintext, nil +} + +// NeedsRotation returns whether the data key needs to be rotated or not. +func (key *MasterKey) NeedsRotation() bool { + return time.Since(key.CreationDate) > (time.Hour * 24 * 30 * 6) +} + +// ToString converts the key to a string representation +func (key *MasterKey) ToString() string { + return key.Arn +} + +// NewMasterKey creates a new MasterKey from an ARN, role and context, setting the creation date to the current date +func NewMasterKey(arn string, role string, context map[string]*string) *MasterKey { + return &MasterKey{ + Arn: arn, + Role: role, + EncryptionContext: context, + CreationDate: time.Now().UTC(), + } +} + +// NewMasterKeyFromArn takes an ARN string and returns a new MasterKey for that ARN +func NewMasterKeyFromArn(arn string, context map[string]*string, awsProfile string) *MasterKey { + k := &MasterKey{} + arn = strings.Replace(arn, " ", "", -1) + roleIndex := strings.Index(arn, "+arn:aws:iam::") + if roleIndex > 0 { + k.Arn = arn[:roleIndex] + k.Role = arn[roleIndex+1:] + } else { + k.Arn = arn + } + k.EncryptionContext = context + k.CreationDate = time.Now().UTC() + return k +} + +// MasterKeysFromArnString takes a comma separated list of AWS KMS ARNs and returns a slice of new MasterKeys for those ARNs +func MasterKeysFromArnString(arn string, context map[string]*string, awsProfile string) []*MasterKey { + var keys []*MasterKey + if arn == "" { + return keys + } + for _, s := range strings.Split(arn, ",") { + keys = append(keys, NewMasterKeyFromArn(s, context, awsProfile)) + } + return keys +} + +func (key MasterKey) createSession() (*session.Session, error) { + re := regexp.MustCompile(arnRegex) + matches := re.FindStringSubmatch(key.Arn) + if matches == nil { + return nil, fmt.Errorf("No valid ARN found in %q", key.Arn) + } + + config := aws.Config{ + Region: aws.String(matches[1]), + Credentials: key.credentials, + } + + opts := session.Options{ + Config: config, + AssumeRoleTokenProvider: stscreds.StdinTokenProvider, + SharedConfigState: session.SharedConfigEnable, + } + sess, err := session.NewSessionWithOptions(opts) + if err != nil { + return nil, err + } + if key.Role != "" { + return key.createStsSession(config, sess) + } + return sess, nil +} + +func (key MasterKey) createStsSession(config aws.Config, sess *session.Session) (*session.Session, error) { + hostname, err := os.Hostname() + if err != nil { + return nil, err + } + stsRoleSessionNameRe, err := regexp.Compile(stsSessionRegex) + if err != nil { + return nil, fmt.Errorf("Failed to compile STS role session name regex: %w", err) + } + sanitizedHostname := stsRoleSessionNameRe.ReplaceAllString(hostname, "") + stsService := sts.New(sess) + name := "sops@" + sanitizedHostname + out, err := stsService.AssumeRole(&sts.AssumeRoleInput{ + RoleArn: &key.Role, RoleSessionName: &name}) + if err != nil { + return nil, fmt.Errorf("Failed to assume role %q: %w", key.Role, err) + } + config.Credentials = credentials.NewStaticCredentials(*out.Credentials.AccessKeyId, + *out.Credentials.SecretAccessKey, *out.Credentials.SessionToken) + sess, err = session.NewSession(&config) + if err != nil { + return nil, fmt.Errorf("Failed to create new aws session: %w", err) + } + return sess, nil +} + +// ToMap converts the MasterKey to a map for serialization purposes +func (key MasterKey) ToMap() map[string]interface{} { + out := make(map[string]interface{}) + out["arn"] = key.Arn + if key.Role != "" { + out["role"] = key.Role + } + out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) + out["enc"] = key.EncryptedKey + if key.EncryptionContext != nil { + outcontext := make(map[string]string) + for k, v := range key.EncryptionContext { + outcontext[k] = *v + } + out["context"] = outcontext + } + return out +} + +// ParseKMSContext takes either a KMS context map or a comma-separated list of KMS context key:value pairs and returns a map +func ParseKMSContext(in interface{}) map[string]*string { + // nonStringValueWarning := "Encryption context contains a non-string value, context will not be used" + out := make(map[string]*string) + + switch in := in.(type) { + case map[string]interface{}: + if len(in) == 0 { + return nil + } + for k, v := range in { + value, ok := v.(string) + if !ok { + // log.Warn(nonStringValueWarning) + return nil + } + out[k] = &value + } + case map[interface{}]interface{}: + if len(in) == 0 { + return nil + } + for k, v := range in { + key, ok := k.(string) + if !ok { + // log.Warn(nonStringValueWarning) + return nil + } + value, ok := v.(string) + if !ok { + // log.Warn(nonStringValueWarning) + return nil + } + out[key] = &value + } + case string: + if in == "" { + return nil + } + for _, kv := range strings.Split(in, ",") { + kv := strings.Split(kv, ":") + if len(kv) != 2 { + // log.Warn(nonStringValueWarning) + return nil + } + out[kv[0]] = &kv[1] + } + } + return out +} diff --git a/internal/sops/awskms/keysource_test.go b/internal/sops/awskms/keysource_test.go new file mode 100644 index 00000000..2a7e6962 --- /dev/null +++ b/internal/sops/awskms/keysource_test.go @@ -0,0 +1,47 @@ +package awskms + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws/credentials" + . "github.com/onsi/gomega" +) + +func TestCreds_ApplyToMasterKey(t *testing.T) { + g := NewWithT(t) + + creds := Creds{ + credentials: credentials.NewStaticCredentials("test-id", "test-secret", "test-token"), + } + key := &MasterKey{} + creds.ApplyToMasterKey(key) + g.Expect(key.credentials).To(Equal(creds.credentials)) +} + +func TestLoadAwsKmsCredsFromYaml(t *testing.T) { + g := NewWithT(t) + credsYaml := []byte(` +aws_access_key_id: test-id +aws_secret_access_key: test-secret +aws_session_token: test-token + `) + creds, err := LoadAwsKmsCredsFromYaml(credsYaml) + g.Expect(err).ToNot(HaveOccurred()) + value, err := creds.credentials.Get() + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(value.AccessKeyID).To(Equal("test-id")) + g.Expect(value.SecretAccessKey).To(Equal("test-secret")) + g.Expect(value.SessionToken).To(Equal("test-token")) +} + +func Test_createSession(t *testing.T) { + g := NewWithT(t) + creds := credentials.NewStaticCredentials("test-id", "test-secret", "") + key := MasterKey{ + Arn: "arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48", + credentials: creds, + } + sess, err := key.createSession() + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(sess).ToNot(BeNil()) +} diff --git a/internal/sops/keyservice/options.go b/internal/sops/keyservice/options.go index 6e2cb8e3..da160e60 100644 --- a/internal/sops/keyservice/options.go +++ b/internal/sops/keyservice/options.go @@ -21,6 +21,7 @@ import ( "go.mozilla.org/sops/v3/keyservice" "github.com/fluxcd/kustomize-controller/internal/sops/age" + "github.com/fluxcd/kustomize-controller/internal/sops/awskms" "github.com/fluxcd/kustomize-controller/internal/sops/azkv" "github.com/fluxcd/kustomize-controller/internal/sops/hcvault" "github.com/fluxcd/kustomize-controller/internal/sops/pgp" @@ -75,3 +76,11 @@ type WithDefaultServer struct { func (o WithDefaultServer) ApplyToServer(s *Server) { s.defaultServer = o.Server } + +type WithAWSKeys struct { + creds *awskms.Creds +} + +func (o WithAWSKeys) ApplyToServer(s *Server) { + s.awsCreds = o.creds +} diff --git a/internal/sops/keyservice/server.go b/internal/sops/keyservice/server.go index 545608e4..5a3a6206 100644 --- a/internal/sops/keyservice/server.go +++ b/internal/sops/keyservice/server.go @@ -11,6 +11,7 @@ import ( "golang.org/x/net/context" "github.com/fluxcd/kustomize-controller/internal/sops/age" + "github.com/fluxcd/kustomize-controller/internal/sops/awskms" "github.com/fluxcd/kustomize-controller/internal/sops/azkv" "github.com/fluxcd/kustomize-controller/internal/sops/hcvault" "github.com/fluxcd/kustomize-controller/internal/sops/pgp" @@ -43,6 +44,11 @@ type Server struct { // When nil, the request will be handled by defaultServer. azureToken *azkv.Token + // awsCreds is the Credentials object used for Encrypt and Decrypt + // operations of AWS KMS requests. + // When nil, the request will be handled by defaultServer. + awsCreds *awskms.Creds + // defaultServer is the fallback server, used to handle any request that // is not eligible to be handled by this Server. defaultServer keyservice.KeyServiceServer @@ -96,6 +102,16 @@ func (ks Server) Encrypt(ctx context.Context, req *keyservice.EncryptRequest) (* Ciphertext: ciphertext, }, nil } + case *keyservice.Key_KmsKey: + if ks.awsCreds != nil { + cipherText, err := ks.encryptWithAWSKMS(k.KmsKey, req.Plaintext) + if err != nil { + return nil, err + } + return &keyservice.EncryptResponse{ + Ciphertext: cipherText, + }, nil + } case *keyservice.Key_AzureKeyvaultKey: if ks.azureToken != nil { ciphertext, err := ks.encryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Plaintext) @@ -144,6 +160,16 @@ func (ks Server) Decrypt(ctx context.Context, req *keyservice.DecryptRequest) (* Plaintext: plaintext, }, nil } + case *keyservice.Key_KmsKey: + if ks.awsCreds != nil { + plaintext, err := ks.decryptWithAWSKMS(k.KmsKey, req.Ciphertext) + if err != nil { + return nil, err + } + return &keyservice.DecryptResponse{ + Plaintext: plaintext, + }, nil + } case *keyservice.Key_AzureKeyvaultKey: if ks.azureToken != nil { plaintext, err := ks.decryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Ciphertext) @@ -232,6 +258,37 @@ func (ks *Server) decryptWithHCVault(key *keyservice.VaultKey, ciphertext []byte return plaintext, err } +func (ks *Server) encryptWithAWSKMS(key *keyservice.KmsKey, plaintext []byte) ([]byte, error) { + context := make(map[string]*string) + for key, val := range key.Context { + context[key] = &val + } + awsKey := awskms.MasterKey{ + Arn: key.Arn, + Role: key.Role, + EncryptionContext: context, + } + ks.awsCreds.ApplyToMasterKey(&awsKey) + if err := awsKey.Encrypt(plaintext); err != nil { + return nil, err + } + return []byte(awsKey.EncryptedKey), nil +} + +func (ks *Server) decryptWithAWSKMS(key *keyservice.KmsKey, cipherText []byte) ([]byte, error) { + context := make(map[string]*string) + for key, val := range key.Context { + context[key] = &val + } + awsKey := awskms.MasterKey{ + Arn: key.Arn, + Role: key.Role, + EncryptionContext: context, + } + ks.awsCreds.ApplyToMasterKey(&awsKey) + return awsKey.Decrypt() +} + func (ks *Server) encryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, plaintext []byte) ([]byte, error) { azureKey := azkv.MasterKey{ VaultURL: key.VaultUrl, diff --git a/internal/sops/keyservice/server_test.go b/internal/sops/keyservice/server_test.go index 2231a930..1e333163 100644 --- a/internal/sops/keyservice/server_test.go +++ b/internal/sops/keyservice/server_test.go @@ -22,11 +22,13 @@ import ( "testing" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/aws/aws-sdk-go/aws/credentials" . "github.com/onsi/gomega" "go.mozilla.org/sops/v3/keyservice" "golang.org/x/net/context" "github.com/fluxcd/kustomize-controller/internal/sops/age" + "github.com/fluxcd/kustomize-controller/internal/sops/awskms" "github.com/fluxcd/kustomize-controller/internal/sops/azkv" "github.com/fluxcd/kustomize-controller/internal/sops/hcvault" "github.com/fluxcd/kustomize-controller/internal/sops/pgp" @@ -147,6 +149,55 @@ func TestServer_EncryptDecrypt_HCVault_Fallback(t *testing.T) { g.Expect(fallback.encryptReqs).To(HaveLen(0)) } +func TestServer_EncryptDecrypt_awskms(t *testing.T) { + g := NewWithT(t) + creds := credentials.NewEnvCredentials() + s := NewServer(WithAWSKeys{creds: awskms.NewCreds(creds)}) + + key := KeyFromMasterKey(awskms.NewMasterKeyFromArn("arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48", nil, "")) + _, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key with AWS KMS")) + + _, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{ + Key: &key, + }) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key with AWS KMS")) +} + +func TestServer_EncryptDecrypt_awskms_Fallback(t *testing.T) { + g := NewWithT(t) + + fallback := NewMockKeyServer() + s := NewServer(WithDefaultServer{Server: fallback}) + + key := KeyFromMasterKey(awskms.NewMasterKeyFromArn("arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48", nil, "")) + encReq := &keyservice.EncryptRequest{ + Key: &key, + Plaintext: []byte("some data key"), + } + _, err := s.Encrypt(context.TODO(), encReq) + g.Expect(err).To(HaveOccurred()) + g.Expect(fallback.encryptReqs).To(HaveLen(1)) + g.Expect(fallback.encryptReqs).To(ContainElement(encReq)) + g.Expect(fallback.decryptReqs).To(HaveLen(0)) + + fallback = NewMockKeyServer() + s = NewServer(WithDefaultServer{Server: fallback}) + + decReq := &keyservice.DecryptRequest{ + Key: &key, + Ciphertext: []byte("some ciphertext"), + } + _, err = s.Decrypt(context.TODO(), decReq) + g.Expect(fallback.decryptReqs).To(HaveLen(1)) + g.Expect(fallback.decryptReqs).To(ContainElement(decReq)) + g.Expect(fallback.encryptReqs).To(HaveLen(0)) +} + func TestServer_EncryptDecrypt_azkv(t *testing.T) { g := NewWithT(t) diff --git a/internal/sops/keyservice/utils_test.go b/internal/sops/keyservice/utils_test.go index 945ef1d0..1cdaf5f8 100644 --- a/internal/sops/keyservice/utils_test.go +++ b/internal/sops/keyservice/utils_test.go @@ -24,6 +24,7 @@ import ( "go.mozilla.org/sops/v3/keyservice" "github.com/fluxcd/kustomize-controller/internal/sops/age" + "github.com/fluxcd/kustomize-controller/internal/sops/awskms" "github.com/fluxcd/kustomize-controller/internal/sops/azkv" "github.com/fluxcd/kustomize-controller/internal/sops/hcvault" "github.com/fluxcd/kustomize-controller/internal/sops/pgp" @@ -51,6 +52,14 @@ func KeyFromMasterKey(k keys.MasterKey) keyservice.Key { }, }, } + case *awskms.MasterKey: + return keyservice.Key{ + KeyType: &keyservice.Key_KmsKey{ + KmsKey: &keyservice.KmsKey{ + Arn: mk.Arn, + }, + }, + } case *azkv.MasterKey: return keyservice.Key{ KeyType: &keyservice.Key_AzureKeyvaultKey{ From c801f7545879bc59139a66fe6f5f116ed7276697 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Thu, 28 Apr 2022 22:49:45 +0530 Subject: [PATCH 2/7] add docs Signed-off-by: Sanskar Jaiswal --- docs/spec/v1beta2/kustomization.md | 22 +++++++++++++++++++++- internal/sops/awskms/keysource.go | 22 ++++++++++++++++++++++ internal/sops/awskms/keysource_test.go | 16 ++++++++++++++++ internal/sops/keyservice/options.go | 18 ++++++++++-------- 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/docs/spec/v1beta2/kustomization.md b/docs/spec/v1beta2/kustomization.md index c5423564..ed9e877c 100644 --- a/docs/spec/v1beta2/kustomization.md +++ b/docs/spec/v1beta2/kustomization.md @@ -1105,6 +1105,25 @@ data: identity.asc: ``` +#### AWS KMS Secret Entry + +To specify credentials for an AWS user account linked to the IAM role with access +to KMS, append a `.data` entry with a fixed `sops.aws-kms` key. + +```yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: sops-keys + namespace: default +stringData: + sops.aws-kms: | + aws_access_key_id: some-access-key-id + aws_secret_access_key: some-aws-secret-access-key + aws_session_token: some-aws-session-token # this field is optional +``` + #### Azure Key Vault Secret entry To specify credentials for Azure Key Vault in a Secret, append a `.data` entry @@ -1233,7 +1252,8 @@ While making use of the [IAM OIDC provider](https://eksctl.io/usage/iamserviceac on your EKS cluster, you can create an IAM Role and Service Account with access to AWS KMS (using at least `kms:Decrypt` and `kms:DescribeKey`). Once these are created, you can annotate the kustomize-controller Service Account with the -Role ARN, granting the controller permissions to decrypt the Secrets. +Role ARN, granting the controller permissions to decrypt the Secrets. Please refer +to the [SOPS guide](https://fluxcd.io/docs/guides/mozilla-sops/#aws) for detailed steps. ```sh kubectl -n flux-system annotate serviceaccount kustomize-controller \ diff --git a/internal/sops/awskms/keysource.go b/internal/sops/awskms/keysource.go index d0009155..d4a73c7e 100644 --- a/internal/sops/awskms/keysource.go +++ b/internal/sops/awskms/keysource.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package awskms import ( @@ -32,16 +48,21 @@ type MasterKey struct { credentials *credentials.Credentials } +// Creds is a wrapper around credentials.Credentials used for authenticating +// when using AWS KMS. type Creds struct { credentials *credentials.Credentials } +// NewCreds creates new Creds object with the provided credentials.Credentials func NewCreds(credentials *credentials.Credentials) *Creds { return &Creds{ credentials: credentials, } } +// LoadAwsKmsCredsFromYaml parses the given yaml returns a Creds object, which contains +// the AWS credentials. func LoadAwsKmsCredsFromYaml(b []byte) (*Creds, error) { credInfo := struct { AccessKeyID string `json:"aws_access_key_id"` @@ -57,6 +78,7 @@ func LoadAwsKmsCredsFromYaml(b []byte) (*Creds, error) { }, nil } +// ApplyToMasterKey configures the credentials the provided key. func (c Creds) ApplyToMasterKey(key *MasterKey) { key.credentials = c.credentials } diff --git a/internal/sops/awskms/keysource_test.go b/internal/sops/awskms/keysource_test.go index 2a7e6962..ac233fa7 100644 --- a/internal/sops/awskms/keysource_test.go +++ b/internal/sops/awskms/keysource_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package awskms import ( diff --git a/internal/sops/keyservice/options.go b/internal/sops/keyservice/options.go index da160e60..fbf63625 100644 --- a/internal/sops/keyservice/options.go +++ b/internal/sops/keyservice/options.go @@ -57,6 +57,16 @@ func (o WithAgeIdentities) ApplyToServer(s *Server) { s.ageIdentities = age.ParsedIdentities(o) } +// WithAWSKeys configurs the AWS credentials on the Server +type WithAWSKeys struct { + creds *awskms.Creds +} + +// ApplyToServer applies this configuration to the given Server. +func (o WithAWSKeys) ApplyToServer(s *Server) { + s.awsCreds = o.creds +} + // WithAzureToken configures the Azure credential token on the Server. type WithAzureToken struct { Token *azkv.Token @@ -76,11 +86,3 @@ type WithDefaultServer struct { func (o WithDefaultServer) ApplyToServer(s *Server) { s.defaultServer = o.Server } - -type WithAWSKeys struct { - creds *awskms.Creds -} - -func (o WithAWSKeys) ApplyToServer(s *Server) { - s.awsCreds = o.creds -} From e5a37b7013d2e5501e52593a5b2a7abb3c611143 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Sat, 30 Apr 2022 15:20:28 +0530 Subject: [PATCH 3/7] migrate aws kms implementation to aws-go-sdk-v2 Signed-off-by: Sanskar Jaiswal --- controllers/kustomization_decryptor.go | 7 +- go.mod | 14 +- go.sum | 26 +++ internal/sops/awskms/keysource.go | 218 ++++++++++--------------- internal/sops/keyservice/options.go | 4 +- internal/sops/keyservice/server.go | 50 +++--- 6 files changed, 155 insertions(+), 164 deletions(-) diff --git a/controllers/kustomization_decryptor.go b/controllers/kustomization_decryptor.go index 0706d0c7..0313aa74 100644 --- a/controllers/kustomization_decryptor.go +++ b/controllers/kustomization_decryptor.go @@ -133,9 +133,9 @@ type KustomizeDecryptor struct { // vaultToken is the Hashicorp Vault token used to authenticate towards // any Vault server. vaultToken string - // awsCreds is the AWS credentials object used to authenticate towards + // awsCredsProvider is the AWS credentials provider object used to authenticate towards // any AWS KMS. - awsCreds *awskms.Creds + awsCredsProvider *awskms.CredsProvider // azureToken is the Azure credential token used to authenticate towards // any Azure Key Vault. azureToken *azkv.Token @@ -229,7 +229,7 @@ func (d *KustomizeDecryptor) ImportKeys(ctx context.Context) error { } case filepath.Ext(DecryptionAWSKmsFile): if name == DecryptionAWSKmsFile { - if d.awsCreds, err = awskms.LoadAwsKmsCredsFromYaml(value); err != nil { + if d.awsCredsProvider, err = awskms.LoadAwsKmsCredsProviderFromYaml(value); err != nil { return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err) } } @@ -547,6 +547,7 @@ func (d *KustomizeDecryptor) loadKeyServiceServers() { if d.azureToken != nil { serverOpts = append(serverOpts, intkeyservice.WithAzureToken{Token: d.azureToken}) } + serverOpts = append(serverOpts, intkeyservice.WithAWSKeys{CredsProvider: d.awsCredsProvider}) server := intkeyservice.NewServer(serverOpts...) d.keyServices = append(make([]keyservice.KeyServiceClient, 0), keyservice.NewCustomLocalClient(server)) } diff --git a/go.mod b/go.mod index 0e4b2802..49565789 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,11 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.4.0 - github.com/aws/aws-sdk-go v1.37.18 + github.com/aws/aws-sdk-go-v2 v1.16.3 + github.com/aws/aws-sdk-go-v2/config v1.15.4 + github.com/aws/aws-sdk-go-v2/credentials v1.12.0 + github.com/aws/aws-sdk-go-v2/service/kms v1.17.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 github.com/cyphar/filepath-securejoin v0.2.3 github.com/dimchansky/utfbom v1.1.1 github.com/drone/envsubst v1.0.3 @@ -83,6 +87,14 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/armon/go-metrics v0.3.10 // indirect github.com/armon/go-radix v1.0.0 // indirect + github.com/aws/aws-sdk-go v1.37.18 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 // indirect + github.com/aws/smithy-go v1.11.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect diff --git a/go.sum b/go.sum index de013c12..ce7be1d9 100644 --- a/go.sum +++ b/go.sum @@ -141,6 +141,32 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.43.43 h1:1L06qzQvl4aC3Skfh5rV7xVhGHjIZoHcqy16NoyQ1o4= github.com/aws/aws-sdk-go v1.43.43/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.37.18 h1:SRdWLg+DqMFWX8HB3UvXyAoZpw9IDIUYnSTwgzOYbqg= +github.com/aws/aws-sdk-go v1.37.18/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go-v2 v1.16.3 h1:0W1TSJ7O6OzwuEvIXAtJGvOeQ0SGAhcpxPN2/NK5EhM= +github.com/aws/aws-sdk-go-v2 v1.16.3/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= +github.com/aws/aws-sdk-go-v2/config v1.15.4 h1:P4mesY1hYUxru4f9SU0XxNKXmzfxsD0FtMIPRBjkH7Q= +github.com/aws/aws-sdk-go-v2/config v1.15.4/go.mod h1:ZijHHh0xd/A+ZY53az0qzC5tT46kt4JVCePf2NX9Lk4= +github.com/aws/aws-sdk-go-v2/credentials v1.12.0 h1:4R/NqlcRFSkR0wxOhgHi+agGpbEr5qMCjn7VqUIJY+E= +github.com/aws/aws-sdk-go-v2/credentials v1.12.0/go.mod h1:9YWk7VW+eyKsoIL6/CljkTrNVWBSK9pkqOPUuijid4A= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 h1:FP8gquGeGHHdfY6G5llaMQDF+HAf20VKc8opRwmjf04= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4/go.mod h1:u/s5/Z+ohUQOPXl00m2yJVyioWDECsbpXTQlaqSlufc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 h1:uFWgo6mGJI1n17nbcvSc6fxVuR3xLNqvXt12JCnEcT8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10/go.mod h1:F+EZtuIwjlv35kRJPyBGcsA4f7bnSoz15zOQ2lJq1Z4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 h1:cnsvEKSoHN4oAN7spMMr0zhEW2MHnhAVpmqQg8E6UcM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4/go.mod h1:8glyUqVIM4AmeenIsPo0oVh3+NUwnsQml2OFupfQW+0= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 h1:6cZRymlLEIlDTEB0+5+An6Zj1CKt6rSE69tOmFeu1nk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11/go.mod h1:0MR+sS1b/yxsfAPvAESrw8NfwUoxMinDyw6EYR9BS2U= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 h1:b16QW0XWl0jWjLABFc1A+uh145Oqv+xDcObNk0iQgUk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4/go.mod h1:uKkN7qmSIsNJVyMtxNQoCEYMvFEXbOg9fwCJPdfp2u8= +github.com/aws/aws-sdk-go-v2/service/kms v1.17.1 h1:8T0uFw+t/+uP0ukowdDQ2fxhh5jh07bM4WI8/KRGtv8= +github.com/aws/aws-sdk-go-v2/service/kms v1.17.1/go.mod h1:0B58/BshOoe7rhRRRtHWVGcXqlJn7gQZmNLyKucFhCU= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 h1:Uw5wBybFQ1UeA9ts0Y07gbv0ncZnIAyw858tDW0NP2o= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.4/go.mod h1:cPDwJwsP4Kff9mldCXAmddjJL6JGQqtA3Mzer2zyr88= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 h1:+xtV90n3abQmgzk1pS++FdxZTrPEDgQng6e4/56WR2A= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.4/go.mod h1:lfSYenAXtavyX2A1LsViglqlG9eEFYxNryTZS5rn3QE= +github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= +github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= diff --git a/internal/sops/awskms/keysource.go b/internal/sops/awskms/keysource.go index d4a73c7e..ff1ff63d 100644 --- a/internal/sops/awskms/keysource.go +++ b/internal/sops/awskms/keysource.go @@ -17,25 +17,28 @@ limitations under the License. package awskms import ( - "encoding/base64" + "context" "fmt" "os" "regexp" "strings" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/credentials/stscreds" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/kms" - "github.com/aws/aws-sdk-go/service/sts" + "encoding/base64" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/sts" "sigs.k8s.io/yaml" ) const ( arnRegex = `^arn:aws[\w-]*:kms:(.+):[0-9]+:(key|alias)/.+$` stsSessionRegex = "[^a-zA-Z0-9=,.@-]+" + // kmsTTL is the duration after which a MasterKey requires rotation. + kmsTTL = time.Hour * 24 * 30 * 6 ) // MasterKey is a AWS KMS key used to encrypt and decrypt sops' data key. @@ -44,26 +47,38 @@ type MasterKey struct { Role string EncryptedKey string CreationDate time.Time - EncryptionContext map[string]*string - credentials *credentials.Credentials + EncryptionContext map[string]string + + credentialsProvider aws.CredentialsProvider + + // epResolver IS ONLY MEANT TO BE USED FOR TESTS. + // it can be used to override the endpoint that the AWS client resolves to + // by default. it's hacky but there is no other choice, since you can't + // specify the endpoint as an env var like you can do with an access key. + epResolver aws.EndpointResolver } -// Creds is a wrapper around credentials.Credentials used for authenticating +// CredsProvider is a wrapper around aws.CredentialsProvider used for authenticating // when using AWS KMS. -type Creds struct { - credentials *credentials.Credentials +type CredsProvider struct { + credsProvider aws.CredentialsProvider } -// NewCreds creates new Creds object with the provided credentials.Credentials -func NewCreds(credentials *credentials.Credentials) *Creds { - return &Creds{ - credentials: credentials, +// NewCredsProvider returns a Creds object with the provided aws.CredentialsProvider +func NewCredsProvider(cp aws.CredentialsProvider) *CredsProvider { + return &CredsProvider{ + credsProvider: cp, } } -// LoadAwsKmsCredsFromYaml parses the given yaml returns a Creds object, which contains -// the AWS credentials. -func LoadAwsKmsCredsFromYaml(b []byte) (*Creds, error) { +// ApplyToMasterKey configures the credentials the provided key. +func (c CredsProvider) ApplyToMasterKey(key *MasterKey) { + key.credentialsProvider = c.credsProvider +} + +// LoadAwsKmsCredsProviderFromYaml parses the given yaml returns a CredsProvider object +// which contains the credentials provider used for authenticating towards AWS KMS. +func LoadAwsKmsCredsProviderFromYaml(b []byte) (*CredsProvider, error) { credInfo := struct { AccessKeyID string `json:"aws_access_key_id"` SecretAccessKey string `json:"aws_secret_access_key"` @@ -72,17 +87,12 @@ func LoadAwsKmsCredsFromYaml(b []byte) (*Creds, error) { if err := yaml.Unmarshal(b, &credInfo); err != nil { return nil, fmt.Errorf("failed to unmarshal AWS credentials file: %w", err) } - creds := credentials.NewStaticCredentials(credInfo.AccessKeyID, credInfo.SecretAccessKey, credInfo.SessionToken) - return &Creds{ - credentials: creds, + return &CredsProvider{ + credsProvider: credentials.NewStaticCredentialsProvider(credInfo.AccessKeyID, + credInfo.SecretAccessKey, credInfo.SessionToken), }, nil } -// ApplyToMasterKey configures the credentials the provided key. -func (c Creds) ApplyToMasterKey(key *MasterKey) { - key.credentials = c.credentials -} - // EncryptedDataKey returns the encrypted data key this master key holds func (key *MasterKey) EncryptedDataKey() []byte { return []byte(key.EncryptedKey) @@ -95,12 +105,16 @@ func (key *MasterKey) SetEncryptedDataKey(enc []byte) { // Encrypt takes a sops data key, encrypts it with KMS and stores the result in the EncryptedKey field func (key *MasterKey) Encrypt(dataKey []byte) error { - sess, err := key.createSession() + cfg, err := key.createKMSConfig() if err != nil { - return fmt.Errorf("failed to create session: %w", err) + return err + } + client := kms.NewFromConfig(*cfg) + input := &kms.EncryptInput{ + KeyId: &key.Arn, + Plaintext: dataKey, } - kmsSvc := kms.New(sess) - out, err := kmsSvc.Encrypt(&kms.EncryptInput{Plaintext: dataKey, KeyId: &key.Arn, EncryptionContext: key.EncryptionContext}) + out, err := client.Encrypt(context.TODO(), input) if err != nil { return fmt.Errorf("failed to encrypt sops data key with AWS KMS: %w", err) } @@ -122,12 +136,17 @@ func (key *MasterKey) Decrypt() ([]byte, error) { if err != nil { return nil, fmt.Errorf("error base64-decoding encrypted data key: %s", err) } - sess, err := key.createSession() + cfg, err := key.createKMSConfig() if err != nil { - return nil, fmt.Errorf("error creating AWS session: %w", err) + return nil, err } - kmsSvc := kms.New(sess) - decrypted, err := kmsSvc.Decrypt(&kms.DecryptInput{CiphertextBlob: k, EncryptionContext: key.EncryptionContext}) + client := kms.NewFromConfig(*cfg) + input := &kms.DecryptInput{ + KeyId: &key.Arn, + CiphertextBlob: k, + EncryptionContext: key.EncryptionContext, + } + decrypted, err := client.Decrypt(context.TODO(), input) if err != nil { return nil, fmt.Errorf("failed to decrypt sops data key with AWS KMS: %w", err) } @@ -145,7 +164,7 @@ func (key *MasterKey) ToString() string { } // NewMasterKey creates a new MasterKey from an ARN, role and context, setting the creation date to the current date -func NewMasterKey(arn string, role string, context map[string]*string) *MasterKey { +func NewMasterKey(arn string, role string, context map[string]string) *MasterKey { return &MasterKey{ Arn: arn, Role: role, @@ -155,7 +174,7 @@ func NewMasterKey(arn string, role string, context map[string]*string) *MasterKe } // NewMasterKeyFromArn takes an ARN string and returns a new MasterKey for that ARN -func NewMasterKeyFromArn(arn string, context map[string]*string, awsProfile string) *MasterKey { +func NewMasterKeyFromArn(arn string, context map[string]string, awsProfile string) *MasterKey { k := &MasterKey{} arn = strings.Replace(arn, " ", "", -1) roleIndex := strings.Index(arn, "+arn:aws:iam::") @@ -170,69 +189,51 @@ func NewMasterKeyFromArn(arn string, context map[string]*string, awsProfile stri return k } -// MasterKeysFromArnString takes a comma separated list of AWS KMS ARNs and returns a slice of new MasterKeys for those ARNs -func MasterKeysFromArnString(arn string, context map[string]*string, awsProfile string) []*MasterKey { - var keys []*MasterKey - if arn == "" { - return keys - } - for _, s := range strings.Split(arn, ",") { - keys = append(keys, NewMasterKeyFromArn(s, context, awsProfile)) - } - return keys -} - -func (key MasterKey) createSession() (*session.Session, error) { - re := regexp.MustCompile(arnRegex) - matches := re.FindStringSubmatch(key.Arn) - if matches == nil { - return nil, fmt.Errorf("No valid ARN found in %q", key.Arn) - } - - config := aws.Config{ - Region: aws.String(matches[1]), - Credentials: key.credentials, - } - - opts := session.Options{ - Config: config, - AssumeRoleTokenProvider: stscreds.StdinTokenProvider, - SharedConfigState: session.SharedConfigEnable, - } - sess, err := session.NewSessionWithOptions(opts) +func (key MasterKey) createKMSConfig() (*aws.Config, error) { + cfg, err := config.LoadDefaultConfig(context.TODO(), func(lo *config.LoadOptions) error { + if key.credentialsProvider != nil { + lo.Credentials = key.credentialsProvider + } + if key.epResolver != nil { + lo.EndpointResolver = key.epResolver + } + return nil + }) if err != nil { - return nil, err + return nil, fmt.Errorf("couldn't load AWS config: %w", err) } if key.Role != "" { - return key.createStsSession(config, sess) + return key.createSTSConfig(&cfg) } - return sess, nil + + return &cfg, nil } -func (key MasterKey) createStsSession(config aws.Config, sess *session.Session) (*session.Session, error) { +func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) { hostname, err := os.Hostname() if err != nil { return nil, err } stsRoleSessionNameRe, err := regexp.Compile(stsSessionRegex) if err != nil { - return nil, fmt.Errorf("Failed to compile STS role session name regex: %w", err) + return nil, fmt.Errorf("failed to compile STS role session name regex: %w", err) } sanitizedHostname := stsRoleSessionNameRe.ReplaceAllString(hostname, "") - stsService := sts.New(sess) name := "sops@" + sanitizedHostname - out, err := stsService.AssumeRole(&sts.AssumeRoleInput{ - RoleArn: &key.Role, RoleSessionName: &name}) - if err != nil { - return nil, fmt.Errorf("Failed to assume role %q: %w", key.Role, err) + + client := sts.NewFromConfig(*config) + input := &sts.AssumeRoleInput{ + RoleArn: &key.Arn, + RoleSessionName: &name, } - config.Credentials = credentials.NewStaticCredentials(*out.Credentials.AccessKeyId, - *out.Credentials.SecretAccessKey, *out.Credentials.SessionToken) - sess, err = session.NewSession(&config) + out, err := client.AssumeRole(context.TODO(), input) if err != nil { - return nil, fmt.Errorf("Failed to create new aws session: %w", err) + return nil, fmt.Errorf("failed to assume role '%s': %w", key.Role, err) } - return sess, nil + config.Credentials = credentials.NewStaticCredentialsProvider(*out.Credentials.AccessKeyId, + *out.Credentials.SecretAccessKey, *out.Credentials.SessionToken, + ) + return config, nil } // ToMap converts the MasterKey to a map for serialization purposes @@ -247,60 +248,9 @@ func (key MasterKey) ToMap() map[string]interface{} { if key.EncryptionContext != nil { outcontext := make(map[string]string) for k, v := range key.EncryptionContext { - outcontext[k] = *v + outcontext[k] = v } out["context"] = outcontext } return out } - -// ParseKMSContext takes either a KMS context map or a comma-separated list of KMS context key:value pairs and returns a map -func ParseKMSContext(in interface{}) map[string]*string { - // nonStringValueWarning := "Encryption context contains a non-string value, context will not be used" - out := make(map[string]*string) - - switch in := in.(type) { - case map[string]interface{}: - if len(in) == 0 { - return nil - } - for k, v := range in { - value, ok := v.(string) - if !ok { - // log.Warn(nonStringValueWarning) - return nil - } - out[k] = &value - } - case map[interface{}]interface{}: - if len(in) == 0 { - return nil - } - for k, v := range in { - key, ok := k.(string) - if !ok { - // log.Warn(nonStringValueWarning) - return nil - } - value, ok := v.(string) - if !ok { - // log.Warn(nonStringValueWarning) - return nil - } - out[key] = &value - } - case string: - if in == "" { - return nil - } - for _, kv := range strings.Split(in, ",") { - kv := strings.Split(kv, ":") - if len(kv) != 2 { - // log.Warn(nonStringValueWarning) - return nil - } - out[kv[0]] = &kv[1] - } - } - return out -} diff --git a/internal/sops/keyservice/options.go b/internal/sops/keyservice/options.go index fbf63625..30cfba9b 100644 --- a/internal/sops/keyservice/options.go +++ b/internal/sops/keyservice/options.go @@ -59,12 +59,12 @@ func (o WithAgeIdentities) ApplyToServer(s *Server) { // WithAWSKeys configurs the AWS credentials on the Server type WithAWSKeys struct { - creds *awskms.Creds + CredsProvider *awskms.CredsProvider } // ApplyToServer applies this configuration to the given Server. func (o WithAWSKeys) ApplyToServer(s *Server) { - s.awsCreds = o.creds + s.awsCredsProvider = o.CredsProvider } // WithAzureToken configures the Azure credential token on the Server. diff --git a/internal/sops/keyservice/server.go b/internal/sops/keyservice/server.go index 5a3a6206..28cca0ad 100644 --- a/internal/sops/keyservice/server.go +++ b/internal/sops/keyservice/server.go @@ -44,10 +44,10 @@ type Server struct { // When nil, the request will be handled by defaultServer. azureToken *azkv.Token - // awsCreds is the Credentials object used for Encrypt and Decrypt + // awsCredsProvider is the Credentials object used for Encrypt and Decrypt // operations of AWS KMS requests. // When nil, the request will be handled by defaultServer. - awsCreds *awskms.Creds + awsCredsProvider *awskms.CredsProvider // defaultServer is the fallback server, used to handle any request that // is not eligible to be handled by this Server. @@ -103,15 +103,13 @@ func (ks Server) Encrypt(ctx context.Context, req *keyservice.EncryptRequest) (* }, nil } case *keyservice.Key_KmsKey: - if ks.awsCreds != nil { - cipherText, err := ks.encryptWithAWSKMS(k.KmsKey, req.Plaintext) - if err != nil { - return nil, err - } - return &keyservice.EncryptResponse{ - Ciphertext: cipherText, - }, nil + cipherText, err := ks.encryptWithAWSKMS(k.KmsKey, req.Plaintext) + if err != nil { + return nil, err } + return &keyservice.EncryptResponse{ + Ciphertext: cipherText, + }, nil case *keyservice.Key_AzureKeyvaultKey: if ks.azureToken != nil { ciphertext, err := ks.encryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Plaintext) @@ -161,15 +159,13 @@ func (ks Server) Decrypt(ctx context.Context, req *keyservice.DecryptRequest) (* }, nil } case *keyservice.Key_KmsKey: - if ks.awsCreds != nil { - plaintext, err := ks.decryptWithAWSKMS(k.KmsKey, req.Ciphertext) - if err != nil { - return nil, err - } - return &keyservice.DecryptResponse{ - Plaintext: plaintext, - }, nil + plaintext, err := ks.decryptWithAWSKMS(k.KmsKey, req.Ciphertext) + if err != nil { + return nil, err } + return &keyservice.DecryptResponse{ + Plaintext: plaintext, + }, nil case *keyservice.Key_AzureKeyvaultKey: if ks.azureToken != nil { plaintext, err := ks.decryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Ciphertext) @@ -259,16 +255,18 @@ func (ks *Server) decryptWithHCVault(key *keyservice.VaultKey, ciphertext []byte } func (ks *Server) encryptWithAWSKMS(key *keyservice.KmsKey, plaintext []byte) ([]byte, error) { - context := make(map[string]*string) + context := make(map[string]string) for key, val := range key.Context { - context[key] = &val + context[key] = val } awsKey := awskms.MasterKey{ Arn: key.Arn, Role: key.Role, EncryptionContext: context, } - ks.awsCreds.ApplyToMasterKey(&awsKey) + if ks.awsCredsProvider != nil { + ks.awsCredsProvider.ApplyToMasterKey(&awsKey) + } if err := awsKey.Encrypt(plaintext); err != nil { return nil, err } @@ -276,16 +274,20 @@ func (ks *Server) encryptWithAWSKMS(key *keyservice.KmsKey, plaintext []byte) ([ } func (ks *Server) decryptWithAWSKMS(key *keyservice.KmsKey, cipherText []byte) ([]byte, error) { - context := make(map[string]*string) + context := make(map[string]string) for key, val := range key.Context { - context[key] = &val + context[key] = val } awsKey := awskms.MasterKey{ Arn: key.Arn, Role: key.Role, EncryptionContext: context, } - ks.awsCreds.ApplyToMasterKey(&awsKey) + awsKey.EncryptedKey = string(cipherText) + + if ks.awsCredsProvider != nil { + ks.awsCredsProvider.ApplyToMasterKey(&awsKey) + } return awsKey.Decrypt() } From 1ee85e5988a95868b717ed81c9febef1183c59ed Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Sat, 30 Apr 2022 15:23:20 +0530 Subject: [PATCH 4/7] add and update tests for awskms and hcvault Signed-off-by: Sanskar Jaiswal --- controllers/kustomization_decryptor_test.go | 2 +- internal/sops/awskms/keysource_test.go | 317 +++++++++++++++++++- internal/sops/hcvault/keysource_test.go | 8 + internal/sops/keyservice/server_test.go | 37 +-- 4 files changed, 314 insertions(+), 50 deletions(-) diff --git a/controllers/kustomization_decryptor_test.go b/controllers/kustomization_decryptor_test.go index d9b2cfe6..02eb4140 100644 --- a/controllers/kustomization_decryptor_test.go +++ b/controllers/kustomization_decryptor_test.go @@ -407,7 +407,7 @@ aws_session_token: test-token`), }, }, inspectFunc: func(g *GomegaWithT, decryptor *KustomizeDecryptor) { - g.Expect(decryptor.awsCreds).ToNot(BeNil()) + g.Expect(decryptor.awsCredsProvider).ToNot(BeNil()) }, }, { diff --git a/internal/sops/awskms/keysource_test.go b/internal/sops/awskms/keysource_test.go index ac233fa7..e61ea631 100644 --- a/internal/sops/awskms/keysource_test.go +++ b/internal/sops/awskms/keysource_test.go @@ -17,21 +17,256 @@ limitations under the License. package awskms import ( + "context" + "encoding/base64" + "fmt" + logger "log" + "os" "testing" + "time" - "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/kms" . "github.com/onsi/gomega" + "github.com/ory/dockertest" ) +var ( + testKMSServerURL string + testKMSARN string +) + +const ( + dummyARN = "arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48" + testLocalKMSTag = "3.11.1" + testLocalKMSImage = "nsmithuk/local-kms" +) + +// TestMain initializes a AWS KMS server using Docker, writes the HTTP address to +// testAWSEndpoint, tries to generate a key for encryption-decryption using a +// backoff retry approach and then sets testKMSARN to the id of the generated key. +// It then runs all the tests, which can make use of the various `test*` variables. +func TestMain(m *testing.M) { + // Uses a sensible default on Windows (TCP/HTTP) and Linux/MacOS (socket) + pool, err := dockertest.NewPool("") + if err != nil { + logger.Fatalf("could not connect to docker: %s", err) + } + + // Pull the image, create a container based on it, and run it + // resource, err := pool.Run("nsmithuk/local-kms", testLocalKMSVersion, []string{}) + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: testLocalKMSImage, + Tag: testLocalKMSTag, + ExposedPorts: []string{"8080"}, + }) + if err != nil { + logger.Fatalf("could not start resource: %s", err) + } + + purgeResource := func() { + if err := pool.Purge(resource); err != nil { + logger.Printf("could not purge resource: %s", err) + } + } + + testKMSServerURL = fmt.Sprintf("http://127.0.0.1:%v", resource.GetPort("8080/tcp")) + masterKey := createTestMasterKey(dummyARN) + + kmsClient, err := createTestKMSClient(masterKey) + if err != nil { + purgeResource() + logger.Fatalf("could not create session: %s", err) + } + + var key *kms.CreateKeyOutput + if err := pool.Retry(func() error { + key, err = kmsClient.CreateKey(context.TODO(), &kms.CreateKeyInput{}) + if err != nil { + return err + } + return nil + }); err != nil { + purgeResource() + logger.Fatalf("could not create key: %s", err) + } + + if key.KeyMetadata.Arn != nil { + testKMSARN = *key.KeyMetadata.Arn + } else { + purgeResource() + logger.Fatalf("could not set arn") + } + + // Run the tests, but only if we succeeded in setting up the Vault server + var code int + if err == nil { + code = m.Run() + } + + // This can't be deferred, as os.Exit simpy does not care + if err := pool.Purge(resource); err != nil { + logger.Fatalf("could not purge resource: %s", err) + } + + os.Exit(code) +} + +func TestMasterKey_Encrypt(t *testing.T) { + g := NewWithT(t) + key := createTestMasterKey(testKMSARN) + dataKey := []byte("thisistheway") + g.Expect(key.Encrypt(dataKey)).To(Succeed()) + g.Expect(key.EncryptedKey).ToNot(BeEmpty()) + + kmsClient, err := createTestKMSClient(key) + g.Expect(err).ToNot(HaveOccurred()) + + k, err := base64.StdEncoding.DecodeString(key.EncryptedKey) + g.Expect(err).ToNot(HaveOccurred()) + + input := &kms.DecryptInput{ + CiphertextBlob: k, + EncryptionContext: key.EncryptionContext, + } + decrypted, err := kmsClient.Decrypt(context.TODO(), input) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(decrypted.Plaintext).To(Equal(dataKey)) +} + +func TestMasterKey_Encrypt_SOPS_Compat(t *testing.T) { + g := NewWithT(t) + + encryptKey := createTestMasterKey(testKMSARN) + dataKey := []byte("encrypt-compat") + g.Expect(encryptKey.Encrypt(dataKey)).To(Succeed()) + + decryptKey := createTestMasterKey(testKMSARN) + decryptKey.credentialsProvider = nil + decryptKey.EncryptedKey = encryptKey.EncryptedKey + t.Setenv("AWS_ACCESS_KEY_ID", "id") + t.Setenv("AWS_SECRET_ACCESS_KEY", "secret") + dec, err := decryptKey.Decrypt() + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(dec).To(Equal(dataKey)) +} + +func TestMasterKey_EncryptIfNeeded(t *testing.T) { + g := NewWithT(t) + + key := createTestMasterKey(testKMSARN) + g.Expect(key.EncryptIfNeeded([]byte("data"))).To(Succeed()) + + encryptedKey := key.EncryptedKey + g.Expect(encryptedKey).ToNot(BeEmpty()) + + g.Expect(key.EncryptIfNeeded([]byte("some other data"))).To(Succeed()) + g.Expect(key.EncryptedKey).To(Equal(encryptedKey)) +} + +func TestMasterKey_EncryptedDataKey(t *testing.T) { + g := NewWithT(t) + + key := &MasterKey{EncryptedKey: "some key"} + g.Expect(key.EncryptedDataKey()).To(BeEquivalentTo(key.EncryptedKey)) +} + +func TestMasterKey_Decrypt(t *testing.T) { + g := NewWithT(t) + + key := createTestMasterKey(testKMSARN) + kmsClient, err := createTestKMSClient(key) + g.Expect(err).ToNot(HaveOccurred()) + + dataKey := []byte("itsalwaysdns") + out, err := kmsClient.Encrypt(context.TODO(), &kms.EncryptInput{ + Plaintext: dataKey, KeyId: &key.Arn, EncryptionContext: key.EncryptionContext, + }) + g.Expect(err).ToNot(HaveOccurred()) + + key.EncryptedKey = base64.StdEncoding.EncodeToString(out.CiphertextBlob) + got, err := key.Decrypt() + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(got).To(Equal(dataKey)) +} + +func TestMasterKey_Decrypt_SOPS_Compat(t *testing.T) { + g := NewWithT(t) + + dataKey := []byte("decrypt-compat") + + encryptKey := createTestMasterKey(testKMSARN) + encryptKey.credentialsProvider = nil + t.Setenv("AWS_ACCESS_KEY_ID", "id") + t.Setenv("AWS_SECRET_ACCESS_KEY", "secret") + + g.Expect(encryptKey.Encrypt(dataKey)).To(Succeed()) + + decryptKey := createTestMasterKey(testKMSARN) + decryptKey.EncryptedKey = encryptKey.EncryptedKey + dec, err := decryptKey.Decrypt() + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(dec).To(Equal(dataKey)) +} + +func TestMasterKey_EncryptDecrypt_RoundTrip(t *testing.T) { + g := NewWithT(t) + + dataKey := []byte("thisistheway") + + encryptKey := createTestMasterKey(testKMSARN) + g.Expect(encryptKey.Encrypt(dataKey)).To(Succeed()) + g.Expect(encryptKey.EncryptedKey).ToNot(BeEmpty()) + + decryptKey := createTestMasterKey(testKMSARN) + decryptKey.EncryptedKey = encryptKey.EncryptedKey + + decryptedData, err := decryptKey.Decrypt() + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(decryptedData).To(Equal(dataKey)) +} + +func TestMasterKey_NeedsRotation(t *testing.T) { + g := NewWithT(t) + + key := NewMasterKeyFromArn(dummyARN, nil, "") + g.Expect(key.NeedsRotation()).To(BeFalse()) + + key.CreationDate = key.CreationDate.Add(-(kmsTTL + time.Second)) + g.Expect(key.NeedsRotation()).To(BeTrue()) +} + +func TestMasterKey_ToMap(t *testing.T) { + g := NewWithT(t) + key := MasterKey{ + Arn: "test-arn", + Role: "test-role", + EncryptedKey: "enc-key", + EncryptionContext: map[string]string{ + "env": "test", + }, + } + g.Expect(key.ToMap()).To(Equal(map[string]interface{}{ + "arn": "test-arn", + "role": "test-role", + "created_at": "0001-01-01T00:00:00Z", + "enc": "enc-key", + "context": map[string]string{ + "env": "test", + }, + })) +} + func TestCreds_ApplyToMasterKey(t *testing.T) { g := NewWithT(t) - creds := Creds{ - credentials: credentials.NewStaticCredentials("test-id", "test-secret", "test-token"), + creds := CredsProvider{ + credsProvider: credentials.NewStaticCredentialsProvider("", "", ""), } key := &MasterKey{} creds.ApplyToMasterKey(key) - g.Expect(key.credentials).To(Equal(creds.credentials)) + g.Expect(key.credentialsProvider).To(Equal(creds.credsProvider)) } func TestLoadAwsKmsCredsFromYaml(t *testing.T) { @@ -40,24 +275,74 @@ func TestLoadAwsKmsCredsFromYaml(t *testing.T) { aws_access_key_id: test-id aws_secret_access_key: test-secret aws_session_token: test-token - `) - creds, err := LoadAwsKmsCredsFromYaml(credsYaml) +`) + credsProvider, err := LoadAwsKmsCredsProviderFromYaml(credsYaml) g.Expect(err).ToNot(HaveOccurred()) - value, err := creds.credentials.Get() + + creds, err := credsProvider.credsProvider.Retrieve(context.TODO()) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(value.AccessKeyID).To(Equal("test-id")) - g.Expect(value.SecretAccessKey).To(Equal("test-secret")) - g.Expect(value.SessionToken).To(Equal("test-token")) + + g.Expect(creds.AccessKeyID).To(Equal("test-id")) + g.Expect(creds.SecretAccessKey).To(Equal("test-secret")) + g.Expect(creds.SessionToken).To(Equal("test-token")) } -func Test_createSession(t *testing.T) { +func Test_createKMSConfig(t *testing.T) { g := NewWithT(t) - creds := credentials.NewStaticCredentials("test-id", "test-secret", "") + key := MasterKey{ - Arn: "arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48", - credentials: creds, + credentialsProvider: credentials.NewStaticCredentialsProvider("test-id", "test-secret", "test-token"), } - sess, err := key.createSession() + cfg, err := key.createKMSConfig() + g.Expect(err).ToNot(HaveOccurred()) + + creds, err := cfg.Credentials.Retrieve(context.TODO()) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(creds.AccessKeyID).To(Equal("test-id")) + g.Expect(creds.SecretAccessKey).To(Equal("test-secret")) + g.Expect(creds.SessionToken).To(Equal("test-token")) + + // test if we fallback to the default way of fetching credentials + // if no static credentials are provided. + key.credentialsProvider = nil + t.Setenv("AWS_ACCESS_KEY_ID", "id") + t.Setenv("AWS_SECRET_ACCESS_KEY", "secret") + t.Setenv("AWS_SESSION_TOKEN", "token") + + cfg, err = key.createKMSConfig() g.Expect(err).ToNot(HaveOccurred()) - g.Expect(sess).ToNot(BeNil()) + + creds, err = cfg.Credentials.Retrieve(context.TODO()) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(creds.AccessKeyID).To(Equal("id")) + g.Expect(creds.SecretAccessKey).To(Equal("secret")) + g.Expect(creds.SessionToken).To(Equal("token")) +} + +func createTestMasterKey(arn string) MasterKey { + return MasterKey{ + Arn: arn, + credentialsProvider: credentials.NewStaticCredentialsProvider("id", "secret", ""), + epResolver: epResolver{}, + } +} + +// epResolver is a dummy resolver that points to the local test KMS server +type epResolver struct{} + +func (e epResolver) ResolveEndpoint(service, region string) (aws.Endpoint, error) { + return aws.Endpoint{ + URL: testKMSServerURL, + }, nil +} + +func createTestKMSClient(key MasterKey) (*kms.Client, error) { + cfg, err := key.createKMSConfig() + if err != nil { + return nil, err + } + + cfg.EndpointResolver = epResolver{} + + return kms.NewFromConfig(*cfg), nil } diff --git a/internal/sops/hcvault/keysource_test.go b/internal/sops/hcvault/keysource_test.go index 75ad8b50..413e8f40 100644 --- a/internal/sops/hcvault/keysource_test.go +++ b/internal/sops/hcvault/keysource_test.go @@ -60,6 +60,12 @@ func TestMain(m *testing.M) { logger.Fatalf("could not start resource: %s", err) } + purgeResource := func() { + if err := pool.Purge(resource); err != nil { + logger.Printf("could not purge resource: %s", err) + } + } + testVaultAddress = fmt.Sprintf("http://127.0.0.1:%v", resource.GetPort("8200/tcp")) // Wait until Vault is ready to serve requests if err := pool.Retry(func() error { @@ -78,10 +84,12 @@ func TestMain(m *testing.M) { } return nil }); err != nil { + purgeResource() logger.Fatalf("could not connect to docker: %s", err) } if err = enableVaultTransit(testVaultAddress, testVaultToken, testEnginePath); err != nil { + purgeResource() logger.Fatalf("could not enable Vault transit: %s", err) } diff --git a/internal/sops/keyservice/server_test.go b/internal/sops/keyservice/server_test.go index 1e333163..b82e0de5 100644 --- a/internal/sops/keyservice/server_test.go +++ b/internal/sops/keyservice/server_test.go @@ -22,7 +22,7 @@ import ( "testing" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go-v2/credentials" . "github.com/onsi/gomega" "go.mozilla.org/sops/v3/keyservice" "golang.org/x/net/context" @@ -151,8 +151,9 @@ func TestServer_EncryptDecrypt_HCVault_Fallback(t *testing.T) { func TestServer_EncryptDecrypt_awskms(t *testing.T) { g := NewWithT(t) - creds := credentials.NewEnvCredentials() - s := NewServer(WithAWSKeys{creds: awskms.NewCreds(creds)}) + s := NewServer(WithAWSKeys{ + CredsProvider: awskms.NewCredsProvider(credentials.StaticCredentialsProvider{}), + }) key := KeyFromMasterKey(awskms.NewMasterKeyFromArn("arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48", nil, "")) _, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{ @@ -168,36 +169,6 @@ func TestServer_EncryptDecrypt_awskms(t *testing.T) { g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key with AWS KMS")) } -func TestServer_EncryptDecrypt_awskms_Fallback(t *testing.T) { - g := NewWithT(t) - - fallback := NewMockKeyServer() - s := NewServer(WithDefaultServer{Server: fallback}) - - key := KeyFromMasterKey(awskms.NewMasterKeyFromArn("arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48", nil, "")) - encReq := &keyservice.EncryptRequest{ - Key: &key, - Plaintext: []byte("some data key"), - } - _, err := s.Encrypt(context.TODO(), encReq) - g.Expect(err).To(HaveOccurred()) - g.Expect(fallback.encryptReqs).To(HaveLen(1)) - g.Expect(fallback.encryptReqs).To(ContainElement(encReq)) - g.Expect(fallback.decryptReqs).To(HaveLen(0)) - - fallback = NewMockKeyServer() - s = NewServer(WithDefaultServer{Server: fallback}) - - decReq := &keyservice.DecryptRequest{ - Key: &key, - Ciphertext: []byte("some ciphertext"), - } - _, err = s.Decrypt(context.TODO(), decReq) - g.Expect(fallback.decryptReqs).To(HaveLen(1)) - g.Expect(fallback.decryptReqs).To(ContainElement(decReq)) - g.Expect(fallback.encryptReqs).To(HaveLen(0)) -} - func TestServer_EncryptDecrypt_azkv(t *testing.T) { g := NewWithT(t) From 326e20c41b8b0a9b2585a861ce604b5b51c1d5b3 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Wed, 4 May 2022 02:05:36 +0530 Subject: [PATCH 5/7] update code comments and kms decryption docs Signed-off-by: Sanskar Jaiswal --- controllers/kustomization_decryptor.go | 6 +- docs/spec/v1beta2/kustomization.md | 36 +++++++- internal/sops/awskms/keysource.go | 111 +++++++++++++++---------- internal/sops/awskms/keysource_test.go | 4 +- internal/sops/keyservice/options.go | 2 +- 5 files changed, 109 insertions(+), 50 deletions(-) diff --git a/controllers/kustomization_decryptor.go b/controllers/kustomization_decryptor.go index 0313aa74..d2451ad4 100644 --- a/controllers/kustomization_decryptor.go +++ b/controllers/kustomization_decryptor.go @@ -133,8 +133,8 @@ type KustomizeDecryptor struct { // vaultToken is the Hashicorp Vault token used to authenticate towards // any Vault server. vaultToken string - // awsCredsProvider is the AWS credentials provider object used to authenticate towards - // any AWS KMS. + // awsCredsProvider is the AWS credentials provider object used to authenticate + // towards any AWS KMS. awsCredsProvider *awskms.CredsProvider // azureToken is the Azure credential token used to authenticate towards // any Azure Key Vault. @@ -229,7 +229,7 @@ func (d *KustomizeDecryptor) ImportKeys(ctx context.Context) error { } case filepath.Ext(DecryptionAWSKmsFile): if name == DecryptionAWSKmsFile { - if d.awsCredsProvider, err = awskms.LoadAwsKmsCredsProviderFromYaml(value); err != nil { + if d.awsCredsProvider, err = awskms.LoadCredsProviderFromYaml(value); err != nil { return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err) } } diff --git a/docs/spec/v1beta2/kustomization.md b/docs/spec/v1beta2/kustomization.md index ed9e877c..0113665d 100644 --- a/docs/spec/v1beta2/kustomization.md +++ b/docs/spec/v1beta2/kustomization.md @@ -1117,7 +1117,7 @@ kind: Secret metadata: name: sops-keys namespace: default -stringData: +data: sops.aws-kms: | aws_access_key_id: some-access-key-id aws_secret_access_key: some-aws-secret-access-key @@ -1261,6 +1261,40 @@ kubectl -n flux-system annotate serviceaccount kustomize-controller \ eks.amazonaws.com/role-arn='arn:aws:iam:::role/' ``` +Furthermore, you can also use the usual [environmentvariables used for specifying AWS +credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-list) +, by patching the kustomize-controller deployment: + +```yaml +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kustomize-controller + namespace: flux-system +spec: + template: + spec: + containers: + - name: manager + env: + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: aws-creds + key: awsAccessKeyID + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: aws-creds + key: awsSecretAccessKey + - name: AWS_SESSION_TOKEN + valueFrom: + secretKeyRef: + name: aws-creds + key: awsSessionToken +``` + In addition to this, the [general SOPS documentation around KMS AWS applies](https://github.com/mozilla/sops#27kms-aws-profiles), allowing you to specify e.g. a `SOPS_KMS_ARN` environment variable. diff --git a/internal/sops/awskms/keysource.go b/internal/sops/awskms/keysource.go index ff1ff63d..83002c5c 100644 --- a/internal/sops/awskms/keysource.go +++ b/internal/sops/awskms/keysource.go @@ -35,36 +35,51 @@ import ( ) const ( - arnRegex = `^arn:aws[\w-]*:kms:(.+):[0-9]+:(key|alias)/.+$` - stsSessionRegex = "[^a-zA-Z0-9=,.@-]+" + // arnRegex matches an AWS ARN. + // valid ARN example: arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48 + arnRegex = `^arn:aws[\w-]*:kms:(.+):[0-9]+:(key|alias)/.+$` + // stsSessionRegex matches an AWS STS session name. + // valid STS session examples: john_s, sops@42WQm042 + stsSessionRegex = "[^a-zA-Z0-9=,.@-_]+" // kmsTTL is the duration after which a MasterKey requires rotation. kmsTTL = time.Hour * 24 * 30 * 6 ) -// MasterKey is a AWS KMS key used to encrypt and decrypt sops' data key. +// MasterKey is an AWS KMS key used to encrypt and decrypt sops' data key. +// Adapted from: https://github.com/mozilla/sops/blob/v3.7.2/kms/keysource.go#L39 +// Modified to accept custom static credentials as opposed to using env vars by default +// and use aws-sdk-go-v2 instead of aws-sdk-go being used in upstream. type MasterKey struct { - Arn string - Role string - EncryptedKey string - CreationDate time.Time + // AWS Role ARN associated with the KMS key. + Arn string + // AWS Role ARN used to assume a role through AWS STS. + Role string + // EncryptedKey stores the data key in it's encrypted form. + EncryptedKey string + // CreationDate is when this MasterKey was created. + CreationDate time.Time + // EncryptionContext provides additional context about the data key. + // Ref: https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context EncryptionContext map[string]string + // credentialsProvider is used to configure the AWS config with the + // necessary credentials. credentialsProvider aws.CredentialsProvider - // epResolver IS ONLY MEANT TO BE USED FOR TESTS. - // it can be used to override the endpoint that the AWS client resolves to - // by default. it's hacky but there is no other choice, since you can't - // specify the endpoint as an env var like you can do with an access key. + // epResolver can be used to override the endpoint the AWS client resolves + // to by default. This is mostly used for testing purposes as it can not be + // injected using e.g. an environment variable. The field is not publicly + // exposed, nor configurable. epResolver aws.EndpointResolver } // CredsProvider is a wrapper around aws.CredentialsProvider used for authenticating -// when using AWS KMS. +// towards AWS KMS. type CredsProvider struct { credsProvider aws.CredentialsProvider } -// NewCredsProvider returns a Creds object with the provided aws.CredentialsProvider +// NewCredsProvider returns a CredsProvider object with the provided aws.CredentialsProvider. func NewCredsProvider(cp aws.CredentialsProvider) *CredsProvider { return &CredsProvider{ credsProvider: cp, @@ -76,9 +91,9 @@ func (c CredsProvider) ApplyToMasterKey(key *MasterKey) { key.credentialsProvider = c.credsProvider } -// LoadAwsKmsCredsProviderFromYaml parses the given yaml returns a CredsProvider object +// LoadCredsProviderFromYaml parses the given YAML returns a CredsProvider object // which contains the credentials provider used for authenticating towards AWS KMS. -func LoadAwsKmsCredsProviderFromYaml(b []byte) (*CredsProvider, error) { +func LoadCredsProviderFromYaml(b []byte) (*CredsProvider, error) { credInfo := struct { AccessKeyID string `json:"aws_access_key_id"` SecretAccessKey string `json:"aws_secret_access_key"` @@ -93,17 +108,18 @@ func LoadAwsKmsCredsProviderFromYaml(b []byte) (*CredsProvider, error) { }, nil } -// EncryptedDataKey returns the encrypted data key this master key holds +// EncryptedDataKey returns the encrypted data key this master key holds. func (key *MasterKey) EncryptedDataKey() []byte { return []byte(key.EncryptedKey) } -// SetEncryptedDataKey sets the encrypted data key for this master key +// SetEncryptedDataKey sets the encrypted data key for this master key. func (key *MasterKey) SetEncryptedDataKey(enc []byte) { key.EncryptedKey = string(enc) } -// Encrypt takes a sops data key, encrypts it with KMS and stores the result in the EncryptedKey field +// Encrypt takes a SOPS data key, encrypts it with KMS and stores the result +// in the EncryptedKey field. func (key *MasterKey) Encrypt(dataKey []byte) error { cfg, err := key.createKMSConfig() if err != nil { @@ -122,7 +138,8 @@ func (key *MasterKey) Encrypt(dataKey []byte) error { return nil } -// EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet +// EncryptIfNeeded encrypts the provided sops' data key and encrypts it, if it +// has not been encrypted yet. func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error { if key.EncryptedKey == "" { return key.Encrypt(dataKey) @@ -155,15 +172,35 @@ func (key *MasterKey) Decrypt() ([]byte, error) { // NeedsRotation returns whether the data key needs to be rotated or not. func (key *MasterKey) NeedsRotation() bool { - return time.Since(key.CreationDate) > (time.Hour * 24 * 30 * 6) + return time.Since(key.CreationDate) > kmsTTL } -// ToString converts the key to a string representation +// ToString converts the key to a string representation. func (key *MasterKey) ToString() string { return key.Arn } -// NewMasterKey creates a new MasterKey from an ARN, role and context, setting the creation date to the current date +// ToMap converts the MasterKey to a map for serialization purposes. +func (key MasterKey) ToMap() map[string]interface{} { + out := make(map[string]interface{}) + out["arn"] = key.Arn + if key.Role != "" { + out["role"] = key.Role + } + out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) + out["enc"] = key.EncryptedKey + if key.EncryptionContext != nil { + outcontext := make(map[string]string) + for k, v := range key.EncryptionContext { + outcontext[k] = v + } + out["context"] = outcontext + } + return out +} + +// NewMasterKey creates a new MasterKey from an ARN, role and context, setting the +// creation date to the current date. func NewMasterKey(arn string, role string, context map[string]string) *MasterKey { return &MasterKey{ Arn: arn, @@ -173,7 +210,8 @@ func NewMasterKey(arn string, role string, context map[string]string) *MasterKey } } -// NewMasterKeyFromArn takes an ARN string and returns a new MasterKey for that ARN +// NewMasterKeyFromArn takes an ARN string and returns a new MasterKey for that +// ARN. func NewMasterKeyFromArn(arn string, context map[string]string, awsProfile string) *MasterKey { k := &MasterKey{} arn = strings.Replace(arn, " ", "", -1) @@ -189,11 +227,15 @@ func NewMasterKeyFromArn(arn string, context map[string]string, awsProfile strin return k } +// createKMSConfig returns a Config configured with the appropriate credentials. func (key MasterKey) createKMSConfig() (*aws.Config, error) { + // Use the credentialsProvider if present, otherwise default to reading credentials + // from the environment. cfg, err := config.LoadDefaultConfig(context.TODO(), func(lo *config.LoadOptions) error { if key.credentialsProvider != nil { lo.Credentials = key.credentialsProvider } + // Set the epResolver, if present. Used ONLY for tests. if key.epResolver != nil { lo.EndpointResolver = key.epResolver } @@ -209,6 +251,8 @@ func (key MasterKey) createKMSConfig() (*aws.Config, error) { return &cfg, nil } +// createSTSConfig uses AWS STS to assume a role and returns a Config configured +// with that role's credentials. func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) { hostname, err := os.Hostname() if err != nil { @@ -223,7 +267,7 @@ func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) { client := sts.NewFromConfig(*config) input := &sts.AssumeRoleInput{ - RoleArn: &key.Arn, + RoleArn: &key.Role, RoleSessionName: &name, } out, err := client.AssumeRole(context.TODO(), input) @@ -235,22 +279,3 @@ func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) { ) return config, nil } - -// ToMap converts the MasterKey to a map for serialization purposes -func (key MasterKey) ToMap() map[string]interface{} { - out := make(map[string]interface{}) - out["arn"] = key.Arn - if key.Role != "" { - out["role"] = key.Role - } - out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) - out["enc"] = key.EncryptedKey - if key.EncryptionContext != nil { - outcontext := make(map[string]string) - for k, v := range key.EncryptionContext { - outcontext[k] = v - } - out["context"] = outcontext - } - return out -} diff --git a/internal/sops/awskms/keysource_test.go b/internal/sops/awskms/keysource_test.go index e61ea631..ef9a5bcc 100644 --- a/internal/sops/awskms/keysource_test.go +++ b/internal/sops/awskms/keysource_test.go @@ -99,7 +99,7 @@ func TestMain(m *testing.M) { logger.Fatalf("could not set arn") } - // Run the tests, but only if we succeeded in setting up the Vault server + // Run the tests, but only if we succeeded in setting up the AWS KMS server. var code int if err == nil { code = m.Run() @@ -276,7 +276,7 @@ aws_access_key_id: test-id aws_secret_access_key: test-secret aws_session_token: test-token `) - credsProvider, err := LoadAwsKmsCredsProviderFromYaml(credsYaml) + credsProvider, err := LoadCredsProviderFromYaml(credsYaml) g.Expect(err).ToNot(HaveOccurred()) creds, err := credsProvider.credsProvider.Retrieve(context.TODO()) diff --git a/internal/sops/keyservice/options.go b/internal/sops/keyservice/options.go index 30cfba9b..f8a3868c 100644 --- a/internal/sops/keyservice/options.go +++ b/internal/sops/keyservice/options.go @@ -57,7 +57,7 @@ func (o WithAgeIdentities) ApplyToServer(s *Server) { s.ageIdentities = age.ParsedIdentities(o) } -// WithAWSKeys configurs the AWS credentials on the Server +// WithAWSKeys configures the AWS credentials on the Server type WithAWSKeys struct { CredsProvider *awskms.CredsProvider } From d7307bbd5153ef0dc9b8e6c3e9702ed12dd4c2f3 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Thu, 19 May 2022 19:28:27 +0530 Subject: [PATCH 6/7] add secret mount note in docs Signed-off-by: Sanskar Jaiswal --- docs/spec/v1beta2/kustomization.md | 14 ++++++++++++-- go.mod | 6 +++--- go.sum | 8 ++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/spec/v1beta2/kustomization.md b/docs/spec/v1beta2/kustomization.md index 0113665d..71f55c38 100644 --- a/docs/spec/v1beta2/kustomization.md +++ b/docs/spec/v1beta2/kustomization.md @@ -1246,7 +1246,7 @@ it is possible to specify global decryption settings on the kustomize-controller Pod. When the controller fails to find credentials on the Kustomization object itself, it will fall back to these defaults. -#### AWS +#### AWS KMS While making use of the [IAM OIDC provider](https://eksctl.io/usage/iamserviceaccounts/) on your EKS cluster, you can create an IAM Role and Service Account with access @@ -1261,7 +1261,7 @@ kubectl -n flux-system annotate serviceaccount kustomize-controller \ eks.amazonaws.com/role-arn='arn:aws:iam:::role/' ``` -Furthermore, you can also use the usual [environmentvariables used for specifying AWS +Furthermore, you can also use the usual [environment variables used for specifying AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-list) , by patching the kustomize-controller deployment: @@ -1299,6 +1299,16 @@ In addition to this, the [general SOPS documentation around KMS AWS applies](https://github.com/mozilla/sops#27kms-aws-profiles), allowing you to specify e.g. a `SOPS_KMS_ARN` environment variable. +> **Note:**: If you're mounting a secret containing the AWS credentials as a file in the `kustomize-controller` pod, +> you'd need to specify an environment variable `$HOME`, since the AWS credentials file is expected to be present +> at `~/.aws`, like so: +```yaml +env: + - name: HOME + value: /home/{$USER} +``` + + #### Azure Key Vault While making use of [AAD Pod Identity](https://github.com/Azure/aad-pod-identity), diff --git a/go.mod b/go.mod index 49565789..f46427fd 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.4.0 - github.com/aws/aws-sdk-go-v2 v1.16.3 + github.com/aws/aws-sdk-go-v2 v1.16.4 github.com/aws/aws-sdk-go-v2/config v1.15.4 github.com/aws/aws-sdk-go-v2/credentials v1.12.0 github.com/aws/aws-sdk-go-v2/service/kms v1.17.1 @@ -87,11 +87,11 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/armon/go-metrics v0.3.10 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/aws/aws-sdk-go v1.37.18 // indirect + github.com/aws/aws-sdk-go v1.43.43 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 // indirect github.com/aws/smithy-go v1.11.2 // indirect diff --git a/go.sum b/go.sum index ce7be1d9..3ff77727 100644 --- a/go.sum +++ b/go.sum @@ -141,10 +141,9 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.43.43 h1:1L06qzQvl4aC3Skfh5rV7xVhGHjIZoHcqy16NoyQ1o4= github.com/aws/aws-sdk-go v1.43.43/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.37.18 h1:SRdWLg+DqMFWX8HB3UvXyAoZpw9IDIUYnSTwgzOYbqg= -github.com/aws/aws-sdk-go v1.37.18/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go-v2 v1.16.3 h1:0W1TSJ7O6OzwuEvIXAtJGvOeQ0SGAhcpxPN2/NK5EhM= github.com/aws/aws-sdk-go-v2 v1.16.3/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= +github.com/aws/aws-sdk-go-v2 v1.16.4 h1:swQTEQUyJF/UkEA94/Ga55miiKFoXmm/Zd67XHgmjSg= +github.com/aws/aws-sdk-go-v2 v1.16.4/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= github.com/aws/aws-sdk-go-v2/config v1.15.4 h1:P4mesY1hYUxru4f9SU0XxNKXmzfxsD0FtMIPRBjkH7Q= github.com/aws/aws-sdk-go-v2/config v1.15.4/go.mod h1:ZijHHh0xd/A+ZY53az0qzC5tT46kt4JVCePf2NX9Lk4= github.com/aws/aws-sdk-go-v2/credentials v1.12.0 h1:4R/NqlcRFSkR0wxOhgHi+agGpbEr5qMCjn7VqUIJY+E= @@ -155,8 +154,9 @@ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 h1:uFWgo6mGJI1n17nbc github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10/go.mod h1:F+EZtuIwjlv35kRJPyBGcsA4f7bnSoz15zOQ2lJq1Z4= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 h1:cnsvEKSoHN4oAN7spMMr0zhEW2MHnhAVpmqQg8E6UcM= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4/go.mod h1:8glyUqVIM4AmeenIsPo0oVh3+NUwnsQml2OFupfQW+0= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 h1:6cZRymlLEIlDTEB0+5+An6Zj1CKt6rSE69tOmFeu1nk= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11/go.mod h1:0MR+sS1b/yxsfAPvAESrw8NfwUoxMinDyw6EYR9BS2U= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12 h1:j0VqrjtgsY1Bx27tD0ysay36/K4kFMWRp9K3ieO9nLU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12/go.mod h1:00c7+ALdPh4YeEUPXJzyU0Yy01nPGOq2+9rUaz05z9g= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 h1:b16QW0XWl0jWjLABFc1A+uh145Oqv+xDcObNk0iQgUk= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4/go.mod h1:uKkN7qmSIsNJVyMtxNQoCEYMvFEXbOg9fwCJPdfp2u8= github.com/aws/aws-sdk-go-v2/service/kms v1.17.1 h1:8T0uFw+t/+uP0ukowdDQ2fxhh5jh07bM4WI8/KRGtv8= From c8a00fb495fbdae7c4c072e0d97e8d7a3fe58bed Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Fri, 20 May 2022 17:39:37 +0530 Subject: [PATCH 7/7] update sops awskms files to use MPL 2.0 Signed-off-by: Sanskar Jaiswal --- internal/sops/awskms/keysource.go | 16 ++++------------ internal/sops/awskms/keysource_test.go | 16 ++++------------ 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/internal/sops/awskms/keysource.go b/internal/sops/awskms/keysource.go index 83002c5c..866f89de 100644 --- a/internal/sops/awskms/keysource.go +++ b/internal/sops/awskms/keysource.go @@ -1,17 +1,9 @@ /* -Copyright 2022 The Flux authors +Copyright (C) 2022 The Flux authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. */ package awskms diff --git a/internal/sops/awskms/keysource_test.go b/internal/sops/awskms/keysource_test.go index ef9a5bcc..38691b0b 100644 --- a/internal/sops/awskms/keysource_test.go +++ b/internal/sops/awskms/keysource_test.go @@ -1,17 +1,9 @@ /* -Copyright 2022 The Flux authors +Copyright (C) 2022 The Flux authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. */ package awskms