diff --git a/go.mod b/go.mod index b221f49e72..1b38d92e5f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.22.1 toolchain go1.22.11 require ( - cloud.google.com/go/kms v1.1.0 cloud.google.com/go/storage v1.21.0 github.com/Azure/azure-sdk-for-go v57.1.0+incompatible github.com/Azure/azure-storage-blob-go v0.14.0 diff --git a/go.sum b/go.sum index 59ef3f736f..f21ce7a63f 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,6 @@ cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/iam v0.1.1 h1:4CapQyNFjiksks1/x7jsvsygFPhihslYk5GptIrlX68= cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw= -cloud.google.com/go/kms v1.1.0 h1:1yc4rLqCkVDS9Zvc7m+3mJ47kw0Uo5Q5+sMjcmUVUeM= -cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -2461,7 +2459,6 @@ golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2664,7 +2661,6 @@ google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= @@ -2752,9 +2748,7 @@ google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= diff --git a/pkg/cloud/amazon/aws_kms.go b/pkg/cloud/amazon/aws_kms.go deleted file mode 100644 index efe876b634..0000000000 --- a/pkg/cloud/amazon/aws_kms.go +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2020 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package amazon - -import ( - "context" - "net/url" - "strings" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/kms" - "github.com/cockroachdb/cockroach/pkg/cloud" - "github.com/cockroachdb/cockroach/pkg/util/log" - "github.com/cockroachdb/errors" -) - -const awsScheme = "aws" - -type awsKMS struct { - kms *kms.KMS - customerMasterKeyID string -} - -var _ cloud.KMS = &awsKMS{} - -func init() { - cloud.RegisterKMSFromURIFactory(MakeAWSKMS, awsScheme) -} - -type kmsURIParams struct { - accessKey string - secret string - tempToken string - endpoint string - region string - auth string -} - -func resolveKMSURIParams(kmsURI url.URL) kmsURIParams { - params := kmsURIParams{ - accessKey: kmsURI.Query().Get(AWSAccessKeyParam), - secret: kmsURI.Query().Get(AWSSecretParam), - tempToken: kmsURI.Query().Get(AWSTempTokenParam), - endpoint: kmsURI.Query().Get(AWSEndpointParam), - region: kmsURI.Query().Get(KMSRegionParam), - auth: kmsURI.Query().Get(cloud.AuthParam), - } - - // AWS secrets often contain + characters, which must be escaped when - // included in a query string; otherwise, they represent a space character. - // More than a few users have been bitten by this. - // - // Luckily, AWS secrets are base64-encoded data and thus will never actually - // contain spaces. We can convert any space characters we see to + - // characters to recover the original secret. - params.secret = strings.Replace(params.secret, " ", "+", -1) - return params -} - -// MakeAWSKMS is the factory method which returns a configured, ready-to-use -// AWS KMS object. -func MakeAWSKMS(ctx context.Context, uri string, env cloud.KMSEnv) (cloud.KMS, error) { - if env.KMSConfig().DisableOutbound { - return nil, errors.New("external IO must be enabled to use AWS KMS") - } - kmsURI, err := url.ParseRequestURI(uri) - if err != nil { - return nil, err - } - - // Extract the URI parameters required to setup the AWS KMS session. - kmsURIParams := resolveKMSURIParams(*kmsURI) - region := kmsURIParams.region - awsConfig := &aws.Config{ - Credentials: credentials.NewStaticCredentials(kmsURIParams.accessKey, - kmsURIParams.secret, kmsURIParams.tempToken), - } - awsConfig.Logger = newLogAdapter(ctx) - if log.V(2) { - awsConfig.LogLevel = awsVerboseLogging - } - - if kmsURIParams.endpoint != "" { - if env.KMSConfig().DisableHTTP { - return nil, errors.New( - "custom endpoints disallowed for aws kms due to --aws-kms-disable-http flag") - } - awsConfig.Endpoint = &kmsURIParams.endpoint - if region == "" { - // TODO(adityamaru): Think about what the correct way to handle this - // situation is. - region = "default-region" - } - client, err := cloud.MakeHTTPClient(env.ClusterSettings()) - if err != nil { - return nil, err - } - awsConfig.HTTPClient = client - } - - // "specified": use credentials provided in URI params; error if not present. - // "implicit": enable SharedConfig, which loads in credentials from environment. - // Detailed in https://docs.aws.amazon.com/sdk-for-go/api/aws/session/ - // "": default to `specified`. - opts := session.Options{} - switch kmsURIParams.auth { - case "", cloud.AuthParamSpecified: - if kmsURIParams.accessKey == "" { - return nil, errors.Errorf( - "%s is set to '%s', but %s is not set", - cloud.AuthParam, - cloud.AuthParamSpecified, - AWSAccessKeyParam, - ) - } - if kmsURIParams.secret == "" { - return nil, errors.Errorf( - "%s is set to '%s', but %s is not set", - cloud.AuthParam, - cloud.AuthParamSpecified, - AWSSecretParam, - ) - } - opts.Config.MergeIn(awsConfig) - case cloud.AuthParamImplicit: - if env.KMSConfig().DisableImplicitCredentials { - return nil, errors.New( - "implicit credentials disallowed for s3 due to --external-io-implicit-credentials flag") - } - opts.SharedConfigState = session.SharedConfigEnable - default: - return nil, errors.Errorf("unsupported value %s for %s", kmsURIParams.auth, cloud.AuthParam) - } - - sess, err := session.NewSessionWithOptions(opts) - if err != nil { - return nil, errors.Wrap(err, "new aws session") - } - if region == "" { - // TODO(adityamaru): Maybe use the KeyID to get the region, similar to how - // we infer the region from the bucket for s3_storage. - return nil, errors.New("could not find the aws kms region") - } - sess.Config.Region = aws.String(region) - return &awsKMS{ - kms: kms.New(sess), - customerMasterKeyID: strings.TrimPrefix(kmsURI.Path, "/"), - }, nil -} - -// MasterKeyID implements the KMS interface. -func (k *awsKMS) MasterKeyID() (string, error) { - return k.customerMasterKeyID, nil -} - -// Encrypt implements the KMS interface. -func (k *awsKMS) Encrypt(ctx context.Context, data []byte) ([]byte, error) { - encryptInput := &kms.EncryptInput{ - KeyId: &k.customerMasterKeyID, - Plaintext: data, - } - - encryptOutput, err := k.kms.Encrypt(encryptInput) - if err != nil { - return nil, err - } - - return encryptOutput.CiphertextBlob, nil -} - -// Decrypt implements the KMS interface. -func (k *awsKMS) Decrypt(ctx context.Context, data []byte) ([]byte, error) { - decryptInput := &kms.DecryptInput{ - KeyId: &k.customerMasterKeyID, - CiphertextBlob: data, - } - - decryptOutput, err := k.kms.Decrypt(decryptInput) - if err != nil { - return nil, err - } - - return decryptOutput.Plaintext, nil -} - -// Close implements the KMS interface. -func (k *awsKMS) Close() error { - return nil -} diff --git a/pkg/cloud/amazon/aws_kms_test.go b/pkg/cloud/amazon/aws_kms_test.go deleted file mode 100644 index b7b8dd6a03..0000000000 --- a/pkg/cloud/amazon/aws_kms_test.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2020 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. -package amazon - -import ( - "context" - "fmt" - "net/url" - "os" - "testing" - - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/cockroachdb/cockroach/pkg/base" - "github.com/cockroachdb/cockroach/pkg/cloud" - "github.com/cockroachdb/cockroach/pkg/settings/cluster" - "github.com/cockroachdb/cockroach/pkg/testutils" - "github.com/cockroachdb/cockroach/pkg/testutils/skip" - "github.com/cockroachdb/cockroach/pkg/util/leaktest" - "github.com/stretchr/testify/require" -) - -var awsKMSTestSettings *cluster.Settings - -func init() { - awsKMSTestSettings = cluster.MakeTestingClusterSettings() -} - -func TestEncryptDecryptAWS(t *testing.T) { - defer leaktest.AfterTest(t)() - - // If environment credentials are not present, we want to - // skip all AWS KMS tests, including auth-implicit, even though - // it is not used in auth-implicit. - _, err := credentials.NewEnvCredentials().Get() - if err != nil { - skip.IgnoreLint(t, "Test only works with AWS credentials") - } - - q := make(url.Values) - expect := map[string]string{ - "AWS_ACCESS_KEY_ID": AWSAccessKeyParam, - "AWS_SECRET_ACCESS_KEY": AWSSecretParam, - } - for env, param := range expect { - v := os.Getenv(env) - if v == "" { - skip.IgnoreLintf(t, "%s env var must be set", env) - } - q.Add(param, v) - } - - // Get AWS KMS region from env variable. - kmsRegion := os.Getenv("AWS_KMS_REGION_A") - if kmsRegion == "" { - skip.IgnoreLint(t, "AWS_KMS_REGION_A env var must be set") - } - q.Add(KMSRegionParam, kmsRegion) - - // The KeyID for AWS can be specified as any of the following: - // - AWS_KEY_ARN - // - AWS_KEY_ID - // - AWS_KEY_ALIAS - for _, id := range []string{"AWS_KMS_KEY_ARN_A", "AWS_KEY_ID", "AWS_KEY_ALIAS"} { - // Get AWS Key identifier from env variable. - keyID := os.Getenv(id) - if keyID == "" { - skip.IgnoreLint(t, fmt.Sprintf("%s env var must be set", id)) - } - - t.Run(fmt.Sprintf("auth-empty-no-cred-%s", id), func(t *testing.T) { - // Set AUTH to specified but don't provide AccessKey params. - params := make(url.Values) - params.Add(cloud.AuthParam, cloud.AuthParamSpecified) - params.Add(KMSRegionParam, kmsRegion) - - uri := fmt.Sprintf("aws:///%s?%s", keyID, params.Encode()) - _, err := cloud.KMSFromURI(context.Background(), uri, &cloud.TestKMSEnv{ExternalIOConfig: &base.ExternalIODirConfig{}}) - require.EqualError(t, err, fmt.Sprintf( - `%s is set to '%s', but %s is not set`, - cloud.AuthParam, - cloud.AuthParamSpecified, - AWSAccessKeyParam, - )) - }) - - t.Run(fmt.Sprintf("auth-implicit-%s", id), func(t *testing.T) { - // You can create an IAM that can access AWS KMS - // in the AWS console, then set it up locally. - // https://docs.aws.com/cli/latest/userguide/cli-configure-role.html - // We only run this test if default role exists. - credentialsProvider := credentials.SharedCredentialsProvider{} - _, err := credentialsProvider.Retrieve() - if err != nil { - skip.IgnoreLint(t, err) - } - - // Set the AUTH and REGION params. - params := make(url.Values) - params.Add(cloud.AuthParam, cloud.AuthParamImplicit) - params.Add(KMSRegionParam, kmsRegion) - - uri := fmt.Sprintf("aws:///%s?%s", keyID, params.Encode()) - cloud.KMSEncryptDecrypt(t, uri, cloud.TestKMSEnv{ - Settings: cluster.NoSettings, - ExternalIOConfig: &base.ExternalIODirConfig{}, - }) - }) - - t.Run(fmt.Sprintf("auth-specified-%s", id), func(t *testing.T) { - // Set AUTH to specified. - q.Set(cloud.AuthParam, cloud.AuthParamSpecified) - uri := fmt.Sprintf("aws:///%s?%s", keyID, q.Encode()) - - cloud.KMSEncryptDecrypt(t, uri, cloud.TestKMSEnv{ - Settings: cluster.NoSettings, - ExternalIOConfig: &base.ExternalIODirConfig{}, - }) - }) - } -} - -func TestPutAWSKMSEndpoint(t *testing.T) { - defer leaktest.AfterTest(t)() - - q := make(url.Values) - expect := map[string]string{ - "AWS_KMS_ENDPOINT": AWSEndpointParam, - "AWS_KMS_ENDPOINT_KEY": AWSAccessKeyParam, - "AWS_KMS_ENDPOINT_REGION": KMSRegionParam, - "AWS_KMS_ENDPOINT_SECRET": AWSSecretParam, - } - for env, param := range expect { - v := os.Getenv(env) - if v == "" { - skip.IgnoreLintf(t, "%s env var must be set", env) - } - q.Add(param, v) - } - - keyARN := os.Getenv("AWS_KMS_KEY_ARN_A") - if keyARN == "" { - skip.IgnoreLint(t, "AWS_KMS_KEY_ARN_A env var must be set") - } - - t.Run("allow-endpoints", func(t *testing.T) { - uri := fmt.Sprintf("aws:///%s?%s", keyARN, q.Encode()) - cloud.KMSEncryptDecrypt(t, uri, cloud.TestKMSEnv{ - Settings: awsKMSTestSettings, - ExternalIOConfig: &base.ExternalIODirConfig{}, - }) - }) - - t.Run("disallow-endpoints", func(t *testing.T) { - uri := fmt.Sprintf("aws:///%s?%s", keyARN, q.Encode()) - _, err := cloud.KMSFromURI(context.Background(), uri, &cloud.TestKMSEnv{ - Settings: awsKMSTestSettings, - ExternalIOConfig: &base.ExternalIODirConfig{DisableHTTP: true}}) - require.True(t, testutils.IsError(err, "custom endpoints disallowed")) - }) -} - -func TestAWSKMSDisallowImplicitCredentials(t *testing.T) { - defer leaktest.AfterTest(t)() - q := make(url.Values) - q.Add(KMSRegionParam, "region") - - // Set AUTH to implicit - q.Add(cloud.AuthParam, cloud.AuthParamImplicit) - - keyARN := os.Getenv("AWS_KMS_KEY_ARN_A") - if keyARN == "" { - skip.IgnoreLint(t, "AWS_KMS_KEY_ARN_A env var must be set") - } - uri := fmt.Sprintf("aws:///%s?%s", keyARN, q.Encode()) - _, err := cloud.KMSFromURI(context.Background(), uri, &cloud.TestKMSEnv{ - Settings: cluster.NoSettings, - ExternalIOConfig: &base.ExternalIODirConfig{DisableImplicitCredentials: true}}) - require.True(t, testutils.IsError(err, "implicit credentials disallowed")) -} diff --git a/pkg/cloud/gcp/gcs_kms.go b/pkg/cloud/gcp/gcs_kms.go deleted file mode 100644 index afac82d14c..0000000000 --- a/pkg/cloud/gcp/gcs_kms.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2020 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package gcp - -import ( - "context" - "hash/crc32" - "net/url" - "strings" - - kms "cloud.google.com/go/kms/apiv1" - "github.com/cockroachdb/cockroach/pkg/cloud" - "github.com/cockroachdb/errors" - "google.golang.org/api/option" - kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" - "google.golang.org/protobuf/types/known/wrapperspb" -) - -const gcsScheme = "gs" - -type gcsKMS struct { - kms *kms.KeyManagementClient - customerMasterKeyID string -} - -var _ cloud.KMS = &gcsKMS{} - -func init() { - cloud.RegisterKMSFromURIFactory(MakeGCSKMS, gcsScheme) -} - -type kmsURIParams struct { - credentials string - auth string - bearerToken string -} - -func resolveKMSURIParams(kmsURI url.URL) kmsURIParams { - params := kmsURIParams{ - credentials: kmsURI.Query().Get(CredentialsParam), - auth: kmsURI.Query().Get(cloud.AuthParam), - bearerToken: kmsURI.Query().Get(BearerTokenParam), - } - - return params -} - -// MakeGCSKMS is the factory method which returns a configured, ready-to-use -// GCS KMS object. -func MakeGCSKMS(ctx context.Context, uri string, env cloud.KMSEnv) (cloud.KMS, error) { - if env.KMSConfig().DisableOutbound { - return nil, errors.New("external IO must be enabled to use GCS KMS") - } - kmsURI, err := url.ParseRequestURI(uri) - if err != nil { - return nil, err - } - - // Extract the URI parameters required to setup the GCS KMS session. - kmsURIParams := resolveKMSURIParams(*kmsURI) - - // Client options to authenticate and start a GCS KMS session. - // Currently only accepting json of service account. - var credentialsOpt []option.ClientOption - - switch kmsURIParams.auth { - case "", cloud.AuthParamSpecified: - if kmsURIParams.credentials != "" { - authOption, err := createAuthOptionFromServiceAccountKey(kmsURIParams.credentials) - if err != nil { - return nil, errors.Wrapf(err, "error getting credentials from %s", CredentialsParam) - } - credentialsOpt = append(credentialsOpt, authOption) - } else if kmsURIParams.bearerToken != "" { - credentialsOpt = append(credentialsOpt, createAuthOptionFromBearerToken(kmsURIParams.bearerToken)) - } else { - return nil, errors.Errorf( - "%s or %s must be set if %q is %q", - CredentialsParam, - BearerTokenParam, - cloud.AuthParam, - cloud.AuthParamSpecified, - ) - } - case cloud.AuthParamImplicit: - if env.KMSConfig().DisableImplicitCredentials { - return nil, errors.New( - "implicit credentials disallowed for gcs due to --external-io-implicit-credentials flag") - } - // If implicit credentials used, no client options needed. - default: - return nil, errors.Errorf("unsupported value %s for %s", kmsURIParams.auth, cloud.AuthParam) - } - - kmc, err := kms.NewKeyManagementClient(ctx, credentialsOpt...) - if err != nil { - return nil, err - } - - // Remove the key version from the cmk if it's present. - // https://cloud.google.com/sdk/gcloud/reference/kms/decrypt - // - For symmetric keys, Cloud KMS detects the decryption key version from the ciphertext. - // If you specify a key version as part of a symmetric decryption request, - // an error is logged and decryption fails. - cmkID := strings.Split(kmsURI.Path, "/cryptoKeyVersions/")[0] - - return &gcsKMS{ - kms: kmc, - customerMasterKeyID: strings.TrimPrefix(cmkID, "/"), - }, nil -} - -// MasterKeyID implements the KMS interface. -func (k *gcsKMS) MasterKeyID() (string, error) { - return k.customerMasterKeyID, nil -} - -// Encrypt implements the KMS interface. -func (k *gcsKMS) Encrypt(ctx context.Context, data []byte) ([]byte, error) { - // Optional but recommended by GCS. - crc32c := func(data []byte) uint32 { - t := crc32.MakeTable(crc32.Castagnoli) - return crc32.Checksum(data, t) - } - plaintextCRC32C := crc32c(data) - - encryptInput := &kmspb.EncryptRequest{ - Name: k.customerMasterKeyID, - Plaintext: data, - PlaintextCrc32C: wrapperspb.Int64(int64(plaintextCRC32C)), - } - - encryptOutput, err := k.kms.Encrypt(ctx, encryptInput) - if err != nil { - return nil, err - } - - // Optional, but recommended by GCS. - // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: - // https://cloud.google.com/kms/docs/data-integrity-guidelines - // TODO(darrylwong): Look into adding some exponential backoff retry behaviour if error is frequent - if !encryptOutput.VerifiedPlaintextCrc32C { - return nil, errors.Errorf("Encrypt: request corrupted in-transit") - } - if int64(crc32c(encryptOutput.Ciphertext)) != encryptOutput.CiphertextCrc32C.Value { - return nil, errors.Errorf("Encrypt: response corrupted in-transit") - } - - return encryptOutput.Ciphertext, nil -} - -// Decrypt implements the KMS interface. -func (k *gcsKMS) Decrypt(ctx context.Context, data []byte) ([]byte, error) { - // Optional but recommended by the documentation - crc32c := func(data []byte) uint32 { - t := crc32.MakeTable(crc32.Castagnoli) - return crc32.Checksum(data, t) - } - ciphertextCRC32C := crc32c(data) - - decryptInput := &kmspb.DecryptRequest{ - Name: k.customerMasterKeyID, - Ciphertext: data, - CiphertextCrc32C: wrapperspb.Int64(int64(ciphertextCRC32C)), - } - - decryptOutput, err := k.kms.Decrypt(ctx, decryptInput) - if err != nil { - return nil, err - } - - // Optional, but recommended: perform integrity verification on result. - // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: - // https://cloud.google.com/kms/docs/data-integrity-guidelines - // TODO(darrylwong): Look into adding some exponential backoff retry behaviour if error is frequent - if int64(crc32c(decryptOutput.Plaintext)) != decryptOutput.PlaintextCrc32C.Value { - return nil, errors.Errorf("Decrypt: response corrupted in-transit") - } - - return decryptOutput.Plaintext, nil -} - -// Close implements the KMS interface. -func (k *gcsKMS) Close() error { - return k.kms.Close() -} diff --git a/pkg/cloud/gcp/gcs_kms_test.go b/pkg/cloud/gcp/gcs_kms_test.go deleted file mode 100644 index 1d49bb94c9..0000000000 --- a/pkg/cloud/gcp/gcs_kms_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2020 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. -package gcp - -import ( - "context" - "fmt" - "net/url" - "os" - "testing" - - kms "cloud.google.com/go/kms/apiv1" - "github.com/cockroachdb/cockroach/pkg/base" - "github.com/cockroachdb/cockroach/pkg/cloud" - "github.com/cockroachdb/cockroach/pkg/settings/cluster" - "github.com/cockroachdb/cockroach/pkg/testutils" - "github.com/cockroachdb/cockroach/pkg/testutils/skip" - "github.com/cockroachdb/cockroach/pkg/util/leaktest" - "github.com/stretchr/testify/require" - "golang.org/x/oauth2/google" -) - -func TestEncryptDecryptGCS(t *testing.T) { - defer leaktest.AfterTest(t)() - - q := make(url.Values) - expect := map[string]string{ - "CREDENTIALS": CredentialsParam, - "GOOGLE_APPLICATION_CREDENTIALS": "GOOGLE_APPLICATION_CREDENTIALS", - } - for env, param := range expect { - v := os.Getenv(env) - if v == "" { - skip.IgnoreLintf(t, "%s env var must be set", env) - } - q.Add(param, v) - } - - // The KeyID for GCS is the following format: - // projects/{project name}/locations/{key region}/keyRings/{keyring name}/cryptoKeys/{key name} - // It can be specified as the following: - // - GCS_KEY_ID - // - GCS_KEY_NAME - for _, id := range []string{"GCS_KEY_ID", "GCS_KEY_NAME"} { - // Get GCS Key identifier from env variable. - keyID := os.Getenv(id) - if keyID == "" { - skip.IgnoreLint(t, fmt.Sprintf("%s env var must be set", id)) - } - - t.Run(fmt.Sprintf("auth-empty-no-cred-%s", id), func(t *testing.T) { - // Set AUTH to specified but don't provide CREDENTIALS params. - params := make(url.Values) - params.Add(cloud.AuthParam, cloud.AuthParamSpecified) - - uri := fmt.Sprintf("gs:///%s?%s", keyID, params.Encode()) - - _, err := cloud.KMSFromURI(context.Background(), uri, &cloud.TestKMSEnv{ExternalIOConfig: &base.ExternalIODirConfig{}}) - require.EqualError(t, err, fmt.Sprintf( - `%s is set to '%s', but %s is not set`, - cloud.AuthParam, - cloud.AuthParamSpecified, - CredentialsParam, - )) - }) - - t.Run(fmt.Sprintf("auth-implicit-%s", id), func(t *testing.T) { - // Set the AUTH params. - params := make(url.Values) - params.Add(cloud.AuthParam, cloud.AuthParamImplicit) - - uri := fmt.Sprintf("gs:///%s?%s", keyID, params.Encode()) - cloud.KMSEncryptDecrypt(t, uri, cloud.TestKMSEnv{ - Settings: cluster.MakeTestingClusterSettings(), - ExternalIOConfig: &base.ExternalIODirConfig{}, - }) - }) - - t.Run(fmt.Sprintf("auth-specified-%s", id), func(t *testing.T) { - // Set AUTH to specified. - q.Set(cloud.AuthParam, cloud.AuthParamSpecified) - uri := fmt.Sprintf("gs:///%s?%s", keyID, q.Encode()) - - cloud.KMSEncryptDecrypt(t, uri, cloud.TestKMSEnv{ - Settings: cluster.MakeTestingClusterSettings(), - ExternalIOConfig: &base.ExternalIODirConfig{}, - }) - }) - - t.Run("auth-specified-bearer-token", func(t *testing.T) { - // Fetch the base64 encoded JSON credentials. - credentials := os.Getenv("GOOGLE_CREDENTIALS_JSON") - if credentials == "" { - skip.IgnoreLint(t, "GOOGLE_CREDENTIALS_JSON env var must be set") - } - - ctx := context.Background() - source, err := google.JWTConfigFromJSON([]byte(credentials), kms.DefaultAuthScopes()...) - require.NoError(t, err, "creating GCS oauth token source from specified credentials") - ts := source.TokenSource(ctx) - - token, err := ts.Token() - require.NoError(t, err, "getting token") - q.Set(BearerTokenParam, token.AccessToken) - - // Set AUTH to specified. - q.Set(cloud.AuthParam, cloud.AuthParamSpecified) - - uri := fmt.Sprintf("gs:///%s?%s", keyID, q.Encode()) - cloud.KMSEncryptDecrypt(t, uri, cloud.TestKMSEnv{ - Settings: cluster.MakeTestingClusterSettings(), - ExternalIOConfig: &base.ExternalIODirConfig{}, - }) - }) - } -} - -func TestGCSKMSDisallowImplicitCredentials(t *testing.T) { - defer leaktest.AfterTest(t)() - q := make(url.Values) - - // Set AUTH to implicit. - q.Add(cloud.AuthParam, cloud.AuthParamImplicit) - for _, id := range []string{"GCS_KEY_ID", "GCS_KEY_NAME"} { - keyID := os.Getenv(id) - if keyID == "" { - skip.IgnoreLint(t, "%s env var must be set", id) - } - uri := fmt.Sprintf("gs:///%s?%s", keyID, q.Encode()) - _, err := cloud.KMSFromURI(context.Background(), uri, &cloud.TestKMSEnv{ - Settings: cluster.NoSettings, - ExternalIOConfig: &base.ExternalIODirConfig{DisableImplicitCredentials: true}}) - require.True(t, testutils.IsError(err, - "implicit credentials disallowed for gcs due to --external-io-implicit-credentials flag"), - ) - } -} diff --git a/pkg/cloud/kms.go b/pkg/cloud/kms.go deleted file mode 100644 index caf4ae46bd..0000000000 --- a/pkg/cloud/kms.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2020 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cloud - -import ( - "context" - "net/url" - - "github.com/cockroachdb/cockroach/pkg/base" - "github.com/cockroachdb/cockroach/pkg/settings/cluster" - "github.com/cockroachdb/errors" -) - -// KMS provides an API to interact with a KMS service. -type KMS interface { - // MasterKeyID will return the identifier used to reference the master key - // associated with the KMS object. - MasterKeyID() (string, error) - // Encrypt returns the ciphertext version of data after encrypting it using - // the KMS. - Encrypt(ctx context.Context, data []byte) ([]byte, error) - // Decrypt returns the plaintext version of data after decrypting it using the - // KMS. - Decrypt(ctx context.Context, data []byte) ([]byte, error) - // Close may be used to perform the necessary cleanup and shutdown of the - // KMS connection. - Close() error -} - -// KMSEnv is the environment in which a KMS is configured and used. -type KMSEnv interface { - ClusterSettings() *cluster.Settings - KMSConfig() *base.ExternalIODirConfig -} - -// KMSFromURIFactory describes a factory function for KMS given a URI. -type KMSFromURIFactory func(ctx context.Context, uri string, env KMSEnv) (KMS, error) - -// Mapping from KMS scheme to its registered factory method. -var kmsFactoryMap = make(map[string]KMSFromURIFactory) - -// RegisterKMSFromURIFactory is used by every concrete KMS implementation to -// register its factory method. -func RegisterKMSFromURIFactory(factory KMSFromURIFactory, scheme string) { - if _, ok := kmsFactoryMap[scheme]; ok { - panic("factory method for " + scheme + " has already been registered") - } - kmsFactoryMap[scheme] = factory -} - -// KMSFromURI is the method used to create a KMS instance from the provided URI. -func KMSFromURI(ctx context.Context, uri string, env KMSEnv) (KMS, error) { - var kmsURL *url.URL - var err error - if kmsURL, err = url.ParseRequestURI(uri); err != nil { - return nil, err - } - - // Find the factory method for the given KMS scheme. - var factory KMSFromURIFactory - var ok bool - if factory, ok = kmsFactoryMap[kmsURL.Scheme]; !ok { - return nil, errors.Newf("no factory method found for scheme %s", kmsURL.Scheme) - } - - return factory(ctx, uri, env) -} diff --git a/pkg/cloud/kms_test_utils.go b/pkg/cloud/kms_test_utils.go deleted file mode 100644 index 1ec1e36653..0000000000 --- a/pkg/cloud/kms_test_utils.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2020 The Cockroach Authors. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cloud - -import ( - "bytes" - "context" - "testing" - - "github.com/cockroachdb/cockroach/pkg/base" - "github.com/cockroachdb/cockroach/pkg/settings/cluster" - "github.com/stretchr/testify/require" -) - -// TestKMSEnv holds the KMS configuration and the cluster settings -type TestKMSEnv struct { - Settings *cluster.Settings - ExternalIOConfig *base.ExternalIODirConfig -} - -var _ KMSEnv = &TestKMSEnv{} - -// ClusterSettings returns the cluster settings -func (e *TestKMSEnv) ClusterSettings() *cluster.Settings { - return e.Settings -} - -// KMSConfig returns the configurable settings of the KMS -func (e *TestKMSEnv) KMSConfig() *base.ExternalIODirConfig { - return e.ExternalIOConfig -} - -// KMSEncryptDecrypt is the method used to test if the given KMS can -// correctly encrypt and decrypt a string -func KMSEncryptDecrypt(t *testing.T, kmsURI string, env TestKMSEnv) { - ctx := context.Background() - kms, err := KMSFromURI(ctx, kmsURI, &env) - require.NoError(t, err) - - t.Run("simple encrypt decrypt", func(t *testing.T) { - sampleBytes := "hello world" - - encryptedBytes, err := kms.Encrypt(ctx, []byte(sampleBytes)) - require.NoError(t, err) - - decryptedBytes, err := kms.Decrypt(ctx, encryptedBytes) - require.NoError(t, err) - - require.True(t, bytes.Equal(decryptedBytes, []byte(sampleBytes))) - - require.NoError(t, kms.Close()) - }) -}