Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions cmd/sops/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand Down
99 changes: 99 additions & 0 deletions cmd/sops/common/common_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 2 additions & 0 deletions cmd/sops/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type decryptOpts struct {
InputPath string
ReadFromStdin bool
IgnoreMAC bool
AwsProfile string
Extract []interface{}
KeyServices []keyservice.KeyServiceClient
DecryptionOrder []string
Expand All @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions cmd/sops/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type editOpts struct {
OutputStore common.Store
InputPath string
IgnoreMAC bool
AwsProfile string
KeyServices []keyservice.KeyServiceClient
DecryptionOrder []string
ShowMasterKeys bool
Expand All @@ -44,6 +45,7 @@ type runEditorUntilOkOpts struct {
InputStore sops.Store
OutputStore common.Store
ShowMasterKeys bool
AwsProfile string
Tree *sops.Tree
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Loading