diff --git a/README.rst b/README.rst index a3bfe1610..085df08ff 100644 --- a/README.rst +++ b/README.rst @@ -515,6 +515,50 @@ To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!) $ sops encrypt --verbose prod/raw.yaml > prod/encrypted.yaml +Encrypting using OVH Key Management Service +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use OVH KMS with SOPS, you need to: + +1. Have an OVH account with KMS service enabled +2. Create a key in OVH KMS that will be used for encryption/decryption +3. Obtain an access certificate and private key for authentication + +You can use OVH KMS in your `.sops.yaml` file like this: + +.. code:: yaml + + creation_rules: + - path_regex: path/to/files/*.yaml + ovh_kms: / + +Or with key groups: + +.. code:: yaml + + creation_rules: + - path_regex: path/to/files/*.yaml + key_groups: + - ovh_kms: + - key_id: / + +Usage + +After configuration, you can use SOPS normally and it will automatically use OVH KMS for encryption/decryption: + +.. code:: sh + + # Set required environment variables + export OVH_CERTIFICATE_PATH=/path/to/certificate.pem + export OVH_CERTIFICATE_KEY_PATH=/path/to/private-key.pem + + # Encrypt a file + sops -e -i secrets.yaml + + # Decrypt a file + sops -d secrets.yaml + + Adding and removing keys ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/cmd/sops/main.go b/cmd/sops/main.go index e3be225a6..ae5438223 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -39,6 +39,7 @@ import ( "github.com/getsops/sops/v3/keyservice" "github.com/getsops/sops/v3/kms" "github.com/getsops/sops/v3/logging" + "github.com/getsops/sops/v3/ovhkms" "github.com/getsops/sops/v3/pgp" "github.com/getsops/sops/v3/stores/dotenv" "github.com/getsops/sops/v3/stores/json" @@ -584,6 +585,7 @@ func main() { vaultURIs := c.StringSlice("hc-vault-transit") azkvs := c.StringSlice("azure-kv") ageRecipients := c.StringSlice("age") + ovhKmses := c.StringSlice("ovh-kms") if c.NArg() != 0 { return common.NewExitError(fmt.Errorf("error: no positional arguments allowed"), codes.ErrorGeneric) } @@ -597,6 +599,14 @@ func main() { for _, kms := range gcpKmses { group = append(group, gcpkms.NewMasterKeyFromResourceID(kms)) } + for _, kms := range ovhKmses { + k, err := ovhkms.NewMasterKeyFromKeyID(kms) + if err != nil { + log.WithError(err).Error("Failed to add key") + continue + } + group = append(group, k) + } for _, uri := range vaultURIs { k, err := hcvault.NewMasterKeyFromURI(uri) if err != nil { @@ -915,6 +925,11 @@ func main() { Usage: "comma separated list of GCP KMS resource IDs", EnvVar: "SOPS_GCP_KMS_IDS", }, + cli.StringFlag{ + Name: "ovh-kms", + Usage: "comma separated list of OVH KMS key IDs", + EnvVar: "SOPS_OVH_KMS_IDS", + }, cli.StringFlag{ Name: "azure-kv", Usage: "comma separated list of Azure Key Vault URLs", @@ -1166,8 +1181,8 @@ func main() { return toExitError(err) } if _, err := os.Stat(fileName); os.IsNotExist(err) { - if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" || - c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" { + if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" || c.String("add-ovh-kms") != "" || + c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" || c.String("rm-ovh-kms") != "" { return common.NewExitError(fmt.Sprintf("Error: cannot add or remove keys on non-existent file %q, use the `edit` subcommand instead.", fileName), codes.CannotChangeKeysFromNonExistentFile) } } @@ -1661,6 +1676,11 @@ func main() { Usage: "comma separated list of GCP KMS resource IDs", EnvVar: "SOPS_GCP_KMS_IDS", }, + cli.StringFlag{ + Name: "ovh-kms", + Usage: "comma separated list of OVH KMS Key IDs with endpoint (egs: eu-west-sbg.okms.ovh.net/12345678-1234-1234-1234-123456789012)", + EnvVar: "SOPS_OVH_KMS_IDS", + }, cli.StringFlag{ Name: "azure-kv", Usage: "comma separated list of Azure Key Vault URLs", @@ -1843,8 +1863,8 @@ func main() { return toExitError(err) } if _, err := os.Stat(fileName); os.IsNotExist(err) { - if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" || - c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" { + if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" || c.String("add-ovh-kms") != "" || + c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" || c.String("rm-ovh-kms") != "" { return common.NewExitError(fmt.Sprintf("Error: cannot add or remove keys on non-existent file %q, use `--kms` and `--pgp` instead.", fileName), codes.CannotChangeKeysFromNonExistentFile) } if isEncryptMode || isDecryptMode || isRotateMode { @@ -2137,7 +2157,7 @@ func getEncryptConfig(c *cli.Context, fileName string) (encryptConfig, error) { }, nil } -func getMasterKeys(c *cli.Context, kmsEncryptionContext map[string]*string, kmsOptionName string, pgpOptionName string, gcpKmsOptionName string, azureKvOptionName string, hcVaultTransitOptionName string, ageOptionName string) ([]keys.MasterKey, error) { +func getMasterKeys(c *cli.Context, kmsEncryptionContext map[string]*string, kmsOptionName string, pgpOptionName string, gcpKmsOptionName string, azureKvOptionName string, hcVaultTransitOptionName string, ageOptionName string, ovhKmsOptionName string) ([]keys.MasterKey, error) { var masterKeys []keys.MasterKey for _, k := range kms.MasterKeysFromArnString(c.String(kmsOptionName), kmsEncryptionContext, c.String("aws-profile")) { masterKeys = append(masterKeys, k) @@ -2169,16 +2189,23 @@ func getMasterKeys(c *cli.Context, kmsEncryptionContext map[string]*string, kmsO for _, k := range ageKeys { masterKeys = append(masterKeys, k) } + ovhKeys, err := ovhkms.MasterKeysFromResourceIDString(c.String(ovhKmsOptionName)) + if err != nil { + return nil, err + } + for _, k := range ovhKeys { + masterKeys = append(masterKeys, k) + } return masterKeys, nil } func getRotateOpts(c *cli.Context, fileName string, inputStore common.Store, outputStore common.Store, svcs []keyservice.KeyServiceClient, decryptionOrder []string) (rotateOpts, error) { kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context")) - addMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "add-kms", "add-pgp", "add-gcp-kms", "add-azure-kv", "add-hc-vault-transit", "add-age") + addMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "add-kms", "add-pgp", "add-gcp-kms", "add-azure-kv", "add-hc-vault-transit", "add-age", "add-ovh-kms") if err != nil { return rotateOpts{}, err } - rmMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "rm-kms", "rm-pgp", "rm-gcp-kms", "rm-azure-kv", "rm-hc-vault-transit", "rm-age") + rmMasterKeys, err := getMasterKeys(c, kmsEncryptionContext, "rm-kms", "rm-pgp", "rm-gcp-kms", "rm-azure-kv", "rm-hc-vault-transit", "rm-age", "rm-ovh-kms") if err != nil { return rotateOpts{}, err } @@ -2323,6 +2350,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) { var azkvKeys []keys.MasterKey var hcVaultMkKeys []keys.MasterKey var ageMasterKeys []keys.MasterKey + var ovhKmsKeys []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) @@ -2369,6 +2397,15 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) { ageMasterKeys = append(ageMasterKeys, k) } } + if c.String("ovh-kms") != "" { + ovhKeys, err := ovhkms.MasterKeysFromResourceIDString(c.String("ovh-kms")) + if err != nil { + return nil, err + } + for _, k := range ovhKeys { + pgpKeys = append(pgpKeys, k) + } + } if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" && c.String("azure-kv") == "" && c.String("hc-vault-transit") == "" && c.String("age") == "" { conf, err := loadConfig(c, file, kmsEncryptionContext) // config file might just not be supplied, without any error @@ -2388,6 +2425,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) { group = append(group, pgpKeys...) group = append(group, hcVaultMkKeys...) group = append(group, ageMasterKeys...) + group = append(group, ovhKmsKeys...) log.Debugf("Master keys available: %+v", group) return []sops.KeyGroup{group}, nil } diff --git a/config/config.go b/config/config.go index fa2e24062..06adcfe31 100644 --- a/config/config.go +++ b/config/config.go @@ -17,6 +17,7 @@ import ( "github.com/getsops/sops/v3/gcpkms" "github.com/getsops/sops/v3/hcvault" "github.com/getsops/sops/v3/kms" + "github.com/getsops/sops/v3/ovhkms" "github.com/getsops/sops/v3/pgp" "github.com/getsops/sops/v3/publish" "gopkg.in/yaml.v3" @@ -133,6 +134,7 @@ type keyGroup struct { KMS []kmsKey GCPKMS []gcpKmsKey `yaml:"gcp_kms"` AzureKV []azureKVKey `yaml:"azure_keyvault"` + OVHKMS []ovhKmsKey `yaml:"ovh_kms"` Vault []string `yaml:"hc_vault"` Age []string `yaml:"age"` PGP []string @@ -142,6 +144,10 @@ type gcpKmsKey struct { ResourceID string `yaml:"resource_id"` } +type ovhKmsKey struct { + KeyID string `yaml:"key_id"` +} + type kmsKey struct { Arn string `yaml:"arn"` Role string `yaml:"role,omitempty"` @@ -178,6 +184,7 @@ type creationRule struct { GCPKMS interface{} `yaml:"gcp_kms"` // string or []string AzureKeyVault interface{} `yaml:"azure_keyvault"` // string or []string VaultURI interface{} `yaml:"hc_vault_transit_uri"` // string or []string + OVHKMS string `yaml:"ovh_kms"` KeyGroups []keyGroup `yaml:"key_groups"` ShamirThreshold int `yaml:"shamir_threshold"` UnencryptedSuffix string `yaml:"unencrypted_suffix"` @@ -332,6 +339,13 @@ func extractMasterKeys(group keyGroup) (sops.KeyGroup, error) { for _, k := range group.AzureKV { keyGroup = append(keyGroup, azkv.NewMasterKey(k.VaultURL, k.Key, k.Version)) } + for _, k := range group.OVHKMS { + if masterKey, err := ovhkms.NewMasterKeyFromKeyID(k.KeyID); err == nil { + keyGroup = append(keyGroup, masterKey) + } else { + return nil, err + } + } for _, k := range group.Vault { if masterKey, err := hcvault.NewMasterKeyFromURI(k); err == nil { keyGroup = append(keyGroup, masterKey) @@ -420,6 +434,13 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[ for _, k := range vaultKeys { keyGroup = append(keyGroup, k) } + ovhKeys, err := ovhkms.MasterKeysFromResourceIDString(cRule.OVHKMS) + if err != nil { + return nil, err + } + for _, k := range ovhKeys { + keyGroup = append(keyGroup, k) + } groups = append(groups, keyGroup) } return groups, nil diff --git a/go.mod b/go.mod index 85d3b19ab..6924c56b6 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e github.com/google/go-cmp v0.7.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 + github.com/google/uuid v1.6.0 github.com/goware/prefixer v0.0.0-20160118172347-395022866408 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/vault/api v1.20.0 @@ -29,6 +30,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-wordwrap v1.0.1 github.com/ory/dockertest/v3 v3.12.0 + github.com/ovh/okms-sdk-go v0.4.3 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 @@ -66,6 +68,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect @@ -100,12 +103,11 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect @@ -118,6 +120,7 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/user v0.3.0 // indirect github.com/moby/term v0.5.2 // indirect + github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runc v1.2.6 // indirect diff --git a/go.sum b/go.sum index 2e117e90d..493ed3740 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,9 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/aws/aws-sdk-go-v2 v1.37.2 h1:xkW1iMYawzcmYFYEV0UCMxc8gSsjCGEhBXQkdQywVbo= github.com/aws/aws-sdk-go-v2 v1.37.2/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg= @@ -105,6 +108,7 @@ github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -189,8 +193,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= @@ -203,6 +207,7 @@ github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/vault/api v1.20.0 h1:KQMHElgudOsr+IbJgmbjHnCTxEpKs9LnozA1D3nozU4= github.com/hashicorp/vault/api v1.20.0/go.mod h1:GZ4pcjfzoOWpkJ3ijHNpEoAxKEsBJnVljyTe3jM2Sms= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -231,6 +236,8 @@ github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -239,6 +246,8 @@ github.com/opencontainers/runc v1.2.6 h1:P7Hqg40bsMvQGCS4S7DJYhUZOISMLJOB2iGX5CO github.com/opencontainers/runc v1.2.6/go.mod h1:dOQeFo29xZKBNeRBI0B19mJtfHv68YgCTh1X+YphA+4= github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= +github.com/ovh/okms-sdk-go v0.4.3 h1:bRndJ723FAOJvdPXAJRVkP6bf/UaCvinNHIYxQ7VrTg= +github.com/ovh/okms-sdk-go v0.4.3/go.mod h1:8iE9R7Wem6kNtcpJr0uRqN+o8A3XGhWnyEAG/V2OsEI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -258,9 +267,11 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/keyservice/keyservice.go b/keyservice/keyservice.go index 321af7942..ef541a564 100644 --- a/keyservice/keyservice.go +++ b/keyservice/keyservice.go @@ -6,6 +6,7 @@ package keyservice import ( "fmt" + "github.com/getsops/sops/v3/ovhkms" "github.com/getsops/sops/v3/age" "github.com/getsops/sops/v3/azkv" @@ -35,6 +36,15 @@ func KeyFromMasterKey(mk keys.MasterKey) Key { }, }, } + case *ovhkms.MasterKey: + return Key{ + KeyType: &Key_OvhKmsKey{ + OvhKmsKey: &OvhKmsKey{ + Endpoint: mk.Endpoint, + KeyId: mk.KeyID, + }, + }, + } case *hcvault.MasterKey: return Key{ KeyType: &Key_VaultKey{ diff --git a/keyservice/keyservice.pb.go b/keyservice/keyservice.pb.go index a810b2805..e75f76e74 100644 --- a/keyservice/keyservice.pb.go +++ b/keyservice/keyservice.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.2 -// protoc v5.28.3 +// protoc v5.29.3 // source: keyservice/keyservice.proto package keyservice @@ -33,6 +33,7 @@ type Key struct { // *Key_AzureKeyvaultKey // *Key_VaultKey // *Key_AgeKey + // *Key_OvhKmsKey KeyType isKey_KeyType `protobuf_oneof:"key_type"` } @@ -115,6 +116,13 @@ func (x *Key) GetAgeKey() *AgeKey { return nil } +func (x *Key) GetOvhKmsKey() *OvhKmsKey { + if x, ok := x.GetKeyType().(*Key_OvhKmsKey); ok { + return x.OvhKmsKey + } + return nil +} + type isKey_KeyType interface { isKey_KeyType() } @@ -143,6 +151,10 @@ type Key_AgeKey struct { AgeKey *AgeKey `protobuf:"bytes,6,opt,name=age_key,json=ageKey,proto3,oneof"` } +type Key_OvhKmsKey struct { + OvhKmsKey *OvhKmsKey `protobuf:"bytes,7,opt,name=ovh_kms_key,json=ovhKmsKey,proto3,oneof"` +} + func (*Key_KmsKey) isKey_KeyType() {} func (*Key_PgpKey) isKey_KeyType() {} @@ -155,6 +167,8 @@ func (*Key_VaultKey) isKey_KeyType() {} func (*Key_AgeKey) isKey_KeyType() {} +func (*Key_OvhKmsKey) isKey_KeyType() {} + type PgpKey struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -481,6 +495,59 @@ func (x *AgeKey) GetRecipient() string { return "" } +type OvhKmsKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Endpoint string `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + KeyId string `protobuf:"bytes,2,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` +} + +func (x *OvhKmsKey) Reset() { + *x = OvhKmsKey{} + mi := &file_keyservice_keyservice_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OvhKmsKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OvhKmsKey) ProtoMessage() {} + +func (x *OvhKmsKey) ProtoReflect() protoreflect.Message { + mi := &file_keyservice_keyservice_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OvhKmsKey.ProtoReflect.Descriptor instead. +func (*OvhKmsKey) Descriptor() ([]byte, []int) { + return file_keyservice_keyservice_proto_rawDescGZIP(), []int{7} +} + +func (x *OvhKmsKey) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +func (x *OvhKmsKey) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + type EncryptRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -492,7 +559,7 @@ type EncryptRequest struct { func (x *EncryptRequest) Reset() { *x = EncryptRequest{} - mi := &file_keyservice_keyservice_proto_msgTypes[7] + mi := &file_keyservice_keyservice_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -504,7 +571,7 @@ func (x *EncryptRequest) String() string { func (*EncryptRequest) ProtoMessage() {} func (x *EncryptRequest) ProtoReflect() protoreflect.Message { - mi := &file_keyservice_keyservice_proto_msgTypes[7] + mi := &file_keyservice_keyservice_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -517,7 +584,7 @@ func (x *EncryptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EncryptRequest.ProtoReflect.Descriptor instead. func (*EncryptRequest) Descriptor() ([]byte, []int) { - return file_keyservice_keyservice_proto_rawDescGZIP(), []int{7} + return file_keyservice_keyservice_proto_rawDescGZIP(), []int{8} } func (x *EncryptRequest) GetKey() *Key { @@ -544,7 +611,7 @@ type EncryptResponse struct { func (x *EncryptResponse) Reset() { *x = EncryptResponse{} - mi := &file_keyservice_keyservice_proto_msgTypes[8] + mi := &file_keyservice_keyservice_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -556,7 +623,7 @@ func (x *EncryptResponse) String() string { func (*EncryptResponse) ProtoMessage() {} func (x *EncryptResponse) ProtoReflect() protoreflect.Message { - mi := &file_keyservice_keyservice_proto_msgTypes[8] + mi := &file_keyservice_keyservice_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -569,7 +636,7 @@ func (x *EncryptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EncryptResponse.ProtoReflect.Descriptor instead. func (*EncryptResponse) Descriptor() ([]byte, []int) { - return file_keyservice_keyservice_proto_rawDescGZIP(), []int{8} + return file_keyservice_keyservice_proto_rawDescGZIP(), []int{9} } func (x *EncryptResponse) GetCiphertext() []byte { @@ -590,7 +657,7 @@ type DecryptRequest struct { func (x *DecryptRequest) Reset() { *x = DecryptRequest{} - mi := &file_keyservice_keyservice_proto_msgTypes[9] + mi := &file_keyservice_keyservice_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -602,7 +669,7 @@ func (x *DecryptRequest) String() string { func (*DecryptRequest) ProtoMessage() {} func (x *DecryptRequest) ProtoReflect() protoreflect.Message { - mi := &file_keyservice_keyservice_proto_msgTypes[9] + mi := &file_keyservice_keyservice_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -615,7 +682,7 @@ func (x *DecryptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DecryptRequest.ProtoReflect.Descriptor instead. func (*DecryptRequest) Descriptor() ([]byte, []int) { - return file_keyservice_keyservice_proto_rawDescGZIP(), []int{9} + return file_keyservice_keyservice_proto_rawDescGZIP(), []int{10} } func (x *DecryptRequest) GetKey() *Key { @@ -642,7 +709,7 @@ type DecryptResponse struct { func (x *DecryptResponse) Reset() { *x = DecryptResponse{} - mi := &file_keyservice_keyservice_proto_msgTypes[10] + mi := &file_keyservice_keyservice_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -654,7 +721,7 @@ func (x *DecryptResponse) String() string { func (*DecryptResponse) ProtoMessage() {} func (x *DecryptResponse) ProtoReflect() protoreflect.Message { - mi := &file_keyservice_keyservice_proto_msgTypes[10] + mi := &file_keyservice_keyservice_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -667,7 +734,7 @@ func (x *DecryptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DecryptResponse.ProtoReflect.Descriptor instead. func (*DecryptResponse) Descriptor() ([]byte, []int) { - return file_keyservice_keyservice_proto_rawDescGZIP(), []int{10} + return file_keyservice_keyservice_proto_rawDescGZIP(), []int{11} } func (x *DecryptResponse) GetPlaintext() []byte { @@ -681,7 +748,7 @@ var File_keyservice_keyservice_proto protoreflect.FileDescriptor var file_keyservice_keyservice_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x6b, 0x65, 0x79, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x6b, 0x65, 0x79, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x02, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc6, 0x02, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x07, 0x6b, 0x6d, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x06, 0x6b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x07, 0x70, 0x67, 0x70, @@ -698,64 +765,71 @@ var file_keyservice_keyservice_proto_rawDesc = []byte{ 0x0b, 0x32, 0x09, 0x2e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x08, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x07, 0x61, 0x67, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x41, 0x67, 0x65, 0x4b, - 0x65, 0x79, 0x48, 0x00, 0x52, 0x06, 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, - 0x6b, 0x65, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2a, 0x0a, 0x06, 0x50, 0x67, 0x70, 0x4b, - 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, - 0x72, 0x69, 0x6e, 0x74, 0x22, 0xbb, 0x01, 0x0a, 0x06, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x61, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x72, - 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x2e, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x77, 0x73, 0x5f, 0x70, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x77, 0x73, 0x50, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x1a, 0x3a, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, - 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0x2c, 0x0a, 0x09, 0x47, 0x63, 0x70, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, - 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, - 0x22, 0x6b, 0x0a, 0x08, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, - 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x50, 0x61, - 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x5d, 0x0a, - 0x10, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, - 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x26, 0x0a, 0x06, - 0x41, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, - 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, - 0x69, 0x65, 0x6e, 0x74, 0x22, 0x46, 0x0a, 0x0e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1c, - 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x31, 0x0a, 0x0f, - 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x1e, 0x0a, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x22, - 0x48, 0x0a, 0x0e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x16, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, - 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x69, 0x70, - 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, - 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x22, 0x2f, 0x0a, 0x0f, 0x44, 0x65, 0x63, - 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, - 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x32, 0x6c, 0x0a, 0x0a, 0x4b, 0x65, - 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x45, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x12, 0x0f, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x07, 0x44, 0x65, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x12, 0x0f, 0x2e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6b, 0x65, - 0x79, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x79, 0x48, 0x00, 0x52, 0x06, 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x0b, + 0x6f, 0x76, 0x68, 0x5f, 0x6b, 0x6d, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0a, 0x2e, 0x4f, 0x76, 0x68, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, + 0x09, 0x6f, 0x76, 0x68, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x6b, 0x65, + 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2a, 0x0a, 0x06, 0x50, 0x67, 0x70, 0x4b, 0x65, 0x79, + 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x22, 0xbb, 0x01, 0x0a, 0x06, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x61, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x72, 0x6e, 0x12, + 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, + 0x6f, 0x6c, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x2e, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x77, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x77, 0x73, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x1a, 0x3a, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x2c, 0x0a, 0x09, 0x47, 0x63, 0x70, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, + 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x22, 0x6b, + 0x0a, 0x08, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x76, 0x61, + 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x1f, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x50, 0x61, 0x74, 0x68, + 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x5d, 0x0a, 0x10, 0x41, + 0x7a, 0x75, 0x72, 0x65, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x12, + 0x1b, 0x0a, 0x09, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x26, 0x0a, 0x06, 0x41, 0x67, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, + 0x6e, 0x74, 0x22, 0x3e, 0x0a, 0x09, 0x4f, 0x76, 0x68, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, + 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6b, + 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, + 0x49, 0x64, 0x22, 0x46, 0x0a, 0x0e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x04, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, + 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x31, 0x0a, 0x0f, 0x45, 0x6e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, + 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x22, 0x48, 0x0a, + 0x0e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x16, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x4b, + 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, + 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x69, 0x70, + 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x22, 0x2f, 0x0a, 0x0f, 0x44, 0x65, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x6c, + 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, + 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x32, 0x6c, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x12, 0x0f, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x07, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x12, 0x0f, 0x2e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6b, 0x65, 0x79, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -770,7 +844,7 @@ func file_keyservice_keyservice_proto_rawDescGZIP() []byte { return file_keyservice_keyservice_proto_rawDescData } -var file_keyservice_keyservice_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_keyservice_keyservice_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_keyservice_keyservice_proto_goTypes = []any{ (*Key)(nil), // 0: Key (*PgpKey)(nil), // 1: PgpKey @@ -779,11 +853,12 @@ var file_keyservice_keyservice_proto_goTypes = []any{ (*VaultKey)(nil), // 4: VaultKey (*AzureKeyVaultKey)(nil), // 5: AzureKeyVaultKey (*AgeKey)(nil), // 6: AgeKey - (*EncryptRequest)(nil), // 7: EncryptRequest - (*EncryptResponse)(nil), // 8: EncryptResponse - (*DecryptRequest)(nil), // 9: DecryptRequest - (*DecryptResponse)(nil), // 10: DecryptResponse - nil, // 11: KmsKey.ContextEntry + (*OvhKmsKey)(nil), // 7: OvhKmsKey + (*EncryptRequest)(nil), // 8: EncryptRequest + (*EncryptResponse)(nil), // 9: EncryptResponse + (*DecryptRequest)(nil), // 10: DecryptRequest + (*DecryptResponse)(nil), // 11: DecryptResponse + nil, // 12: KmsKey.ContextEntry } var file_keyservice_keyservice_proto_depIdxs = []int32{ 2, // 0: Key.kms_key:type_name -> KmsKey @@ -792,18 +867,19 @@ var file_keyservice_keyservice_proto_depIdxs = []int32{ 5, // 3: Key.azure_keyvault_key:type_name -> AzureKeyVaultKey 4, // 4: Key.vault_key:type_name -> VaultKey 6, // 5: Key.age_key:type_name -> AgeKey - 11, // 6: KmsKey.context:type_name -> KmsKey.ContextEntry - 0, // 7: EncryptRequest.key:type_name -> Key - 0, // 8: DecryptRequest.key:type_name -> Key - 7, // 9: KeyService.Encrypt:input_type -> EncryptRequest - 9, // 10: KeyService.Decrypt:input_type -> DecryptRequest - 8, // 11: KeyService.Encrypt:output_type -> EncryptResponse - 10, // 12: KeyService.Decrypt:output_type -> DecryptResponse - 11, // [11:13] is the sub-list for method output_type - 9, // [9:11] is the sub-list for method input_type - 9, // [9:9] is the sub-list for extension type_name - 9, // [9:9] is the sub-list for extension extendee - 0, // [0:9] is the sub-list for field type_name + 7, // 6: Key.ovh_kms_key:type_name -> OvhKmsKey + 12, // 7: KmsKey.context:type_name -> KmsKey.ContextEntry + 0, // 8: EncryptRequest.key:type_name -> Key + 0, // 9: DecryptRequest.key:type_name -> Key + 8, // 10: KeyService.Encrypt:input_type -> EncryptRequest + 10, // 11: KeyService.Decrypt:input_type -> DecryptRequest + 9, // 12: KeyService.Encrypt:output_type -> EncryptResponse + 11, // 13: KeyService.Decrypt:output_type -> DecryptResponse + 12, // [12:14] is the sub-list for method output_type + 10, // [10:12] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name } func init() { file_keyservice_keyservice_proto_init() } @@ -818,6 +894,7 @@ func file_keyservice_keyservice_proto_init() { (*Key_AzureKeyvaultKey)(nil), (*Key_VaultKey)(nil), (*Key_AgeKey)(nil), + (*Key_OvhKmsKey)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -825,7 +902,7 @@ func file_keyservice_keyservice_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_keyservice_keyservice_proto_rawDesc, NumEnums: 0, - NumMessages: 12, + NumMessages: 13, NumExtensions: 0, NumServices: 1, }, diff --git a/keyservice/keyservice.proto b/keyservice/keyservice.proto index 8bf62f89b..3056614bf 100644 --- a/keyservice/keyservice.proto +++ b/keyservice/keyservice.proto @@ -10,6 +10,7 @@ message Key { AzureKeyVaultKey azure_keyvault_key = 4; VaultKey vault_key = 5; AgeKey age_key = 6; + OvhKmsKey ovh_kms_key = 7; } } @@ -44,6 +45,11 @@ message AgeKey { string recipient = 1; } +message OvhKmsKey { + string endpoint = 1; + string key_id = 2; +} + message EncryptRequest { Key key = 1; bytes plaintext = 2; diff --git a/keyservice/keyservice_grpc.pb.go b/keyservice/keyservice_grpc.pb.go index d278b82d9..ca85e7c5c 100644 --- a/keyservice/keyservice_grpc.pb.go +++ b/keyservice/keyservice_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.28.3 +// - protoc v5.29.3 // source: keyservice/keyservice.proto package keyservice diff --git a/keyservice/server.go b/keyservice/server.go index 9f2b486a6..ef8811505 100644 --- a/keyservice/server.go +++ b/keyservice/server.go @@ -8,6 +8,7 @@ import ( "github.com/getsops/sops/v3/gcpkms" "github.com/getsops/sops/v3/hcvault" "github.com/getsops/sops/v3/kms" + "github.com/getsops/sops/v3/ovhkms" "github.com/getsops/sops/v3/pgp" "golang.org/x/net/context" "google.golang.org/grpc/codes" @@ -49,6 +50,18 @@ func (ks *Server) encryptWithGcpKms(key *GcpKmsKey, plaintext []byte) ([]byte, e return []byte(gcpKmsKey.EncryptedKey), nil } +func (ks *Server) encryptWithOvhKms(key *OvhKmsKey, plaintext []byte) ([]byte, error) { + ovhKmsKey := ovhkms.MasterKey{ + Endpoint: key.Endpoint, + KeyID: key.KeyId, + } + err := ovhKmsKey.Encrypt(plaintext) + if err != nil { + return nil, err + } + return []byte(ovhKmsKey.EncryptedKey), nil +} + func (ks *Server) encryptWithAzureKeyVault(key *AzureKeyVaultKey, plaintext []byte) ([]byte, error) { azkvKey := azkv.MasterKey{ VaultURL: key.VaultUrl, @@ -110,6 +123,16 @@ func (ks *Server) decryptWithGcpKms(key *GcpKmsKey, ciphertext []byte) ([]byte, return []byte(plaintext), err } +func (ks *Server) decryptWithOvhKms(key *OvhKmsKey, ciphertext []byte) ([]byte, error) { + ovhKmsKey := ovhkms.MasterKey{ + Endpoint: key.Endpoint, + KeyID: key.KeyId, + } + ovhKmsKey.EncryptedKey = string(ciphertext) + plaintext, err := ovhKmsKey.Decrypt() + return []byte(plaintext), err +} + func (ks *Server) decryptWithAzureKeyVault(key *AzureKeyVaultKey, ciphertext []byte) ([]byte, error) { azkvKey := azkv.MasterKey{ VaultURL: key.VaultUrl, @@ -196,6 +219,14 @@ func (ks Server) Encrypt(ctx context.Context, response = &EncryptResponse{ Ciphertext: ciphertext, } + case *Key_OvhKmsKey: + ciphertext, err := ks.encryptWithOvhKms(k.OvhKmsKey, 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: @@ -222,6 +253,8 @@ func keyToString(key *Key) string { return fmt.Sprintf("Azure Key Vault key with URL %s/keys/%s/%s", k.AzureKeyvaultKey.VaultUrl, k.AzureKeyvaultKey.Name, k.AzureKeyvaultKey.Version) case *Key_VaultKey: return fmt.Sprintf("Hashicorp Vault key with URI %s/v1/%s/keys/%s", k.VaultKey.VaultAddress, k.VaultKey.EnginePath, k.VaultKey.KeyName) + case *Key_OvhKmsKey: + return fmt.Sprintf("OVH KMS key with Key ID %s", k.OvhKmsKey.KeyId) default: return "Unknown key type" } @@ -298,6 +331,14 @@ func (ks Server) Decrypt(ctx context.Context, response = &DecryptResponse{ Plaintext: plaintext, } + case *Key_OvhKmsKey: + plaintext, err := ks.decryptWithOvhKms(k.OvhKmsKey, req.Ciphertext) + if err != nil { + return nil, err + } + response = &DecryptResponse{ + Plaintext: plaintext, + } case nil: return nil, status.Errorf(codes.NotFound, "Must provide a key") default: diff --git a/ovhkms/keysource.go b/ovhkms/keysource.go new file mode 100644 index 000000000..4a5c990b1 --- /dev/null +++ b/ovhkms/keysource.go @@ -0,0 +1,272 @@ +package ovhkms + +import ( + "context" + "crypto/tls" + "encoding/base64" + "fmt" + "github.com/google/uuid" + "net/http" + "os" + "regexp" + "strings" + "time" + + "github.com/ovh/okms-sdk-go" + "github.com/sirupsen/logrus" + + "github.com/getsops/sops/v3/logging" +) + +const ( + // KeyTypeIdentifier is the string used to identify an OVH KMS MasterKey. + KeyTypeIdentifier = "ovh_kms" + + // CertificateFileEnv is the environment variable containing the path to the certificate file. + CertificateFileEnv = "OVH_CERTIFICATE_FILE" + + // CertificateKeyFileEnv is the environment variable containing the path to the certificate key file. + CertificateKeyFileEnv = "OVH_CERTIFICATE_KEY_FILE" +) + +var ( + // log is the global logger for any OVH KMS MasterKey. + log *logrus.Logger + + // ovhKmsTTL is the duration after which a MasterKey requires rotation. + ovhKmsTTL = time.Hour * 24 * 30 * 6 + + // ovhEndpointRegex is used to validate and extract components from the OVH KMS endpoint. + ovhEndpointRegex = regexp.MustCompile(`^([^/]+)/([^/]+)$`) +) + +func init() { + log = logging.NewLogger("OVH_KMS") +} + +// MasterKey is an OVH KMS key used to encrypt and decrypt SOPS' data key. +type MasterKey struct { + // Endpoint is the OVH KMS endpoint. + Endpoint string + + // KeyID is the UUID of the key in OVH KMS. + KeyID string + + // EncryptedKey contains the SOPS data key encrypted with the OVH KMS key. + EncryptedKey string + + // CreationDate of the MasterKey, used to determine if the EncryptedKey + // needs rotation. + CreationDate time.Time + + // certificateFile is the path to the certificate file used for authentication. + certificateFile string + + // certificateKeyFile is the path to the certificate key file used for authentication. + certificateKeyFile string +} + +// NewMasterKeyFromKeyID creates a new MasterKey from an OVH KMS key ID +// in the format /. +func NewMasterKeyFromKeyID(keyID string) (*MasterKey, error) { + matches := ovhEndpointRegex.FindStringSubmatch(keyID) + if matches == nil || len(matches) != 3 { + // If no match, assume it's just a key ID and endpoint not provider + return nil, fmt.Errorf("not a vaild key (should be like this: +"+ + "example.okms.ovh.net/keyId), got: %v", keyID) + } + + return &MasterKey{ + Endpoint: matches[1], + KeyID: matches[2], + CreationDate: time.Now().UTC(), + }, nil +} + +// MasterKeysFromResourceIDString creates a list of MasterKeys from a comma-separated +// string of OVH KMS key IDs. +func MasterKeysFromResourceIDString(resourceID string) ([]*MasterKey, error) { + var keys []*MasterKey + if resourceID == "" { + return keys, nil + } + + for _, resourceID := range strings.Split(resourceID, ",") { + if resourceID == "" { + continue + } + mk, err := NewMasterKeyFromKeyID(resourceID) + if err != nil { + return nil, err + } + keys = append(keys, mk) + } + + return keys, nil +} + +// Encrypt takes a SOPS data key, encrypts it with OVH KMS, and stores +// the result in the EncryptedKey field. +func (key *MasterKey) Encrypt(dataKey []byte) error { + return key.EncryptContext(context.Background(), dataKey) +} + +// EncryptContext takes a SOPS data key, encrypts it with OVH KMS, and stores +// the result in the EncryptedKey field. +func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error { + // UUID key + KeyID, err := uuid.Parse(key.KeyID) + if err != nil { + log.WithField("KeyID", key.KeyID).Info("Encryption failed: invalid UUID") + return fmt.Errorf("failed to parse UUID '%s': %w", key.KeyID, err) + } + + client, err := key.getClient() + if err != nil { + log.WithField("KeyID", key.KeyID).Info("Encryption failed") + return err + } + + // Base64 encode the data key + plaintext := base64.StdEncoding.EncodeToString(dataKey) + + // Encrypt the data key using OVH KMS + resp, err := client.Encrypt(ctx, KeyID, "", []byte(plaintext)) + if err != nil { + log.WithField("KeyID", key.KeyID).Info("Encryption failed") + return fmt.Errorf("failed to encrypt sops data key with OVH KMS key '%s': %w", key.KeyID, err) + } + + // Store the encrypted key + key.EncryptedKey = resp + log.WithField("KeyID", key.KeyID).Info("Encryption successful") + return nil +} + +// EncryptIfNeeded encrypts the provided SOPS data key, if it has not 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 OVH KMS and returns the result. +func (key *MasterKey) Decrypt() ([]byte, error) { + return key.DecryptContext(context.Background()) +} + +// DecryptContext decrypts the EncryptedKey field with OVH KMS and returns the result. +func (key *MasterKey) DecryptContext(ctx context.Context) ([]byte, error) { + // UUID key + KeyID, err := uuid.Parse(key.KeyID) + if err != nil { + log.WithField("KeyID", key.KeyID).Info("Decryption failed: invalid UUID") + return nil, fmt.Errorf("failed to parse UUID '%s': %w", key.KeyID, err) + } + + client, err := key.getClient() + if err != nil { + log.WithField("KeyID", key.KeyID).Info("Decryption failed") + return nil, err + } + + // Decrypt the data key using OVH KMS + resp, err := client.Decrypt(ctx, KeyID, "", string(key.EncryptedKey)) + if err != nil { + log.WithField("KeyID", key.KeyID).Info("Decryption failed") + return nil, fmt.Errorf("failed to decrypt sops data key with OVH KMS key '%s': %w", key.KeyID, err) + } + + // Decode the base64 plaintext + dataKey, err := base64.StdEncoding.DecodeString(string(resp)) + if err != nil { + log.WithField("KeyID", key.KeyID).Info("Decryption failed: invalid base64 plaintext") + return nil, fmt.Errorf("failed to decode base64 plaintext: %w", err) + } + + log.WithField("KeyID", key.KeyID).Info("Decryption successful") + return dataKey, nil +} + +// NeedsRotation returns whether the data key needs to be rotated or not. +func (key *MasterKey) NeedsRotation() bool { + return time.Since(key.CreationDate) > (ovhKmsTTL) +} + +// ToString converts the key to a string representation. +func (key *MasterKey) ToString() string { + return fmt.Sprintf("%s/%s", key.Endpoint, key.KeyID) +} + +// ToMap converts the MasterKey to a map for serialization purposes. +func (key MasterKey) ToMap() map[string]interface{} { + out := make(map[string]interface{}) + out["endpoint"] = key.Endpoint + out["key_id"] = key.KeyID + out["enc"] = key.EncryptedKey + out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) + return out +} + +// TypeToIdentifier returns the string identifier for the MasterKey type. +func (key *MasterKey) TypeToIdentifier() string { + return KeyTypeIdentifier +} + +// 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) +} + +// getClient returns an OVH KMS client configured with the certificate +// from environment variables. +func (key *MasterKey) getClient() (*okms.Client, error) { + // Get certificate paths from environment variables if not set + certFile := key.certificateFile + certKeyFile := key.certificateKeyFile + + if certFile == "" { + certFile = os.Getenv(CertificateFileEnv) + } + + if certKeyFile == "" { + certKeyFile = os.Getenv(CertificateKeyFileEnv) + } + + if certFile == "" || certKeyFile == "" { + return nil, fmt.Errorf("OVH KMS certificate file paths not provided. Set %s and %s environment variables", + CertificateFileEnv, CertificateKeyFileEnv) + } + + // Create OVH KMS client + cert, err := tls.LoadX509KeyPair(certFile, certKeyFile) + if err != nil { + return nil, fmt.Errorf("failed to load certificate file: %w", err) + } + + httpClient := http.Client{ + Transport: &http.Transport{TLSClientConfig: &tls.Config{ + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + }}, + } + client, err := okms.NewRestAPIClientWithHttp(fmt.Sprintf("https://%s", key.Endpoint), &httpClient) + if err != nil { + return nil, fmt.Errorf("failed to create OVH KMS client: %w", err) + } + + return client, nil +} + +// SetCertificateFiles sets the certificate file paths for authentication. +func (key *MasterKey) SetCertificateFiles(certFile, certKeyFile string) { + key.certificateFile = certFile + key.certificateKeyFile = certKeyFile +} diff --git a/ovhkms/keysource_test.go b/ovhkms/keysource_test.go new file mode 100644 index 000000000..d3b69763c --- /dev/null +++ b/ovhkms/keysource_test.go @@ -0,0 +1,275 @@ +package ovhkms + +import ( + "context" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewMasterKeyFromKeyID(t *testing.T) { + tests := []struct { + name string + keyID string + expectErr bool + expected *MasterKey + }{ + { + name: "Full format with endpoint and key ID", + keyID: "eu-west-sbg.okms.ovh.net/12345678-1234-1234-1234-123456789012", + expected: &MasterKey{ + Endpoint: "eu-west-sbg.okms.ovh.net", + KeyID: "12345678-1234-1234-1234-123456789012", + }, + expectErr: false, + }, + { + name: "Different endpoint", + keyID: "ca-east-tor.okms.ovh.net/12345678-1234-1234-1234-123456789012", + expected: &MasterKey{ + Endpoint: "ca-east-tor.okms.ovh.net", + KeyID: "12345678-1234-1234-1234-123456789012", + }, + expectErr: false, + }, + { + name: "No endpoint (invalid format)", + keyID: "12345678-1234-1234-1234-123456789012", + expectErr: true, + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewMasterKeyFromKeyID(tt.keyID) + + if tt.expectErr { + assert.Error(t, err) + assert.Nil(t, got) + } else { + assert.NoError(t, err) + assert.NotNil(t, got) + assert.Equal(t, tt.expected.Endpoint, got.Endpoint) + assert.Equal(t, tt.expected.KeyID, got.KeyID) + } + }) + } +} + +func TestMasterKeysFromResourceIDString(t *testing.T) { + tests := []struct { + name string + resourceID string + expected int + }{ + { + name: "Empty string", + resourceID: "", + expected: 0, + }, + { + name: "Single key ID", + resourceID: "eu-west-sbg.okms.ovh.net/12345678-1234-1234-1234-123456789012", + expected: 1, + }, + { + name: "Multiple key IDs", + resourceID: "eu-west-sbg.okms.ovh.net/12345678-1234-1234-1234-123456789012,ca-east-tor.okms.ovh.net/be03bbff-1f9e-5f98-9d11-35a393bbf673", + expected: 2, + }, + { + name: "Multiple key IDs with empty entry", + resourceID: "eu-west-sbg.okms.ovh.net/12345678-1234-1234-1234-123456789012,,ca-east-tor.okms.ovh.net/be03bbff-1f9e-5f98-9d11-35a393bbf673", + expected: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + keys, _ := MasterKeysFromResourceIDString(tt.resourceID) + assert.Equal(t, tt.expected, len(keys)) + }) + } +} + +func TestMasterKey_NeedsRotation(t *testing.T) { + key := &MasterKey{ + CreationDate: time.Now().UTC(), + } + + // New key doesn't need rotation + assert.False(t, key.NeedsRotation()) + + // Set creation date to exceed TTL + key.CreationDate = key.CreationDate.Add(-(ovhKmsTTL + time.Second)) + assert.True(t, key.NeedsRotation()) +} + +func TestMasterKey_ToString(t *testing.T) { + tests := []struct { + name string + key *MasterKey + expected string + }{ + { + name: "Full key representation", + key: &MasterKey{ + Endpoint: "eu-west-sbg.okms.ovh.net", + KeyID: "12345678-1234-1234-1234-123456789012", + }, + expected: "eu-west-sbg.okms.ovh.net/12345678-1234-1234-1234-123456789012", + }, + { + name: "Different endpoint", + key: &MasterKey{ + Endpoint: "ca-east-tor.okms.ovh.net", + KeyID: "be03bbff-1f9e-5f98-9d11-35a393bbf673", + }, + expected: "ca-east-tor.okms.ovh.net/be03bbff-1f9e-5f98-9d11-35a393bbf673", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.key.ToString() + assert.Equal(t, tt.expected, got) + }) + } +} + +func TestMasterKey_ToMap(t *testing.T) { + key := &MasterKey{ + Endpoint: "eu-west-sbg.okms.ovh.net", + KeyID: "12345678-1234-1234-1234-123456789012", + EncryptedKey: "encrypted-data-key", + CreationDate: time.Date(2025, 7, 31, 12, 0, 0, 0, time.UTC), + } + + expected := map[string]interface{}{ + "endpoint": "eu-west-sbg.okms.ovh.net", + "key_id": "12345678-1234-1234-1234-123456789012", + "enc": "encrypted-data-key", + "created_at": "2025-07-31T12:00:00Z", + } + + got := key.ToMap() + assert.Equal(t, expected, got) +} + +func TestMasterKey_TypeToIdentifier(t *testing.T) { + key := &MasterKey{} + assert.Equal(t, KeyTypeIdentifier, key.TypeToIdentifier()) +} + +func TestMasterKey_EncryptedDataKey(t *testing.T) { + key := &MasterKey{EncryptedKey: "encrypted-data-key"} + assert.Equal(t, []byte("encrypted-data-key"), key.EncryptedDataKey()) +} + +func TestMasterKey_SetEncryptedDataKey(t *testing.T) { + key := &MasterKey{} + key.SetEncryptedDataKey([]byte("new-encrypted-key")) + assert.Equal(t, "new-encrypted-key", key.EncryptedKey) +} + +func TestMasterKey_getClient(t *testing.T) { + // Save original env vars + origCertFile := os.Getenv(CertificateFileEnv) + origCertKeyFile := os.Getenv(CertificateKeyFileEnv) + defer func() { + os.Setenv(CertificateFileEnv, origCertFile) + os.Setenv(CertificateKeyFileEnv, origCertKeyFile) + }() + + // Test with missing env vars + os.Unsetenv(CertificateFileEnv) + os.Unsetenv(CertificateKeyFileEnv) + + key := &MasterKey{ + Endpoint: "eu-west-sbg.okms.ovh.net", + KeyID: "12345678-1234-1234-1234-123456789012", + } + + // Should fail without certificate files + client, err := key.getClient() + assert.Error(t, err) + assert.Nil(t, client) + + // Test with direct certificate files + key.SetCertificateFiles("/path/to/cert.pem", "/path/to/key.pem") + + // The test will still fail because the files don't exist, but we can check if the paths were set + _, err = key.getClient() + assert.Error(t, err) // Will fail with file not found or similar + assert.Equal(t, "/path/to/cert.pem", key.certificateFile) + assert.Equal(t, "/path/to/key.pem", key.certificateKeyFile) + + // Test with env vars + key = &MasterKey{ + Endpoint: "eu-west-sbg.okms.ovh.net", + KeyID: "12345678-1234-1234-1234-123456789012", + } + + os.Setenv(CertificateFileEnv, "/path/to/env/cert.pem") + os.Setenv(CertificateKeyFileEnv, "/path/to/env/key.pem") + + // The test will still fail because the files don't exist, but we can check if it tries to use env vars + _, err = key.getClient() + assert.Error(t, err) // Will fail with file not found or similar +} + +func TestMasterKey_SetCertificateFiles(t *testing.T) { + key := &MasterKey{} + key.SetCertificateFiles("/path/to/cert.pem", "/path/to/key.pem") + + assert.Equal(t, "/path/to/cert.pem", key.certificateFile) + assert.Equal(t, "/path/to/key.pem", key.certificateKeyFile) +} + +func TestMasterKey_EncryptIfNeeded(t *testing.T) { + key := &MasterKey{} + // Should call Encrypt if EncryptedKey is empty + err := key.EncryptIfNeeded([]byte("dummy")) + assert.Error(t, err) // getClient will fail due to missing certs + + // Should not call Encrypt if EncryptedKey is already set + key.EncryptedKey = "already-encrypted" + err = key.EncryptIfNeeded([]byte("dummy")) + assert.NoError(t, err) +} + +func TestMasterKey_EncryptContext_InvalidUUID(t *testing.T) { + key := &MasterKey{ + Endpoint: "eu-west-sbg.okms.ovh.net", + KeyID: "not-a-uuid", + } + err := key.EncryptContext(context.Background(), []byte("test")) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse UUID") +} + +func TestMasterKey_DecryptContext_InvalidUUID(t *testing.T) { + key := &MasterKey{ + Endpoint: "eu-west-sbg.okms.ovh.net", + KeyID: "not-a-uuid", + EncryptedKey: "some-encrypted-data", + } + _, err := key.DecryptContext(context.Background()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse UUID") +} + +func TestMasterKey_DecryptContext_InvalidBase64(t *testing.T) { + key := &MasterKey{ + Endpoint: "eu-west-sbg.okms.ovh.net", + KeyID: "12345678-1234-1234-1234-123456789012", + EncryptedKey: "@@@not-base64@@@", + } + // Inject a fake getClient that returns a mock response + key.SetCertificateFiles("/nonexistent.crt", "/nonexistent.key") + _, err := key.DecryptContext(context.Background()) + assert.Error(t, err) // Should still error due to cert loading +} diff --git a/stores/stores.go b/stores/stores.go index bdc026a00..9891b729e 100644 --- a/stores/stores.go +++ b/stores/stores.go @@ -20,6 +20,7 @@ import ( "github.com/getsops/sops/v3/gcpkms" "github.com/getsops/sops/v3/hcvault" "github.com/getsops/sops/v3/kms" + "github.com/getsops/sops/v3/ovhkms" "github.com/getsops/sops/v3/pgp" ) @@ -46,6 +47,7 @@ type Metadata struct { KeyGroups []keygroup `yaml:"key_groups,omitempty" json:"key_groups,omitempty"` KMSKeys []kmskey `yaml:"kms,omitempty" json:"kms,omitempty"` GCPKMSKeys []gcpkmskey `yaml:"gcp_kms,omitempty" json:"gcp_kms,omitempty"` + OVHKMSKeys []ovhkmskey `yaml:"ovh_kms,omitempty" json:"ovh_kms,omitempty"` AzureKeyVaultKeys []azkvkey `yaml:"azure_kv,omitempty" json:"azure_kv,omitempty"` VaultKeys []vaultkey `yaml:"hc_vault,omitempty" json:"hc_vault,omitempty"` AgeKeys []agekey `yaml:"age,omitempty" json:"age,omitempty"` @@ -66,6 +68,7 @@ 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"` + OVHKMSKeys []ovhkmskey `yaml:"ovh_kms,omitempty" json:"ovh_kms,omitempty"` AzureKeyVaultKeys []azkvkey `yaml:"azure_kv,omitempty" json:"azure_kv,omitempty"` VaultKeys []vaultkey `yaml:"hc_vault" json:"hc_vault"` AgeKeys []agekey `yaml:"age" json:"age"` @@ -92,6 +95,13 @@ type gcpkmskey struct { EncryptedDataKey string `yaml:"enc" json:"enc"` } +type ovhkmskey struct { + Endpoint string `yaml:"endpoint" json:"endpoint"` + KeyID string `yaml:"key_id" json:"key_id"` + CreatedAt string `yaml:"created_at" json:"created_at"` + EncryptedDataKey string `yaml:"enc" json:"enc"` +} + type vaultkey struct { VaultAddress string `yaml:"vault_address" json:"vault_address"` EnginePath string `yaml:"engine_path" json:"engine_path"` @@ -132,6 +142,7 @@ func MetadataFromInternal(sopsMetadata sops.Metadata) Metadata { m.PGPKeys = pgpKeysFromGroup(group) m.KMSKeys = kmsKeysFromGroup(group) m.GCPKMSKeys = gcpkmsKeysFromGroup(group) + m.OVHKMSKeys = ovhkmsKeysFromGroup(group) m.VaultKeys = vaultKeysFromGroup(group) m.AzureKeyVaultKeys = azkvKeysFromGroup(group) m.AgeKeys = ageKeysFromGroup(group) @@ -141,6 +152,7 @@ func MetadataFromInternal(sopsMetadata sops.Metadata) Metadata { KMSKeys: kmsKeysFromGroup(group), PGPKeys: pgpKeysFromGroup(group), GCPKMSKeys: gcpkmsKeysFromGroup(group), + OVHKMSKeys: ovhkmsKeysFromGroup(group), VaultKeys: vaultKeysFromGroup(group), AzureKeyVaultKeys: azkvKeysFromGroup(group), AgeKeys: ageKeysFromGroup(group), @@ -195,6 +207,21 @@ func gcpkmsKeysFromGroup(group sops.KeyGroup) (keys []gcpkmskey) { return } +func ovhkmsKeysFromGroup(group sops.KeyGroup) (keys []ovhkmskey) { + for _, key := range group { + switch key := key.(type) { + case *ovhkms.MasterKey: + keys = append(keys, ovhkmskey{ + Endpoint: key.Endpoint, + KeyID: key.KeyID, + CreatedAt: key.CreationDate.Format(time.RFC3339), + EncryptedDataKey: key.EncryptedKey, + }) + } + } + return +} + func vaultKeysFromGroup(group sops.KeyGroup) (keys []vaultkey) { for _, key := range group { switch key := key.(type) { @@ -294,7 +321,7 @@ func (m *Metadata) ToInternal() (sops.Metadata, error) { }, nil } -func internalGroupFrom(kmsKeys []kmskey, pgpKeys []pgpkey, gcpKmsKeys []gcpkmskey, azkvKeys []azkvkey, vaultKeys []vaultkey, ageKeys []agekey) (sops.KeyGroup, error) { +func internalGroupFrom(kmsKeys []kmskey, pgpKeys []pgpkey, gcpKmsKeys []gcpkmskey, azkvKeys []azkvkey, vaultKeys []vaultkey, ageKeys []agekey, ovhKmsKeys []ovhkmskey) (sops.KeyGroup, error) { var internalGroup sops.KeyGroup for _, kmsKey := range kmsKeys { k, err := kmsKey.toInternal() @@ -338,13 +365,20 @@ func internalGroupFrom(kmsKeys []kmskey, pgpKeys []pgpkey, gcpKmsKeys []gcpkmske } internalGroup = append(internalGroup, k) } + for _, ovhKmsKey := range ovhKmsKeys { + k, err := ovhKmsKey.toInternal() + if err != nil { + return nil, err + } + internalGroup = append(internalGroup, k) + } return internalGroup, nil } func (m *Metadata) internalKeygroups() ([]sops.KeyGroup, error) { var internalGroups []sops.KeyGroup - if len(m.PGPKeys) > 0 || len(m.KMSKeys) > 0 || len(m.GCPKMSKeys) > 0 || len(m.AzureKeyVaultKeys) > 0 || len(m.VaultKeys) > 0 || len(m.AgeKeys) > 0 { - internalGroup, err := internalGroupFrom(m.KMSKeys, m.PGPKeys, m.GCPKMSKeys, m.AzureKeyVaultKeys, m.VaultKeys, m.AgeKeys) + if len(m.PGPKeys) > 0 || len(m.KMSKeys) > 0 || len(m.GCPKMSKeys) > 0 || len(m.AzureKeyVaultKeys) > 0 || len(m.VaultKeys) > 0 || len(m.AgeKeys) > 0 || len(m.OVHKMSKeys) > 0 { + internalGroup, err := internalGroupFrom(m.KMSKeys, m.PGPKeys, m.GCPKMSKeys, m.AzureKeyVaultKeys, m.VaultKeys, m.AgeKeys, m.OVHKMSKeys) if err != nil { return nil, err } @@ -352,7 +386,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, group.AzureKeyVaultKeys, group.VaultKeys, group.AgeKeys) + internalGroup, err := internalGroupFrom(group.KMSKeys, group.PGPKeys, group.GCPKMSKeys, group.AzureKeyVaultKeys, group.VaultKeys, group.AgeKeys, group.OVHKMSKeys) if err != nil { return nil, err } @@ -391,6 +425,19 @@ func (gcpKmsKey *gcpkmskey) toInternal() (*gcpkms.MasterKey, error) { }, nil } +func (ovhKmsKey *ovhkmskey) toInternal() (*ovhkms.MasterKey, error) { + creationDate, err := time.Parse(time.RFC3339, ovhKmsKey.CreatedAt) + if err != nil { + return nil, err + } + return &ovhkms.MasterKey{ + Endpoint: ovhKmsKey.Endpoint, + KeyID: ovhKmsKey.KeyID, + EncryptedKey: ovhKmsKey.EncryptedDataKey, + CreationDate: creationDate, + }, nil +} + func (azkvKey *azkvkey) toInternal() (*azkv.MasterKey, error) { creationDate, err := time.Parse(time.RFC3339, azkvKey.CreatedAt) if err != nil {