mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-26 08:10:20 +00:00
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:
parent
b43d28d35b
commit
9317e2afb4
@ -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()
|
||||||
|
@ -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, §ion)
|
||||||
|
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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user