Skip to content

Commit eb23541

Browse files
Drum Ogilviefelixfontein
authored andcommitted
feat(azkv): Skipping key-version will get latest key
This is a supported behaviour for Azure Key Vault, and is very helpful when it comes to doing rotation of those keys, as updated versions will automatically get fetched by sops for new files with no changes to the config in the repo. This brings AZKV in line with sops' AWS/GCP integrations, which do not require you to specify a key version. Thank you to @felixfontein for some RST syntax help. Co-authored-by: Felix Fontein <felix@fontein.de> Signed-off-by: Drum Ogilvie <me@daogilvie.com>
1 parent 97637cd commit eb23541

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

README.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,11 @@ a key. This has the following form::
378378
379379
https://${VAULT_URL}/keys/${KEY_NAME}/${KEY_VERSION}
380380
381+
You can omit the version, and have just a trailing slash, and this will use
382+
whatever the latest version of the key is::
383+
384+
https://${VAULT_URL}/keys/${KEY_NAME}/
385+
381386
To create a Key Vault and assign your service principal permissions on it
382387
from the commandline:
383388
@@ -401,6 +406,10 @@ Now you can encrypt a file using::
401406
402407
$ sops encrypt --azure-kv https://sops.vault.azure.net/keys/sops-key/some-string test.yaml > test.enc.yaml
403408
409+
or, without the version::
410+
411+
$ sops encrypt --azure-kv https://sops.vault.azure.net/keys/sops-key/ test.yaml > test.enc.yaml
412+
404413
And decrypt it using::
405414
406415
$ sops decrypt test.enc.yaml

azkv/keysource.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,21 @@ func NewMasterKey(vaultURL string, keyName string, keyVersion string) *MasterKey
7979
// MasterKey. The URL format is {vaultUrl}/keys/{keyName}/{keyVersion}.
8080
func NewMasterKeyFromURL(url string) (*MasterKey, error) {
8181
url = strings.TrimSpace(url)
82-
re := regexp.MustCompile("^(https://[^/]+)/keys/([^/]+)/([^/]+)$")
82+
re := regexp.MustCompile("^(https://[^/]+)/keys/([^/]+)(/[^/]+)?$")
8383
parts := re.FindStringSubmatch(url)
8484
if len(parts) < 3 {
85-
return nil, fmt.Errorf("could not parse %q into a valid Azure Key Vault MasterKey", url)
85+
return nil, fmt.Errorf("could not parse %q into a valid Azure Key Vault MasterKey %v", url, parts)
8686
}
87-
return NewMasterKey(parts[1], parts[2], parts[3]), nil
87+
// Blank key versions are supported in Azure Key Vault, as they default to the latest
88+
// version of the key. We need to put the actual version in the sops metadata block though
89+
var key *MasterKey
90+
if len(parts[3]) > 1 {
91+
key = NewMasterKey(parts[1], parts[2], parts[3][1:])
92+
} else {
93+
key = NewMasterKey(parts[1], parts[2], "")
94+
}
95+
err := key.ensureKeyHasVersion(context.Background())
96+
return key, err
8897
}
8998

9099
// MasterKeysFromURLs takes a comma separated list of Azure Key Vault URLs,
@@ -145,6 +154,36 @@ func (key *MasterKey) Encrypt(dataKey []byte) error {
145154
return key.EncryptContext(context.Background(), dataKey)
146155
}
147156

157+
func (key *MasterKey) ensureKeyHasVersion(ctx context.Context) error {
158+
if (key.Version != "") {
159+
// Nothing to do
160+
return nil
161+
}
162+
163+
token, err := key.getTokenCredential()
164+
165+
if err != nil {
166+
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Encryption failed")
167+
return fmt.Errorf("failed to get Azure token credential to retrieve key version: %w", err)
168+
}
169+
170+
c, err := azkeys.NewClient(key.VaultURL, token, key.clientOptions)
171+
if err != nil {
172+
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Encryption failed")
173+
return fmt.Errorf("failed to construct Azure Key Vault client to retrieve key version: %w", err)
174+
}
175+
176+
kdetail, err := c.GetKey(ctx, key.Name, key.Version, nil)
177+
if err != nil {
178+
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Encryption failed")
179+
return fmt.Errorf("failed to fetch Azure Key to retrieve key version: %w", err)
180+
}
181+
key.Version = kdetail.Key.KID.Version()
182+
183+
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Version fetch succeeded")
184+
return nil
185+
}
186+
148187
// EncryptContext takes a SOPS data key, encrypts it with Azure Key Vault, and stores
149188
// the result in the EncryptedKey field.
150189
func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error {

0 commit comments

Comments
 (0)