1
0
mirror of https://github.com/getsops/sops.git synced 2026-02-05 12:45:21 +01:00

Merge pull request #1919 from daogilvie/allow-azure-keyvault-empty-version

feat(azkv): Skipping key-version will get latest key
This commit is contained in:
Felix Fontein
2025-09-09 21:16:49 +02:00
committed by GitHub
2 changed files with 51 additions and 3 deletions

View File

@@ -378,6 +378,11 @@ a key. This has the following form::
https://${VAULT_URL}/keys/${KEY_NAME}/${KEY_VERSION}
You can omit the version, and have just a trailing slash, and this will use
whatever the latest version of the key is::
https://${VAULT_URL}/keys/${KEY_NAME}/
To create a Key Vault and assign your service principal permissions on it
from the commandline:
@@ -401,6 +406,10 @@ Now you can encrypt a file using::
$ sops encrypt --azure-kv https://sops.vault.azure.net/keys/sops-key/some-string test.yaml > test.enc.yaml
or, without the version::
$ sops encrypt --azure-kv https://sops.vault.azure.net/keys/sops-key/ test.yaml > test.enc.yaml
And decrypt it using::
$ sops decrypt test.enc.yaml

View File

@@ -79,12 +79,21 @@ func NewMasterKey(vaultURL string, keyName string, keyVersion string) *MasterKey
// MasterKey. The URL format is {vaultUrl}/keys/{keyName}/{keyVersion}.
func NewMasterKeyFromURL(url string) (*MasterKey, error) {
url = strings.TrimSpace(url)
re := regexp.MustCompile("^(https://[^/]+)/keys/([^/]+)/([^/]+)$")
re := regexp.MustCompile("^(https://[^/]+)/keys/([^/]+)(/[^/]*)?$")
parts := re.FindStringSubmatch(url)
if len(parts) < 3 {
return nil, fmt.Errorf("could not parse %q into a valid Azure Key Vault MasterKey", url)
return nil, fmt.Errorf("could not parse %q into a valid Azure Key Vault MasterKey %v", url, parts)
}
return NewMasterKey(parts[1], parts[2], parts[3]), nil
// Blank key versions are supported in Azure Key Vault, as they default to the latest
// version of the key. We need to put the actual version in the sops metadata block though
var key *MasterKey
if len(parts[3]) > 1 {
key = NewMasterKey(parts[1], parts[2], parts[3][1:])
} else {
key = NewMasterKey(parts[1], parts[2], "")
}
err := key.ensureKeyHasVersion(context.Background())
return key, err
}
// MasterKeysFromURLs takes a comma separated list of Azure Key Vault URLs,
@@ -145,6 +154,36 @@ func (key *MasterKey) Encrypt(dataKey []byte) error {
return key.EncryptContext(context.Background(), dataKey)
}
func (key *MasterKey) ensureKeyHasVersion(ctx context.Context) error {
if (key.Version != "") {
// Nothing to do
return nil
}
token, err := key.getTokenCredential()
if err != nil {
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Encryption failed")
return fmt.Errorf("failed to get Azure token credential to retrieve key version: %w", err)
}
c, err := azkeys.NewClient(key.VaultURL, token, key.clientOptions)
if err != nil {
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Encryption failed")
return fmt.Errorf("failed to construct Azure Key Vault client to retrieve key version: %w", err)
}
kdetail, err := c.GetKey(ctx, key.Name, key.Version, nil)
if err != nil {
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Encryption failed")
return fmt.Errorf("failed to fetch Azure Key to retrieve key version: %w", err)
}
key.Version = kdetail.Key.KID.Version()
log.WithFields(logrus.Fields{"key": key.Name, "version": key.Version}).Info("Version fetch succeeded")
return nil
}
// EncryptContext takes a SOPS data key, encrypts it with Azure Key Vault, and stores
// the result in the EncryptedKey field.
func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error {