diff --git a/.gitignore b/.gitignore index 5eb4a860c2..eb11be7ad5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ bin/ dist/ functional-tests/sops +functional-tests/target vendor/ profile.out diff --git a/README.rst b/README.rst index 6fdc2497f7..2e4e3f8c1b 100644 --- a/README.rst +++ b/README.rst @@ -221,7 +221,7 @@ the ``--age`` option or the **SOPS_AGE_RECIPIENTS** environment variable: When decrypting a file with the corresponding identity, SOPS will look for a text file name ``keys.txt`` located in a ``sops`` subdirectory of your user -configuration directory. +configuration directory. - **Linux** @@ -276,7 +276,7 @@ It is also possible to use ``updatekeys``, when adding or removing age recipient +++ age1qe5lxzzeppw5k79vxn3872272sgy224g2nzqlzy3uljs84say3yqgvd0sw Is this okay? (y/n):y 2022/02/09 16:32:04 File /iac/solution1/secret.enc.yaml synced with new keys - + Encrypting using GCP KMS ~~~~~~~~~~~~~~~~~~~~~~~~ GCP KMS has support for authorization with the use of `Application Default Credentials @@ -300,7 +300,7 @@ you can enable application default credentials using the sdk: Using OAauth tokens you can authorize by doing this: .. code:: sh - + $ export GOOGLE_OAUTH_ACCESS_TOKEN= Or if you are logged in you can authorize by generating an access token: @@ -469,7 +469,7 @@ Encrypting using Hashicorp Vault We assume you have an instance (or more) of Vault running and you have privileged access to it. For instructions on how to deploy a secure instance of Vault, refer to Hashicorp's official documentation. -To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!) +To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!) .. code:: sh @@ -479,11 +479,11 @@ To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!) .. code:: sh $ # Substitute this with the address Vault is running on - $ export VAULT_ADDR=http://127.0.0.1:8200 + $ export VAULT_ADDR=http://127.0.0.1:8200 $ # this may not be necessary in case you previously used `vault login` for production use - $ export VAULT_TOKEN=toor - + $ export VAULT_TOKEN=toor + $ # to check if Vault started and is configured correctly $ vault status Key Value @@ -522,7 +522,103 @@ To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!) hc_vault_transit_uri: "$VAULT_ADDR/v1/sops/keys/thirdkey" EOF - $ sops encrypt --verbose prod/raw.yaml > prod/encrypted.yaml + $ sops --verbose -e prod/raw.yaml > prod/encrypted.yaml + +Encrypting using OCI KMS +~~~~~~~~~~~~~~~~~~~~~~~~ + +OCI KMS authentication is resolved in the following order (env-first, then cloud identity, then local fallbacks): + +1. OCI CLI environment variables (OCI_CLI_*) +2. SDK environment variables (OCI_*), e.g. `OCI_tenancy_ocid` +3. Config file only when explicitly pointed by `OCI_CLI_CONFIG_FILE`/`OCI_CLI_PROFILE` +4. Instance Principals (when running on OCI Compute with appropriate IAM policies) +5. SDK DefaultConfigProvider as a last resort (e.g. `~/.oci/config`, TF_VAR_*) + +Examples +~~~~~~~~ + +- Using OCI CLI-style env (API key): + +.. code:: bash + + export OCI_CLI_TENANCY=ocid1.tenancy.oc1..xxxx + export OCI_CLI_USER=ocid1.user.oc1..xxxx + export OCI_CLI_REGION=us-ashburn-1 + export OCI_CLI_FINGERPRINT=aa:bb:cc:dd:... + export OCI_CLI_KEY_FILE=$HOME/.oci/oci_api_key.pem + +- Using OCI CLI-style env (SSO/security token): + +.. code:: bash + + # Create a session with the OCI CLI, then point SOPS to the token file + oci session authenticate + export OCI_CLI_AUTH=security_token + export OCI_CLI_SECURITY_TOKEN_FILE="$HOME/.oci/sessions//token" + +- Using SDK env (API key): + +.. code:: bash + + export OCI_tenancy_ocid=ocid1.tenancy.oc1..xxxx + export OCI_user_ocid=ocid1.user.oc1..xxxx + export OCI_region=eu-frankfurt-1 + export OCI_fingerprint=aa:bb:cc:dd:... + export OCI_private_key_path=$HOME/.oci/oci_api_key.pem + +- Using a config file via env: + +.. code:: bash + + export OCI_CLI_CONFIG_FILE=$HOME/.oci/config + export OCI_CLI_PROFILE=DEFAULT + +- Running on OCI Compute (Instance Principals): + No env required; ensure the instance has IAM permissions for KMS operations. + +Encrypting/decrypting with OCI KMS requires a KMS OCID. You can use the +cloud console the get the OCID of an existing key or you can create one using the `oci` +CLI: + +.. code:: sh + + $ export compartment_id= + $ export display_name= + $ export vault_type= + $ OCI_CLI_AUTH=security_token oci kms management vault create --compartment-id $compartment_id --display-name $display_name --vault-type $vault_type + # you should see a JSON summarizing the created resource + # for help: https://docs.cloud.oracle.com/en-us/iaas/tools/oci-cli/latest/oci_cli_docs/cmdref/kms/management/vault/create.html + +Now we need to create a key. First of all we need to define a shape for it with: + +.. code:: bash + + $ cat << EOF > key-shape.json + { + "algorithm": "AES", + "length": 32 + } + EOF + +Now we can create the key with + +.. code:: sh + + $ export compartment_id= + $ export display_name= + # you can grab the endpoint from the vault page on the portal, it should be something like: https://asdadsasdagz5aacmg-management.kms..oraclecloud.com + $ OCI_CLI_AUTH=security_token oci kms management key create --compartment-id $compartment_id --display-name $display_name --endpoint --key-shape file://key-shape.json + # you should see a JSON summarizing the created resource, we need to grab the OCID of the key from it + # for help: https://docs.cloud.oracle.com/en-us/iaas/tools/oci-cli/latest/oci_cli_docs/cmdref/kms/management/key/create.html + +Now you can encrypt a file using:: + + $ sops --encrypt --oci-kms ocid1.key.oc1..asdadsasdagz5aacmg.abwgiljtjasdasdasdagugpfe7wrtngukihgkybqxcoozz7sbh6lq test.yaml > test.enc.yaml + +And decrypt it using:: + + $ sops --decrypt test.enc.yaml Adding and removing keys ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1655,8 +1751,8 @@ will encrypt the values under the ``data`` and ``stringData`` keys in a YAML fil containing kubernetes secrets. It will not encrypt other values that help you to navigate the file, like ``metadata`` which contains the secrets' names. -Conversely, you can opt in to only leave certain keys without encrypting by using the -``--unencrypted-regex`` option, which will leave the values unencrypted of those keys +Conversely, you can opt in to only leave certain keys without encrypting by using the +``--unencrypted-regex`` option, which will leave the values unencrypted of those keys that match the supplied regular expression. For example, this command: .. code:: sh diff --git a/cmd/sops/main.go b/cmd/sops/main.go index 74f493ec89..a952d7187f 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/ocikms" "github.com/getsops/sops/v3/pgp" "github.com/getsops/sops/v3/stores" "github.com/getsops/sops/v3/stores/dotenv" @@ -1184,8 +1185,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-oci-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-oci-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) } } @@ -1699,6 +1700,11 @@ func main() { Usage: "comma separated list of age recipients", EnvVar: "SOPS_AGE_RECIPIENTS", }, + cli.StringFlag{ + Name: "oci-kms", + Usage: "comma separated list of OCI KMS OCIDs", + EnvVar: "SOPS_OCI_KMS_OCIDS", + }, cli.BoolFlag{ Name: "in-place, i", Usage: "write output back to the same file instead of stdout", @@ -1759,6 +1765,14 @@ func main() { Name: "rm-age", Usage: "remove the provided comma-separated list of age recipients from the list of master keys on the given file", }, + cli.StringFlag{ + Name: "add-oci-kms", + Usage: "add the provided comma-separated list of OCI KMS keys OCIDs to the list of master keys on the given file", + }, + cli.StringFlag{ + Name: "rm-oci-kms", + Usage: "remove the provided comma-separated list of OCI KMS keys OCIDs from the list of master keys on the given file", + }, cli.StringFlag{ Name: "add-pgp", Usage: "add the provided comma-separated list of PGP fingerprints to the list of master keys on the given file", @@ -2191,7 +2205,7 @@ func getEncryptConfig(c *cli.Context, fileName string, inputStore common.Store, }, 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, ociOptionName 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) @@ -2228,11 +2242,11 @@ func getMasterKeys(c *cli.Context, kmsEncryptionContext map[string]*string, kmsO 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-oci-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-oci-kms") if err != nil { return rotateOpts{}, err } @@ -2381,6 +2395,7 @@ func keyGroups(c *cli.Context, file string, optionalConfig *config.Config) ([]so var azkvKeys []keys.MasterKey var hcVaultMkKeys []keys.MasterKey var ageMasterKeys []keys.MasterKey + var ociMasterKeys []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) @@ -2427,7 +2442,12 @@ func keyGroups(c *cli.Context, file string, optionalConfig *config.Config) ([]so ageMasterKeys = append(ageMasterKeys, k) } } - if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" && c.String("azure-kv") == "" && c.String("hc-vault-transit") == "" && c.String("age") == "" { + if c.String("oci-kms") != "" { + for _, k := range ocikms.MasterKeysFromOCIDString(c.String("oci-kms")) { + ociMasterKeys = append(ociMasterKeys, k) + } + } + if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" && c.String("azure-kv") == "" && c.String("hc-vault-transit") == "" && c.String("age") == "" && c.String("oci-kms") == "" { conf := optionalConfig var err error if conf == nil { @@ -2450,6 +2470,7 @@ func keyGroups(c *cli.Context, file string, optionalConfig *config.Config) ([]so group = append(group, pgpKeys...) group = append(group, hcVaultMkKeys...) group = append(group, ageMasterKeys...) + group = append(group, ociMasterKeys...) log.Debugf("Master keys available: %+v", group) return []sops.KeyGroup{group}, nil } diff --git a/config/config.go b/config/config.go index 6a67e06198..5ba7484279 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/ocikms" "github.com/getsops/sops/v3/pgp" "github.com/getsops/sops/v3/publish" "go.yaml.in/yaml/v3" @@ -135,6 +136,7 @@ type keyGroup struct { AzureKV []azureKVKey `yaml:"azure_keyvault"` Vault []string `yaml:"hc_vault"` Age []string `yaml:"age"` + OCIKMS []string `yaml:"oci_kms"` PGP []string `yaml:"pgp"` } @@ -173,6 +175,7 @@ type creationRule struct { PathRegex string `yaml:"path_regex"` KMS interface{} `yaml:"kms"` // string or []string AwsProfile string `yaml:"aws_profile"` + OCIKMS string `yaml:"oci_kms"` Age interface{} `yaml:"age"` // string or []string PGP interface{} `yaml:"pgp"` // string or []string GCPKMS interface{} `yaml:"gcp_kms"` // string or []string @@ -320,6 +323,9 @@ func extractMasterKeys(group keyGroup) (sops.KeyGroup, error) { keyGroup = append(keyGroup, key) } } + for _, k := range group.OCIKMS { + keyGroup = append(keyGroup, ocikms.NewMasterKeyFromOCID(k)) + } for _, k := range group.PGP { keyGroup = append(keyGroup, pgp.NewMasterKeyFromFingerprint(k)) } @@ -362,6 +368,9 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[ if err != nil { return nil, err } + for _, k := range group.OCIKMS { + keyGroup = append(keyGroup, ocikms.NewMasterKeyFromOCID(k)) + } groups = append(groups, keyGroup) } } else { @@ -402,6 +411,9 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[ for _, k := range gcpkms.MasterKeysFromResourceIDString(strings.Join(gcpkmsKeys, ",")) { keyGroup = append(keyGroup, k) } + for _, k := range ocikms.MasterKeysFromOCIDString(cRule.OCIKMS) { + keyGroup = append(keyGroup, k) + } azKeys, err := getKeysWithValidation(cRule.GetAzureKeyVaultKeys, "azure_keyvault") if err != nil { return nil, err diff --git a/go.mod b/go.mod index 6306a86285..98cf9d6da2 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,8 @@ require ( github.com/lib/pq v1.10.9 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-wordwrap v1.0.1 + github.com/ontariosystems/oci-cli-env-provider v0.1.0 + github.com/oracle/oci-go-sdk/v65 v65.101.0 github.com/ory/dockertest/v3 v3.12.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 @@ -97,6 +99,7 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/gofrs/flock v0.12.1 // indirect 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 @@ -126,10 +129,12 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sony/gobreaker v1.0.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect diff --git a/go.sum b/go.sum index 90e6f16fb2..f9cb012817 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= @@ -154,20 +156,28 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8 h1:ZI8gCoCjGzPsum4L21jHdQs8shFBIQih1TM9Rd/c+EQ= +github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -231,12 +241,20 @@ 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/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= +github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/ontariosystems/oci-cli-env-provider v0.1.0 h1:xDEhUOXQrskVdyKPymw0tIvvhs8H/piKBCXNgOZ+Agc= +github.com/ontariosystems/oci-cli-env-provider v0.1.0/go.mod h1:7DFGsibH1hm9k4A31Ggf6Lg83ItgeC31I9G/xsVoiIQ= 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= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runc v1.2.6 h1:P7Hqg40bsMvQGCS4S7DJYhUZOISMLJOB2iGX5COWiPk= github.com/opencontainers/runc v1.2.6/go.mod h1:dOQeFo29xZKBNeRBI0B19mJtfHv68YgCTh1X+YphA+4= +github.com/oracle/oci-go-sdk/v65 v65.101.0 h1:EErMOuw98JXi0P7DgPg5zjouCA5s61iWD5tFWNCVLHk= +github.com/oracle/oci-go-sdk/v65 v65.101.0/go.mod h1:RGiXfpDDmRRlLtqlStTzeBjjdUNXyqm3KXKyLCm3A/Q= github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -248,19 +266,23 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ= +github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= 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/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= @@ -279,8 +301,11 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -303,19 +328,39 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= @@ -323,21 +368,55 @@ golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwE golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= @@ -346,6 +425,12 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/keyservice/keyservice.go b/keyservice/keyservice.go index 321af79420..13d884be8a 100644 --- a/keyservice/keyservice.go +++ b/keyservice/keyservice.go @@ -13,6 +13,7 @@ import ( "github.com/getsops/sops/v3/hcvault" "github.com/getsops/sops/v3/keys" "github.com/getsops/sops/v3/kms" + "github.com/getsops/sops/v3/ocikms" "github.com/getsops/sops/v3/pgp" ) @@ -78,6 +79,15 @@ func KeyFromMasterKey(mk keys.MasterKey) Key { }, }, } + case *ocikms.MasterKey: + return Key{ + KeyType: &Key_OciKey{ + OciKey: &OciKey{ + Ocid: mk.Ocid, + }, + }, + } + 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 a810b28053..8be514d9c3 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.2 // source: keyservice/keyservice.proto package keyservice @@ -33,6 +33,7 @@ type Key struct { // *Key_AzureKeyvaultKey // *Key_VaultKey // *Key_AgeKey + // *Key_OciKey KeyType isKey_KeyType `protobuf_oneof:"key_type"` } @@ -115,6 +116,13 @@ func (x *Key) GetAgeKey() *AgeKey { return nil } +func (x *Key) GetOciKey() *OciKey { + if x, ok := x.GetKeyType().(*Key_OciKey); ok { + return x.OciKey + } + 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_OciKey struct { + OciKey *OciKey `protobuf:"bytes,7,opt,name=oci_key,json=ociKey,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_OciKey) isKey_KeyType() {} + type PgpKey struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -481,6 +495,51 @@ func (x *AgeKey) GetRecipient() string { return "" } +type OciKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ocid string `protobuf:"bytes,1,opt,name=ocid,proto3" json:"ocid,omitempty"` +} + +func (x *OciKey) Reset() { + *x = OciKey{} + mi := &file_keyservice_keyservice_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OciKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OciKey) ProtoMessage() {} + +func (x *OciKey) 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 OciKey.ProtoReflect.Descriptor instead. +func (*OciKey) Descriptor() ([]byte, []int) { + return file_keyservice_keyservice_proto_rawDescGZIP(), []int{7} +} + +func (x *OciKey) GetOcid() string { + if x != nil { + return x.Ocid + } + return "" +} + type EncryptRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -492,7 +551,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 +563,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 +576,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 +603,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 +615,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 +628,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 +649,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 +661,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 +674,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 +701,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 +713,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 +726,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 +740,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, 0xbc, 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 +757,69 @@ 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, 0x22, 0x0a, 0x07, + 0x6f, 0x63, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, + 0x4f, 0x63, 0x69, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x63, 0x69, 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, 0x1c, 0x0a, 0x06, 0x4f, 0x63, 0x69, 0x4b, + 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6f, 0x63, 0x69, 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 +834,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 +843,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 + (*OciKey)(nil), // 7: OciKey + (*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 +857,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.oci_key:type_name -> OciKey + 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 +884,7 @@ func file_keyservice_keyservice_proto_init() { (*Key_AzureKeyvaultKey)(nil), (*Key_VaultKey)(nil), (*Key_AgeKey)(nil), + (*Key_OciKey)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -825,7 +892,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 8bf62f89bf..54bb3f4d65 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; + OciKey oci_key = 7; } } @@ -44,6 +45,10 @@ message AgeKey { string recipient = 1; } +message OciKey { + string ocid = 1; +} + message EncryptRequest { Key key = 1; bytes plaintext = 2; diff --git a/keyservice/keyservice_grpc.pb.go b/keyservice/keyservice_grpc.pb.go index d278b82d97..f0ab3b45e3 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.2 // source: keyservice/keyservice.proto package keyservice diff --git a/keyservice/server.go b/keyservice/server.go index 9f2b486a67..565723c267 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/ocikms" "github.com/getsops/sops/v3/pgp" "golang.org/x/net/context" "google.golang.org/grpc/codes" @@ -87,6 +88,17 @@ func (ks *Server) encryptWithAge(key *AgeKey, plaintext []byte) ([]byte, error) return []byte(ageKey.EncryptedKey), nil } +func (ks *Server) encryptWithOciKms(key *OciKey, plaintext []byte) ([]byte, error) { + ociKmsKey := ocikms.MasterKey{ + Ocid: key.Ocid, + } + err := ociKmsKey.Encrypt(plaintext) + if err != nil { + return nil, err + } + return []byte(ociKmsKey.EncryptedKey), nil +} + func (ks *Server) decryptWithPgp(key *PgpKey, ciphertext []byte) ([]byte, error) { pgpKey := pgp.NewMasterKeyFromFingerprint(key.Fingerprint) pgpKey.EncryptedKey = string(ciphertext) @@ -141,6 +153,15 @@ func (ks *Server) decryptWithAge(key *AgeKey, ciphertext []byte) ([]byte, error) return []byte(plaintext), err } +func (ks *Server) decryptWithOciKms(key *OciKey, ciphertext []byte) ([]byte, error) { + ociKmsKey := ocikms.MasterKey{ + Ocid: key.Ocid, + } + ociKmsKey.EncryptedKey = string(ciphertext) + plaintext, err := ociKmsKey.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, @@ -196,6 +217,14 @@ func (ks Server) Encrypt(ctx context.Context, response = &EncryptResponse{ Ciphertext: ciphertext, } + case *Key_OciKey: + ciphertext, err := ks.encryptWithOciKms(k.OciKey, 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 +251,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_OciKey: + return fmt.Sprintf("OCI KMS key with OCID %s", k.OciKey.Ocid) default: return "Unknown key type" } @@ -298,6 +329,14 @@ func (ks Server) Decrypt(ctx context.Context, response = &DecryptResponse{ Plaintext: plaintext, } + case *Key_OciKey: + plaintext, err := ks.decryptWithOciKms(k.OciKey, 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/ocikms/config_provider.go b/ocikms/config_provider.go new file mode 100644 index 0000000000..b3e4a6a6c3 --- /dev/null +++ b/ocikms/config_provider.go @@ -0,0 +1,125 @@ +package ocikms + +import ( + "crypto/rsa" + "os" + "sync" + + ocep "github.com/ontariosystems/oci-cli-env-provider" + "github.com/oracle/oci-go-sdk/v65/common" + "github.com/oracle/oci-go-sdk/v65/common/auth" +) + +// newIPProvider is a variable to allow tests to stub the Instance Principal provider factory +var newIPProvider = auth.InstancePrincipalConfigurationProvider + +// configurationProvider composes multiple OCI configuration providers to make +// authentication work seamlessly across environments. +// Order of precedence (composing provider will try each in order until one works): +// 1) OCI_CLI_* environment variables (via ontariosystems/oci-cli-env-provider) +// 2) OCI_* environment variables (native SDK env provider) +// 3) Config file providers (OCI_CLI_CONFIG_FILE/PROFILE if set) +// 4) Default config provider (~/.oci/config, TF_VAR_*) +// 5) Instance Principals (when running on OCI compute) - lazily evaluated as last resort +func configurationProvider() (common.ConfigurationProvider, error) { + var providers []common.ConfigurationProvider + + // 1) Prefer the CLI-compatible envs used widely in CI/containers (envs only; no implicit fallbacks) + providers = append(providers, ocep.OciCliEnvironmentConfigurationProvider()) + + // 2) Native SDK envs (OCI_tenancy_ocid, OCI_user_ocid, OCI_fingerprint, OCI_private_key_path, OCI_region) + providers = append(providers, common.ConfigurationProviderEnvironmentVariables("OCI", "")) + + // 3) File-based fallbacks + if cfg := os.Getenv(OCICLIConfigFile); cfg != "" { + if prof := os.Getenv(OCICLIProfile); prof != "" { + if p, err := common.ConfigurationProviderFromFileWithProfile(cfg, prof, ""); err == nil { + providers = append(providers, p) + } + } else { + if p, err := common.ConfigurationProviderFromFile(cfg, ""); err == nil { + providers = append(providers, p) + } + } + } + + // 4) Default config provider (~/.oci/config, TF_VAR_*) + providers = append(providers, common.DefaultConfigProvider()) + + // 5) Instance principals for compute instances (lazy, only called if nothing else works) + providers = append(providers, &lazyConfigurationProvider{factory: newIPProvider}) + + return common.ComposingConfigurationProvider(providers) +} + +// lazyConfigurationProvider wraps a ConfigurationProvider factory function and defers its +// creation until the first method call. This is useful for expensive providers +// like Instance Principal that may timeout or fail in non-OCI environments. +type lazyConfigurationProvider struct { + factory func() (common.ConfigurationProvider, error) + provider common.ConfigurationProvider + once sync.Once + err error +} + +var _ common.ConfigurationProvider = (*lazyConfigurationProvider)(nil) + +func (l *lazyConfigurationProvider) init() { + l.provider, l.err = l.factory() +} + +func (l *lazyConfigurationProvider) TenancyOCID() (string, error) { + l.once.Do(l.init) + if l.err != nil { + return "", l.err + } + return l.provider.TenancyOCID() +} + +func (l *lazyConfigurationProvider) UserOCID() (string, error) { + l.once.Do(l.init) + if l.err != nil { + return "", l.err + } + return l.provider.UserOCID() +} + +func (l *lazyConfigurationProvider) KeyFingerprint() (string, error) { + l.once.Do(l.init) + if l.err != nil { + return "", l.err + } + return l.provider.KeyFingerprint() +} + +func (l *lazyConfigurationProvider) Region() (string, error) { + l.once.Do(l.init) + if l.err != nil { + return "", l.err + } + return l.provider.Region() +} + +func (l *lazyConfigurationProvider) KeyID() (string, error) { + l.once.Do(l.init) + if l.err != nil { + return "", l.err + } + return l.provider.KeyID() +} + +func (l *lazyConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { + l.once.Do(l.init) + if l.err != nil { + return nil, l.err + } + return l.provider.PrivateRSAKey() +} + +func (l *lazyConfigurationProvider) AuthType() (common.AuthConfig, error) { + l.once.Do(l.init) + if l.err != nil { + return common.AuthConfig{}, l.err + } + return l.provider.AuthType() +} diff --git a/ocikms/config_provider_test.go b/ocikms/config_provider_test.go new file mode 100644 index 0000000000..12b106a88d --- /dev/null +++ b/ocikms/config_provider_test.go @@ -0,0 +1,503 @@ +package ocikms + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/oracle/oci-go-sdk/v65/common" + "github.com/stretchr/testify/require" +) + +// writeTempRSAKey writes an unencrypted PKCS#1 RSA private key to a temp file. +func writeTempRSAKey(t *testing.T, dir string) string { + t.Helper() + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("generate key: %v", err) + } + keyBytes := x509.MarshalPKCS1PrivateKey(key) + pemBlock := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes} + pemData := pem.EncodeToMemory(pemBlock) + path := filepath.Join(dir, "oci-test-private-key.pem") + if err := os.WriteFile(path, pemData, 0600); err != nil { + t.Fatalf("write key: %v", err) + } + return path +} + +// writeOCIConfig writes a minimal ~/.oci/config style file. +func writeOCIConfig(t *testing.T, path string, profile string, user string, tenancy string, region string, fingerprint string, keyFile string) { + t.Helper() + content := strings.Join([]string{ + "[" + profile + "]", + "user=" + user, + "fingerprint=" + fingerprint, + "key_file=" + keyFile, + "tenancy=" + tenancy, + "region=" + region, + "", + }, "\n") + if err := os.WriteFile(path, []byte(content), 0600); err != nil { + t.Fatalf("write config: %v", err) + } +} + +// clearOCIEnv clears OCI SDK environment variables to prevent interference +func clearOCIEnv(t *testing.T) { + t.Helper() + envVars := []string{ + "OCI_tenancy_ocid", + "OCI_user_ocid", + "OCI_region", + "OCI_fingerprint", + "OCI_private_key_path", + } + for _, env := range envVars { + t.Setenv(env, "") + } +} + +// clearCLIOCIEnv clears OCI CLI environment variables to prevent interference +func clearCLIOCIEnv() { + envVars := []string{ + OCICLITenancy, + OCICLIUser, + OCICLIRegion, + OCICLIFingerprint, + OCICLIKeyFile, + } + for _, env := range envVars { + os.Unsetenv(env) + } +} + +// disableIPProvider disables Instance Principal provider in tests +func disableIPProvider(t *testing.T) { + old := newIPProvider + t.Cleanup(func() { newIPProvider = old }) + newIPProvider = func() (common.ConfigurationProvider, error) { + return nil, fmt.Errorf("ip disabled in tests") + } +} + +func TestConfigurationProvider_OCI_CLI_Env(t *testing.T) { + // Disable IP network path in tests by overriding factory + disableIPProvider(t) + // Isolate HOME to avoid default file provider interference + t.Setenv(HomeEnv, t.TempDir()) + + // Generate key + keyDir := t.TempDir() + keyPath := writeTempRSAKey(t, keyDir) + + // Set OCI_CLI_* envs + t.Setenv(OCICLITenancy, "ocid1.tenancy.oc1..exampletenancy") + t.Setenv(OCICLIUser, "ocid1.user.oc1..exampleuser") + t.Setenv(OCICLIRegion, "us-ashburn-1") + t.Setenv(OCICLIFingerprint, "aa:bb:cc:dd") + t.Setenv(OCICLIKeyFile, keyPath) + + // Ensure other providers are not set by accident + // Native SDK env provider uses lower-case suffixes with prefix OCI_ + clearOCIEnv(t) + + prov, err := configurationProvider() + require.NoError(t, err) + + tenancy, err := prov.TenancyOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.tenancy.oc1..exampletenancy", tenancy) + + user, err := prov.UserOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.user.oc1..exampleuser", user) + + region, err := prov.Region() + require.NoError(t, err) + require.Equal(t, "us-ashburn-1", region) + + fp, err := prov.KeyFingerprint() + require.NoError(t, err) + require.Equal(t, "aa:bb:cc:dd", fp) +} + +func TestConfigurationProvider_OCI_Env(t *testing.T) { + disableIPProvider(t) + // Isolate HOME + t.Setenv(HomeEnv, t.TempDir()) + + keyDir := t.TempDir() + keyPath := writeTempRSAKey(t, keyDir) + + // SDK env provider expects lower-case suffixes + t.Setenv(OCITenancyOCID, "ocid1.tenancy.oc1..ten") + t.Setenv(OCIUserOCID, "ocid1.user.oc1..usr") + t.Setenv(OCIRegion, "eu-frankfurt-1") + t.Setenv(OCIFingerprint, "11:22:33:44") + t.Setenv(OCIPrivateKeyPath, keyPath) + + // Ensure CLI envs are not set (unset, not empty strings) + clearCLIOCIEnv() + + prov, err := configurationProvider() + require.NoError(t, err) + + tenancy, err := prov.TenancyOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.tenancy.oc1..ten", tenancy) + + user, err := prov.UserOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.user.oc1..usr", user) + + region, err := prov.Region() + require.NoError(t, err) + require.Equal(t, "eu-frankfurt-1", region) + + fp, err := prov.KeyFingerprint() + require.NoError(t, err) + require.Equal(t, "11:22:33:44", fp) +} + +func TestConfigurationProvider_FileViaEnv(t *testing.T) { + disableIPProvider(t) + // Isolate HOME + t.Setenv(HomeEnv, t.TempDir()) + + d := t.TempDir() + keyPath := writeTempRSAKey(t, d) + cfgPath := filepath.Join(d, "config") + writeOCIConfig(t, cfgPath, "DEFAULT", "ocid1.user.oc1..fileusr", "ocid1.tenancy.oc1..fileten", "uk-london-1", "ff:ee:dd:cc", keyPath) + + // Point to config via env + t.Setenv(OCICLIConfigFile, cfgPath) + // Explicit profile not required; default is DEFAULT + + // Ensure env-based providers are not set + clearCLIOCIEnv() + + clearOCIEnv(t) + + prov, err := configurationProvider() + require.NoError(t, err) + + tenancy, err := prov.TenancyOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.tenancy.oc1..fileten", tenancy) + + user, err := prov.UserOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.user.oc1..fileusr", user) + + region, err := prov.Region() + require.NoError(t, err) + require.Equal(t, "uk-london-1", region) + + fp, err := prov.KeyFingerprint() + require.NoError(t, err) + require.Equal(t, "ff:ee:dd:cc", fp) +} + +func TestConfigurationProvider_DefaultFileFallback(t *testing.T) { + disableIPProvider(t) + // Set HOME to a temp dir and create ~/.oci/config + home := t.TempDir() + if runtime.GOOS == "windows" { + // USERPROFILE is also consulted on Windows + t.Setenv(UserProfileEnv, home) + } + t.Setenv(HomeEnv, home) + + ociDir := filepath.Join(home, ".oci") + if err := os.MkdirAll(ociDir, 0700); err != nil { + t.Fatalf("mkdir: %v", err) + } + keyPath := writeTempRSAKey(t, ociDir) + cfgPath := filepath.Join(ociDir, "config") + writeOCIConfig(t, cfgPath, "DEFAULT", "ocid1.user.oc1..defusr", "ocid1.tenancy.oc1..deften", "ap-tokyo-1", "00:aa:bb:cc", keyPath) + + // Ensure no env points to explicit file and env providers are empty + os.Unsetenv(OCICLIConfigFile) + + clearCLIOCIEnv() + + clearOCIEnv(t) + + prov, err := common.ConfigurationProviderFromFile(cfgPath, "") + require.NoError(t, err) + + tenancy, err := prov.TenancyOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.tenancy.oc1..deften", tenancy) + + user, err := prov.UserOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.user.oc1..defusr", user) + + region, err := prov.Region() + require.NoError(t, err) + require.Equal(t, "ap-tokyo-1", region) + + fp, err := prov.KeyFingerprint() + require.NoError(t, err) + require.Equal(t, "00:aa:bb:cc", fp) +} + +// ipStubProvider implements common.ConfigurationProvider to stub Instance Principal in tests +type ipStubProvider struct{} + +func (ipStubProvider) TenancyOCID() (string, error) { return "ocid1.tenancy.oc1..ipstub", nil } +func (ipStubProvider) UserOCID() (string, error) { return "", nil } +func (ipStubProvider) KeyFingerprint() (string, error) { return "ip:stub:fp", nil } +func (ipStubProvider) Region() (string, error) { return "me-dubai-1", nil } +func (ipStubProvider) KeyID() (string, error) { return "ST$ipstub", nil } +func (ipStubProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { + // generate a small key for completeness + k, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + return nil, err + } + return k, nil +} +func (ipStubProvider) AuthType() (common.AuthConfig, error) { return common.AuthConfig{}, nil } + +func TestConfigurationProvider_InstancePrincipal_Stubbed(t *testing.T) { + // Override IP factory to return stub, no network + old := newIPProvider + t.Cleanup(func() { newIPProvider = old }) + newIPProvider = func() (common.ConfigurationProvider, error) { return ipStubProvider{}, nil } + + // Isolate environment so that only IP path is viable + t.Setenv(HomeEnv, t.TempDir()) + os.Unsetenv(OCICLIConfigFile) + os.Unsetenv(OCICLIProfile) + + // Clear CLI envs + clearCLIOCIEnv() + + // Clear native SDK envs + clearOCIEnv(t) + + prov, err := configurationProvider() + require.NoError(t, err) + + tenancy, err := prov.TenancyOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.tenancy.oc1..ipstub", tenancy) + + region, err := prov.Region() + require.NoError(t, err) + require.Equal(t, "me-dubai-1", region) + + fp, err := prov.KeyFingerprint() + require.NoError(t, err) + require.Equal(t, "ip:stub:fp", fp) +} + +// TestConfigurationProvider_EarlyExit_SkipsInstancePrincipal verifies that +// when environment variables provide valid credentials, Instance Principal +// is NOT attempted (performance optimization). +func TestConfigurationProvider_EarlyExit_SkipsInstancePrincipal(t *testing.T) { + // Track whether Instance Principal provider was called + ipCalled := false + old := newIPProvider + t.Cleanup(func() { newIPProvider = old }) + newIPProvider = func() (common.ConfigurationProvider, error) { + ipCalled = true + // Return an error - if this is called, we want to know + return nil, fmt.Errorf("Instance Principal should not be called when env vars work") + } + + // Isolate HOME + t.Setenv(HomeEnv, t.TempDir()) + + // Generate key for env var auth + keyDir := t.TempDir() + keyPath := writeTempRSAKey(t, keyDir) + + // Set OCI_CLI_* env vars (highest priority) + t.Setenv(OCICLITenancy, "ocid1.tenancy.oc1..envtest") + t.Setenv(OCICLIUser, "ocid1.user.oc1..envtest") + t.Setenv(OCICLIRegion, "us-phoenix-1") + t.Setenv(OCICLIFingerprint, "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99") + t.Setenv(OCICLIKeyFile, keyPath) + + prov, err := configurationProvider() + require.NoError(t, err) + + // Verify we got credentials from env vars + tenancy, err := prov.TenancyOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.tenancy.oc1..envtest", tenancy) + + region, err := prov.Region() + require.NoError(t, err) + require.Equal(t, "us-phoenix-1", region) + + // CRITICAL: Instance Principal should NOT have been called + require.False(t, ipCalled, "Instance Principal provider should NOT be called when env vars provide valid credentials (early exit optimization)") +} + +// TestConfigurationProvider_EarlyExit_FallsBackToInstancePrincipal verifies that +// when environment variables are missing or invalid, Instance Principal IS attempted. +func TestConfigurationProvider_EarlyExit_FallsBackToInstancePrincipal(t *testing.T) { + // Track whether Instance Principal provider was called + ipCalled := false + old := newIPProvider + t.Cleanup(func() { newIPProvider = old }) + newIPProvider = func() (common.ConfigurationProvider, error) { + ipCalled = true + return ipStubProvider{}, nil + } + + // Isolate environment - NO valid env vars or config files + t.Setenv(HomeEnv, t.TempDir()) + os.Unsetenv(OCICLIConfigFile) + os.Unsetenv(OCICLIProfile) + clearCLIOCIEnv() + clearOCIEnv(t) + + prov, err := configurationProvider() + require.NoError(t, err) + + tenancy, err := prov.TenancyOCID() + require.NoError(t, err) + + // Should have gotten Instance Principal credentials + require.Equal(t, "ocid1.tenancy.oc1..ipstub", tenancy) + + // CRITICAL: Instance Principal SHOULD have been called as fallback + require.True(t, ipCalled, "Instance Principal provider SHOULD be called when env vars don't provide credentials") +} + +// Test suite for lazyConfigurationProvider +func TestLazyProvider_FactoryNotCalledUntilFirstUse(t *testing.T) { + factoryCalled := false + factory := func() (common.ConfigurationProvider, error) { + factoryCalled = true + return ipStubProvider{}, nil + } + + lp := &lazyConfigurationProvider{factory: factory} + + // Factory should NOT be called just by creating the lazy provider + require.False(t, factoryCalled, "Factory should not be called on lazy provider creation") + + // Call a method - this should trigger factory + _, err := lp.TenancyOCID() + require.NoError(t, err) + require.True(t, factoryCalled, "Factory should be called on first method invocation") +} + +func TestLazyProvider_FactoryCalledOnlyOnce(t *testing.T) { + callCount := 0 + factory := func() (common.ConfigurationProvider, error) { + callCount++ + return ipStubProvider{}, nil + } + + lp := &lazyConfigurationProvider{factory: factory} + + // Call multiple methods + _, _ = lp.TenancyOCID() + _, _ = lp.Region() + _, _ = lp.KeyFingerprint() + _, _ = lp.UserOCID() + _, _ = lp.KeyID() + _, _ = lp.PrivateRSAKey() + _, _ = lp.AuthType() + + // Factory should only be called once despite 7 method calls + require.Equal(t, 1, callCount, "Factory should only be called once via sync.Once") +} + +func TestLazyProvider_PropagatesFactoryError(t *testing.T) { + expectedErr := fmt.Errorf("factory initialization failed") + factory := func() (common.ConfigurationProvider, error) { + return nil, expectedErr + } + + lp := &lazyConfigurationProvider{factory: factory} + + // All methods should return the factory error + _, err := lp.TenancyOCID() + require.ErrorIs(t, err, expectedErr) + + _, err = lp.Region() + require.ErrorIs(t, err, expectedErr) + + _, err = lp.KeyFingerprint() + require.ErrorIs(t, err, expectedErr) +} + +func TestLazyProvider_AllMethodsWorkAfterInit(t *testing.T) { + factory := func() (common.ConfigurationProvider, error) { + return ipStubProvider{}, nil + } + + lp := &lazyConfigurationProvider{factory: factory} + + // Test all ConfigurationProvider methods work correctly + tenancy, err := lp.TenancyOCID() + require.NoError(t, err) + require.Equal(t, "ocid1.tenancy.oc1..ipstub", tenancy) + + region, err := lp.Region() + require.NoError(t, err) + require.Equal(t, "me-dubai-1", region) + + fp, err := lp.KeyFingerprint() + require.NoError(t, err) + require.Equal(t, "ip:stub:fp", fp) + + keyID, err := lp.KeyID() + require.NoError(t, err) + require.Equal(t, "ST$ipstub", keyID) + + user, err := lp.UserOCID() + require.NoError(t, err) + require.Equal(t, "", user) + + key, err := lp.PrivateRSAKey() + require.NoError(t, err) + require.NotNil(t, key) + + authType, err := lp.AuthType() + require.NoError(t, err) + require.Equal(t, common.AuthConfig{}, authType) +} + +func TestLazyProvider_ConcurrentAccess(t *testing.T) { + callCount := 0 + factory := func() (common.ConfigurationProvider, error) { + callCount++ + return ipStubProvider{}, nil + } + + lp := &lazyConfigurationProvider{factory: factory} + + // Simulate concurrent access from multiple goroutines + done := make(chan bool, 10) + for i := 0; i < 10; i++ { + go func() { + _, _ = lp.TenancyOCID() + done <- true + }() + } + + // Wait for all goroutines + for i := 0; i < 10; i++ { + <-done + } + + // Factory should still only be called once (sync.Once is thread-safe) + require.Equal(t, 1, callCount, "Factory should only be called once even with concurrent access") +} diff --git a/ocikms/consts.go b/ocikms/consts.go new file mode 100644 index 0000000000..25ad44ace9 --- /dev/null +++ b/ocikms/consts.go @@ -0,0 +1,53 @@ +package ocikms + +// Key type constants +const ( + // KeyTypeIdentifier is the string used to identify an OCI KMS MasterKey in configuration + KeyTypeIdentifier = "oci_kms" +) + +// OCI CLI environment variables (used by oci-cli-env-provider) +const ( + // OCICLIConfigFile is the environment variable for OCI CLI config file path + OCICLIConfigFile = "OCI_CLI_CONFIG_FILE" + // OCICLIProfile is the environment variable for OCI CLI profile name + OCICLIProfile = "OCI_CLI_PROFILE" + // OCICLITenancy is the environment variable for OCI CLI tenancy OCID + OCICLITenancy = "OCI_CLI_TENANCY" + // OCICLIUser is the environment variable for OCI CLI user OCID + OCICLIUser = "OCI_CLI_USER" + // OCICLIRegion is the environment variable for OCI CLI region + OCICLIRegion = "OCI_CLI_REGION" + // OCICLIFingerprint is the environment variable for OCI CLI key fingerprint + OCICLIFingerprint = "OCI_CLI_FINGERPRINT" + // OCICLIKeyFile is the environment variable for OCI CLI private key file path + OCICLIKeyFile = "OCI_CLI_KEY_FILE" +) + +// OCI native SDK environment variables (lowercase after OCI_ prefix) +const ( + // OCITenancyOCID is the environment variable for OCI tenancy OCID (OCI_tenancy_ocid) + OCITenancyOCID = "OCI_tenancy_ocid" + // OCIUserOCID is the environment variable for OCI user OCID (OCI_user_ocid) + OCIUserOCID = "OCI_user_ocid" + // OCIRegion is the environment variable for OCI region (OCI_region) + OCIRegion = "OCI_region" + // OCIFingerprint is the environment variable for OCI key fingerprint (OCI_fingerprint) + OCIFingerprint = "OCI_fingerprint" + // OCIPrivateKeyPath is the environment variable for OCI private key path (OCI_private_key_path) + OCIPrivateKeyPath = "OCI_private_key_path" +) + +// Other environment variables +const ( + // HomeEnv is the HOME environment variable + HomeEnv = "HOME" + // UserProfileEnv is the USERPROFILE environment variable (Windows) + UserProfileEnv = "USERPROFILE" +) + +// Logger constants +const ( + // LoggerName is the name used for the OCI KMS logger + LoggerName = "OCIKMS" +) diff --git a/ocikms/keysource.go b/ocikms/keysource.go new file mode 100644 index 0000000000..aca08e2409 --- /dev/null +++ b/ocikms/keysource.go @@ -0,0 +1,265 @@ +package ocikms + +import ( + "context" + "encoding/base64" + "fmt" + "strings" + "time" + + "github.com/getsops/sops/v3/logging" + + "github.com/oracle/oci-go-sdk/v65/common" + "github.com/oracle/oci-go-sdk/v65/keymanagement" + "github.com/sirupsen/logrus" +) + +var ( + // log is the global logger for any OCI KMS MasterKey. + log *logrus.Logger + // ocikmsTTL is the duration after which a MasterKey requires rotation. + ocikmsTTL = time.Hour * 24 * 30 * 6 +) + +const ( + // ocidParts is the number of parts in an OCID, separated by ".", eg: "ocid1.key.oc1.uk-london-1.aaaalgz5aacmg.aaaailjtjbkbc5ufsorrihgv2agugpfe7wrtngukihgkybqxcoozz7sbh6lq" + ocidParts = 6 +) + +func init() { + log = logging.NewLogger(LoggerName) +} + +// MasterKey is an Oracle Cloud KMS key used to encrypt and decrypt sops' data key. +type MasterKey struct { + // Ocid is the Oracle Cloud Identifier for the KMS key + Ocid string + // EncryptedKey stores the SOPS data key in its encrypted form + EncryptedKey string + // CreationDate is when this MasterKey was created + CreationDate time.Time + + // configProvider is used to configure the OCI client with credentials. + // It can be injected by a (local) keyservice.KeyServiceServer using + // ConfigurationProvider.ApplyToMasterKey. If nil, a fresh config + // provider is created on each operation which tries multiple auth methods. + configProvider common.ConfigurationProvider + // httpClient is used to override the default HTTP client used by the OCI client. + // Mostly useful for testing purposes. + httpClient common.HTTPRequestDispatcher +} + +func NewMasterKeyFromOCID(ocid string) *MasterKey { + return &MasterKey{ + Ocid: strings.Replace(ocid, " ", "", -1), + CreationDate: time.Now().UTC(), + } +} + +func MasterKeysFromOCIDString(ocids string) []*MasterKey { + var keys []*MasterKey + if ocids == "" { + return keys + } + for _, s := range strings.Split(ocids, ",") { + keys = append(keys, NewMasterKeyFromOCID(s)) + } + return keys +} + +// createCryptoClient creates a new OCI KMS client. It uses the injected configProvider +// if available, otherwise creates a new one on each call. If httpClient is set, it uses +// that for HTTP requests (useful for testing). +func (key *MasterKey) createCryptoClient() (client keymanagement.KmsCryptoClient, err error) { + region, vaultExt, err := extractRefs(key) + if err != nil { + log.WithField("ocid", key.Ocid).Errorf("Failed to extract region and vault from OCID: %s", err) + return client, fmt.Errorf("failed to parse OCI KMS key OCID: %w", err) + } + + cryptoEndpointTemplate := fmt.Sprintf("https://%s-crypto.kms.{region}.{secondLevelDomain}", vaultExt) + cryptoEndpoint := common.StringToRegion(region).EndpointForTemplate("kms", cryptoEndpointTemplate) + log.WithField("endpoint", cryptoEndpoint).Info("Creating OCI KMS client") + + // Use injected config provider if available, otherwise create a fresh one + cfg := key.configProvider + if cfg == nil { + cfg, err = configurationProvider() + if err != nil { + return client, fmt.Errorf("failed to create OCI configuration provider: %w", err) + } + } + + client, err = keymanagement.NewKmsCryptoClientWithConfigurationProvider(cfg, cryptoEndpoint) + if err != nil { + return client, fmt.Errorf("failed to create OCI KMS client: %w", err) + } + + // Inject custom HTTP client if provided (for testing) + if key.httpClient != nil { + client.HTTPClient = key.httpClient + } + + return client, nil +} + +func extractRefs(key *MasterKey) (string, string, error) { + parts := strings.Split(key.Ocid, ".") + if len(parts) != ocidParts { + return "", "", fmt.Errorf("invalid OCID format '%s': expected %d parts, got %d", key.Ocid, ocidParts, len(parts)) + } + region := parts[3] + vaultExt := parts[4] + return region, vaultExt, nil +} + +// 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 OCI KMS and stores the result +// in the EncryptedKey field. +// +// Consider using EncryptContext instead. +func (key *MasterKey) Encrypt(dataKey []byte) error { + return key.EncryptContext(context.Background(), dataKey) +} + +// EncryptContext takes a sops data key, encrypts it with OCI KMS and stores the result +// in the EncryptedKey field. +func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error { + c, err := key.createCryptoClient() + if err != nil { + log.WithField("ocid", key.Ocid).Info("Encryption failed") + return fmt.Errorf("failed to create OCI KMS service: %w", err) + } + + data := base64.StdEncoding.EncodeToString(dataKey) + + res, err := c.Encrypt(ctx, keymanagement.EncryptRequest{ + EncryptDataDetails: keymanagement.EncryptDataDetails{ + KeyId: common.String(key.Ocid), + Plaintext: &data, + }, + RequestMetadata: common.RequestMetadata{}, + }) + + if err != nil { + log.WithError(err).WithField("ocid", key.Ocid). + Error("Encryption failed") + return fmt.Errorf("failed to encrypt sops data key with OCI KMS key: %w", err) + } + + key.EncryptedKey = *res.EncryptedData.Ciphertext + log.WithField("ocid", key.Ocid).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 OCI KMS and returns the result. +// +// Consider using DecryptContext instead. +func (key *MasterKey) Decrypt() ([]byte, error) { + return key.DecryptContext(context.Background()) +} + +// DecryptContext decrypts the EncryptedKey field with OCI KMS and returns the result. +func (key *MasterKey) DecryptContext(ctx context.Context) ([]byte, error) { + c, err := key.createCryptoClient() + if err != nil { + log.WithField("ocid", key.Ocid).Info("Decryption failed") + return nil, fmt.Errorf("failed to create OCI KMS service: %w", err) + } + + res, err := c.Decrypt(ctx, keymanagement.DecryptRequest{ + DecryptDataDetails: keymanagement.DecryptDataDetails{ + Ciphertext: &key.EncryptedKey, + KeyId: &key.Ocid, + }, + }) + + if err != nil { + log.WithError(err).WithField("ocid", key.Ocid).Error("Decryption failed") + return nil, fmt.Errorf("failed to decrypt sops data key with OCI KMS key: %w", err) + } + + plaintext, err := base64.StdEncoding.DecodeString(*res.Plaintext) + if err != nil { + log.WithError(err).WithField("ocid", key.Ocid).Error("Decryption failed") + return nil, fmt.Errorf("failed to base64 decode OCI KMS decrypted key: %w", err) + } + + log.WithField("ocid", key.Ocid).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) > ocikmsTTL +} + +// ToString converts the key to a string representation +func (key *MasterKey) ToString() string { + return key.Ocid +} + +// ToMap converts the MasterKey to a map for serialization purposes +func (key MasterKey) ToMap() map[string]interface{} { + out := make(map[string]interface{}) + out["ocid"] = key.Ocid + out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) + out["enc"] = key.EncryptedKey + return out +} + +// TypeToIdentifier returns the string identifier for the MasterKey type. +func (key *MasterKey) TypeToIdentifier() string { + return KeyTypeIdentifier +} + +// ConfigurationProvider is a wrapper around common.ConfigurationProvider used for +// authentication towards OCI KMS. +type ConfigurationProvider struct { + provider common.ConfigurationProvider +} + +// NewConfigurationProvider creates a new ConfigurationProvider with the provided +// common.ConfigurationProvider. +func NewConfigurationProvider(cp common.ConfigurationProvider) *ConfigurationProvider { + return &ConfigurationProvider{provider: cp} +} + +// ApplyToMasterKey configures the ConfigurationProvider on the provided key. +func (c ConfigurationProvider) ApplyToMasterKey(key *MasterKey) { + key.configProvider = c.provider +} + +// HTTPClient is a wrapper around common.HTTPRequestDispatcher used for +// configuring the OCI KMS client HTTP requests. +type HTTPClient struct { + client common.HTTPRequestDispatcher +} + +// NewHTTPClient creates a new HTTPClient with the provided common.HTTPRequestDispatcher. +func NewHTTPClient(hc common.HTTPRequestDispatcher) *HTTPClient { + return &HTTPClient{client: hc} +} + +// ApplyToMasterKey configures the HTTP client on the provided key. +func (h HTTPClient) ApplyToMasterKey(key *MasterKey) { + key.httpClient = h.client +} diff --git a/ocikms/keysource_integration_test.go b/ocikms/keysource_integration_test.go new file mode 100644 index 0000000000..d74c137ef9 --- /dev/null +++ b/ocikms/keysource_integration_test.go @@ -0,0 +1,495 @@ +package ocikms + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "testing" + "time" + + "github.com/oracle/oci-go-sdk/v65/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + // testOCID is a valid OCID format for testing + testOCID = "ocid1.key.oc1.uk-london-1.aaaalgz5aacmg.aaaailjtjbkbc5ufsorrihgv2agugpfe7wrtngukihgkybqxcoozz7sbh6lq" + // testDataKey is a dummy 32-byte data key for testing + testDataKey = "testtesttesttesttesttesttest1234" +) + +// mockHTTPClient implements common.HTTPRequestDispatcher for testing +type mockHTTPClient struct { + // requests stores all requests made for verification + requests []*http.Request + // responses is a queue of responses to return + responses []*http.Response + // errors is a queue of errors to return + errors []error + // currentIndex tracks which response to return next + currentIndex int +} + +func newMockHTTPClient() *mockHTTPClient { + return &mockHTTPClient{ + requests: make([]*http.Request, 0), + responses: make([]*http.Response, 0), + errors: make([]error, 0), + } +} + +// Do implements the common.HTTPRequestDispatcher interface +func (m *mockHTTPClient) Do(req *http.Request) (*http.Response, error) { + // Store the request for verification + m.requests = append(m.requests, req) + + if m.currentIndex >= len(m.responses) && m.currentIndex >= len(m.errors) { + return nil, fmt.Errorf("mock client: no more responses configured") + } + + // Return error if configured + if m.currentIndex < len(m.errors) && m.errors[m.currentIndex] != nil { + err := m.errors[m.currentIndex] + m.currentIndex++ + return nil, err + } + + // Return response if configured + if m.currentIndex < len(m.responses) { + resp := m.responses[m.currentIndex] + m.currentIndex++ + return resp, nil + } + + return nil, fmt.Errorf("mock client: no response or error configured for request %d", m.currentIndex) +} + +// addResponse adds a mock HTTP response to the queue +func (m *mockHTTPClient) addResponse(statusCode int, body string) { + resp := &http.Response{ + StatusCode: statusCode, + Body: io.NopCloser(strings.NewReader(body)), + Header: make(http.Header), + } + resp.Header.Set("Content-Type", "application/json") + m.responses = append(m.responses, resp) +} + +// addError adds an error to the queue +func (m *mockHTTPClient) addError(err error) { + m.errors = append(m.errors, err) +} + +// getLastRequest returns the most recent request made +func (m *mockHTTPClient) getLastRequest() *http.Request { + if len(m.requests) == 0 { + return nil + } + return m.requests[len(m.requests)-1] +} + +// mockConfigProvider implements common.ConfigurationProvider for testing +type mockConfigProvider struct { + privateKey *rsa.PrivateKey +} + +func newMockConfigProvider() mockConfigProvider { + // Generate a test RSA key (required by OCI SDK for request signing) + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic(fmt.Sprintf("failed to generate test RSA key: %v", err)) + } + return mockConfigProvider{ + privateKey: privateKey, + } +} + +func (m mockConfigProvider) TenancyOCID() (string, error) { + return "ocid1.tenancy.oc1..test", nil +} + +func (m mockConfigProvider) UserOCID() (string, error) { + return "ocid1.user.oc1..test", nil +} + +func (m mockConfigProvider) KeyFingerprint() (string, error) { + return "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00", nil +} + +func (m mockConfigProvider) Region() (string, error) { + return "uk-london-1", nil +} + +func (m mockConfigProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { + return m.privateKey, nil +} + +func (m mockConfigProvider) KeyID() (string, error) { + tenancy, _ := m.TenancyOCID() + user, _ := m.UserOCID() + fingerprint, _ := m.KeyFingerprint() + return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil +} + +func (m mockConfigProvider) AuthType() (common.AuthConfig, error) { + return common.AuthConfig{ + AuthType: common.UserPrincipal, + }, nil +} + +// createTestMasterKey creates a MasterKey configured for testing with mock HTTP client +func createTestMasterKey(ocid string, mockHTTP *mockHTTPClient) *MasterKey { + key := NewMasterKeyFromOCID(ocid) + + // Inject mock config provider to avoid real auth + configProvider := NewConfigurationProvider(newMockConfigProvider()) + configProvider.ApplyToMasterKey(key) + + // Inject mock HTTP client + if mockHTTP != nil { + httpClient := NewHTTPClient(mockHTTP) + httpClient.ApplyToMasterKey(key) + } + + return key +} + +// createEncryptResponse creates a mock OCI KMS encrypt response +func createEncryptResponse(ciphertext string) string { + response := map[string]interface{}{ + "ciphertext": ciphertext, + } + data, _ := json.Marshal(response) + return string(data) +} + +// createDecryptResponse creates a mock OCI KMS decrypt response +func createDecryptResponse(plaintext string) string { + response := map[string]interface{}{ + "plaintext": plaintext, + } + data, _ := json.Marshal(response) + return string(data) +} + +func TestEncryptContext(t *testing.T) { + tests := []struct { + name string + dataKey []byte + mockResponse string + mockStatusCode int + mockError error + expectError bool + errorContains string + }{ + { + name: "successful encryption", + dataKey: []byte(testDataKey), + mockResponse: createEncryptResponse("ENCRYPTED_DATA_KEY_BASE64"), + mockStatusCode: 200, + expectError: false, + }, + { + name: "network error", + dataKey: []byte(testDataKey), + mockError: fmt.Errorf("network timeout"), + expectError: true, + errorContains: "failed to encrypt sops data key with OCI KMS key", + }, + { + name: "HTTP 500 error", + dataKey: []byte(testDataKey), + mockResponse: `{"code":"InternalServerError","message":"Internal server error"}`, + mockStatusCode: 500, + expectError: true, + errorContains: "failed to encrypt sops data key with OCI KMS key", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTP := newMockHTTPClient() + + if tt.mockError != nil { + mockHTTP.addError(tt.mockError) + } else { + mockHTTP.addResponse(tt.mockStatusCode, tt.mockResponse) + } + + key := createTestMasterKey(testOCID, mockHTTP) + + err := key.EncryptContext(context.Background(), tt.dataKey) + + if tt.expectError { + assert.Error(t, err) + if tt.errorContains != "" { + assert.Contains(t, err.Error(), tt.errorContains) + } + } else { + assert.NoError(t, err) + assert.NotEmpty(t, key.EncryptedKey) + assert.Equal(t, "ENCRYPTED_DATA_KEY_BASE64", key.EncryptedKey) + } + + // Verify request was made (unless error before request) + if tt.mockError == nil || tt.mockStatusCode > 0 { + assert.Greater(t, len(mockHTTP.requests), 0, "should have made at least one HTTP request") + } + }) + } +} + +func TestDecryptContext(t *testing.T) { + dataKeyBase64 := base64.StdEncoding.EncodeToString([]byte(testDataKey)) + + tests := []struct { + name string + encryptedKey string + mockResponse string + mockStatusCode int + mockError error + expectError bool + errorContains string + expectedPlain []byte + }{ + { + name: "successful decryption", + encryptedKey: "ENCRYPTED_DATA_KEY_BASE64", + mockResponse: createDecryptResponse(dataKeyBase64), + mockStatusCode: 200, + expectError: false, + expectedPlain: []byte(testDataKey), + }, + { + name: "network error", + encryptedKey: "ENCRYPTED_DATA_KEY_BASE64", + mockError: fmt.Errorf("connection refused"), + expectError: true, + errorContains: "failed to decrypt sops data key with OCI KMS key", + }, + { + name: "invalid ciphertext", + encryptedKey: "INVALID_CIPHERTEXT", + mockResponse: `{"code":"InvalidCiphertext","message":"The ciphertext is invalid"}`, + mockStatusCode: 400, + expectError: true, + errorContains: "failed to decrypt sops data key with OCI KMS key", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHTTP := newMockHTTPClient() + + if tt.mockError != nil { + mockHTTP.addError(tt.mockError) + } else { + mockHTTP.addResponse(tt.mockStatusCode, tt.mockResponse) + } + + key := createTestMasterKey(testOCID, mockHTTP) + key.EncryptedKey = tt.encryptedKey + + plaintext, err := key.DecryptContext(context.Background()) + + if tt.expectError { + assert.Error(t, err) + if tt.errorContains != "" { + assert.Contains(t, err.Error(), tt.errorContains) + } + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedPlain, plaintext) + } + }) + } +} + +func TestHTTPClientInjection(t *testing.T) { + mockHTTP := newMockHTTPClient() + mockHTTP.addResponse(200, createEncryptResponse("ENCRYPTED")) + + key := NewMasterKeyFromOCID(testOCID) + + // Inject config provider (required for client creation) + configProvider := NewConfigurationProvider(newMockConfigProvider()) + configProvider.ApplyToMasterKey(key) + + // Inject HTTP client + httpClient := NewHTTPClient(mockHTTP) + httpClient.ApplyToMasterKey(key) + + // Perform encryption + err := key.EncryptContext(context.Background(), []byte("test")) + require.NoError(t, err) + + // Verify our mock client was used + assert.Equal(t, 1, len(mockHTTP.requests), "should have used injected HTTP client") +} + +func TestEncryptDecryptRoundTrip(t *testing.T) { + dataKey := []byte("this-is-a-32-byte-test-key-12345") + dataKeyBase64 := base64.StdEncoding.EncodeToString(dataKey) + ciphertext := "MOCK_ENCRYPTED_CIPHERTEXT_BASE64" + + mockHTTP := newMockHTTPClient() + + // Mock encrypt response + mockHTTP.addResponse(200, createEncryptResponse(ciphertext)) + // Mock decrypt response + mockHTTP.addResponse(200, createDecryptResponse(dataKeyBase64)) + + key := createTestMasterKey(testOCID, mockHTTP) + + // Encrypt + err := key.EncryptContext(context.Background(), dataKey) + require.NoError(t, err) + assert.Equal(t, ciphertext, key.EncryptedKey) + + // Decrypt + decrypted, err := key.DecryptContext(context.Background()) + require.NoError(t, err) + assert.Equal(t, dataKey, decrypted) + + // Verify two requests were made + assert.Equal(t, 2, len(mockHTTP.requests)) +} + +func TestContextCancellation(t *testing.T) { + mockHTTP := newMockHTTPClient() + mockHTTP.addResponse(200, createEncryptResponse("ENCRYPTED")) + + key := createTestMasterKey(testOCID, mockHTTP) + + // Create a context that's already cancelled + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + // Attempt encryption with cancelled context + err := key.EncryptContext(ctx, []byte("test")) + + // Should fail due to context cancellation + // Note: actual behavior depends on when OCI SDK checks context + // This test documents the expected behavior + _ = err // May or may not error depending on when context is checked +} + +func TestEncryptIfNeeded(t *testing.T) { + dataKey := []byte("test-data-key-32-bytes-long-1234") + + t.Run("encrypts when EncryptedKey is empty", func(t *testing.T) { + mockHTTP := newMockHTTPClient() + mockHTTP.addResponse(200, createEncryptResponse("ENCRYPTED")) + + key := createTestMasterKey(testOCID, mockHTTP) + key.EncryptedKey = "" // Explicitly empty + + err := key.EncryptIfNeeded(dataKey) + require.NoError(t, err) + assert.Equal(t, "ENCRYPTED", key.EncryptedKey) + assert.Equal(t, 1, len(mockHTTP.requests)) + }) + + t.Run("skips encryption when EncryptedKey exists", func(t *testing.T) { + mockHTTP := newMockHTTPClient() + // Don't add any responses - should not be called + + key := createTestMasterKey(testOCID, mockHTTP) + key.EncryptedKey = "ALREADY_ENCRYPTED" + + err := key.EncryptIfNeeded(dataKey) + require.NoError(t, err) + assert.Equal(t, "ALREADY_ENCRYPTED", key.EncryptedKey) + assert.Equal(t, 0, len(mockHTTP.requests), "should not make HTTP request") + }) +} + +func TestNeedsRotation(t *testing.T) { + t.Run("new key does not need rotation", func(t *testing.T) { + key := NewMasterKeyFromOCID(testOCID) + assert.False(t, key.NeedsRotation()) + }) + + t.Run("old key needs rotation", func(t *testing.T) { + key := NewMasterKeyFromOCID(testOCID) + // Set creation date to 7 months ago (> 6 months) + key.CreationDate = time.Now().UTC().Add(-7 * 30 * 24 * time.Hour) + assert.True(t, key.NeedsRotation()) + }) + + t.Run("6-month-old key does not need rotation", func(t *testing.T) { + key := NewMasterKeyFromOCID(testOCID) + // Set creation date to just under 6 months ago + key.CreationDate = time.Now().UTC().Add(-6*30*24*time.Hour + time.Hour) + // Should not need rotation (> is used, not >=) + assert.False(t, key.NeedsRotation()) + }) +} + +func TestToString(t *testing.T) { + key := NewMasterKeyFromOCID(testOCID) + assert.Equal(t, testOCID, key.ToString()) +} + +func TestTypeToIdentifier(t *testing.T) { + key := NewMasterKeyFromOCID(testOCID) + assert.Equal(t, KeyTypeIdentifier, key.TypeToIdentifier()) + assert.Equal(t, "oci_kms", key.TypeToIdentifier()) +} + +func TestExtractRefs(t *testing.T) { + tests := []struct { + name string + ocid string + expectError bool + expectedRegion string + expectedVault string + }{ + { + name: "valid OCID", + ocid: "ocid1.key.oc1.uk-london-1.aaaalgz5aacmg.aaaailjtjbkbc5ufsorrihgv2agugpfe7wrtngukihgkybqxcoozz7sbh6lq", + expectError: false, + expectedRegion: "uk-london-1", + expectedVault: "aaaalgz5aacmg", + }, + { + name: "valid OCID 2", + ocid: "ocid1.vault.oc1.iad.asdadsasdagz5aacmg.abwgiljtjasdasdasdagugpfe7wrtngukihgkybqxcoozz7sbh6lq", + expectError: false, + expectedRegion: "iad", + expectedVault: "asdadsasdagz5aacmg", + }, + { + name: "invalid OCID - too few parts", + ocid: "ocid1.key.oc1", + expectError: true, + }, + { + name: "invalid OCID - too many parts", + ocid: "ocid1.key.oc1.region.vault.extra.extra", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + key := NewMasterKeyFromOCID(tt.ocid) + region, vault, err := extractRefs(key) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedRegion, region) + assert.Equal(t, tt.expectedVault, vault) + } + }) + } +} diff --git a/ocikms/keysource_test.go b/ocikms/keysource_test.go new file mode 100644 index 0000000000..e3efe1c629 --- /dev/null +++ b/ocikms/keysource_test.go @@ -0,0 +1,36 @@ +package ocikms + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestMasterKeysFromOCIDString(t *testing.T) { + s := "ocid1.key.oc1.uk-london-1.aaaalgz5aacmg.aaaailjtjbkbc5ufsorrihgv2agugpfe7wrtngukihgkybqxcoozz7sbh6lq, ocid1.key.oc1.uk-london-1.bbbblgz5aacmg.bbbbiljtjbkbc5ufsorrihgv2agugpfe7wrtngukihgkybqxcoozz7sbh6lq" + ks := MasterKeysFromOCIDString(s) + k1 := ks[0] + k2 := ks[1] + expectedOcid1 := "ocid1.key.oc1.uk-london-1.aaaalgz5aacmg.aaaailjtjbkbc5ufsorrihgv2agugpfe7wrtngukihgkybqxcoozz7sbh6lq" + expectedOcid2 := "ocid1.key.oc1.uk-london-1.bbbblgz5aacmg.bbbbiljtjbkbc5ufsorrihgv2agugpfe7wrtngukihgkybqxcoozz7sbh6lq" + if k1.Ocid != expectedOcid1 { + t.Errorf("Ocid mismatch. Expected %s, found %s", expectedOcid1, k1.Ocid) + } + if k2.Ocid != expectedOcid2 { + t.Errorf("Ocid mismatch. Expected %s, found %s", expectedOcid2, k2.Ocid) + } +} + +func TestKeyToMap(t *testing.T) { + key := MasterKey{ + CreationDate: time.Date(2016, time.October, 31, 10, 0, 0, 0, time.UTC), + Ocid: "foo", + EncryptedKey: "this is encrypted", + } + assert.Equal(t, map[string]interface{}{ + "ocid": "foo", + "enc": "this is encrypted", + "created_at": "2016-10-31T10:00:00Z", + }, key.ToMap()) +} diff --git a/stores/stores.go b/stores/stores.go index 4d7f3788c8..18fca58272 100644 --- a/stores/stores.go +++ b/stores/stores.go @@ -21,6 +21,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/ocikms" "github.com/getsops/sops/v3/pgp" ) @@ -50,6 +51,7 @@ type Metadata struct { 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"` + OCIKMSKeys []ocikmskey `yaml:"oci_kms" json:"oci_kms"` LastModified string `yaml:"lastmodified" json:"lastmodified"` MessageAuthenticationCode string `yaml:"mac" json:"mac"` PGPKeys []pgpkey `yaml:"pgp,omitempty" json:"pgp,omitempty"` @@ -70,6 +72,7 @@ type keygroup struct { AzureKeyVaultKeys []azkvkey `yaml:"azure_kv,omitempty" json:"azure_kv,omitempty"` VaultKeys []vaultkey `yaml:"hc_vault" json:"hc_vault"` AgeKeys []agekey `yaml:"age" json:"age"` + OCIKMSKeys []ocikmskey `yaml:"oci_kms" json:"oci_kms"` } type pgpkey struct { @@ -114,6 +117,12 @@ type agekey struct { EncryptedDataKey string `yaml:"enc" json:"enc"` } +type ocikmskey struct { + Ocid string `yaml:"ocid" json:"ocid"` + 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 @@ -136,6 +145,7 @@ func MetadataFromInternal(sopsMetadata sops.Metadata) Metadata { m.VaultKeys = vaultKeysFromGroup(group) m.AzureKeyVaultKeys = azkvKeysFromGroup(group) m.AgeKeys = ageKeysFromGroup(group) + m.OCIKMSKeys = ocikmsKeysFromGroup(group) } else { for _, group := range sopsMetadata.KeyGroups { m.KeyGroups = append(m.KeyGroups, keygroup{ @@ -145,6 +155,7 @@ func MetadataFromInternal(sopsMetadata sops.Metadata) Metadata { VaultKeys: vaultKeysFromGroup(group), AzureKeyVaultKeys: azkvKeysFromGroup(group), AgeKeys: ageKeysFromGroup(group), + OCIKMSKeys: ocikmsKeysFromGroup(group), }) } } @@ -241,6 +252,20 @@ func ageKeysFromGroup(group sops.KeyGroup) (keys []agekey) { return } +func ocikmsKeysFromGroup(group sops.KeyGroup) (keys []ocikmskey) { + for _, key := range group { + switch key := key.(type) { + case *ocikms.MasterKey: + keys = append(keys, ocikmskey{ + Ocid: key.Ocid, + 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) @@ -295,7 +320,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, ociKmsKeys []ocikmskey) (sops.KeyGroup, error) { var internalGroup sops.KeyGroup for _, kmsKey := range kmsKeys { k, err := kmsKey.toInternal() @@ -339,13 +364,20 @@ func internalGroupFrom(kmsKeys []kmskey, pgpKeys []pgpkey, gcpKmsKeys []gcpkmske } internalGroup = append(internalGroup, k) } + for _, ociKmsKey := range ociKmsKeys { + k, err := ociKmsKey.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.OCIKMSKeys) > 0 { + internalGroup, err := internalGroupFrom(m.KMSKeys, m.PGPKeys, m.GCPKMSKeys, m.AzureKeyVaultKeys, m.VaultKeys, m.AgeKeys, m.OCIKMSKeys) if err != nil { return nil, err } @@ -353,7 +385,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.OCIKMSKeys) if err != nil { return nil, err } @@ -439,6 +471,18 @@ func (ageKey *agekey) toInternal() (*age.MasterKey, error) { }, nil } +func (ociKmsKey *ocikmskey) toInternal() (*ocikms.MasterKey, error) { + creationDate, err := time.Parse(time.RFC3339, ociKmsKey.CreatedAt) + if err != nil { + return nil, err + } + return &ocikms.MasterKey{ + Ocid: ociKmsKey.Ocid, + EncryptedKey: ociKmsKey.EncryptedDataKey, + CreationDate: creationDate, + }, nil +} + // ExampleComplexTree is an example sops.Tree object exhibiting complex relationships var ExampleComplexTree = sops.Tree{ Branches: sops.TreeBranches{