From b43d28d35bc362c47e23360799e601a5b4cd4d37 Mon Sep 17 00:00:00 2001 From: Niels de Vos Date: Thu, 18 Mar 2021 13:47:16 +0100 Subject: [PATCH] util: add API for KMS Provider plugins The KMSProvider struct is a simple, extendable type that can be used to register KMS providers with an internal kmsManager. Helper functions for creating and configuring KMS providers will also be located in the new kms.go file. This makes things more modular and better maintainable. Signed-off-by: Niels de Vos --- internal/util/kms.go | 136 ++++++++++++++++++++++++++++++++++++++ internal/util/kms_test.go | 54 +++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 internal/util/kms.go create mode 100644 internal/util/kms_test.go diff --git a/internal/util/kms.go b/internal/util/kms.go new file mode 100644 index 000000000..76e0b578a --- /dev/null +++ b/internal/util/kms.go @@ -0,0 +1,136 @@ +/* +Copyright 2021 The Ceph-CSI 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 util + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // kmsProviderKey is the name of the KMS provider that is registered at + // the kmsManager. This is used in the ConfigMap configuration options. + kmsProviderKey = "KMS_PROVIDER" +) + +// getKMSConfig returns the (.Data) contents of the ConfigMap. +// +// FIXME: Ceph-CSI should not talk to Kubernetes directly. +func getKMSConfig(ns, configmap string) (map[string]string, error) { + c := NewK8sClient() + cm, err := c.CoreV1().ConfigMaps(ns).Get(context.Background(), + configmap, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + return cm.Data, nil +} + +// getKMSProvider inspects the configuration and tries to identify what +// KMSProvider is expected to be used with it. This returns the +// KMSProvider.UniqueID. +func getKMSProvider(config map[string]interface{}) (string, error) { + var name string + + providerName, ok := config[kmsTypeKey] + if ok { + name, ok = providerName.(string) + if !ok { + return "", fmt.Errorf("could not convert KMS provider"+ + "type (%v) to string", providerName) + } + return name, nil + } + + providerName, ok = config[kmsProviderKey] + if ok { + name, ok = providerName.(string) + if !ok { + return "", fmt.Errorf("could not convert KMS provider"+ + "type (%v) to string", providerName) + } + return name, nil + } + + return "", fmt.Errorf("failed to get KMS provider, missing"+ + "configuration option %q or %q", kmsTypeKey, kmsProviderKey) +} + +// KMSInitializerArgs get passed to KMSInitializerFunc when a new instance of a +// KMSProvider is initialized. +type KMSInitializerArgs struct { + Id, Tenant string + Config map[string]interface{} + Secrets map[string]string +} + +// KMSInitializerFunc gets called when the KMSProvider needs to be +// instantiated. +type KMSInitializerFunc func(args KMSInitializerArgs) (EncryptionKMS, error) + +type KMSProvider struct { + UniqueID string + Initializer KMSInitializerFunc +} + +type kmsProviderList struct { + providers map[string]KMSProvider +} + +// kmsManager is used to create instances for a KMS provider. +var kmsManager = kmsProviderList{providers: map[string]KMSProvider{}} + +// RegisterKMSProvider uses kmsManager to register the given KMSProvider. The +// KMSProvider.Initializer function will get called when a new instance of the +// KMS is required. +func RegisterKMSProvider(provider KMSProvider) bool { + // validate uniqueness of the UniqueID + if provider.UniqueID == "" { + panic("a provider MUST set a UniqueID") + } + _, ok := kmsManager.providers[provider.UniqueID] + if ok { + panic("duplicate tegistration of KMSProvider.UniqueID: " + provider.UniqueID) + } + + // validate the Initializer + if provider.Initializer == nil { + panic("a provider MUST have an Initializer") + } + + kmsManager.providers[provider.UniqueID] = provider + + return true +} + +func (kf *kmsProviderList) buildKMS(providerName, kmsID, tenant string, config map[string]interface{}, secrets map[string]string) (EncryptionKMS, error) { + provider, ok := kf.providers[providerName] + if !ok { + return nil, fmt.Errorf("could not find KMS provider %q", + providerName) + } + + return provider.Initializer(KMSInitializerArgs{ + Id: kmsID, + Tenant: tenant, + Config: config, + Secrets: secrets, + }) +} diff --git a/internal/util/kms_test.go b/internal/util/kms_test.go new file mode 100644 index 000000000..d52748130 --- /dev/null +++ b/internal/util/kms_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2021 The Ceph-CSI 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 util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func noinitKMS(id, tenant string, config map[string]interface{}, secrets map[string]string) (EncryptionKMS, error) { + return nil, nil +} + +func TestRegisterKMSProvider(t *testing.T) { + tests := []struct { + provider KMSProvider + panics bool + }{{ + KMSProvider{ + UniqueID: "incomplete-provider", + }, + true, + }, { + KMSProvider{ + UniqueID: "initializer-only", + Initializer: noinitKMS, + }, + false, + }} + + for _, test := range tests { + provider := test.provider + if test.panics { + assert.Panics(t, func() { RegisterKMSProvider(provider) }) + } else { + assert.True(t, RegisterKMSProvider(provider)) + } + } +}