diff --git a/test/library/encryption/kms/provider.go b/test/library/encryption/kms/provider.go new file mode 100644 index 0000000000..03753d004e --- /dev/null +++ b/test/library/encryption/kms/provider.go @@ -0,0 +1,45 @@ +package kms + +import ( + "testing" + + configv1 "github.com/openshift/api/config/v1" +) + +// KMSTestProvider pairs a KMS encryption config with its prerequisite setup. +// This allows the same test cases to run against any KMS provider (Vault, AWS, etc.) +// by simply iterating over a slice of providers. +type KMSTestProvider struct { + Name string + EncryptionConfig configv1.APIServerEncryption + // Setup is called before the test runs to ensure any prerequisites + // (secrets, credentials, infrastructure) are in place. + // Pass nil if no setup is needed. + Setup func(t testing.TB) +} + +// DefaultVaultTestProvider is a ready-to-use Vault KMS provider for e2e tests. +var DefaultVaultTestProvider = KMSTestProvider{ + Name: "Vault", + EncryptionConfig: DefaultVaultKMSEncryptionConfig, + Setup: EnsureVaultAppRoleSecret, +} + +// SetupAll runs the Setup function for each provider that has one. +func SetupAll(t testing.TB, providers []KMSTestProvider) { + t.Helper() + for _, p := range providers { + if p.Setup != nil { + p.Setup(t) + } + } +} + +// EncryptionConfigs extracts the APIServerEncryption configs from a slice of providers. +func EncryptionConfigs(providers []KMSTestProvider) []configv1.APIServerEncryption { + configs := make([]configv1.APIServerEncryption, len(providers)) + for i, p := range providers { + configs[i] = p.EncryptionConfig + } + return configs +} diff --git a/test/library/encryption/kms/vault.go b/test/library/encryption/kms/vault.go index ff09697334..56c7272bc2 100644 --- a/test/library/encryption/kms/vault.go +++ b/test/library/encryption/kms/vault.go @@ -1,11 +1,54 @@ package kms import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + configv1 "github.com/openshift/api/config/v1" + library "github.com/openshift/library-go/test/library/encryption" +) + +const ( + DefaultVaultNamespace = "vault-kms" + DefaultVaultCredentialsSecret = "vault-credentials" + DefaultVaultAppRoleSecretName = "vault-approle-secret" + DefaultVaultKMSPluginImage = "quay.io/openshifttest/mock-kms-plugin@sha256:03bb07a2c08b509653c4c70217a06a4b389c10b4d87922f50ee5eac82db5e140" + DefaultVaultAddress = "https://vault.vault-kms.svc:8200" + DefaultVaultEnterpriseNS = "admin" + DefaultVaultTransitMount = "transit" + DefaultVaultTransitKey = "kms-key" + DefaultAppRoleTargetNamespace = "openshift-config" ) -// DefaultFakeKMSPluginConfig is a fake Vault KMS configuration used by tests. -// The values are not real and are likely to change as the KMS integration evolves. +// DefaultVaultKMSEncryptionConfig is the standard Vault KMS encryption config +// used by CI e2e tests. Consumers can use this directly or copy/modify as needed. +var DefaultVaultKMSEncryptionConfig = configv1.APIServerEncryption{ + Type: configv1.EncryptionTypeKMS, + KMS: configv1.KMSPluginConfig{ + Type: configv1.VaultKMSProvider, + Vault: configv1.VaultKMSPluginConfig{ + KMSPluginImage: DefaultVaultKMSPluginImage, + VaultAddress: DefaultVaultAddress, + VaultNamespace: DefaultVaultEnterpriseNS, + TransitMount: DefaultVaultTransitMount, + TransitKey: DefaultVaultTransitKey, + Authentication: configv1.VaultAuthentication{ + Type: configv1.VaultAuthenticationTypeAppRole, + AppRole: configv1.VaultAppRoleAuthentication{ + Secret: configv1.VaultSecretReference{Name: DefaultVaultAppRoleSecretName}, + }, + }, + }, + }, +} + +// DefaultFakeKMSPluginConfig is a fake Vault KMS configuration used by unit tests. var DefaultFakeKMSPluginConfig = configv1.KMSPluginConfig{ Type: configv1.VaultKMSProvider, Vault: configv1.VaultKMSPluginConfig{ @@ -20,3 +63,40 @@ var DefaultFakeKMSPluginConfig = configv1.KMSPluginConfig{ TransitKey: "test-transit-key", }, } + +// EnsureVaultAppRoleSecret reads credentials from the vault-credentials secret +// (created by a CI step) and creates/updates the AppRole secret in openshift-config. +func EnsureVaultAppRoleSecret(t testing.TB) { + t.Helper() + ctx := context.Background() + cs := library.GetClients(t) + + creds, err := cs.Kube.CoreV1().Secrets(DefaultVaultNamespace).Get(ctx, DefaultVaultCredentialsSecret, metav1.GetOptions{}) + require.NoError(t, err, "failed to read %s/%s secret (was the vault-install CI step run?)", DefaultVaultNamespace, DefaultVaultCredentialsSecret) + + roleID, ok := creds.Data["role-id"] + require.Truef(t, ok && len(roleID) > 0, "missing or empty key %q in %s/%s", "role-id", DefaultVaultNamespace, DefaultVaultCredentialsSecret) + secretID, ok := creds.Data["secret-id"] + require.Truef(t, ok && len(secretID) > 0, "missing or empty key %q in %s/%s", "secret-id", DefaultVaultNamespace, DefaultVaultCredentialsSecret) + + appRoleSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: DefaultVaultAppRoleSecretName, + Namespace: DefaultAppRoleTargetNamespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "roleID": roleID, + "secretID": secretID, + }, + } + _, err = cs.Kube.CoreV1().Secrets(DefaultAppRoleTargetNamespace).Create(ctx, appRoleSecret, metav1.CreateOptions{}) + if apierrors.IsAlreadyExists(err) { + existing, getErr := cs.Kube.CoreV1().Secrets(DefaultAppRoleTargetNamespace).Get(ctx, DefaultVaultAppRoleSecretName, metav1.GetOptions{}) + require.NoError(t, getErr, "failed to get existing AppRole secret for update") + appRoleSecret.ResourceVersion = existing.ResourceVersion + _, err = cs.Kube.CoreV1().Secrets(DefaultAppRoleTargetNamespace).Update(ctx, appRoleSecret, metav1.UpdateOptions{}) + } + require.NoError(t, err, "failed to create/update AppRole secret") + t.Logf("Created/updated AppRole secret %s in %s", DefaultVaultAppRoleSecretName, DefaultAppRoleTargetNamespace) +}