From 9fa4f0e90e17bff1908e62f896e01dcedc29aa53 Mon Sep 17 00:00:00 2001 From: Calle Pettersson Date: Sun, 17 Jun 2018 22:50:30 +0200 Subject: [PATCH] Add support for Azure Key Vault --- Makefile | 5 +- azkv/keysource.go | 187 ++++++++++++++++++++++++++++++++++++ azkv/keysource_test.go | 102 ++++++++++++++++++++ cmd/sops/main.go | 49 +++++++++- config/config.go | 18 +++- keyservice/keyservice.go | 11 +++ keyservice/keyservice.pb.go | 146 ++++++++++++++++++++-------- keyservice/keyservice.proto | 7 ++ keyservice/server.go | 43 +++++++++ stores/stores.go | 70 ++++++++++++-- 10 files changed, 582 insertions(+), 56 deletions(-) create mode 100644 azkv/keysource.go create mode 100644 azkv/keysource_test.go diff --git a/Makefile b/Makefile index e2431e56d..553fe256a 100644 --- a/Makefile +++ b/Makefile @@ -30,9 +30,12 @@ test: showcoverage: test $(GO) tool cover -html=coverage.out -generate: +generate: keyservice/keyservice.pb.go $(GO) generate +%.pb.go: %.proto + protoc --go_out=plugins=grpc:. $< + functional-tests: $(GO) build -o functional-tests/sops go.mozilla.org/sops/cmd/sops cd functional-tests && cargo test diff --git a/azkv/keysource.go b/azkv/keysource.go new file mode 100644 index 000000000..5a3eb647b --- /dev/null +++ b/azkv/keysource.go @@ -0,0 +1,187 @@ +/* +Package azkv contains an implementation of the go.mozilla.org/sops/keys.MasterKey interface that encrypts and decrypts the +data key using Azure Key Vault with the Azure Go SDK. +*/ +package azkv //import "go.mozilla.org/sops/azkv" + +import ( + "context" + "encoding/base64" + "fmt" + "regexp" + "strings" + "time" + + "go.mozilla.org/sops/logging" + + "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" + "github.com/Azure/azure-sdk-for-go/services/keyvault/auth" + "github.com/sirupsen/logrus" +) + +var log *logrus.Logger + +func init() { + log = logging.NewLogger("AZKV") +} + +// MasterKey is a Azure Key Vault key used to encrypt and decrypt sops' data key. +type MasterKey struct { + VaultURL string + Name string + Version string + + EncryptedKey string + CreationDate time.Time +} + +func newKeyVaultClient() (keyvault.BaseClient, error) { + var err error + c := keyvault.New() + c.Authorizer, err = auth.NewAuthorizerFromEnvironment() + if err != nil { + log.WithError(err).Error("Failed to create Azure authorizer") + return c, err + } + + return c, nil +} + +// NewMasterKey creates a new MasterKey from an URL, key name and version, setting the creation date to the current date +func NewMasterKey(vaultURL string, keyName string, keyVersion string) *MasterKey { + return &MasterKey{ + VaultURL: vaultURL, + Name: keyName, + Version: keyVersion, + CreationDate: time.Now().UTC(), + } +} + +// MasterKeysFromURLs takes a comma separated list of Azure Key Vault URLs and returns a slice of new MasterKeys for them +func MasterKeysFromURLs(urls string) []*MasterKey { + var keys []*MasterKey + if urls == "" { + return keys + } + for _, s := range strings.Split(urls, ",") { + keys = append(keys, NewMasterKeyFromURL(s)) + } + return keys +} + +// NewMasterKeyFromResourceID takes an Azure Key Vault key URL and returns a new MasterKey +// URL format is {vaultUrl}/keys/{key-name}/{key-version} +func NewMasterKeyFromURL(url string) *MasterKey { + k := &MasterKey{} + re := regexp.MustCompile("^(https://[^/]+)/keys/([^/]+)/([^/]+)$") + parts := re.FindStringSubmatch(url) + if parts == nil || len(parts) < 2 { + log.Error("No match!") + // !? + } + + k.VaultURL = parts[1] + k.Name = parts[2] + k.Version = parts[3] + k.CreationDate = time.Now().UTC() + return k +} + +// EncryptedDataKey returns the encrypted data key this master key holds +func (key *MasterKey) EncryptedDataKey() []byte { + return []byte(key.EncryptedKey) +} + +// SetEncryptedDataKey sets the encrypted data key for this master key +func (key *MasterKey) SetEncryptedDataKey(enc []byte) { + key.EncryptedKey = string(enc) +} + +// Encrypt takes a sops data key, encrypts it with Key Vault and stores the result in the EncryptedKey field +func (key *MasterKey) Encrypt(dataKey []byte) error { + c, err := newKeyVaultClient() + if err != nil { + return err + } + data := base64.RawURLEncoding.EncodeToString(dataKey) + p := keyvault.KeyOperationsParameters{Value: &data, Algorithm: keyvault.RSAOAEP256} + + res, err := c.Encrypt(context.Background(), key.VaultURL, key.Name, key.Version, p) + if err != nil { + log.WithError(err).WithFields(logrus.Fields{ + "key": key.Name, + "version": key.Version, + }).Error("Encryption failed") + return fmt.Errorf("Failed to encrypt data: %v", err) + } + + key.EncryptedKey = *res.Result + log.WithFields(logrus.Fields{ + "key": key.Name, + "version": key.Version, + }).Info("Encryption succeeded") + + return nil +} + +// EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet +func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error { + if key.EncryptedKey == "" { + return key.Encrypt(dataKey) + } + return nil +} + +// Decrypt decrypts the EncryptedKey field with Azure Key Vault and returns the result. +func (key *MasterKey) Decrypt() ([]byte, error) { + c, err := newKeyVaultClient() + if err != nil { + return nil, err + } + p := keyvault.KeyOperationsParameters{Value: &key.EncryptedKey, Algorithm: keyvault.RSAOAEP256} + + res, err := c.Decrypt(context.TODO(), key.VaultURL, key.Name, key.Version, p) + if err != nil { + log.WithError(err).WithFields(logrus.Fields{ + "key": key.Name, + "version": key.Version, + }).Error("Decryption failed") + return nil, fmt.Errorf("Error decrypting key: %v", err) + } + + plaintext, err := base64.RawURLEncoding.DecodeString(*res.Result) + if err != nil { + log.WithError(err).WithFields(logrus.Fields{ + "key": key.Name, + "version": key.Version, + }).Error("Decryption failed") + return nil, err + } + + log.WithFields(logrus.Fields{ + "key": key.Name, + "version": key.Version, + }).Info("Decryption succeeded") + return plaintext, nil +} + +// NeedsRotation returns whether the data key needs to be rotated or not. +func (key *MasterKey) NeedsRotation() bool { + return time.Since(key.CreationDate) > (time.Hour * 24 * 30 * 6) +} + +// ToString converts the key to a string representation +func (key *MasterKey) ToString() string { + return fmt.Sprintf("%s/keys/%s/%s", key.VaultURL, key.Name, key.Version) +} + +// ToMap converts the MasterKey to a map for serialization purposes +func (key MasterKey) ToMap() map[string]interface{} { + out := make(map[string]interface{}) + out["vaultUrl"] = key.VaultURL + out["key"] = key.Name + out["version"] = key.Version + out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) + out["enc"] = key.EncryptedKey + return out +} diff --git a/azkv/keysource_test.go b/azkv/keysource_test.go new file mode 100644 index 000000000..84e6cd19a --- /dev/null +++ b/azkv/keysource_test.go @@ -0,0 +1,102 @@ +package azkv + +import ( + "flag" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestAzureKeySourceFromUrl(t *testing.T) { + cases := []struct { + name string + input string + expectedFoundKeys int + expectedKeys []MasterKey + }{ + { + name: "Single url", + input: "https://test.vault.azure.net/keys/test-key/a2a690a4fcc04166b739da342a912c90", + expectedFoundKeys: 1, + expectedKeys: []MasterKey{ + { + VaultURL: "https://test.vault.azure.net", + Name: "test-key", + Version: "a2a690a4fcc04166b739da342a912c90", + }, + }, + }, + { + name: "Multiple url", + input: "https://test.vault.azure.net/keys/test-key/a2a690a4fcc04166b739da342a912c90,https://test2.vault.azure.net/keys/another-test-key/cf0021e8b743453bae758e7fbf71b60e", + expectedFoundKeys: 2, + expectedKeys: []MasterKey{ + { + VaultURL: "https://test.vault.azure.net", + Name: "test-key", + Version: "a2a690a4fcc04166b739da342a912c90", + }, + { + VaultURL: "https://test2.vault.azure.net", + Name: "another-test-key", + Version: "cf0021e8b743453bae758e7fbf71b60e", + }, + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + keys := MasterKeysFromURLs(c.input) + if c.expectedFoundKeys != len(keys) { + t.Errorf("Unexpected number of keys returned, expected %d, got %d", c.expectedFoundKeys, len(keys)) + } + for idx := range keys { + assert.Equal(t, c.expectedKeys[idx].VaultURL, keys[idx].VaultURL) + assert.Equal(t, c.expectedKeys[idx].Name, keys[idx].Name) + assert.Equal(t, c.expectedKeys[idx].Version, keys[idx].Version) + } + }) + } +} + +func TestKeyToMap(t *testing.T) { + key := MasterKey{ + CreationDate: time.Date(2016, time.October, 31, 10, 0, 0, 0, time.UTC), + VaultURL: "https://test.vault.azure.net", + Name: "test-key", + Version: "1", + EncryptedKey: "this is encrypted", + } + assert.Equal(t, map[string]interface{}{ + "vaultUrl": key.VaultURL, + "key": key.Name, + "version": key.Version, + "enc": "this is encrypted", + "created_at": "2016-10-31T10:00:00Z", + }, key.ToMap()) +} + +var azureKeyAcceptanceTestUrl = flag.String("azure-key", "", "URL to Azure Key Vault (note that this can incur real costs!)") + +func TestRoundtrip(t *testing.T) { + if *azureKeyAcceptanceTestUrl == "" { + t.Skip("Azure URL not provided, skipping acceptance test") + } + + input := []byte("test-string") + + key := NewMasterKeyFromURL(*azureKeyAcceptanceTestUrl) + err := key.Encrypt(input) + if err != nil { + t.Fatal(err) + } + + output, err := key.Decrypt() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, input, output) +} diff --git a/cmd/sops/main.go b/cmd/sops/main.go index f32616aaa..e9d4a235f 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -21,6 +21,7 @@ import ( "github.com/sirupsen/logrus" "go.mozilla.org/sops/aes" _ "go.mozilla.org/sops/audit" + "go.mozilla.org/sops/azkv" "go.mozilla.org/sops/cmd/sops/codes" "go.mozilla.org/sops/cmd/sops/common" "go.mozilla.org/sops/cmd/sops/subcommand/groups" @@ -59,7 +60,7 @@ func main() { }, } app.Name = "sops" - app.Usage = "sops - encrypted file editor with AWS KMS, GCP KMS and GPG support" + app.Usage = "sops - encrypted file editor with AWS KMS, GCP KMS, Azure Key Vault and GPG support" app.ArgsUsage = "sops [options] file" app.Version = version app.Authors = []cli.Author{ @@ -78,6 +79,10 @@ func main() { (you need to setup google application default credentials. See https://developers.google.com/identity/protocols/application-default-credentials) + To encrypt or decrypt a document with Azure Key Vault, specify the + Azure Key Vault key URL in the --azure-kv flag or in the SOPS_AZURE_KEYVAULT_URL + environment variable. + To encrypt or decrypt using PGP, specify the PGP fingerprint in the -p flag or in the SOPS_PGP_FP environment variable. @@ -88,7 +93,7 @@ func main() { or decrypting existing documents can be done with "sops file" or "sops -d file" respectively. The KMS and PGP keys listed in the encrypted documents are used then. To manage master keys in existing documents, use - the "add-{kms,pgp,gcp-kms}" and "rm-{kms,pgp,gcp-kms}" flags. + the "add-{kms,pgp,gcp-kms,azure-kv}" and "rm-{kms,pgp,gcp-kms,azure-kv}" flags. To use a different GPG binary than the one in your PATH, set SOPS_GPG_EXEC. @@ -160,6 +165,10 @@ func main() { Name: "gcp-kms", Usage: "the GCP KMS Resource ID the new group should contain. Can be specified more than once", }, + cli.StringSliceFlag{ + Name: "azure-kv", + Usage: "the Azure Key Vault key URL the new group should contain. Can be specified more than once", + }, cli.BoolFlag{ Name: "in-place, i", Usage: "write output back to the same file instead of stdout", @@ -176,6 +185,7 @@ func main() { Action: func(c *cli.Context) error { pgpFps := c.StringSlice("pgp") kmsArns := c.StringSlice("kms") + azkvs := c.StringSlice("azkv") var group sops.KeyGroup for _, fp := range pgpFps { group = append(group, pgp.NewMasterKeyFromFingerprint(fp)) @@ -183,6 +193,10 @@ func main() { for _, arn := range kmsArns { group = append(group, kms.NewMasterKeyFromArn(arn, kms.ParseKMSContext(c.String("encryption-context")))) } + // NOTE: Why isn't GCP here? + for _, url := range azkvs { + group = append(group, azkv.NewMasterKeyFromURL(url)) + } return groups.Add(groups.AddOpts{ InputPath: c.String("file"), InPlace: c.Bool("in-place"), @@ -288,6 +302,11 @@ func main() { Usage: "comma separated list of GCP KMS resource IDs", EnvVar: "SOPS_GCP_KMS_IDS", }, + cli.StringFlag{ + Name: "azure-kv", + Usage: "comma separated list of Azure Key Vault URLs", + EnvVar: "SOPS_AZURE_KEYVAULT_URLS", + }, cli.StringFlag{ Name: "pgp, p", Usage: "comma separated list of PGP fingerprints", @@ -321,6 +340,14 @@ func main() { Name: "rm-gcp-kms", Usage: "remove the provided comma-separated list of GCP KMS key resource IDs from the list of master keys on the given file", }, + cli.StringFlag{ + Name: "add-azure-kv", + Usage: "add the provided comma-separated list of Azure Key Vault key URLs to the list of master keys on the given file", + }, + cli.StringFlag{ + Name: "rm-azure-kv", + Usage: "remove the provided comma-separated list of Azure Key Vault key URLs from the list of master keys on the given file", + }, cli.StringFlag{ Name: "add-kms", Usage: "add the provided comma-separated list of KMS ARNs to the list of master keys on the given file", @@ -380,7 +407,8 @@ func main() { } fileName := c.Args()[0] if _, err := os.Stat(fileName); os.IsNotExist(err) { - if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" { + if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-azure-kv") != "" || + c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-azure-kv") != "" { return common.NewExitError("Error: cannot add or remove keys on non-existent files, use `--kms` and `--pgp` instead.", codes.CannotChangeKeysFromNonExistentFile) } if c.Bool("encrypt") || c.Bool("decrypt") || c.Bool("rotate") { @@ -468,6 +496,9 @@ func main() { for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("add-gcp-kms")) { addMasterKeys = append(addMasterKeys, k) } + for _, k := range azkv.MasterKeysFromURLs(c.String("add-azure-kv")) { + addMasterKeys = append(addMasterKeys, k) + } var rmMasterKeys []keys.MasterKey for _, k := range kms.MasterKeysFromArnString(c.String("rm-kms"), kmsEncryptionContext) { @@ -479,6 +510,9 @@ func main() { for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("rm-gcp-kms")) { rmMasterKeys = append(rmMasterKeys, k) } + for _, k := range azkv.MasterKeysFromURLs(c.String("rm-azure-kv")) { + rmMasterKeys = append(rmMasterKeys, k) + } output, err = rotate(rotateOpts{ OutputStore: outputStore, InputStore: inputStore, @@ -673,6 +707,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) { var kmsKeys []keys.MasterKey var pgpKeys []keys.MasterKey var cloudKmsKeys []keys.MasterKey + var azkvKeys []keys.MasterKey kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context")) if c.String("encryption-context") != "" && kmsEncryptionContext == nil { return nil, common.NewExitError("Invalid KMS encryption context format", codes.ErrorInvalidKMSEncryptionContextFormat) @@ -687,12 +722,17 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) { cloudKmsKeys = append(cloudKmsKeys, k) } } + if c.String("azure-kv") != "" { + for _, k := range azkv.MasterKeysFromURLs(c.String("azure-kv")) { + azkvKeys = append(azkvKeys, k) + } + } if c.String("pgp") != "" { for _, k := range pgp.MasterKeysFromFingerprintString(c.String("pgp")) { pgpKeys = append(pgpKeys, k) } } - if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" { + if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" && c.String("azure-kv") == "" { conf, err := loadConfig(c, file, kmsEncryptionContext) // config file might just not be supplied, without any error if conf == nil { @@ -707,6 +747,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) { var group sops.KeyGroup group = append(group, kmsKeys...) group = append(group, cloudKmsKeys...) + group = append(group, azkvKeys...) group = append(group, pgpKeys...) return []sops.KeyGroup{group}, nil } diff --git a/config/config.go b/config/config.go index 9ec5bd899..efae22f02 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,7 @@ import ( "github.com/mozilla-services/yaml" "github.com/sirupsen/logrus" "go.mozilla.org/sops" + "go.mozilla.org/sops/azkv" "go.mozilla.org/sops/gcpkms" "go.mozilla.org/sops/kms" "go.mozilla.org/sops/logging" @@ -63,9 +64,10 @@ type configFile struct { } type keyGroup struct { - KMS []kmsKey - GCPKMS []gcpKmsKey `yaml:"gcp_kms"` - PGP []string + KMS []kmsKey + GCPKMS []gcpKmsKey `yaml:"gcp_kms"` + AzureKV []azureKVKey `yaml:"azure_keyvault"` + PGP []string } type gcpKmsKey struct { @@ -78,12 +80,19 @@ type kmsKey struct { Context map[string]*string `yaml:"context"` } +type azureKVKey struct { + VaultURL string `yaml:"vaultUrl"` + Key string `yaml:"key"` + Version string `yaml:"version"` +} + type creationRule struct { FilenameRegex string `yaml:"filename_regex"` PathRegex string `yaml:"path_regex"` KMS string PGP string GCPKMS string `yaml:"gcp_kms"` + AzureKeyVault string `yaml:"azure_keyvault"` KeyGroups []keyGroup `yaml:"key_groups"` ShamirThreshold int `yaml:"shamir_threshold"` UnencryptedSuffix string `yaml:"unencrypted_suffix"` @@ -172,6 +181,9 @@ func loadForFileFromBytes(confBytes []byte, filePath string, kmsEncryptionContex for _, k := range gcpkms.MasterKeysFromResourceIDString(rule.GCPKMS) { keyGroup = append(keyGroup, k) } + for _, k := range azkv.MasterKeysFromURLs(rule.AzureKeyVault) { + keyGroup = append(keyGroup, k) + } groups = append(groups, keyGroup) } return &Config{ diff --git a/keyservice/keyservice.go b/keyservice/keyservice.go index c4049951c..7d426b65a 100644 --- a/keyservice/keyservice.go +++ b/keyservice/keyservice.go @@ -7,6 +7,7 @@ package keyservice import ( "fmt" + "go.mozilla.org/sops/azkv" "go.mozilla.org/sops/gcpkms" "go.mozilla.org/sops/keys" "go.mozilla.org/sops/kms" @@ -46,6 +47,16 @@ func KeyFromMasterKey(mk keys.MasterKey) Key { }, }, } + case *azkv.MasterKey: + return Key{ + KeyType: &Key_AzureKeyvaultKey{ + AzureKeyvaultKey: &AzureKeyVaultKey{ + VaultUrl: mk.VaultURL, + Name: mk.Name, + Version: mk.Version, + }, + }, + } default: panic(fmt.Sprintf("Tried to convert unknown MasterKey type %T to keyservice.Key", mk)) } diff --git a/keyservice/keyservice.pb.go b/keyservice/keyservice.pb.go index 560a4ba25..b13f7552c 100644 --- a/keyservice/keyservice.pb.go +++ b/keyservice/keyservice.pb.go @@ -1,17 +1,18 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// source: keyservice.proto +// source: keyservice/keyservice.proto /* Package keyservice is a generated protocol buffer package. It is generated from these files: - keyservice.proto + keyservice/keyservice.proto It has these top-level messages: Key PgpKey KmsKey GcpKmsKey + AzureKeyVaultKey EncryptRequest EncryptResponse DecryptRequest @@ -44,6 +45,7 @@ type Key struct { // *Key_KmsKey // *Key_PgpKey // *Key_GcpKmsKey + // *Key_AzureKeyvaultKey KeyType isKey_KeyType `protobuf_oneof:"key_type"` } @@ -65,10 +67,14 @@ type Key_PgpKey struct { type Key_GcpKmsKey struct { GcpKmsKey *GcpKmsKey `protobuf:"bytes,3,opt,name=gcp_kms_key,json=gcpKmsKey,oneof"` } +type Key_AzureKeyvaultKey struct { + AzureKeyvaultKey *AzureKeyVaultKey `protobuf:"bytes,4,opt,name=azure_keyvault_key,json=azureKeyvaultKey,oneof"` +} -func (*Key_KmsKey) isKey_KeyType() {} -func (*Key_PgpKey) isKey_KeyType() {} -func (*Key_GcpKmsKey) isKey_KeyType() {} +func (*Key_KmsKey) isKey_KeyType() {} +func (*Key_PgpKey) isKey_KeyType() {} +func (*Key_GcpKmsKey) isKey_KeyType() {} +func (*Key_AzureKeyvaultKey) isKey_KeyType() {} func (m *Key) GetKeyType() isKey_KeyType { if m != nil { @@ -98,12 +104,20 @@ func (m *Key) GetGcpKmsKey() *GcpKmsKey { return nil } +func (m *Key) GetAzureKeyvaultKey() *AzureKeyVaultKey { + if x, ok := m.GetKeyType().(*Key_AzureKeyvaultKey); ok { + return x.AzureKeyvaultKey + } + return nil +} + // XXX_OneofFuncs is for the internal use of the proto package. func (*Key) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { return _Key_OneofMarshaler, _Key_OneofUnmarshaler, _Key_OneofSizer, []interface{}{ (*Key_KmsKey)(nil), (*Key_PgpKey)(nil), (*Key_GcpKmsKey)(nil), + (*Key_AzureKeyvaultKey)(nil), } } @@ -126,6 +140,11 @@ func _Key_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { if err := b.EncodeMessage(x.GcpKmsKey); err != nil { return err } + case *Key_AzureKeyvaultKey: + b.EncodeVarint(4<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.AzureKeyvaultKey); err != nil { + return err + } case nil: default: return fmt.Errorf("Key.KeyType has unexpected type %T", x) @@ -160,6 +179,14 @@ func _Key_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (b err := b.DecodeMessage(msg) m.KeyType = &Key_GcpKmsKey{msg} return true, err + case 4: // key_type.azure_keyvault_key + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(AzureKeyVaultKey) + err := b.DecodeMessage(msg) + m.KeyType = &Key_AzureKeyvaultKey{msg} + return true, err default: return false, nil } @@ -184,6 +211,11 @@ func _Key_OneofSizer(msg proto.Message) (n int) { n += proto.SizeVarint(3<<3 | proto.WireBytes) n += proto.SizeVarint(uint64(s)) n += s + case *Key_AzureKeyvaultKey: + s := proto.Size(x.AzureKeyvaultKey) + n += proto.SizeVarint(4<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s case nil: default: panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) @@ -255,6 +287,38 @@ func (m *GcpKmsKey) GetResourceId() string { return "" } +type AzureKeyVaultKey struct { + VaultUrl string `protobuf:"bytes,1,opt,name=vault_url,json=vaultUrl" json:"vault_url,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version" json:"version,omitempty"` +} + +func (m *AzureKeyVaultKey) Reset() { *m = AzureKeyVaultKey{} } +func (m *AzureKeyVaultKey) String() string { return proto.CompactTextString(m) } +func (*AzureKeyVaultKey) ProtoMessage() {} +func (*AzureKeyVaultKey) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +func (m *AzureKeyVaultKey) GetVaultUrl() string { + if m != nil { + return m.VaultUrl + } + return "" +} + +func (m *AzureKeyVaultKey) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *AzureKeyVaultKey) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + type EncryptRequest struct { Key *Key `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` Plaintext []byte `protobuf:"bytes,2,opt,name=plaintext,proto3" json:"plaintext,omitempty"` @@ -263,7 +327,7 @@ type EncryptRequest struct { func (m *EncryptRequest) Reset() { *m = EncryptRequest{} } func (m *EncryptRequest) String() string { return proto.CompactTextString(m) } func (*EncryptRequest) ProtoMessage() {} -func (*EncryptRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } +func (*EncryptRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } func (m *EncryptRequest) GetKey() *Key { if m != nil { @@ -286,7 +350,7 @@ type EncryptResponse struct { func (m *EncryptResponse) Reset() { *m = EncryptResponse{} } func (m *EncryptResponse) String() string { return proto.CompactTextString(m) } func (*EncryptResponse) ProtoMessage() {} -func (*EncryptResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } +func (*EncryptResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } func (m *EncryptResponse) GetCiphertext() []byte { if m != nil { @@ -303,7 +367,7 @@ type DecryptRequest struct { func (m *DecryptRequest) Reset() { *m = DecryptRequest{} } func (m *DecryptRequest) String() string { return proto.CompactTextString(m) } func (*DecryptRequest) ProtoMessage() {} -func (*DecryptRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } +func (*DecryptRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } func (m *DecryptRequest) GetKey() *Key { if m != nil { @@ -326,7 +390,7 @@ type DecryptResponse struct { func (m *DecryptResponse) Reset() { *m = DecryptResponse{} } func (m *DecryptResponse) String() string { return proto.CompactTextString(m) } func (*DecryptResponse) ProtoMessage() {} -func (*DecryptResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } +func (*DecryptResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } func (m *DecryptResponse) GetPlaintext() []byte { if m != nil { @@ -340,6 +404,7 @@ func init() { proto.RegisterType((*PgpKey)(nil), "PgpKey") proto.RegisterType((*KmsKey)(nil), "KmsKey") proto.RegisterType((*GcpKmsKey)(nil), "GcpKmsKey") + proto.RegisterType((*AzureKeyVaultKey)(nil), "AzureKeyVaultKey") proto.RegisterType((*EncryptRequest)(nil), "EncryptRequest") proto.RegisterType((*EncryptResponse)(nil), "EncryptResponse") proto.RegisterType((*DecryptRequest)(nil), "DecryptRequest") @@ -448,37 +513,42 @@ var _KeyService_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "keyservice.proto", + Metadata: "keyservice/keyservice.proto", } -func init() { proto.RegisterFile("keyservice.proto", fileDescriptor0) } +func init() { proto.RegisterFile("keyservice/keyservice.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 405 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0xcd, 0xae, 0x93, 0x40, - 0x14, 0x2e, 0xe5, 0x4a, 0xe5, 0x70, 0x73, 0x21, 0x93, 0x1b, 0x73, 0xd3, 0x18, 0x6d, 0x66, 0xd5, - 0x98, 0x66, 0x8c, 0xb8, 0x31, 0x5d, 0xaa, 0xd5, 0x1a, 0x36, 0x06, 0x1f, 0x80, 0x54, 0x7a, 0x44, - 0x02, 0x85, 0x71, 0x98, 0x36, 0xce, 0x13, 0xf8, 0x0e, 0x3e, 0xad, 0x61, 0x18, 0x68, 0xa9, 0x9b, - 0xbb, 0x3b, 0xf3, 0xf1, 0xcd, 0xf7, 0x33, 0x1c, 0x08, 0x0a, 0x54, 0x0d, 0x8a, 0x53, 0x9e, 0x22, - 0xe3, 0xa2, 0x96, 0x35, 0xfd, 0x63, 0x81, 0x1d, 0xa1, 0x22, 0x14, 0x66, 0xc5, 0xa1, 0x49, 0x0a, - 0x54, 0x0f, 0xd6, 0xc2, 0x5a, 0x7a, 0xe1, 0x8c, 0x45, 0x87, 0x26, 0x42, 0xb5, 0x9d, 0xc4, 0x4e, - 0xa1, 0xa7, 0x96, 0xc3, 0x33, 0xae, 0x39, 0x53, 0xc3, 0xf9, 0x9a, 0x71, 0xc3, 0xe1, 0x7a, 0x22, - 0x2b, 0xf0, 0xb2, 0x94, 0x27, 0xbd, 0x96, 0xad, 0x79, 0xc0, 0x3e, 0xa7, 0x7c, 0x90, 0x73, 0xb3, - 0xfe, 0xf0, 0x1e, 0xe0, 0x69, 0x81, 0x2a, 0x91, 0x8a, 0x23, 0x7d, 0x05, 0x4e, 0xa7, 0x46, 0x16, - 0xe0, 0xfd, 0xc8, 0xab, 0x0c, 0x05, 0x17, 0x79, 0x25, 0x75, 0x1e, 0x37, 0xbe, 0x84, 0xe8, 0x5f, - 0x0b, 0x9c, 0x4e, 0x82, 0x04, 0x60, 0xef, 0x44, 0x65, 0x48, 0xed, 0x48, 0x08, 0xdc, 0x88, 0xba, - 0x44, 0x9d, 0xd1, 0x8d, 0xf5, 0x4c, 0x18, 0xcc, 0xd2, 0xba, 0x92, 0xf8, 0x5b, 0x3e, 0xd8, 0x0b, - 0x7b, 0xe9, 0x85, 0xf7, 0xa6, 0x1e, 0xfb, 0xd0, 0xc1, 0x9b, 0x4a, 0x0a, 0x15, 0xf7, 0xa4, 0xf9, - 0x1a, 0x6e, 0x2f, 0x3f, 0xb4, 0x2e, 0xfd, 0xd3, 0xb8, 0x71, 0x3b, 0x92, 0x7b, 0x78, 0x72, 0xda, - 0x95, 0xc7, 0xde, 0xa6, 0x3b, 0xac, 0xa7, 0xef, 0x2c, 0xba, 0x02, 0x77, 0xa8, 0x4b, 0x5e, 0x82, - 0x27, 0xb0, 0xa9, 0x8f, 0x22, 0xc5, 0x24, 0xdf, 0x1b, 0x01, 0xe8, 0xa1, 0x2f, 0x7b, 0xfa, 0x09, - 0xee, 0x36, 0x55, 0x2a, 0x14, 0x97, 0x31, 0xfe, 0x3a, 0x62, 0x23, 0xc9, 0xb3, 0xb3, 0x97, 0x17, - 0xde, 0xb0, 0x08, 0x55, 0xe7, 0xf8, 0x1c, 0x5c, 0x5e, 0xee, 0xf2, 0xae, 0x45, 0xeb, 0x7a, 0x1b, - 0x9f, 0x01, 0xfa, 0x06, 0xfc, 0x41, 0xa7, 0xe1, 0x75, 0xd5, 0x20, 0x79, 0x01, 0x90, 0xe6, 0xfc, - 0x27, 0x0a, 0x7d, 0xc3, 0xd2, 0x37, 0x2e, 0x10, 0xba, 0x85, 0xbb, 0x8f, 0xf8, 0x28, 0xeb, 0xb1, - 0xd2, 0xf4, 0x3f, 0xa5, 0xd7, 0xe0, 0x0f, 0x4a, 0xc6, 0x7c, 0x94, 0xd6, 0xba, 0x4a, 0x1b, 0x96, - 0x00, 0x11, 0xaa, 0x6f, 0xdd, 0x2a, 0xb6, 0x7f, 0xc7, 0x64, 0x27, 0x3e, 0x1b, 0xbf, 0xc6, 0x3c, - 0x60, 0x57, 0xb5, 0xe8, 0xa4, 0xe5, 0x1b, 0x3b, 0xe2, 0xb3, 0x71, 0x85, 0x79, 0xc0, 0xae, 0x92, - 0xd0, 0xc9, 0x77, 0x47, 0xef, 0xfa, 0xdb, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xa7, 0xb0, 0x93, - 0xf5, 0xff, 0x02, 0x00, 0x00, + // 485 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4d, 0x6f, 0xd4, 0x30, + 0x10, 0x6d, 0x36, 0xcb, 0x6e, 0x33, 0xa9, 0xba, 0xc1, 0xaa, 0xd0, 0x6a, 0x8b, 0x60, 0xe5, 0x53, + 0x85, 0x2a, 0x57, 0x2c, 0x17, 0xd4, 0x5b, 0x81, 0x42, 0x51, 0x2e, 0x28, 0x08, 0x6e, 0x68, 0x15, + 0xd2, 0x21, 0x44, 0xc9, 0x26, 0xc6, 0x71, 0x22, 0xcc, 0x4f, 0xe1, 0x27, 0xf1, 0xab, 0x90, 0x1d, + 0x27, 0xfb, 0xc1, 0x85, 0xdb, 0xf8, 0xf9, 0xcd, 0x9b, 0x79, 0xcf, 0x32, 0x9c, 0xe7, 0xa8, 0x6a, + 0x14, 0x6d, 0x96, 0xe0, 0xd5, 0xb6, 0x64, 0x5c, 0x54, 0xb2, 0xa2, 0x7f, 0x1c, 0x70, 0x43, 0x54, + 0x84, 0xc2, 0x34, 0xdf, 0xd4, 0xeb, 0x1c, 0xd5, 0xdc, 0x59, 0x3a, 0x17, 0xfe, 0x6a, 0xca, 0xc2, + 0x4d, 0x1d, 0xa2, 0xba, 0x3b, 0x8a, 0x26, 0xb9, 0xa9, 0x34, 0x87, 0xa7, 0xdc, 0x70, 0x46, 0x96, + 0xf3, 0x21, 0xe5, 0x96, 0xc3, 0x4d, 0x45, 0x2e, 0xc1, 0x4f, 0x13, 0xbe, 0xee, 0xb5, 0x5c, 0xc3, + 0x03, 0xf6, 0x2e, 0xe1, 0x83, 0x9c, 0x97, 0xf6, 0x07, 0x72, 0x03, 0x24, 0xfe, 0xd5, 0x08, 0xd4, + 0xdc, 0x36, 0x6e, 0x0a, 0x69, 0x9a, 0xc6, 0xa6, 0xe9, 0x21, 0xbb, 0xd1, 0x57, 0x21, 0xaa, 0xcf, + 0xfa, 0xa6, 0xeb, 0x0d, 0x62, 0x8b, 0xb5, 0x16, 0x7b, 0x05, 0x70, 0x9c, 0xa3, 0x5a, 0x4b, 0xc5, + 0x91, 0x3e, 0x83, 0x49, 0xb7, 0x10, 0x59, 0x82, 0xff, 0x2d, 0x2b, 0x53, 0x14, 0x5c, 0x64, 0xa5, + 0x34, 0x96, 0xbc, 0x68, 0x17, 0xa2, 0xbf, 0x1d, 0x98, 0xd8, 0x2d, 0x02, 0x70, 0x63, 0x51, 0x5a, + 0x92, 0x2e, 0x09, 0x81, 0xb1, 0xa8, 0x0a, 0x34, 0x36, 0xbd, 0xc8, 0xd4, 0x84, 0xc1, 0x34, 0xa9, + 0x4a, 0x89, 0x3f, 0xe5, 0xdc, 0x5d, 0xba, 0x17, 0xfe, 0xea, 0xcc, 0x26, 0xc4, 0x5e, 0x77, 0xf0, + 0x6d, 0x29, 0x85, 0x8a, 0x7a, 0xd2, 0xe2, 0x1a, 0x4e, 0x76, 0x2f, 0xf4, 0x94, 0x3e, 0x5d, 0x2f, + 0xd2, 0x25, 0x39, 0x83, 0x07, 0x6d, 0x5c, 0x34, 0xfd, 0x98, 0xee, 0x70, 0x3d, 0x7a, 0xe9, 0xd0, + 0x4b, 0xf0, 0x86, 0xc4, 0xc8, 0x53, 0xf0, 0x05, 0xd6, 0x55, 0x23, 0x12, 0x5c, 0x67, 0xf7, 0x56, + 0x00, 0x7a, 0xe8, 0xfd, 0x3d, 0xfd, 0x02, 0xc1, 0x61, 0x54, 0xe4, 0x1c, 0xbc, 0x2e, 0xd0, 0x46, + 0x14, 0xb6, 0xe5, 0xd8, 0x00, 0x9f, 0x44, 0xa1, 0xed, 0x95, 0xf1, 0x66, 0xb0, 0xa7, 0x6b, 0x32, + 0x87, 0x69, 0x8b, 0xa2, 0xce, 0xaa, 0xd2, 0x3c, 0x9a, 0x17, 0xf5, 0x47, 0xfa, 0x16, 0x4e, 0x6f, + 0xcb, 0x44, 0x28, 0x2e, 0x23, 0xfc, 0xd1, 0x60, 0x2d, 0xc9, 0xa3, 0xad, 0x15, 0x7f, 0x35, 0x66, + 0x21, 0xaa, 0xce, 0xd0, 0x63, 0xf0, 0x78, 0x11, 0x67, 0x5d, 0x48, 0x5a, 0xfc, 0x24, 0xda, 0x02, + 0xf4, 0x39, 0xcc, 0x06, 0x9d, 0x9a, 0x57, 0x65, 0x8d, 0xe4, 0x09, 0x40, 0x92, 0xf1, 0xef, 0x28, + 0x4c, 0x87, 0x63, 0x3a, 0x76, 0x10, 0x7a, 0x07, 0xa7, 0x6f, 0xf0, 0xbf, 0x46, 0xef, 0x2b, 0x8d, + 0xfe, 0x51, 0xba, 0x82, 0xd9, 0xa0, 0x64, 0x87, 0xef, 0x6d, 0xeb, 0x1c, 0x6c, 0xbb, 0x2a, 0x00, + 0x42, 0x54, 0x1f, 0xbb, 0xcf, 0xa2, 0x1f, 0xdf, 0xee, 0x4e, 0x66, 0x6c, 0x3f, 0x8d, 0x45, 0xc0, + 0x0e, 0x6c, 0xd1, 0x23, 0xcd, 0xb7, 0xe3, 0xc8, 0x8c, 0xed, 0x5b, 0x58, 0x04, 0xec, 0x60, 0x13, + 0x7a, 0xf4, 0x75, 0x62, 0x7e, 0xe3, 0x8b, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x29, 0x21, + 0x4e, 0xac, 0x03, 0x00, 0x00, } diff --git a/keyservice/keyservice.proto b/keyservice/keyservice.proto index ddc87121f..58c72bf6d 100644 --- a/keyservice/keyservice.proto +++ b/keyservice/keyservice.proto @@ -5,6 +5,7 @@ message Key { KmsKey kms_key = 1; PgpKey pgp_key = 2; GcpKmsKey gcp_kms_key = 3; + AzureKeyVaultKey azure_keyvault_key = 4; } } @@ -22,6 +23,12 @@ message GcpKmsKey { string resource_id = 1; } +message AzureKeyVaultKey { + string vault_url = 1; + string name = 2; + string version = 3; +} + message EncryptRequest { Key key = 1; bytes plaintext = 2; diff --git a/keyservice/server.go b/keyservice/server.go index 40c342667..e4e42b111 100644 --- a/keyservice/server.go +++ b/keyservice/server.go @@ -3,6 +3,7 @@ package keyservice import ( "fmt" + "go.mozilla.org/sops/azkv" "go.mozilla.org/sops/gcpkms" "go.mozilla.org/sops/kms" "go.mozilla.org/sops/pgp" @@ -55,6 +56,19 @@ func (ks *Server) encryptWithGcpKms(key *GcpKmsKey, plaintext []byte) ([]byte, e return []byte(gcpKmsKey.EncryptedKey), nil } +func (ks *Server) encryptWithAzureKeyVault(key *AzureKeyVaultKey, plaintext []byte) ([]byte, error) { + azkvKey := azkv.MasterKey{ + VaultURL: key.VaultUrl, + Name: key.Name, + Version: key.Version, + } + err := azkvKey.Encrypt(plaintext) + if err != nil { + return nil, err + } + return []byte(azkvKey.EncryptedKey), nil +} + func (ks *Server) decryptWithPgp(key *PgpKey, ciphertext []byte) ([]byte, error) { pgpKey := pgp.NewMasterKeyFromFingerprint(key.Fingerprint) pgpKey.EncryptedKey = string(ciphertext) @@ -86,6 +100,17 @@ func (ks *Server) decryptWithGcpKms(key *GcpKmsKey, ciphertext []byte) ([]byte, return []byte(plaintext), err } +func (ks *Server) decryptWithAzureKeyVault(key *AzureKeyVaultKey, ciphertext []byte) ([]byte, error) { + azkvKey := azkv.MasterKey{ + VaultURL: key.VaultUrl, + Name: key.Name, + Version: key.Version, + } + azkvKey.EncryptedKey = string(ciphertext) + plaintext, err := azkvKey.Decrypt() + return []byte(plaintext), err +} + // Encrypt takes an encrypt request and encrypts the provided plaintext with the provided key, returning the encrypted // result func (ks Server) Encrypt(ctx context.Context, @@ -117,6 +142,14 @@ func (ks Server) Encrypt(ctx context.Context, response = &EncryptResponse{ Ciphertext: ciphertext, } + case *Key_AzureKeyvaultKey: + ciphertext, err := ks.encryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Plaintext) + if err != nil { + return nil, err + } + response = &EncryptResponse{ + Ciphertext: ciphertext, + } case nil: return nil, status.Errorf(codes.NotFound, "Must provide a key") default: @@ -139,6 +172,8 @@ func keyToString(key Key) string { return fmt.Sprintf("AWS KMS key with ARN %s", k.KmsKey.Arn) case *Key_GcpKmsKey: return fmt.Sprintf("GCP KMS key with resource ID %s", k.GcpKmsKey.ResourceId) + case *Key_AzureKeyvaultKey: + return fmt.Sprintf("Azure Key Vault key with URL %s/keys/%s/%s", k.AzureKeyvaultKey.VaultUrl, k.AzureKeyvaultKey.Name, k.AzureKeyvaultKey.Version) default: return fmt.Sprintf("Unknown key type") } @@ -191,6 +226,14 @@ func (ks Server) Decrypt(ctx context.Context, response = &DecryptResponse{ Plaintext: plaintext, } + case *Key_AzureKeyvaultKey: + plaintext, err := ks.decryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Ciphertext) + if err != nil { + return nil, err + } + response = &DecryptResponse{ + Plaintext: plaintext, + } case nil: return nil, grpc.Errorf(codes.NotFound, "Must provide a key") default: diff --git a/stores/stores.go b/stores/stores.go index 0f0c57571..19d860a4b 100644 --- a/stores/stores.go +++ b/stores/stores.go @@ -15,6 +15,7 @@ import ( "fmt" "go.mozilla.org/sops" + "go.mozilla.org/sops/azkv" "go.mozilla.org/sops/gcpkms" "go.mozilla.org/sops/kms" "go.mozilla.org/sops/pgp" @@ -38,6 +39,7 @@ type Metadata struct { KeyGroups []keygroup `yaml:"key_groups,omitempty" json:"key_groups,omitempty"` KMSKeys []kmskey `yaml:"kms" json:"kms"` GCPKMSKeys []gcpkmskey `yaml:"gcp_kms" json:"gcp_kms"` + AzureKeyVaultKeys []azkvkey `yaml:"azure_kv" json:"azure_kv"` LastModified string `yaml:"lastmodified" json:"lastmodified"` MessageAuthenticationCode string `yaml:"mac" json:"mac"` PGPKeys []pgpkey `yaml:"pgp" json:"pgp"` @@ -47,9 +49,10 @@ type Metadata struct { } type keygroup struct { - PGPKeys []pgpkey `yaml:"pgp,omitempty" json:"pgp,omitempty"` - KMSKeys []kmskey `yaml:"kms,omitempty" json:"kms,omitempty"` - GCPKMSKeys []gcpkmskey `yaml:"gcp_kms,omitempty" json:"gcp_kms,omitempty"` + PGPKeys []pgpkey `yaml:"pgp,omitempty" json:"pgp,omitempty"` + KMSKeys []kmskey `yaml:"kms,omitempty" json:"kms,omitempty"` + GCPKMSKeys []gcpkmskey `yaml:"gcp_kms,omitempty" json:"gcp_kms,omitempty"` + AzureKeyVaultKeys []azkvkey `yaml:"azure_kv,omitempty" json:"azure_kv,omitempty"` } type pgpkey struct { @@ -72,6 +75,14 @@ type gcpkmskey struct { EncryptedDataKey string `yaml:"enc" json:"enc"` } +type azkvkey struct { + VaultURL string `yaml:"vault_url" json:"vault_url"` + Name string `yaml:"name" json:"name"` + Version string `yaml:"version" json:"version"` + CreatedAt string `yaml:"created_at" json:"created_at"` + EncryptedDataKey string `yaml:"enc" json:"enc"` +} + // MetadataFromInternal converts an internal SOPS metadata representation to a representation appropriate for storage func MetadataFromInternal(sopsMetadata sops.Metadata) Metadata { var m Metadata @@ -86,12 +97,14 @@ func MetadataFromInternal(sopsMetadata sops.Metadata) Metadata { m.PGPKeys = pgpKeysFromGroup(group) m.KMSKeys = kmsKeysFromGroup(group) m.GCPKMSKeys = gcpkmsKeysFromGroup(group) + m.AzureKeyVaultKeys = azkvKeysFromGroup(group) } else { for _, group := range sopsMetadata.KeyGroups { m.KeyGroups = append(m.KeyGroups, keygroup{ - KMSKeys: kmsKeysFromGroup(group), - PGPKeys: pgpKeysFromGroup(group), - GCPKMSKeys: gcpkmsKeysFromGroup(group), + KMSKeys: kmsKeysFromGroup(group), + PGPKeys: pgpKeysFromGroup(group), + GCPKMSKeys: gcpkmsKeysFromGroup(group), + AzureKeyVaultKeys: azkvKeysFromGroup(group), }) } } @@ -142,6 +155,22 @@ func gcpkmsKeysFromGroup(group sops.KeyGroup) (keys []gcpkmskey) { return } +func azkvKeysFromGroup(group sops.KeyGroup) (keys []azkvkey) { + for _, key := range group { + switch key := key.(type) { + case *azkv.MasterKey: + keys = append(keys, azkvkey{ + VaultURL: key.VaultURL, + Name: key.Name, + Version: key.Version, + CreatedAt: key.CreationDate.Format(time.RFC3339), + EncryptedDataKey: key.EncryptedKey, + }) + } + } + return +} + // ToInternal converts a storage-appropriate Metadata struct to a SOPS internal representation func (m *Metadata) ToInternal() (sops.Metadata, error) { lastModified, err := time.Parse(time.RFC3339, m.LastModified) @@ -169,7 +198,7 @@ func (m *Metadata) ToInternal() (sops.Metadata, error) { }, nil } -func internalGroupFrom(kmsKeys []kmskey, pgpKeys []pgpkey, gcpKmsKeys []gcpkmskey) (sops.KeyGroup, error) { +func internalGroupFrom(kmsKeys []kmskey, pgpKeys []pgpkey, gcpKmsKeys []gcpkmskey, azkvKeys []azkvkey) (sops.KeyGroup, error) { var internalGroup sops.KeyGroup for _, kmsKey := range kmsKeys { k, err := kmsKey.toInternal() @@ -185,6 +214,13 @@ func internalGroupFrom(kmsKeys []kmskey, pgpKeys []pgpkey, gcpKmsKeys []gcpkmske } internalGroup = append(internalGroup, k) } + for _, azkvKey := range azkvKeys { + k, err := azkvKey.toInternal() + if err != nil { + return nil, err + } + internalGroup = append(internalGroup, k) + } for _, pgpKey := range pgpKeys { k, err := pgpKey.toInternal() if err != nil { @@ -197,8 +233,8 @@ func internalGroupFrom(kmsKeys []kmskey, pgpKeys []pgpkey, gcpKmsKeys []gcpkmske func (m *Metadata) internalKeygroups() ([]sops.KeyGroup, error) { var internalGroups []sops.KeyGroup - if len(m.PGPKeys) > 0 || len(m.KMSKeys) > 0 || len(m.GCPKMSKeys) > 0 { - internalGroup, err := internalGroupFrom(m.KMSKeys, m.PGPKeys, m.GCPKMSKeys) + if len(m.PGPKeys) > 0 || len(m.KMSKeys) > 0 || len(m.GCPKMSKeys) > 0 || len(m.AzureKeyVaultKeys) > 0 { + internalGroup, err := internalGroupFrom(m.KMSKeys, m.PGPKeys, m.GCPKMSKeys, m.AzureKeyVaultKeys) if err != nil { return nil, err } @@ -206,7 +242,7 @@ func (m *Metadata) internalKeygroups() ([]sops.KeyGroup, error) { return internalGroups, nil } else if len(m.KeyGroups) > 0 { for _, group := range m.KeyGroups { - internalGroup, err := internalGroupFrom(group.KMSKeys, group.PGPKeys, group.GCPKMSKeys) + internalGroup, err := internalGroupFrom(group.KMSKeys, group.PGPKeys, group.GCPKMSKeys, group.AzureKeyVaultKeys) if err != nil { return nil, err } @@ -244,6 +280,20 @@ func (gcpKmsKey *gcpkmskey) toInternal() (*gcpkms.MasterKey, error) { }, nil } +func (azkvKey *azkvkey) toInternal() (*azkv.MasterKey, error) { + creationDate, err := time.Parse(time.RFC3339, azkvKey.CreatedAt) + if err != nil { + return nil, err + } + return &azkv.MasterKey{ + VaultURL: azkvKey.VaultURL, + Name: azkvKey.Name, + Version: azkvKey.Version, + EncryptedKey: azkvKey.EncryptedDataKey, + CreationDate: creationDate, + }, nil +} + func (pgpKey *pgpkey) toInternal() (*pgp.MasterKey, error) { creationDate, err := time.Parse(time.RFC3339, pgpKey.CreatedAt) if err != nil {