util: rewrite GetKMS() to use KMS provider plugin API

GetKMS() is the public API that initilizes the KMS providers on demand.
Each provider identifies itself with a KMS-Type, and adds its own
initialization function to a switch/case construct. This is not well
maintainable.

The new GetKMS() can be used the same way, but uses the new kmsManager
interface to create and configure the KMS provider instances.

All existing KMS providers are converted to use the new kmsManager
plugins API.

Signed-off-by: Niels de Vos <ndevos@redhat.com>
This commit is contained in:
Niels de Vos 2021-03-18 13:50:38 +01:00 committed by mergify[bot]
parent b43d28d35b
commit 9317e2afb4
9 changed files with 278 additions and 147 deletions

View File

@ -19,11 +19,8 @@ package util
import ( import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"os"
"path" "path"
"strings" "strings"
@ -34,22 +31,12 @@ const (
mapperFilePrefix = "luks-rbd-" mapperFilePrefix = "luks-rbd-"
mapperFilePathPrefix = "/dev/mapper" mapperFilePathPrefix = "/dev/mapper"
kmsTypeKey = "encryptionKMSType"
// kmsConfigPath is the location of the vault config file // kmsConfigPath is the location of the vault config file
kmsConfigPath = "/etc/ceph-csi-encryption-kms-config/config.json" kmsConfigPath = "/etc/ceph-csi-encryption-kms-config/config.json"
// Passphrase size - 20 bytes is 160 bits to satisfy: // Passphrase size - 20 bytes is 160 bits to satisfy:
// https://tools.ietf.org/html/rfc6749#section-10.10 // https://tools.ietf.org/html/rfc6749#section-10.10
encryptionPassphraseSize = 20 encryptionPassphraseSize = 20
// podNamespace ENV should be set in the cephcsi container
podNamespace = "POD_NAMESPACE"
// kmsConfigMapName env to read a ConfigMap by name
kmsConfigMapName = "KMS_CONFIGMAP_NAME"
// defaultConfigMapToRead default ConfigMap name to fetch kms connection details
defaultConfigMapToRead = "csi-kms-connection-details"
) )
var ( var (
@ -183,68 +170,6 @@ func (i integratedDEK) DecryptDEK(volumeID, encyptedDEK string) (string, error)
return encyptedDEK, nil return encyptedDEK, nil
} }
// GetKMS returns an instance of Key Management System.
//
// - tenant is the owner of the Volume, used to fetch the Vault Token from the
// Kubernetes Namespace where the PVC lives
// - kmsID is the service name of the KMS configuration
// - secrets contain additional details, like TLS certificates to connect to
// the KMS
func GetKMS(tenant, kmsID string, secrets map[string]string) (EncryptionKMS, error) {
if kmsID == "" || kmsID == defaultKMSType {
return initSecretsKMS(secrets)
}
var config map[string]interface{}
// #nosec
content, err := ioutil.ReadFile(kmsConfigPath)
if err != nil {
if !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to read kms configuration from %s: %w",
kmsConfigPath, err)
}
// If the configmap is not mounted to the CSI pods read the configmap
// the kubernetes.
namespace := os.Getenv(podNamespace)
if namespace == "" {
return nil, fmt.Errorf("%q is not set", podNamespace)
}
name := os.Getenv(kmsConfigMapName)
if name == "" {
name = defaultConfigMapToRead
}
config, err = getVaultConfiguration(namespace, name)
if err != nil {
return nil, fmt.Errorf("failed to read kms configuration from configmap %s in namespace %s: %w",
namespace, name, err)
}
} else {
err = json.Unmarshal(content, &config)
if err != nil {
return nil, fmt.Errorf("failed to parse kms configuration: %w", err)
}
}
kmsConfig, ok := config[kmsID].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("missing encryption KMS configuration with %s", kmsID)
}
kmsType, ok := kmsConfig[kmsTypeKey]
if !ok {
return nil, fmt.Errorf("encryption KMS configuration for %s is missing KMS type", kmsID)
}
switch kmsType {
case kmsTypeSecretsMetadata:
return initSecretsMetadataKMS(kmsID, secrets)
case kmsTypeVault:
return InitVaultKMS(kmsID, kmsConfig, secrets)
case kmsTypeVaultTokens:
return InitVaultTokensKMS(tenant, kmsID, kmsConfig)
}
return nil, fmt.Errorf("unknown encryption KMS type %s", kmsType)
}
// StoreNewCryptoPassphrase generates a new passphrase and saves it in the KMS. // StoreNewCryptoPassphrase generates a new passphrase and saves it in the KMS.
func (ve *VolumeEncryption) StoreNewCryptoPassphrase(volumeID string) error { func (ve *VolumeEncryption) StoreNewCryptoPassphrase(volumeID string) error {
passphrase, err := generateNewEncryptionPassphrase() passphrase, err := generateNewEncryptionPassphrase()

View File

@ -18,7 +18,10 @@ package util
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"os"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -27,20 +30,122 @@ const (
// kmsProviderKey is the name of the KMS provider that is registered at // kmsProviderKey is the name of the KMS provider that is registered at
// the kmsManager. This is used in the ConfigMap configuration options. // the kmsManager. This is used in the ConfigMap configuration options.
kmsProviderKey = "KMS_PROVIDER" kmsProviderKey = "KMS_PROVIDER"
// kmsTypeKey is the name of the KMS provider that is registered at
// the kmsManager. This is used in the configfile configuration
// options.
kmsTypeKey = "encryptionKMSType"
// podNamespace ENV should be set in the cephcsi container
podNamespace = "POD_NAMESPACE"
// kmsConfigMapName env to read a ConfigMap by name
kmsConfigMapName = "KMS_CONFIGMAP_NAME"
// defaultConfigMapToRead default ConfigMap name to fetch kms connection details
defaultConfigMapToRead = "csi-kms-connection-details"
) )
// getKMSConfig returns the (.Data) contents of the ConfigMap. // GetKMS returns an instance of Key Management System.
// //
// FIXME: Ceph-CSI should not talk to Kubernetes directly. // - tenant is the owner of the Volume, used to fetch the Vault Token from the
func getKMSConfig(ns, configmap string) (map[string]string, error) { // Kubernetes Namespace where the PVC lives
c := NewK8sClient() // - kmsID is the service name of the KMS configuration
cm, err := c.CoreV1().ConfigMaps(ns).Get(context.Background(), // - secrets contain additional details, like TLS certificates to connect to
configmap, metav1.GetOptions{}) // the KMS
func GetKMS(tenant, kmsID string, secrets map[string]string) (EncryptionKMS, error) {
if kmsID == "" || kmsID == defaultKMSType {
return initSecretsKMS(secrets)
}
config, err := getKMSConfiguration()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return cm.Data, nil // config contains a list of KMS connections, indexed by kmsID
section, ok := config[kmsID]
if !ok {
return nil, fmt.Errorf("could not get KMS configuration "+
"for %q", kmsID)
}
// kmsConfig can have additional sub-sections
kmsConfig, ok := section.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("failed to convert KMS configuration "+
"section: %s", kmsID)
}
return kmsManager.buildKMS(kmsID, tenant, kmsConfig, secrets)
}
// getKMSConfiguration reads the configuration file from the filesystem, or if
// that fails the ConfigMap directly. The returned map contains all the KMS
// configuration sections, each keyed by its own kmsID.
func getKMSConfiguration() (map[string]interface{}, error) {
var config map[string]interface{}
// #nosec
content, err := ioutil.ReadFile(kmsConfigPath)
if err == nil {
// kmsConfigPath exists and was successfully read
err = json.Unmarshal(content, &config)
if err != nil {
return nil, fmt.Errorf("failed to parse KMS "+
"configuration: %w", err)
}
} else {
// an error occurred while reading kmsConfigPath
if !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to read KMS "+
"configuration from %s: %w", kmsConfigPath,
err)
}
// If the configmap is not mounted to the CSI pods read the
// configmap the kubernetes.
config, err = getKMSConfigMap()
if err != nil {
return nil, err
}
}
return config, nil
}
// getKMSConfigMap returns the contents of the ConfigMap.
//
// FIXME: Ceph-CSI should not talk to Kubernetes directly.
func getKMSConfigMap() (map[string]interface{}, error) {
ns := os.Getenv(podNamespace)
if ns == "" {
return nil, fmt.Errorf("%q is not set in the environment",
podNamespace)
}
cmName := os.Getenv(kmsConfigMapName)
if cmName == "" {
cmName = defaultConfigMapToRead
}
c := NewK8sClient()
cm, err := c.CoreV1().ConfigMaps(ns).Get(context.Background(),
cmName, metav1.GetOptions{})
if err != nil {
return nil, err
}
// convert cm.Data from map[string]interface{}
kmsConfig := make(map[string]interface{})
for kmsID, data := range cm.BinaryData {
section := make(map[string]interface{})
err = json.Unmarshal(data, &section)
if err != nil {
return nil, fmt.Errorf("could not convert contents "+
"of %q to s config section", kmsID)
}
kmsConfig[kmsID] = section
}
return kmsConfig, nil
} }
// getKMSProvider inspects the configuration and tries to identify what // getKMSProvider inspects the configuration and tries to identify what
@ -76,7 +181,7 @@ func getKMSProvider(config map[string]interface{}) (string, error) {
// KMSInitializerArgs get passed to KMSInitializerFunc when a new instance of a // KMSInitializerArgs get passed to KMSInitializerFunc when a new instance of a
// KMSProvider is initialized. // KMSProvider is initialized.
type KMSInitializerArgs struct { type KMSInitializerArgs struct {
Id, Tenant string ID, Tenant string
Config map[string]interface{} Config map[string]interface{}
Secrets map[string]string Secrets map[string]string
} }
@ -120,7 +225,12 @@ func RegisterKMSProvider(provider KMSProvider) bool {
return true return true
} }
func (kf *kmsProviderList) buildKMS(providerName, kmsID, tenant string, config map[string]interface{}, secrets map[string]string) (EncryptionKMS, error) { func (kf *kmsProviderList) buildKMS(kmsID, tenant string, config map[string]interface{}, secrets map[string]string) (EncryptionKMS, error) {
providerName, err := getKMSProvider(config)
if err != nil {
return nil, err
}
provider, ok := kf.providers[providerName] provider, ok := kf.providers[providerName]
if !ok { if !ok {
return nil, fmt.Errorf("could not find KMS provider %q", return nil, fmt.Errorf("could not find KMS provider %q",
@ -128,7 +238,7 @@ func (kf *kmsProviderList) buildKMS(providerName, kmsID, tenant string, config m
} }
return provider.Initializer(KMSInitializerArgs{ return provider.Initializer(KMSInitializerArgs{
Id: kmsID, ID: kmsID,
Tenant: tenant, Tenant: tenant,
Config: config, Config: config,
Secrets: secrets, Secrets: secrets,

View File

@ -22,7 +22,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func noinitKMS(id, tenant string, config map[string]interface{}, secrets map[string]string) (EncryptionKMS, error) { func noinitKMS(args KMSInitializerArgs) (EncryptionKMS, error) {
return nil, nil return nil, nil
} }

View File

@ -93,11 +93,16 @@ type SecretsMetadataKMS struct {
encryptionKMSID string encryptionKMSID string
} }
var _ = RegisterKMSProvider(KMSProvider{
UniqueID: kmsTypeSecretsMetadata,
Initializer: initSecretsMetadataKMS,
})
// initSecretsMetadataKMS initializes a SecretsMetadataKMS that wraps a // initSecretsMetadataKMS initializes a SecretsMetadataKMS that wraps a
// SecretsKMS, so that the passphrase from the StorageClass secrets can be used // SecretsKMS, so that the passphrase from the StorageClass secrets can be used
// for encrypting/decrypting DEKs that are stored in a detached DEKStore. // for encrypting/decrypting DEKs that are stored in a detached DEKStore.
func initSecretsMetadataKMS(encryptionKMSID string, secrets map[string]string) (EncryptionKMS, error) { func initSecretsMetadataKMS(args KMSInitializerArgs) (EncryptionKMS, error) {
eKMS, err := initSecretsKMS(secrets) eKMS, err := initSecretsKMS(args.Secrets)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -109,7 +114,7 @@ func initSecretsMetadataKMS(encryptionKMSID string, secrets map[string]string) (
smKMS := SecretsMetadataKMS{} smKMS := SecretsMetadataKMS{}
smKMS.SecretsKMS = sKMS smKMS.SecretsKMS = sKMS
smKMS.encryptionKMSID = encryptionKMSID smKMS.encryptionKMSID = args.ID
return smKMS, nil return smKMS, nil
} }

View File

@ -41,17 +41,22 @@ func TestGenerateCipher(t *testing.T) {
} }
func TestInitSecretsMetadataKMS(t *testing.T) { func TestInitSecretsMetadataKMS(t *testing.T) {
secrets := map[string]string{} args := KMSInitializerArgs{
ID: "secrets-metadata-unit-test",
Tenant: "tenant",
Config: nil,
Secrets: map[string]string{},
}
// passphrase it not set, init should fail // passphrase it not set, init should fail
kms, err := initSecretsMetadataKMS("secrets-metadata-unit-test", secrets) kms, err := initSecretsMetadataKMS(args)
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, kms) assert.Nil(t, kms)
// set a passphrase to get a working KMS // set a passphrase to get a working KMS
secrets[encryptionPassphraseKey] = "my-passphrase-from-kubernetes" args.Secrets[encryptionPassphraseKey] = "my-passphrase-from-kubernetes"
kms, err = initSecretsMetadataKMS("secrets-metadata-unit-test", secrets) kms, err = initSecretsMetadataKMS(args)
assert.NoError(t, err) assert.NoError(t, err)
require.NotNil(t, kms) require.NotNil(t, kms)
assert.Equal(t, "secrets-metadata-unit-test", kms.GetID()) assert.Equal(t, "secrets-metadata-unit-test", kms.GetID())
@ -62,9 +67,15 @@ func TestWorkflowSecretsMetadataKMS(t *testing.T) {
secrets := map[string]string{ secrets := map[string]string{
encryptionPassphraseKey: "my-passphrase-from-kubernetes", encryptionPassphraseKey: "my-passphrase-from-kubernetes",
} }
args := KMSInitializerArgs{
ID: "secrets-metadata-unit-test",
Tenant: "tenant",
Config: nil,
Secrets: secrets,
}
volumeID := "csi-vol-1b00f5f8-b1c1-11e9-8421-9243c1f659f0" volumeID := "csi-vol-1b00f5f8-b1c1-11e9-8421-9243c1f659f0"
kms, err := initSecretsMetadataKMS("secrets-metadata-unit-test", secrets) kms, err := initSecretsMetadataKMS(args)
assert.NoError(t, err) assert.NoError(t, err)
require.NotNil(t, kms) require.NotNil(t, kms)
@ -90,3 +101,8 @@ func TestWorkflowSecretsMetadataKMS(t *testing.T) {
assert.NotEqual(t, "", decryptedDEK) assert.NotEqual(t, "", decryptedDEK)
assert.Equal(t, plainDEK, decryptedDEK) assert.Equal(t, plainDEK, decryptedDEK)
} }
func TestSecretsMetadataKMSRegistered(t *testing.T) {
_, ok := kmsManager.providers[kmsTypeSecretsMetadata]
assert.True(t, ok)
}

View File

@ -260,21 +260,26 @@ func (vc *vaultConnection) Destroy() {
} }
} }
var _ = RegisterKMSProvider(KMSProvider{
UniqueID: kmsTypeVault,
Initializer: initVaultKMS,
})
// InitVaultKMS returns an interface to HashiCorp Vault KMS. // InitVaultKMS returns an interface to HashiCorp Vault KMS.
func InitVaultKMS(kmsID string, config map[string]interface{}, secrets map[string]string) (EncryptionKMS, error) { func initVaultKMS(args KMSInitializerArgs) (EncryptionKMS, error) {
kms := &VaultKMS{} kms := &VaultKMS{}
err := kms.initConnection(kmsID, config) err := kms.initConnection(args.ID, args.Config)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize Vault connection: %w", err) return nil, fmt.Errorf("failed to initialize Vault connection: %w", err)
} }
err = kms.initCertificates(config, secrets) err = kms.initCertificates(args.Config, args.Secrets)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize Vault certificates: %w", err) return nil, fmt.Errorf("failed to initialize Vault certificates: %w", err)
} }
vaultAuthPath := vaultDefaultAuthPath vaultAuthPath := vaultDefaultAuthPath
err = setConfigString(&vaultAuthPath, config, "vaultAuthPath") err = setConfigString(&vaultAuthPath, args.Config, "vaultAuthPath")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -285,7 +290,7 @@ func InitVaultKMS(kmsID string, config map[string]interface{}, secrets map[strin
} }
vaultRole := vaultDefaultRole vaultRole := vaultDefaultRole
err = setConfigString(&vaultRole, config, "vaultRole") err = setConfigString(&vaultRole, args.Config, "vaultRole")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -293,7 +298,7 @@ func InitVaultKMS(kmsID string, config map[string]interface{}, secrets map[strin
// vault.VaultBackendPathKey is "secret/" by default, use vaultPassphraseRoot if configured // vault.VaultBackendPathKey is "secret/" by default, use vaultPassphraseRoot if configured
vaultPassphraseRoot := "" vaultPassphraseRoot := ""
err = setConfigString(&vaultPassphraseRoot, config, "vaultPassphraseRoot") err = setConfigString(&vaultPassphraseRoot, args.Config, "vaultPassphraseRoot")
if err == nil { if err == nil {
// the old example did have "/v1/secret/", convert that format // the old example did have "/v1/secret/", convert that format
if strings.HasPrefix(vaultPassphraseRoot, "/v1/") { if strings.HasPrefix(vaultPassphraseRoot, "/v1/") {
@ -306,7 +311,7 @@ func InitVaultKMS(kmsID string, config map[string]interface{}, secrets map[strin
} }
kms.vaultPassphrasePath = vaultDefaultPassphrasePath kms.vaultPassphrasePath = vaultDefaultPassphrasePath
err = setConfigString(&kms.vaultPassphrasePath, config, "vaultPassphrasePath") err = setConfigString(&kms.vaultPassphrasePath, args.Config, "vaultPassphrasePath")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -20,6 +20,8 @@ import (
"errors" "errors"
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestDetectAuthMountPath(t *testing.T) { func TestDetectAuthMountPath(t *testing.T) {
@ -95,3 +97,8 @@ func TestSetConfigString(t *testing.T) {
t.Error("optionDefaultOverload should have been updated") t.Error("optionDefaultOverload should have been updated")
} }
} }
func TestVaultKMSRegistered(t *testing.T) {
_, ok := kmsManager.providers[kmsTypeVault]
assert.True(t, ok)
}

View File

@ -96,38 +96,44 @@ func (v *vaultTokenConf) convertStdVaultToCSIConfig(s *standardVault) {
} }
} }
// getVaultConfiguration fetches the vault configuration from the kubernetes // convertConfig takes the keys/values in standard Vault environment variable
// configmap and converts the standard vault configuration (see json tag of // format, and converts them to the format that is used in the configuration
// standardVault structure) to the CSI vault configuration. // file.
func getVaultConfiguration(namespace, name string) (map[string]interface{}, error) { // This uses JSON marshaling and unmarshaling to map the Vault environment
c := NewK8sClient() // configuration into bytes, then in the standardVault struct, which is passed
cm, err := c.CoreV1().ConfigMaps(namespace).Get(context.Background(), name, metav1.GetOptions{}) // through convertStdVaultToCSIConfig before converting back to a
// map[string]interface{} configuration.
//
// FIXME: this can surely be simplified?!
func transformConfig(svMap map[string]interface{}) (map[string]interface{}, error) {
// convert the map to JSON
data, err := json.Marshal(svMap)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to convert config %T to JSON: %w", svMap, err)
} }
config := make(map[string]interface{})
// convert the standard vault configuration to CSI vault configuration and // convert the JSON back to a standardVault struct
// store it in the map that CSI expects sv := &standardVault{}
for k, v := range cm.Data { err = json.Unmarshal(data, sv)
sv := &standardVault{} if err != nil {
err = json.Unmarshal([]byte(v), sv) return nil, fmt.Errorf("failed to Unmarshal the vault configuration: %w", err)
if err != nil {
return nil, fmt.Errorf("failed to Unmarshal the vault configuration for %q: %w", k, err)
}
vc := vaultTokenConf{}
vc.convertStdVaultToCSIConfig(sv)
data, err := json.Marshal(vc)
if err != nil {
return nil, fmt.Errorf("failed to Marshal the CSI vault configuration for %q: %w", k, err)
}
jsonMap := make(map[string]interface{})
err = json.Unmarshal(data, &jsonMap)
if err != nil {
return nil, fmt.Errorf("failed to Unmarshal the CSI vault configuration for %q: %w", k, err)
}
config[k] = jsonMap
} }
return config, nil
// convert the standardVault struct to a vaultTokenConf struct
vc := vaultTokenConf{}
vc.convertStdVaultToCSIConfig(sv)
data, err = json.Marshal(vc)
if err != nil {
return nil, fmt.Errorf("failed to Marshal the CSI vault configuration: %w", err)
}
// convert the vaultTokenConf struct to a map[string]interface{}
jsonMap := make(map[string]interface{})
err = json.Unmarshal(data, &jsonMap)
if err != nil {
return nil, fmt.Errorf("failed to Unmarshal the CSI vault configuration: %w", err)
}
return jsonMap, nil
} }
/* /*
@ -171,11 +177,29 @@ type VaultTokensKMS struct {
TokenName string TokenName string
} }
var _ = RegisterKMSProvider(KMSProvider{
UniqueID: kmsTypeVaultTokens,
Initializer: initVaultTokensKMS,
})
// InitVaultTokensKMS returns an interface to HashiCorp Vault KMS. // InitVaultTokensKMS returns an interface to HashiCorp Vault KMS.
func InitVaultTokensKMS(tenant, kmsID string, config map[string]interface{}) (EncryptionKMS, error) { // InitVaultTokensKMS returns an interface to HashiCorp Vault KMS.
func initVaultTokensKMS(args KMSInitializerArgs) (EncryptionKMS, error) {
var err error
config := args.Config
_, ok := config[kmsProviderKey]
if ok {
// configuration comes from the ConfigMap, needs to be
// converted to vaultTokenConf type
config, err = transformConfig(config)
if err != nil {
return nil, fmt.Errorf("failed to convert configuration: %w", err)
}
}
kms := &VaultTokensKMS{} kms := &VaultTokensKMS{}
kms.Tenant = tenant err = kms.initConnection(args.ID, args.Config)
err := kms.initConnection(kmsID, config)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize Vault connection: %w", err) return nil, fmt.Errorf("failed to initialize Vault connection: %w", err)
} }
@ -190,14 +214,15 @@ func InitVaultTokensKMS(tenant, kmsID string, config map[string]interface{}) (En
} }
// fetch the configuration for the tenant // fetch the configuration for the tenant
if tenant != "" { if args.Tenant != "" {
kms.Tenant = args.Tenant
tenantsMap, ok := config["tenants"] tenantsMap, ok := config["tenants"]
if ok { if ok {
// tenants is a map per tenant, containing key/values // tenants is a map per tenant, containing key/values
tenants, ok := tenantsMap.(map[string]map[string]interface{}) tenants, ok := tenantsMap.(map[string]map[string]interface{})
if ok { if ok {
// get the map for the tenant of the current operation // get the map for the tenant of the current operation
tenantConfig, ok := tenants[tenant] tenantConfig, ok := tenants[args.Tenant]
if ok { if ok {
// override connection details from the tenant // override connection details from the tenant
err = kms.parseConfig(tenantConfig) err = kms.parseConfig(tenantConfig)
@ -216,9 +241,9 @@ func InitVaultTokensKMS(tenant, kmsID string, config map[string]interface{}) (En
// fetch the Vault Token from the Secret (TokenName) in the Kubernetes // fetch the Vault Token from the Secret (TokenName) in the Kubernetes
// Namespace (tenant) // Namespace (tenant)
kms.vaultConfig[api.EnvVaultToken], err = getToken(tenant, kms.TokenName) kms.vaultConfig[api.EnvVaultToken], err = getToken(args.Tenant, kms.TokenName)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed fetching token from %s/%s: %w", tenant, kms.TokenName, err) return nil, fmt.Errorf("failed fetching token from %s/%s: %w", args.Tenant, kms.TokenName, err)
} }
err = kms.initCertificates(config) err = kms.initCertificates(config)

View File

@ -21,6 +21,9 @@ import (
"errors" "errors"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestParseConfig(t *testing.T) { func TestParseConfig(t *testing.T) {
@ -34,7 +37,7 @@ func TestParseConfig(t *testing.T) {
t.Errorf("unexpected error (%T): %s", err, err) t.Errorf("unexpected error (%T): %s", err, err)
} }
// fill default options (normally done in InitVaultTokensKMS) // fill default options (normally done in initVaultTokensKMS)
config["vaultAddress"] = "https://vault.default.cluster.svc" config["vaultAddress"] = "https://vault.default.cluster.svc"
config["tenantConfigName"] = vaultTokensDefaultConfigName config["tenantConfigName"] = vaultTokensDefaultConfigName
config["tenantTokenName"] = vaultTokensDefaultTokenName config["tenantTokenName"] = vaultTokensDefaultTokenName
@ -65,7 +68,7 @@ func TestParseConfig(t *testing.T) {
// TestInitVaultTokensKMS verifies that passing partial and complex // TestInitVaultTokensKMS verifies that passing partial and complex
// configurations get applied correctly. // configurations get applied correctly.
// //
// When vault.New() is called at the end of InitVaultTokensKMS(), errors will // When vault.New() is called at the end of initVaultTokensKMS(), errors will
// mention the missing VAULT_TOKEN, and that is expected. // mention the missing VAULT_TOKEN, and that is expected.
func TestInitVaultTokensKMS(t *testing.T) { func TestInitVaultTokensKMS(t *testing.T) {
if true { if true {
@ -74,39 +77,44 @@ func TestInitVaultTokensKMS(t *testing.T) {
return return
} }
config := make(map[string]interface{}) args := KMSInitializerArgs{
ID: "vault-tokens-config",
Tenant: "bob",
Config: make(map[string]interface{}),
Secrets: nil,
}
// empty config map // empty config map
_, err := InitVaultTokensKMS("bob", "vault-tokens-config", config) _, err := initVaultTokensKMS(args)
if !errors.Is(err, errConfigOptionMissing) { if !errors.Is(err, errConfigOptionMissing) {
t.Errorf("unexpected error (%T): %s", err, err) t.Errorf("unexpected error (%T): %s", err, err)
} }
// fill required options // fill required options
config["vaultAddress"] = "https://vault.default.cluster.svc" args.Config["vaultAddress"] = "https://vault.default.cluster.svc"
// parsing with all required options // parsing with all required options
_, err = InitVaultTokensKMS("bob", "vault-tokens-config", config) _, err = initVaultTokensKMS(args)
if err != nil && !strings.Contains(err.Error(), "VAULT_TOKEN") { if err != nil && !strings.Contains(err.Error(), "VAULT_TOKEN") {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
// fill tenants // fill tenants
tenants := make(map[string]interface{}) tenants := make(map[string]interface{})
config["tenants"] = tenants args.Config["tenants"] = tenants
// empty tenants list // empty tenants list
_, err = InitVaultTokensKMS("bob", "vault-tokens-config", config) _, err = initVaultTokensKMS(args)
if err != nil && !strings.Contains(err.Error(), "VAULT_TOKEN") { if err != nil && !strings.Contains(err.Error(), "VAULT_TOKEN") {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
// add tenant "bob" // add tenant "bob"
bob := make(map[string]interface{}) bob := make(map[string]interface{})
config["tenants"].(map[string]interface{})["bob"] = bob
bob["vaultAddress"] = "https://vault.bob.example.org" bob["vaultAddress"] = "https://vault.bob.example.org"
args.Config["tenants"].(map[string]interface{})["bob"] = bob
_, err = InitVaultTokensKMS("bob", "vault-tokens-config", config) _, err = initVaultTokensKMS(args)
if err != nil && !strings.Contains(err.Error(), "VAULT_TOKEN") { if err != nil && !strings.Contains(err.Error(), "VAULT_TOKEN") {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
@ -158,3 +166,33 @@ func TestStdVaultToCSIConfig(t *testing.T) {
t.Errorf("unexpected value for VaultCAVerify: %s", v.VaultCAVerify) t.Errorf("unexpected value for VaultCAVerify: %s", v.VaultCAVerify)
} }
} }
func TestTransformConfig(t *testing.T) {
cm := make(map[string]interface{})
cm["KMS_PROVIDER"] = "vaulttokens"
cm["VAULT_ADDR"] = "https://vault.example.com"
cm["VAULT_BACKEND_PATH"] = "/secret"
cm["VAULT_CACERT"] = ""
cm["VAULT_TLS_SERVER_NAME"] = "vault.example.com"
cm["VAULT_CLIENT_CERT"] = ""
cm["VAULT_CLIENT_KEY"] = ""
cm["VAULT_NAMESPACE"] = "a-department"
cm["VAULT_SKIP_VERIFY"] = "true" // inverse of "vaultCAVerify"
config, err := transformConfig(cm)
require.NoError(t, err)
assert.Equal(t, config["encryptionKMSType"], cm["KMS_PROVIDER"])
assert.Equal(t, config["vaultAddress"], cm["VAULT_ADDR"])
assert.Equal(t, config["vaultBackendPath"], cm["VAULT_BACKEND_PATH"])
assert.Equal(t, config["vaultCAFromSecret"], cm["VAULT_CACERT"])
assert.Equal(t, config["vaultTLSServerName"], cm["VAULT_TLS_SERVER_NAME"])
assert.Equal(t, config["vaultClientCertFromSecret"], cm["VAULT_CLIENT_CERT"])
assert.Equal(t, config["vaultClientCertKeyFromSecret"], cm["VAULT_CLIENT_KEY"])
assert.Equal(t, config["vaultNamespace"], cm["VAULT_NAMESPACE"])
assert.Equal(t, config["vaultCAVerify"], "false")
}
func TestVaultTokensKMSRegistered(t *testing.T) {
_, ok := kmsManager.providers[kmsTypeVaultTokens]
assert.True(t, ok)
}