Skip to content

Commit

Permalink
fix awskms config creation and expand tests
Browse files Browse the repository at this point in the history
Signed-off-by: Sanskar Jaiswal <[email protected]>
  • Loading branch information
Sanskar Jaiswal committed May 27, 2022
1 parent febdb1b commit 6be4f5b
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 54 deletions.
2 changes: 0 additions & 2 deletions docs/spec/v1beta2/kustomization.md
Original file line number Diff line number Diff line change
Expand Up @@ -1316,8 +1316,6 @@ spec:
secretKeyRef:
name: aws-creds
key: awsSessionToken
- name: AWS_PROFILE
value: "aws-profile"
```

In addition to this, the
Expand Down
25 changes: 21 additions & 4 deletions internal/sops/awskms/keysource.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const (
stsSessionRegex = "[^a-zA-Z0-9=,.@-_]+"
// kmsTTL is the duration after which a MasterKey requires rotation.
kmsTTL = time.Hour * 24 * 30 * 6
// roleSessionNameLengthLimit is the AWS role session name length limit.
roleSessionNameLengthLimit = 64
)

// MasterKey is an AWS KMS key used to encrypt and decrypt sops' data key.
Expand Down Expand Up @@ -121,8 +123,9 @@ func (key *MasterKey) Encrypt(dataKey []byte) error {
}
client := kms.NewFromConfig(*cfg)
input := &kms.EncryptInput{
KeyId: &key.Arn,
Plaintext: dataKey,
KeyId: &key.Arn,
Plaintext: dataKey,
EncryptionContext: key.EncryptionContext,
}
out, err := client.Encrypt(context.TODO(), input)
if err != nil {
Expand Down Expand Up @@ -224,15 +227,25 @@ func NewMasterKeyFromArn(arn string, context map[string]string, awsProfile strin

// 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.
re, err := regexp.Compile(arnRegex)
if err != nil {
return nil, fmt.Errorf("failed to compile ARN regex: %w", err)
}
matches := re.FindStringSubmatch(key.Arn)
if matches == nil {
return nil, fmt.Errorf("No valid ARN found in '%s'", key.Arn)
}
region := matches[1]
cfg, err := config.LoadDefaultConfig(context.TODO(), func(lo *config.LoadOptions) error {
// Use the credentialsProvider if present, otherwise default to reading credentials
// from the environment.
if key.credentialsProvider != nil {
lo.Credentials = key.credentialsProvider
}
if key.AwsProfile != "" {
lo.SharedConfigProfile = key.AwsProfile
}
lo.Region = region

// Set the epResolver, if present. Used ONLY for tests.
if key.epResolver != nil {
Expand Down Expand Up @@ -261,8 +274,12 @@ func (key MasterKey) createSTSConfig(config *aws.Config) (*aws.Config, error) {
if err != nil {
return nil, fmt.Errorf("failed to compile STS role session name regex: %w", err)
}

sanitizedHostname := stsRoleSessionNameRe.ReplaceAllString(hostname, "")
name := "sops@" + sanitizedHostname
if len(name) >= roleSessionNameLengthLimit {
name = name[:roleSessionNameLengthLimit]
}

client := sts.NewFromConfig(*config)
input := &sts.AssumeRoleInput{
Expand Down
124 changes: 76 additions & 48 deletions internal/sops/awskms/keysource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,56 +282,84 @@ aws_session_token: test-token
}

func Test_createKMSConfig(t *testing.T) {
g := NewWithT(t)

key := MasterKey{
credentialsProvider: credentials.NewStaticCredentialsProvider("test-id", "test-secret", "test-token"),
AwsProfile: "test-profile",
}
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"))
// ConfigSources is a slice of config.Config, which in turn is an interface.
// Since we use a generic config, we skip config.EnvConfig and
// config.SharedConfig and then use the reflect package to make sure our
// profile has been registered
for _, cfgSrc := range cfg.ConfigSources {
if _, ok := cfgSrc.(config.EnvConfig); ok {
continue
}
if _, ok := cfgSrc.(config.SharedConfig); ok {
continue
}
src := reflect.ValueOf(cfgSrc)
profile := src.FieldByName("SharedConfigProfile")
g.Expect(profile.String()).To(Equal("test-profile"))
tests := []struct {
name string
key MasterKey
assertFunc func(g *WithT, cfg *aws.Config, err error)
fallback bool
}{
{
name: "master key with invalid arn fails",
key: MasterKey{
Arn: "arn:gcp:kms:antartica-north-2::key/45e6-aca6-a5b005693a48",
},
assertFunc: func(g *WithT, _ *aws.Config, err error) {
g.Expect(err).To(HaveOccurred())
},
},
{
name: "master key with with proper configuration passes",
key: MasterKey{
credentialsProvider: credentials.NewStaticCredentialsProvider("test-id", "test-secret", "test-token"),
AwsProfile: "test-profile",
Arn: "arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48",
},
assertFunc: func(g *WithT, cfg *aws.Config, err error) {
g.Expect(err).ToNot(HaveOccurred())
// g.Expect(cfg.Region).To(Equal("us-west-2"))

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"))

// ConfigSources is a slice of config.Config, which in turn is an interface.
// Since we use a generic config, we skip config.EnvConfig and
// config.SharedConfig and then use the reflect package to make sure our
// profile has been registered
for _, cfgSrc := range cfg.ConfigSources {
if _, ok := cfgSrc.(config.EnvConfig); ok {
continue
}
if _, ok := cfgSrc.(config.SharedConfig); ok {
continue
}
src := reflect.ValueOf(cfgSrc)
profile := src.FieldByName("SharedConfigProfile")
g.Expect(profile.String()).To(Equal("test-profile"))
}
},
},
{
name: "master key without creds and profile falls back to the environment variables",
key: MasterKey{
Arn: "arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48",
},
fallback: true,
assertFunc: func(g *WithT, cfg *aws.Config, err error) {
g.Expect(err).ToNot(HaveOccurred())

creds, err := cfg.Credentials.Retrieve(context.TODO())
g.Expect(creds.AccessKeyID).To(Equal("id"))
g.Expect(creds.SecretAccessKey).To(Equal("secret"))
g.Expect(creds.SessionToken).To(Equal("token"))
},
},
}

// test if we fallback to the default way of fetching credentials
// if no static credentials are provided.
key.credentialsProvider = nil
key.AwsProfile = ""
t.Setenv("AWS_ACCESS_KEY_ID", "id")
t.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
t.Setenv("AWS_SESSION_TOKEN", "token")
t.Setenv("AWS_PROFILE", "profile")

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("id"))
g.Expect(creds.SecretAccessKey).To(Equal("secret"))
g.Expect(creds.SessionToken).To(Equal("token"))
for _, cfgSrc := range cfg.ConfigSources {
src := cfgSrc.(config.EnvConfig)
g.Expect(src.SharedConfigProfile).To(Equal("profile"))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
// Set the environment variables if we want to fallback
if tt.fallback {
t.Setenv("AWS_ACCESS_KEY_ID", "id")
t.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
t.Setenv("AWS_SESSION_TOKEN", "token")
}
cfg, err := tt.key.createKMSConfig()
tt.assertFunc(g, cfg, err)
})
}
}

Expand Down

0 comments on commit 6be4f5b

Please sign in to comment.