From 7ebee3dc7b8f2c5fdf02cac7afca3c72d0af4496 Mon Sep 17 00:00:00 2001 From: Cedric Kienzler Date: Sun, 20 Mar 2022 22:49:11 +0100 Subject: [PATCH] This fixes a bug with age encryption when specifying multiple age recipients I encountered an issue when I tried so specify multiple age recipients in the .sops.yaml config file of my repository. I tried running `sops --age 'agePubKey1,agePubKey2' -e -i values.secret.yaml` which produced an appropriate file with two entries in the `/sops/age/-` part of the encrypted yaml file. However, I then continued to set multiple recipients in my .sops.yaml file to simplify handling: ```yaml creation_rules: - encrypted_regex: '^(data|stringData|spec)$' age: 'agePubKey1,agePubKey2' ``` However, this resulted in encryption only being done for the first specified agePubKey, not the second or third one. After digging a bit trough the code, I think this should fix it. I verified the fix locally on my machine and got it working. Also adding some unit tests and extending the repository examples so they can be decrypted using the age keys provided in `age/keys.txt` Signed-off-by: Cedric Kienzler --- age/keys.txt | 3 +++ age/keysource_test.go | 37 +++++++++++++++++++++++++++---------- cmd/sops/main.go | 6 ++++-- config/config.go | 6 ++++-- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/age/keys.txt b/age/keys.txt index c56eb4fab..9b998fcc8 100644 --- a/age/keys.txt +++ b/age/keys.txt @@ -1,3 +1,6 @@ # created: 2020-07-18T03:16:47-07:00 # public key: age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw AGE-SECRET-KEY-1NJT5YCS2LWU4V4QAJQ6R4JNU7LXPDX602DZ9NUFANVU5GDTGUWCQ5T59M6 +# created: 2021-12-12T01:39:30+01:00 +# public key: age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep +AGE-SECRET-KEY-1T0Z66WSXS6RMNCPSL7P2E8N4Q7SUD8VMG9ND27S08JL7Y2XAU9EQECHDS7 \ No newline at end of file diff --git a/age/keysource_test.go b/age/keysource_test.go index 0b30b355d..556770087 100644 --- a/age/keysource_test.go +++ b/age/keysource_test.go @@ -31,26 +31,43 @@ func TestMasterKeyFromRecipientWithLeadingAndTrailingSpaces(t *testing.T) { assert.Equal(key.Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") } +func TestMasterKeysFromRecipientsWithMultiple(t *testing.T) { + assert := assert.New(t) + + keys, err := MasterKeysFromRecipients("age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw,age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep") + + assert.NoError(err) + + assert.Equal(len(keys), 2) + assert.Equal(keys[0].Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") + assert.Equal(keys[1].Recipient, "age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep") +} + func TestAge(t *testing.T) { assert := assert.New(t) - key, err := MasterKeyFromRecipient("age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") + keys, err := MasterKeysFromRecipients("age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw,age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep") assert.NoError(err) - assert.Equal("age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw", key.ToString()) + assert.Equal(len(keys), 2) + assert.Equal(keys[0].Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") + assert.Equal(keys[1].Recipient, "age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep") dataKey := []byte("abcdefghijklmnopqrstuvwxyz123456") - err = key.Encrypt(dataKey) - assert.NoError(err) + for _, key := range keys { + err = key.Encrypt(dataKey) + assert.NoError(err) - _, filename, _, _ := runtime.Caller(0) - err = os.Setenv(SopsAgeKeyFileEnv, path.Join(path.Dir(filename), "keys.txt")) - assert.NoError(err) + _, filename, _, _ := runtime.Caller(0) + err = os.Setenv("SOPS_AGE_KEY_FILE", path.Join(path.Dir(filename), "keys.txt")) + assert.NoError(err) + + decryptedKey, err := key.Decrypt() + assert.NoError(err) + assert.Equal(dataKey, decryptedKey) + } - decryptedKey, err := key.Decrypt() - assert.NoError(err) - assert.Equal(dataKey, decryptedKey) } func TestAgeDotEnv(t *testing.T) { diff --git a/cmd/sops/main.go b/cmd/sops/main.go index 148daa729..307dc45df 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -442,12 +442,14 @@ func main() { group = append(group, k) } for _, recipient := range ageRecipients { - k, err := age.MasterKeyFromRecipient(recipient) + keys, err := age.MasterKeysFromRecipients(recipient) if err != nil { log.WithError(err).Error("Failed to add key") continue } - group = append(group, k) + for _, key := range keys { + group = append(group, key) + } } return groups.Add(groups.AddOpts{ InputPath: c.String("file"), diff --git a/config/config.go b/config/config.go index 061d038e2..a4db7b868 100644 --- a/config/config.go +++ b/config/config.go @@ -153,11 +153,13 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[ for _, group := range cRule.KeyGroups { var keyGroup sops.KeyGroup for _, k := range group.Age { - key, err := age.MasterKeyFromRecipient(k) + keys, err := age.MasterKeysFromRecipients(k) if err != nil { return nil, err } - keyGroup = append(keyGroup, key) + for _, key := range keys { + keyGroup = append(keyGroup, key) + } } for _, k := range group.PGP { keyGroup = append(keyGroup, pgp.NewMasterKeyFromFingerprint(k))