diff --git a/cmd/sops/common/common.go b/cmd/sops/common/common.go index 6d6fa0751a..13ac3f31cf 100644 --- a/cmd/sops/common/common.go +++ b/cmd/sops/common/common.go @@ -246,6 +246,36 @@ type GenericDecryptOpts struct { IgnoreMAC bool KeyServices []keyservice.KeyServiceClient DecryptionOrder []string + AwsProfile string +} + +func forEachKMSKey(groups []sops.KeyGroup, fn func(*kms.MasterKey)) { + for _, group := range groups { + for _, key := range group { + if kmsKey, ok := key.(*kms.MasterKey); ok { + fn(kmsKey) + } + } + } +} + +// SetAWSProfile overrides, for this invocation only, the AWS profile used by +// all AWS KMS master keys in the given key groups. The override is not +// serialized, so it is never written to a file. It does nothing if profile is +// empty. +func SetAWSProfile(groups []sops.KeyGroup, profile string) { + if profile != "" { + forEachKMSKey(groups, func(k *kms.MasterKey) { k.AwsProfileOverride = profile }) + } +} + +// PersistAWSProfile sets the AWS profile stored with all AWS KMS master keys +// in the given key groups, so it is written to the files they are used for. +// It does nothing if profile is empty. +func PersistAWSProfile(groups []sops.KeyGroup, profile string) { + if profile != "" { + forEachKMSKey(groups, func(k *kms.MasterKey) { k.AwsProfile = profile }) + } } // LoadEncryptedFileWithBugFixes is a wrapper around LoadEncryptedFile which includes @@ -255,6 +285,7 @@ func LoadEncryptedFileWithBugFixes(opts GenericDecryptOpts) (*sops.Tree, error) if err != nil { return nil, err } + SetAWSProfile(tree.Metadata.KeyGroups, opts.AwsProfile) encCtxBug, err := DetectKMSEncryptionContextBug(tree) if err != nil { @@ -265,6 +296,8 @@ func LoadEncryptedFileWithBugFixes(opts GenericDecryptOpts) (*sops.Tree, error) if err != nil { return nil, err } + // The fix may have reloaded the tree from disk. + SetAWSProfile(tree.Metadata.KeyGroups, opts.AwsProfile) } return tree, nil diff --git a/cmd/sops/common/common_test.go b/cmd/sops/common/common_test.go new file mode 100644 index 0000000000..1585974b95 --- /dev/null +++ b/cmd/sops/common/common_test.go @@ -0,0 +1,99 @@ +package common + +import ( + "os" + "path/filepath" + "testing" + + "github.com/getsops/sops/v3" + "github.com/getsops/sops/v3/age" + "github.com/getsops/sops/v3/kms" + "github.com/getsops/sops/v3/stores/json" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const kmsProfileFile = `{ + "data": "ENC[AES256_GCM,data:abcd,iv:abcd,tag:abcd,type:str]", + "sops": { + "kms": [ + { + "arn": "arn:aws:kms:us-west-2:927034868273:key/e9fc75db-05e9-44c1-9c35-633922bac347", + "created_at": "2020-01-01T00:00:00Z", + "enc": "AQIDAHhfoo", + "aws_profile": "stored" + } + ], + "lastmodified": "2020-01-01T00:00:00Z", + "mac": "ENC[AES256_GCM,data:abcd,iv:abcd,tag:abcd,type:str]", + "version": "3.9.0" + } +}` + +func TestLoadEncryptedFileWithBugFixesAwsProfile(t *testing.T) { + path := filepath.Join(t.TempDir(), "file.json") + require.NoError(t, os.WriteFile(path, []byte(kmsProfileFile), 0o600)) + + tree, err := LoadEncryptedFileWithBugFixes(GenericDecryptOpts{ + InputStore: &json.Store{}, + InputPath: path, + AwsProfile: "override", + }) + require.NoError(t, err) + key, ok := tree.Metadata.KeyGroups[0][0].(*kms.MasterKey) + require.True(t, ok) + assert.Equal(t, "override", key.EffectiveAwsProfile()) + assert.Equal(t, "stored", key.AwsProfile) + + // A tree reloaded from text (as in the editor loop of edit) needs the + // override applied again. + reloaded, err := (&json.Store{}).LoadEncryptedFile([]byte(kmsProfileFile)) + require.NoError(t, err) + SetAWSProfile(reloaded.Metadata.KeyGroups, "override") + assert.Equal(t, "override", reloaded.Metadata.KeyGroups[0][0].(*kms.MasterKey).EffectiveAwsProfile()) +} + +func TestLoadEncryptedFileWithBugFixesAwsProfileNotEmitted(t *testing.T) { + path := filepath.Join(t.TempDir(), "file.json") + require.NoError(t, os.WriteFile(path, []byte(kmsProfileFile), 0o600)) + store := &json.Store{} + tree, err := LoadEncryptedFileWithBugFixes(GenericDecryptOpts{ + InputStore: store, + InputPath: path, + AwsProfile: "override", + }) + require.NoError(t, err) + out, err := store.EmitEncryptedFile(*tree) + require.NoError(t, err) + assert.Contains(t, string(out), `"aws_profile": "stored"`) + assert.NotContains(t, string(out), "override") +} + +func TestSetAWSProfile(t *testing.T) { + newGroups := func() []sops.KeyGroup { + return []sops.KeyGroup{{&kms.MasterKey{Arn: "a", AwsProfile: "stored"}, &age.MasterKey{Recipient: "r"}}} + } + for name, tc := range map[string]struct{ profile, want string }{ + "empty is a no-op": {"", ""}, + "sets on KMS keys only": {"override", "override"}, + } { + t.Run(name, func(t *testing.T) { + groups := newGroups() + SetAWSProfile(groups, tc.profile) + key := groups[0][0].(*kms.MasterKey) + assert.Equal(t, tc.want, key.AwsProfileOverride) + assert.Equal(t, "stored", key.AwsProfile) + assert.Equal(t, newGroups()[0][1], groups[0][1]) + }) + } +} + +func TestPersistAWSProfile(t *testing.T) { + groups := []sops.KeyGroup{{&kms.MasterKey{Arn: "a", AwsProfile: "stored"}}} + PersistAWSProfile(groups, "") + assert.Equal(t, "stored", groups[0][0].(*kms.MasterKey).AwsProfile) + PersistAWSProfile(groups, "new") + key := groups[0][0].(*kms.MasterKey) + assert.Equal(t, "new", key.AwsProfile) + assert.Empty(t, key.AwsProfileOverride) +} diff --git a/cmd/sops/decrypt.go b/cmd/sops/decrypt.go index d0da0ddf18..e82874fa6c 100644 --- a/cmd/sops/decrypt.go +++ b/cmd/sops/decrypt.go @@ -21,6 +21,7 @@ type decryptOpts struct { InputPath string ReadFromStdin bool IgnoreMAC bool + AwsProfile string Extract []interface{} KeyServices []keyservice.KeyServiceClient DecryptionOrder []string @@ -33,6 +34,7 @@ func decryptTree(opts decryptOpts) (tree *sops.Tree, err error) { InputPath: opts.InputPath, ReadFromStdin: opts.ReadFromStdin, IgnoreMAC: opts.IgnoreMAC, + AwsProfile: opts.AwsProfile, KeyServices: opts.KeyServices, }) if err != nil { diff --git a/cmd/sops/edit.go b/cmd/sops/edit.go index bfc3a37b94..b0989e5fb8 100644 --- a/cmd/sops/edit.go +++ b/cmd/sops/edit.go @@ -28,6 +28,7 @@ type editOpts struct { OutputStore common.Store InputPath string IgnoreMAC bool + AwsProfile string KeyServices []keyservice.KeyServiceClient DecryptionOrder []string ShowMasterKeys bool @@ -44,6 +45,7 @@ type runEditorUntilOkOpts struct { InputStore sops.Store OutputStore common.Store ShowMasterKeys bool + AwsProfile string Tree *sops.Tree } @@ -79,6 +81,7 @@ func edit(opts editOpts) ([]byte, error) { InputStore: opts.InputStore, InputPath: opts.InputPath, IgnoreMAC: opts.IgnoreMAC, + AwsProfile: opts.AwsProfile, KeyServices: opts.KeyServices, }) if err != nil { @@ -199,6 +202,7 @@ func editTreeImpl(tmpfile *os.File, tmpfileName string, opts editOpts, tree *sop OriginalHash: origHash, TmpFileName: tmpfileName, ShowMasterKeys: opts.ShowMasterKeys, + AwsProfile: opts.AwsProfile, Tree: tree}) if err != nil { return createError(err) @@ -267,6 +271,8 @@ func runEditorUntilOk(opts runEditorUntilOkOpts) error { waitForKeyPress() continue } + // The reloaded keys have lost the AWS profile override + common.SetAWSProfile(t.Metadata.KeyGroups, opts.AwsProfile) // Replace the whole tree, because otherwise newBranches would // contain the SOPS metadata opts.Tree = &t diff --git a/cmd/sops/main.go b/cmd/sops/main.go index 8092a3f400..3f4a0e76ce 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -208,6 +208,10 @@ func main() { Usage: "comma separated list of decryption key types", EnvVar: "SOPS_DECRYPTION_ORDER", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", + }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if c.NArg() != 2 { @@ -236,6 +240,7 @@ func main() { KeyServices: svcs, DecryptionOrder: order, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), } if c.Bool("background") { @@ -322,6 +327,10 @@ func main() { Usage: "comma separated list of decryption key types", EnvVar: "SOPS_DECRYPTION_ORDER", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", + }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if c.NArg() != 2 { @@ -354,6 +363,7 @@ func main() { KeyServices: svcs, DecryptionOrder: order, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), } output, err := decrypt(opts) @@ -405,6 +415,10 @@ func main() { Usage: "comma separated list of decryption key types", EnvVar: "SOPS_DECRYPTION_ORDER", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", + }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if c.Bool("verbose") || c.GlobalBool("verbose") { @@ -456,6 +470,7 @@ func main() { Interactive: !c.Bool("yes"), OmitExtensions: c.Bool("omit-extensions"), Recursive: c.Bool("recursive"), + AwsProfile: c.String("aws-profile"), }) if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil { return cliErr @@ -572,7 +587,7 @@ func main() { }, cli.StringFlag{ Name: "aws-profile", - Usage: "The AWS profile to use for requests to AWS", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", }, cli.StringSliceFlag{ Name: "gcp-kms", @@ -669,6 +684,7 @@ func main() { Group: group, GroupThreshold: c.Int("shamir-secret-sharing-threshold"), KeyServices: keyservices(c), + AwsProfile: c.String("aws-profile"), }) }, }, @@ -688,6 +704,10 @@ func main() { Name: "shamir-secret-sharing-threshold", Usage: "the number of master keys required to retrieve the data key with shamir", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", + }, }, keyserviceFlags...), ArgsUsage: `[index]`, @@ -716,6 +736,7 @@ func main() { Group: uint(group), GroupThreshold: c.Int("shamir-secret-sharing-threshold"), KeyServices: keyservices(c), + AwsProfile: c.String("aws-profile"), }) }, }, @@ -734,6 +755,10 @@ func main() { Name: "input-type", Usage: "currently ini, json, yaml, dotenv and binary are supported. If not set, sops will use the file's extension to determine the type", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", + }, }, keyserviceFlags...), Action: func(c *cli.Context) error { var err error @@ -755,6 +780,7 @@ func main() { InputPath: path, ShamirThreshold: c.Int("shamir-secret-sharing-threshold"), KeyServices: keyservices(c), + AwsProfile: c.String("aws-profile"), Interactive: !c.Bool("yes"), ConfigPath: configPath, InputType: c.String("input-type"), @@ -820,6 +846,10 @@ func main() { Usage: "comma separated list of decryption key types", EnvVar: "SOPS_DECRYPTION_ORDER", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", + }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if c.Bool("verbose") { @@ -884,6 +914,7 @@ func main() { KeyServices: svcs, DecryptionOrder: order, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), }) if err != nil { return toExitError(err) @@ -938,7 +969,7 @@ func main() { }, cli.StringFlag{ Name: "aws-profile", - Usage: "The AWS profile to use for requests to AWS", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", }, cli.StringFlag{ Name: "gcp-kms", @@ -1200,6 +1231,10 @@ func main() { Usage: "comma separated list of decryption key types", EnvVar: "SOPS_DECRYPTION_ORDER", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", + }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if c.Bool("verbose") { @@ -1297,7 +1332,7 @@ func main() { }, cli.StringFlag{ Name: "aws-profile", - Usage: "The AWS profile to use for requests to AWS", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", }, cli.StringFlag{ Name: "gcp-kms", @@ -1413,6 +1448,7 @@ func main() { KeyServices: svcs, DecryptionOrder: order, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), ShowMasterKeys: c.Bool("show-master-keys"), } if fileExists { @@ -1488,6 +1524,10 @@ func main() { Name: "idempotent", Usage: "do nothing if the given index already has the given value", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", + }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if c.Bool("verbose") { @@ -1559,6 +1599,7 @@ func main() { KeyServices: svcs, DecryptionOrder: order, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), Value: value, TreePath: path, }) @@ -1616,6 +1657,10 @@ func main() { Name: "idempotent", Usage: "do nothing if the given index does not exist", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", + }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if c.Bool("verbose") { @@ -1656,6 +1701,7 @@ func main() { KeyServices: svcs, DecryptionOrder: order, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), TreePath: path, }) if err != nil { @@ -1710,7 +1756,7 @@ func main() { }, cli.StringFlag{ Name: "aws-profile", - Usage: "The AWS profile to use for requests to AWS", + Usage: "AWS profile to use for all AWS KMS requests; overrides any aws_profile stored in the file or .sops.yaml", }, cli.StringFlag{ Name: "gcp-kms", @@ -2010,6 +2056,7 @@ func main() { KeyServices: svcs, DecryptionOrder: order, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), }) } if isRotateMode { @@ -2041,6 +2088,7 @@ func main() { KeyServices: svcs, DecryptionOrder: order, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), Value: value, TreePath: path, }) @@ -2057,6 +2105,7 @@ func main() { KeyServices: svcs, DecryptionOrder: order, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), ShowMasterKeys: c.Bool("show-master-keys"), } if fileExists { @@ -2303,6 +2352,7 @@ func getRotateOpts(c *cli.Context, fileName string, inputStore common.Store, out KeyServices: svcs, DecryptionOrder: decryptionOrder, IgnoreMAC: c.Bool("ignore-mac"), + AwsProfile: c.String("aws-profile"), AddMasterKeys: addMasterKeys, RemoveMasterKeys: rmMasterKeys, }, nil @@ -2510,6 +2560,7 @@ func keyGroups(c *cli.Context, file string, optionalConfig *config.Config) ([]so } return nil, fmt.Errorf("%s", errMsg) } + common.PersistAWSProfile(conf.KeyGroups, c.String("aws-profile")) return conf.KeyGroups, err } var group sops.KeyGroup diff --git a/cmd/sops/rotate.go b/cmd/sops/rotate.go index 072b558e5f..e741792ee7 100644 --- a/cmd/sops/rotate.go +++ b/cmd/sops/rotate.go @@ -17,6 +17,7 @@ type rotateOpts struct { OutputStore sops.Store InputPath string IgnoreMAC bool + AwsProfile string AddMasterKeys []keys.MasterKey RemoveMasterKeys []keys.MasterKey KeyServices []keyservice.KeyServiceClient @@ -29,6 +30,7 @@ func rotate(opts rotateOpts) ([]byte, error) { InputStore: opts.InputStore, InputPath: opts.InputPath, IgnoreMAC: opts.IgnoreMAC, + AwsProfile: opts.AwsProfile, KeyServices: opts.KeyServices, DecryptionOrder: opts.DecryptionOrder, }) diff --git a/cmd/sops/set.go b/cmd/sops/set.go index 7c56f77757..13d82d2111 100644 --- a/cmd/sops/set.go +++ b/cmd/sops/set.go @@ -15,6 +15,7 @@ type setOpts struct { OutputStore sops.Store InputPath string IgnoreMAC bool + AwsProfile string TreePath []interface{} Value interface{} KeyServices []keyservice.KeyServiceClient @@ -29,6 +30,7 @@ func set(opts setOpts) ([]byte, bool, error) { InputStore: opts.InputStore, InputPath: opts.InputPath, IgnoreMAC: opts.IgnoreMAC, + AwsProfile: opts.AwsProfile, KeyServices: opts.KeyServices, }) if err != nil { diff --git a/cmd/sops/subcommand/groups/add.go b/cmd/sops/subcommand/groups/add.go index 716d83ea65..13f121a33f 100644 --- a/cmd/sops/subcommand/groups/add.go +++ b/cmd/sops/subcommand/groups/add.go @@ -18,6 +18,7 @@ type AddOpts struct { InPlace bool KeyServices []keyservice.KeyServiceClient DecryptionOrder []string + AwsProfile string } // Add adds a key group to a SOPS file @@ -26,6 +27,7 @@ func Add(opts AddOpts) error { if err != nil { return err } + common.SetAWSProfile(tree.Metadata.KeyGroups, opts.AwsProfile) dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder) if err != nil { return err diff --git a/cmd/sops/subcommand/groups/delete.go b/cmd/sops/subcommand/groups/delete.go index 67f32ef255..b740b0891a 100644 --- a/cmd/sops/subcommand/groups/delete.go +++ b/cmd/sops/subcommand/groups/delete.go @@ -20,6 +20,7 @@ type DeleteOpts struct { InPlace bool KeyServices []keyservice.KeyServiceClient DecryptionOrder []string + AwsProfile string } // Delete deletes a key group from a SOPS file @@ -28,6 +29,7 @@ func Delete(opts DeleteOpts) error { if err != nil { return err } + common.SetAWSProfile(tree.Metadata.KeyGroups, opts.AwsProfile) dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder) if err != nil { return err diff --git a/cmd/sops/subcommand/publish/publish.go b/cmd/sops/subcommand/publish/publish.go index aed1118de5..c83bb8dd34 100644 --- a/cmd/sops/subcommand/publish/publish.go +++ b/cmd/sops/subcommand/publish/publish.go @@ -37,6 +37,7 @@ type Opts struct { OmitExtensions bool Recursive bool RootPath string + AwsProfile string } // Run publish operation @@ -73,6 +74,7 @@ func Run(opts Opts) error { if err != nil { return err } + common.SetAWSProfile(tree.Metadata.KeyGroups, opts.AwsProfile) data := map[string]interface{}{} diff --git a/cmd/sops/subcommand/updatekeys/updatekeys.go b/cmd/sops/subcommand/updatekeys/updatekeys.go index 9dec066a67..ffb8b0326b 100644 --- a/cmd/sops/subcommand/updatekeys/updatekeys.go +++ b/cmd/sops/subcommand/updatekeys/updatekeys.go @@ -21,6 +21,7 @@ type Opts struct { Interactive bool ConfigPath string InputType string + AwsProfile string } // UpdateKeys update the keys for a given file @@ -51,6 +52,7 @@ func updateFile(opts Opts) error { if err != nil { return err } + common.SetAWSProfile(tree.Metadata.KeyGroups, opts.AwsProfile) conf, err := config.LoadCreationRuleForFile(opts.ConfigPath, opts.InputPath, make(map[string]*string)) if err != nil { return err @@ -58,6 +60,7 @@ func updateFile(opts Opts) error { if conf == nil { return fmt.Errorf("The config file %s does not contain any creation rule", opts.ConfigPath) } + common.SetAWSProfile(conf.KeyGroups, opts.AwsProfile) diffs := common.DiffKeyGroups(tree.Metadata.KeyGroups, conf.KeyGroups) keysWillChange := false diff --git a/cmd/sops/unset.go b/cmd/sops/unset.go index bb369748c3..74c19ba717 100644 --- a/cmd/sops/unset.go +++ b/cmd/sops/unset.go @@ -15,6 +15,7 @@ type unsetOpts struct { OutputStore sops.Store InputPath string IgnoreMAC bool + AwsProfile string TreePath []interface{} KeyServices []keyservice.KeyServiceClient DecryptionOrder []string @@ -27,6 +28,7 @@ func unset(opts unsetOpts) ([]byte, error) { InputStore: opts.InputStore, InputPath: opts.InputPath, IgnoreMAC: opts.IgnoreMAC, + AwsProfile: opts.AwsProfile, KeyServices: opts.KeyServices, }) if err != nil { diff --git a/keyservice/keyservice.go b/keyservice/keyservice.go index 04125f7510..440a0e0695 100644 --- a/keyservice/keyservice.go +++ b/keyservice/keyservice.go @@ -57,7 +57,7 @@ func KeyFromMasterKey(mk keys.MasterKey) Key { Arn: mk.Arn, Role: mk.Role, Context: ctx, - AwsProfile: mk.AwsProfile, + AwsProfile: mk.EffectiveAwsProfile(), }, }, } diff --git a/keyservice/keyservice_test.go b/keyservice/keyservice_test.go new file mode 100644 index 0000000000..d71b905829 --- /dev/null +++ b/keyservice/keyservice_test.go @@ -0,0 +1,17 @@ +package keyservice + +import ( + "testing" + + "github.com/getsops/sops/v3/kms" + "github.com/stretchr/testify/assert" +) + +func TestKeyFromMasterKeyAwsProfileOverride(t *testing.T) { + mk := kms.NewMasterKeyWithProfile("arn:aws:kms:us-west-2:111122223333:key/abc", "", nil, "stored") + key := KeyFromMasterKey(mk) + assert.Equal(t, "stored", key.GetKmsKey().AwsProfile) + mk.AwsProfileOverride = "override" + key = KeyFromMasterKey(mk) + assert.Equal(t, "override", key.GetKmsKey().AwsProfile) +} diff --git a/kms/keysource.go b/kms/keysource.go index b24b5ac3fa..8aa25592b0 100644 --- a/kms/keysource.go +++ b/kms/keysource.go @@ -69,6 +69,9 @@ type MasterKey struct { // AwsProfile is the profile to use for loading configuration and credentials. // Ref: https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-profiles AwsProfile string + // AwsProfileOverride takes precedence over AwsProfile when set. It is never + // serialized, so it only affects the current invocation (see ToMap). + AwsProfileOverride string // credentialsProvider is used to configure the AWS client config with // credentials. It can be injected by a (local) keyservice.KeyServiceServer @@ -387,6 +390,15 @@ func (key *MasterKey) TypeToIdentifier() string { return KeyTypeIdentifier } +// EffectiveAwsProfile returns the AWS profile to use for requests: the +// override if set, the profile stored with the key otherwise. +func (key MasterKey) EffectiveAwsProfile() string { + if key.AwsProfileOverride != "" { + return key.AwsProfileOverride + } + return key.AwsProfile +} + // createKMSConfig returns an AWS config with the credentialsProvider of the // MasterKey, or the default configuration sources. func (key MasterKey) createKMSConfig(ctx context.Context) (*aws.Config, error) { @@ -403,8 +415,8 @@ func (key MasterKey) createKMSConfig(ctx context.Context) (*aws.Config, error) { if key.credentialsProvider != nil { lo.Credentials = key.credentialsProvider } - if key.AwsProfile != "" { - lo.SharedConfigProfile = key.AwsProfile + if profile := key.EffectiveAwsProfile(); profile != "" { + lo.SharedConfigProfile = profile } lo.Region = region if key.httpClient != nil { diff --git a/kms/keysource_test.go b/kms/keysource_test.go index d4fde1ce81..d9c7c5cb8c 100644 --- a/kms/keysource_test.go +++ b/kms/keysource_test.go @@ -372,6 +372,16 @@ func TestMasterKey_NeedsRotation(t *testing.T) { assert.True(t, key.NeedsRotation()) } +func TestMasterKey_EffectiveAwsProfile(t *testing.T) { + key := NewMasterKeyWithProfile(dummyARN, "", nil, "stored") + assert.Equal(t, "stored", key.EffectiveAwsProfile()) + key.AwsProfileOverride = "override" + assert.Equal(t, "override", key.EffectiveAwsProfile()) + assert.Equal(t, "stored", key.AwsProfile) + assert.NotContains(t, fmt.Sprint(key.ToMap()), "override") + assert.NotContains(t, key.ToString(), "override") +} + func TestMasterKey_ToString(t *testing.T) { dummyARNWithRole := fmt.Sprintf("%s+arn:aws:iam::my-role", dummyARN)