From 6a4afc897c2ed4fb80f1b6121a06f86bc8095cd8 Mon Sep 17 00:00:00 2001 From: Saksham Sharma Date: Mon, 28 Aug 2017 15:24:04 +0530 Subject: [PATCH 1/2] Unify cloudprovided and normal KMS plugins --- cmd/kube-apiserver/app/server.go | 13 +++- .../server/options/encryptionconfig/config.go | 25 ++----- .../encryptionconfig/encryptionconfig_test.go | 66 +------------------ .../options/encryptionconfig/plugins.go | 26 ++------ .../server/options/encryptionconfig/types.go | 18 +---- 5 files changed, 25 insertions(+), 123 deletions(-) diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index d1728e6a7ed..df00872b9b1 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -259,6 +259,14 @@ func CreateKubeAPIServerConfig(s *options.ServerRunOptions, nodeTunneler tunnele return nil, nil, nil, nil, nil, utilerrors.NewAggregate(errs) } + if s.CloudProvider != nil { + // Initialize the cloudprovider once, to give it a chance to register KMS plugins, if any. + _, err := cloudprovider.InitCloudProvider(s.CloudProvider.CloudProvider, s.CloudProvider.CloudConfigFile) + if err != nil { + return nil, nil, nil, nil, nil, err + } + } + genericConfig, sharedInformers, versionedInformers, insecureServingOptions, serviceResolver, err := BuildGenericConfig(s) if err != nil { return nil, nil, nil, nil, nil, err @@ -539,7 +547,8 @@ func BuildAuthorizer(s *options.ServerRunOptions, sharedInformers informers.Shar return authorizationConfig.New() } -// BuildStorageFactory constructs the storage factory +// BuildStorageFactory constructs the storage factory. If encryption at rest is used, it expects +// all supported KMS plugins to be registered in the KMS plugin registry before being called. func BuildStorageFactory(s *options.ServerRunOptions) (*serverstorage.DefaultStorageFactory, error) { storageGroupsToEncodingVersion, err := s.StorageSerialization.StorageGroupsToEncodingVersion() if err != nil { @@ -581,7 +590,7 @@ func BuildStorageFactory(s *options.ServerRunOptions) (*serverstorage.DefaultSto storageFactory.SetEtcdLocation(groupResource, servers) } - if s.Etcd.EncryptionProviderConfigFilepath != "" { + if len(s.Etcd.EncryptionProviderConfigFilepath) != 0 { transformerOverrides, err := encryptionconfig.GetTransformerOverrides(s.Etcd.EncryptionProviderConfigFilepath) if err != nil { return nil, err diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go index fd5421b1acc..ad2f0927c6a 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go @@ -36,11 +36,10 @@ import ( ) const ( - aesCBCTransformerPrefixV1 = "k8s:enc:aescbc:v1:" - aesGCMTransformerPrefixV1 = "k8s:enc:aesgcm:v1:" - secretboxTransformerPrefixV1 = "k8s:enc:secretbox:v1:" - kmsTransformerPrefixV1 = "k8s:enc:kms:v1:" - cloudProvidedKMSTransformerPrefixV1 = "k8s:enc:cloudprovidedkms:v1:" + aesCBCTransformerPrefixV1 = "k8s:enc:aescbc:v1:" + aesGCMTransformerPrefixV1 = "k8s:enc:aesgcm:v1:" + secretboxTransformerPrefixV1 = "k8s:enc:secretbox:v1:" + kmsTransformerPrefixV1 = "k8s:enc:kms:v1:" ) // GetTransformerOverrides returns the transformer overrides by reading and parsing the encryption provider configuration file @@ -163,19 +162,7 @@ func GetPrefixTransformers(config *ResourceConfig) ([]value.PrefixTransformer, e if pluginFound == false { return nil, fmt.Errorf("KMS plugin %q not found", provider.KMS.Name) } - transformer, err = getEnvelopePrefixTransformer(provider.KMS.CoreKMSConfig, envelopeService, kmsTransformerPrefixV1) - found = true - } - - if provider.CloudProvidedKMS != nil { - if found == true { - return nil, fmt.Errorf("more than one provider specified in a single element, should split into different list elements") - } - envelopeService, err := KMSPluginRegistry.getCloudProvidedPlugin(provider.CloudProvidedKMS.Name) - if err != nil { - return nil, fmt.Errorf("could not configure Cloud-Provided KMS plugin %q, %v", provider.CloudProvidedKMS.Name, err) - } - transformer, err = getEnvelopePrefixTransformer(provider.CloudProvidedKMS.CoreKMSConfig, envelopeService, cloudProvidedKMSTransformerPrefixV1) + transformer, err = getEnvelopePrefixTransformer(provider.KMS, envelopeService, kmsTransformerPrefixV1) found = true } @@ -296,7 +283,7 @@ func GetSecretboxPrefixTransformer(config *SecretboxConfig) (value.PrefixTransfo // getEnvelopePrefixTransformer returns a prefix transformer from the provided config. // envelopeService is used as the root of trust. -func getEnvelopePrefixTransformer(config *CoreKMSConfig, envelopeService envelope.Service, prefix string) (value.PrefixTransformer, error) { +func getEnvelopePrefixTransformer(config *KMSConfig, envelopeService envelope.Service, prefix string) (value.PrefixTransformer, error) { envelopeTransformer, err := envelope.NewEnvelopeTransformer(envelopeService, config.CacheSize, aestransformer.NewCBCTransformer) if err != nil { return value.PrefixTransformer{}, err diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/encryptionconfig_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/encryptionconfig_test.go index 22bb3b38766..8233bb7eefd 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/encryptionconfig_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/encryptionconfig_test.go @@ -58,9 +58,6 @@ resources: name: testprovider configfile: testproviderconfig cachesize: 10 - - cloudprovidedkms: - name: testprovider - cachesize: 10 - aescbc: keys: - name: key1 @@ -100,9 +97,6 @@ resources: secret: c2VjcmV0IGlzIHNlY3VyZQ== - name: key2 secret: dGhpcyBpcyBwYXNzd29yZA== - - cloudprovidedkms: - name: testprovider - cachesize: 10 - identity: {} ` @@ -124,9 +118,6 @@ resources: configfile: testproviderconfig cachesize: 10 - identity: {} - - cloudprovidedkms: - name: testprovider - cachesize: 10 - secretbox: keys: - name: key1 @@ -150,9 +141,6 @@ resources: keys: - name: key1 secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY= - - cloudprovidedkms: - name: testprovider - cachesize: 10 - aescbc: keys: - name: key1 @@ -194,45 +182,6 @@ resources: - name: key2 secret: dGhpcyBpcyBwYXNzd29yZA== - identity: {} - - cloudprovidedkms: - name: testprovider - cachesize: 10 - - aesgcm: - keys: - - name: key1 - secret: c2VjcmV0IGlzIHNlY3VyZQ== - - name: key2 - secret: dGhpcyBpcyBwYXNzd29yZA== -` - - correctConfigWithCloudProvidedKMSFirst = ` -kind: EncryptionConfig -apiVersion: v1 -resources: - - resources: - - secrets - providers: - - cloudprovidedkms: - name: testprovider - cachesize: 10 - - kms: - name: testprovider - configfile: testproviderconfig - cachesize: 10 - - secretbox: - keys: - - name: key1 - secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY= - - aescbc: - keys: - - name: key1 - secret: c2VjcmV0IGlzIHNlY3VyZQ== - - name: key2 - secret: dGhpcyBpcyBwYXNzd29yZA== - - identity: {} - - cloudprovidedkms: - name: testprovider - cachesize: 10 - aesgcm: keys: - name: key1 @@ -300,15 +249,9 @@ var _ envelope.Service = &testEnvelopeService{} func TestEncryptionProviderConfigCorrect(t *testing.T) { os.OpenFile(testEnvelopeServiceConfigPath, os.O_CREATE, 0666) defer os.Remove(testEnvelopeServiceConfigPath) - KMSPluginRegistry.Register(testEnvelopeServiceProviderName, func(config io.Reader) (envelope.Service, error) { + KMSPluginRegistry.Register(testEnvelopeServiceProviderName, func(_ io.Reader) (envelope.Service, error) { return &testEnvelopeService{}, nil }) - KMSPluginRegistry.RegisterCloudProvidedKMSPlugin(func(name string) (envelope.Service, error) { - if name == testEnvelopeServiceProviderName { - return &testEnvelopeService{}, nil - } - return nil, fmt.Errorf("no such cloud provided KMS plugin registered: %q", name) - }) // Creates compound/prefix transformers with different ordering of available transformers. // Transforms data using one of them, and tries to untransform using the others. @@ -338,18 +281,12 @@ func TestEncryptionProviderConfigCorrect(t *testing.T) { t.Fatalf("error while parsing configuration file: %s.\nThe file was:\n%s", err, correctConfigWithKMSFirst) } - cloudProvidedKMSFirstTransformerOverrides, err := ParseEncryptionConfiguration(strings.NewReader(correctConfigWithCloudProvidedKMSFirst)) - if err != nil { - t.Fatalf("error while parsing configuration file: %s.\nThe file was:\n%s", err, correctConfigWithCloudProvidedKMSFirst) - } - // Pick the transformer for any of the returned resources. identityFirstTransformer := identityFirstTransformerOverrides[schema.ParseGroupResource("secrets")] aesGcmFirstTransformer := aesGcmFirstTransformerOverrides[schema.ParseGroupResource("secrets")] aesCbcFirstTransformer := aesCbcFirstTransformerOverrides[schema.ParseGroupResource("secrets")] secretboxFirstTransformer := secretboxFirstTransformerOverrides[schema.ParseGroupResource("secrets")] kmsFirstTransformer := kmsFirstTransformerOverrides[schema.ParseGroupResource("secrets")] - cloudProvidedKMSFirstTransformer := cloudProvidedKMSFirstTransformerOverrides[schema.ParseGroupResource("secrets")] context := value.DefaultContext([]byte(sampleContextText)) originalText := []byte(sampleText) @@ -363,7 +300,6 @@ func TestEncryptionProviderConfigCorrect(t *testing.T) { {secretboxFirstTransformer, "secretboxFirst"}, {identityFirstTransformer, "identityFirst"}, {kmsFirstTransformer, "kmsFirst"}, - {cloudProvidedKMSFirstTransformer, "cloudProvidedKMSFirst"}, } for _, testCase := range transformers { diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/plugins.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/plugins.go index b45e130f204..310c7238f59 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/plugins.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/plugins.go @@ -18,7 +18,6 @@ package encryptionconfig import ( "bytes" - "fmt" "io" "io/ioutil" "reflect" @@ -35,13 +34,10 @@ import ( // the parameter is nil. type Factory func(config io.Reader) (envelope.Service, error) -type cloudKMSFactory func(name string) (envelope.Service, error) - // KMSPlugins contains all registered KMS options. type KMSPlugins struct { lock sync.RWMutex registry map[string]Factory - cloudKMS cloudKMSFactory } var ( @@ -59,7 +55,8 @@ var ( type PluginEnabledFunc func(name string, config io.Reader) bool // Register registers a plugin Factory by name. This -// is expected to happen during app startup. +// is expected to happen during app startup. It does not allow +// registering a plugin by the same name twice. func (ps *KMSPlugins) Register(name string, plugin Factory) { ps.lock.Lock() defer ps.lock.Unlock() @@ -69,15 +66,10 @@ func (ps *KMSPlugins) Register(name string, plugin Factory) { } if found { glog.Fatalf("KMS plugin %q was registered twice", name) + } else { + ps.registry[name] = plugin + glog.V(1).Infof("Registered KMS plugin %q", name) } - glog.V(1).Infof("Registered KMS plugin %q", name) - ps.registry[name] = plugin -} - -// RegisterCloudProvidedKMSPlugin registers the cloud's KMS provider as -// an envelope.Service. This service is provided by the cloudprovider interface. -func (ps *KMSPlugins) RegisterCloudProvidedKMSPlugin(cloudKMSGetter cloudKMSFactory) { - ps.cloudKMS = cloudKMSGetter } // getPlugin creates an instance of the named plugin. It returns `false` if the @@ -111,14 +103,6 @@ func (ps *KMSPlugins) fetchPluginFromRegistry(name string) (Factory, bool) { return f, found } -// getCloudProvidedPlugin creates an instance of the named cloud provided KMS plugin. -func (ps *KMSPlugins) getCloudProvidedPlugin(name string) (envelope.Service, error) { - if ps.cloudKMS == nil { - return nil, fmt.Errorf("no cloud registered for KMS plugins") - } - return ps.cloudKMS(name) -} - // splitStream reads the stream bytes and constructs two copies of it. func splitStream(config io.Reader) (io.Reader, io.Reader, error) { if config == nil || reflect.ValueOf(config).IsNil() { diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/types.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/types.go index 1e77fd08686..1603e044a31 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/types.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/types.go @@ -47,9 +47,6 @@ type ProviderConfig struct { Identity *IdentityConfig `json:"identity,omitempty"` // kms contains the name, cache size and path to configuration file for a KMS based envelope transformer. KMS *KMSConfig `json:"kms,omitempty"` - // cloudProvidedKMSConfig contains the name and cache size for a KMS based envelope transformer which uses - // the KMS provided by the cloud. - CloudProvidedKMS *CloudProvidedKMSConfig `json:"cloudprovidedkms,omitempty"` } // AESConfig contains the API configuration for an AES transformer. @@ -77,24 +74,13 @@ type Key struct { // IdentityConfig is an empty struct to allow identity transformer in provider configuration. type IdentityConfig struct{} -// CoreKMSConfig contains the name and cache sized for a KMS based envelope transformer. -type CoreKMSConfig struct { +// KMSConfig contains the name, cache size and path to configuration file for a KMS based envelope transformer. +type KMSConfig struct { // name is the name of the KMS plugin to be used. Name string `json:"name"` // cacheSize is the maximum number of secrets which are cached in memory. The default value is 1000. // +optional CacheSize int `json:"cachesize,omitempty"` -} - -// KMSConfig contains the name, cache size and path to configuration file for a KMS based envelope transformer. -type KMSConfig struct { - *CoreKMSConfig // configfile is the path to the configuration file for the named KMS provider. ConfigFile string `json:"configfile"` } - -// CloudProvidedKMSConfig contains the name and cache size for a KMS based envelope transformer which uses -// the KMS provided by the cloud. -type CloudProvidedKMSConfig struct { - *CoreKMSConfig -} From 3e11baf702a25a83ca79cd5d335c8ebe19ee61ce Mon Sep 17 00:00:00 2001 From: Saksham Sharma Date: Mon, 28 Aug 2017 16:02:05 +0530 Subject: [PATCH 2/2] Add Google Cloud KMS plugin for encryption --- cluster/gce/gci/configure-helper.sh | 4 + pkg/cloudprovider/providers/gce/BUILD | 3 + pkg/cloudprovider/providers/gce/gce.go | 15 +++ pkg/cloudprovider/providers/gce/kms.go | 167 +++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 pkg/cloudprovider/providers/gce/kms.go diff --git a/cluster/gce/gci/configure-helper.sh b/cluster/gce/gci/configure-helper.sh index eb3e3caa186..4235c296139 100644 --- a/cluster/gce/gci/configure-helper.sh +++ b/cluster/gce/gci/configure-helper.sh @@ -1410,6 +1410,10 @@ function start-kube-apiserver { if [[ -n "${ENCRYPTION_PROVIDER_CONFIG:-}" ]]; then local encryption_provider_config_path="/etc/srv/kubernetes/encryption-provider-config.yml" + if [[ -n "${GOOGLE_CLOUD_KMS_CONFIG_FILE_NAME:-}" && -n "${GOOGLE_CLOUD_KMS_CONFIG:-}" ]]; then + echo "${GOOGLE_CLOUD_KMS_CONFIG}" | base64 --decode > "${GOOGLE_CLOUD_KMS_CONFIG_FILE_NAME}" + fi + echo "${ENCRYPTION_PROVIDER_CONFIG}" | base64 --decode > "${encryption_provider_config_path}" params+=" --experimental-encryption-provider-config=${encryption_provider_config_path}" fi diff --git a/pkg/cloudprovider/providers/gce/BUILD b/pkg/cloudprovider/providers/gce/BUILD index e2d5e25bf34..4dba3184d4c 100644 --- a/pkg/cloudprovider/providers/gce/BUILD +++ b/pkg/cloudprovider/providers/gce/BUILD @@ -37,6 +37,7 @@ go_library( "gce_urlmap.go", "gce_util.go", "gce_zones.go", + "kms.go", "metrics.go", "token_source.go", ], @@ -70,6 +71,8 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/apiserver/pkg/server/options/encryptionconfig:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/util/flowcontrol:go_default_library", diff --git a/pkg/cloudprovider/providers/gce/gce.go b/pkg/cloudprovider/providers/gce/gce.go index c11c961d2d0..3284e7890f8 100644 --- a/pkg/cloudprovider/providers/gce/gce.go +++ b/pkg/cloudprovider/providers/gce/gce.go @@ -31,6 +31,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/server/options/encryptionconfig" + "k8s.io/apiserver/pkg/storage/value/encrypt/envelope" "k8s.io/client-go/util/flowcontrol" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/controller" @@ -189,6 +191,10 @@ type CloudConfig struct { AlphaFeatureGate *AlphaFeatureGate } +// kmsPluginRegisterOnce prevents the cloudprovider from registering its KMS plugin +// more than once in the KMS plugin registry. +var kmsPluginRegisterOnce sync.Once + func init() { cloudprovider.RegisterCloudProvider( ProviderName, @@ -434,6 +440,15 @@ func CreateGCECloud(config *CloudConfig) (*GCECloud, error) { } gce.manager = &GCEServiceManager{gce} + + // Registering the KMS plugin only the first time. + kmsPluginRegisterOnce.Do(func() { + // Register the Google Cloud KMS based service in the KMS plugin registry. + encryptionconfig.KMSPluginRegistry.Register(KMSServiceName, func(config io.Reader) (envelope.Service, error) { + return gce.getGCPCloudKMSService(config) + }) + }) + return gce, nil } diff --git a/pkg/cloudprovider/providers/gce/kms.go b/pkg/cloudprovider/providers/gce/kms.go new file mode 100644 index 00000000000..fbe62f523b5 --- /dev/null +++ b/pkg/cloudprovider/providers/gce/kms.go @@ -0,0 +1,167 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gce + +import ( + "encoding/base64" + "fmt" + "io" + + "github.com/golang/glog" + cloudkms "google.golang.org/api/cloudkms/v1" + "google.golang.org/api/googleapi" + gcfg "gopkg.in/gcfg.v1" + "k8s.io/apiserver/pkg/storage/value/encrypt/envelope" +) + +const ( + // KMSServiceName is the name of the cloudkms provider registered by this cloud. + KMSServiceName = "gcp-cloudkms" + + defaultGKMSKeyRing = "google-container-engine" + defaultGKMSKeyRingLocation = "global" +) + +// gkmsConfig contains the GCE specific KMS configuration for setting up a KMS connection. +type gkmsConfig struct { + Global struct { + // location is the KMS location of the KeyRing to be used for encryption. + // It can be found by checking the available KeyRings in the IAM UI. + // This is not the same as the GCP location of the project. + // +optional + Location string `gcfg:"kms-location"` + // keyRing is the keyRing of the hosted key to be used. The default value is "google-kubernetes". + // +optional + KeyRing string `gcfg:"kms-keyring"` + // cryptoKey is the name of the key to be used for encryption of Data-Encryption-Keys. + CryptoKey string `gcfg:"kms-cryptokey"` + } +} + +// readGCPCloudKMSConfig parses and returns the configuration parameters for Google Cloud KMS. +func readGCPCloudKMSConfig(reader io.Reader) (*gkmsConfig, error) { + cfg := &gkmsConfig{} + if err := gcfg.FatalOnly(gcfg.ReadInto(cfg, reader)); err != nil { + glog.Errorf("Couldn't read Google Cloud KMS config: %v", err) + return nil, err + } + return cfg, nil +} + +// gkmsService provides Encrypt and Decrypt methods which allow cryptographic operations +// using Google Cloud KMS service. +type gkmsService struct { + parentName string + cloudkmsService *cloudkms.Service +} + +// getGCPCloudKMSService provides a Google Cloud KMS based implementation of envelope.Service. +func (gce *GCECloud) getGCPCloudKMSService(config io.Reader) (envelope.Service, error) { + kmsConfig, err := readGCPCloudKMSConfig(config) + if err != nil { + return nil, err + } + + // Hosting on GCE/GKE with Google KMS encryption provider + cloudkmsService := gce.GetKMSService() + + // Set defaults for location and keyRing. + location := kmsConfig.Global.Location + if len(location) == 0 { + location = defaultGKMSKeyRingLocation + } + keyRing := kmsConfig.Global.KeyRing + if len(keyRing) == 0 { + keyRing = defaultGKMSKeyRing + } + + cryptoKey := kmsConfig.Global.CryptoKey + if len(cryptoKey) == 0 { + return nil, fmt.Errorf("missing cryptoKey for cloudprovided KMS: " + KMSServiceName) + } + + parentName := fmt.Sprintf("projects/%s/locations/%s", gce.projectID, location) + + // Create the keyRing if it does not exist yet + _, err = cloudkmsService.Projects.Locations.KeyRings.Create(parentName, + &cloudkms.KeyRing{}).KeyRingId(keyRing).Do() + if err != nil && unrecoverableCreationError(err) { + return nil, err + } + parentName = parentName + "/keyRings/" + keyRing + + // Create the cryptoKey if it does not exist yet + _, err = cloudkmsService.Projects.Locations.KeyRings.CryptoKeys.Create(parentName, + &cloudkms.CryptoKey{ + Purpose: "ENCRYPT_DECRYPT", + }).CryptoKeyId(cryptoKey).Do() + if err != nil && unrecoverableCreationError(err) { + return nil, err + } + parentName = parentName + "/cryptoKeys/" + cryptoKey + + service := &gkmsService{ + parentName: parentName, + cloudkmsService: cloudkmsService, + } + + // Sanity check before startup. For non-GCP clusters, the user's account may not have permissions to create + // the key. We need to verify the existence of the key before apiserver startup. + _, err = service.Encrypt([]byte("test")) + if err != nil { + return nil, fmt.Errorf("failed to encrypt data using Google cloudkms, using key %s. Ensure that the keyRing and cryptoKey exist. Got error: %v", parentName, err) + } + + return service, nil +} + +// Decrypt decrypts a base64 representation of encrypted bytes. +func (t *gkmsService) Decrypt(data string) ([]byte, error) { + resp, err := t.cloudkmsService.Projects.Locations.KeyRings.CryptoKeys. + Decrypt(t.parentName, &cloudkms.DecryptRequest{ + Ciphertext: data, + }).Do() + if err != nil { + return nil, err + } + return base64.StdEncoding.DecodeString(resp.Plaintext) +} + +// Encrypt encrypts bytes, and returns base64 representation of the ciphertext. +func (t *gkmsService) Encrypt(data []byte) (string, error) { + resp, err := t.cloudkmsService.Projects.Locations.KeyRings.CryptoKeys. + Encrypt(t.parentName, &cloudkms.EncryptRequest{ + Plaintext: base64.StdEncoding.EncodeToString(data), + }).Do() + if err != nil { + return "", err + } + return resp.Ciphertext, nil +} + +// unrecoverableCreationError decides if Kubernetes should ignore the encountered Google KMS +// error. Only to be used for errors seen while creating a KeyRing or CryptoKey. +func unrecoverableCreationError(err error) bool { + apiError, isAPIError := err.(*googleapi.Error) + // 409 means the object exists. + // 403 means we do not have permission to create the object, the user must do it. + // Else, it is an unrecoverable error. + if !isAPIError || (apiError.Code != 409 && apiError.Code != 403) { + return true + } + return false +}