Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand Down
45 changes: 42 additions & 3 deletions azkv/keysource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
Loading