package e2e import ( "fmt" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kubernetes/test/e2e/framework" ) const ( // defaultVaultBackendPath is the default VAULT_BACKEND_PATH for secrets. defaultVaultBackendPath = "secret/" ) // kmsConfig is an interface that should be used when passing a configuration // for a KMS to validation functions. This allows the validation functions to // work independently from the actual KMS. type kmsConfig interface { canGetPassphrase() bool getPassphrase(f *framework.Framework, key string) (string, string) canVerifyKeyDestroyed() bool verifyKeyDestroyed(f *framework.Framework, key string) (bool, string) } // simpleKMS is to be used for KMS configurations that do not offer options to // validate the passphrase stored in the KMS. type simpleKMS struct { provider string } // vaultConfig describes the configuration of the Hashicorp Vault service that // is used to store the encryption passphrase in. type vaultConfig struct { *simpleKMS backendPath string // destroyKeys indicates that a Vault config needs to destroy the // metadata of deleted keys in addition to the data destroyKeys bool } // The following variables describe different KMS services as they are defined // in the kms-config.yaml file. These variables can be passed on to validation // functions when a StorageClass has a KMS enabled. var ( noKMS kmsConfig secretsMetadataKMS = &simpleKMS{ provider: "secrets-metadata", } vaultKMS = &vaultConfig{ simpleKMS: &simpleKMS{ provider: "vault", }, backendPath: defaultVaultBackendPath + "ceph-csi/", destroyKeys: true, } vaultTokensKMS = &vaultConfig{ simpleKMS: &simpleKMS{ provider: "vaulttokens", }, backendPath: defaultVaultBackendPath, destroyKeys: true, } vaultTenantSAKMS = &vaultConfig{ simpleKMS: &simpleKMS{ provider: "vaulttenantsa", }, backendPath: "tenant/", destroyKeys: false, } ) func (sk *simpleKMS) String() string { return sk.provider } // canGetPassphrase returns false for the basic KMS configuration as there is // currently no way to fetch the passphrase. func (sk *simpleKMS) canGetPassphrase() bool { return false } func (sk *simpleKMS) getPassphrase(f *framework.Framework, key string) (string, string) { return "", "" } func (sk *simpleKMS) canVerifyKeyDestroyed() bool { return false } func (sk *simpleKMS) verifyKeyDestroyed(f *framework.Framework, key string) (bool, string) { return false, "" } func (vc *vaultConfig) String() string { return fmt.Sprintf("%s (backend path %q)", vc.simpleKMS, vc.backendPath) } // canGetPassphrase returns true for the Hashicorp Vault KMS configurations as // the Vault CLI can be used to retrieve the passphrase. func (vc *vaultConfig) canGetPassphrase() bool { return true } // getPassphrase method will execute few commands to try read the secret for // specified key from inside the vault container: // * authenticate with vault and ignore any stdout (we do not need output) // * issue get request for particular key // resulting in stdOut (first entry in tuple) - output that contains the key // or stdErr (second entry in tuple) - error getting the key. func (vc *vaultConfig) getPassphrase(f *framework.Framework, key string) (string, string) { vaultAddr := fmt.Sprintf("http://vault.%s.svc.cluster.local:8200", cephCSINamespace) loginCmd := fmt.Sprintf("vault login -address=%s sample_root_token_id > /dev/null", vaultAddr) readSecret := fmt.Sprintf("vault kv get -address=%s -field=data %s%s", vaultAddr, vc.backendPath, key) cmd := fmt.Sprintf("%s && %s", loginCmd, readSecret) opt := metav1.ListOptions{ LabelSelector: "app=vault", } stdOut, stdErr := execCommandInPodAndAllowFail(f, cmd, cephCSINamespace, &opt) return strings.TrimSpace(stdOut), strings.TrimSpace(stdErr) } // canVerifyKeyDestroyed returns true in case the Vault configuration for the // KMS setup destroys the keys in addition to (soft) deleting the contents. func (vc *vaultConfig) canVerifyKeyDestroyed() bool { return vc.destroyKeys } // verifyKeyDestroyed checks for the metadata of a deleted key. If the // deletion_time from the metadata can be read, the key has not been destroyed // but only (soft) deleted. func (vc *vaultConfig) verifyKeyDestroyed(f *framework.Framework, key string) (bool, string) { vaultAddr := fmt.Sprintf("http://vault.%s.svc.cluster.local:8200", cephCSINamespace) loginCmd := fmt.Sprintf("vault login -address=%s sample_root_token_id > /dev/null", vaultAddr) readDeletionTime := fmt.Sprintf("vault kv metadata get -address=%s -field=deletion_time %s%s", vaultAddr, vc.backendPath, key) cmd := fmt.Sprintf("%s && %s", loginCmd, readDeletionTime) opt := metav1.ListOptions{ LabelSelector: "app=vault", } stdOut, stdErr := execCommandInPodAndAllowFail(f, cmd, cephCSINamespace, &opt) // in case stdOut contains something, it will be the deletion_time // when the deletion_time is set, the metadata is still available and not destroyed if strings.TrimSpace(stdOut) != "" { return false, stdOut } // when stdOut is empty, assume the key is completely destroyed return true, stdErr }