From ffe81f90d886067edaa28e1c67977c35ba6a3da8 Mon Sep 17 00:00:00 2001 From: gangwgr Date: Thu, 2 Jul 2026 18:46:03 +0530 Subject: [PATCH] Adding key identifier in KMS to KMS cases --- test/library/encryption/assertion.go | 90 ++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/test/library/encryption/assertion.go b/test/library/encryption/assertion.go index 02261356ba..794700940f 100644 --- a/test/library/encryption/assertion.go +++ b/test/library/encryption/assertion.go @@ -6,6 +6,8 @@ import ( "encoding/hex" "fmt" "reflect" + "strconv" + "strings" "testing" "time" @@ -179,3 +181,91 @@ func encryptionModeFromEtcdValue(data []byte) (string, bool) { func hasPrefixAndTrailingData(data, prefix []byte) bool { return bytes.HasPrefix(data, prefix) && len(data) > len(prefix) } + +// kmsPluginNameFromEtcdValue extracts the KMS provider name from etcd data encrypted with KMS v2. +// In OpenShift the name is "{operatorKeyID}_{resource}", e.g. "2_secrets". +func kmsPluginNameFromEtcdValue(data []byte) (string, bool) { + if !bytes.HasPrefix(data, []byte(kmsTransformerPrefixV2)) { + return "", false + } + rest := data[len(kmsTransformerPrefixV2):] + i := bytes.IndexByte(rest, ':') + if i <= 0 { + return "", false + } + return string(rest[:i]), true +} + +// operatorKeyIDFromKMSPluginName extracts the operator key ID from a KMS plugin name. +func operatorKeyIDFromKMSPluginName(pluginName string) (string, bool) { + keyID, _, ok := strings.Cut(pluginName, "_") + if !ok || keyID == "" { + return "", false + } + return keyID, true +} + +// kmsPrefixForDiagnostics returns the readable KMS encryption prefix from etcd data. +// Only the ASCII prefix is included (transformer + plugin name); encrypted payload bytes are omitted. +func kmsPrefixForDiagnostics(data []byte) string { + if len(data) == 0 { + return "" + } + pluginName, ok := kmsPluginNameFromEtcdValue(data) + if ok { + return kmsTransformerPrefixV2 + pluginName + ":" + } + if bytes.HasPrefix(data, []byte(kmsTransformerPrefixV2)) { + return kmsTransformerPrefixV2 + "" + } + return "" +} + +func expectedKMSPrefix(operatorKeyID, resource string) string { + return fmt.Sprintf("%s%s_%s:", kmsTransformerPrefixV2, operatorKeyID, resource) +} + +// OperatorKeyIDFromEncryptionKeyMeta returns the numeric operator key ID from an encryption-key secret name. +func OperatorKeyIDFromEncryptionKeyMeta(t testing.TB, meta EncryptionKeyMeta) string { + t.Helper() + keyID, ok := encryptionKeyNameToKeyID(meta.Name) + require.True(t, ok, "invalid encryption key secret name %q", meta.Name) + return strconv.FormatUint(keyID, 10) +} + +// AssertEtcdValueEncryptedWithOperatorKeyID verifies raw etcd data starts with +// k8s:enc:kms:v2:{expectedKeyID}_{gr.Resource}:. +func AssertEtcdValueEncryptedWithOperatorKeyID(t testing.TB, raw []byte, expectedKeyID string, gr schema.GroupResource) { + t.Helper() + // OpenShift builds KMS plugin names as "{keyID}_{resource}" where resource is the + // bare resource name (gr.Resource), not gr.String() — see stateToProviders in encryptiondata/config.go. + resource := gr.Resource + want := expectedKMSPrefix(expectedKeyID, resource) + t.Logf("Verifying etcd encryption prefix for resource %q matches operator keyID %q", resource, expectedKeyID) + + if !bytes.HasPrefix(raw, []byte(want)) { + gotPrefix := kmsPrefixForDiagnostics(raw) + gotKeyID := "" + if pluginName, ok := kmsPluginNameFromEtcdValue(raw); ok { + if keyID, ok := operatorKeyIDFromKMSPluginName(pluginName); ok { + gotKeyID = keyID + } + } + t.Errorf("etcd data for resource %q: want prefix %q, got %q (operator keyID %q, expected %q)", + resource, want, gotPrefix, gotKeyID, expectedKeyID) + } else { + t.Logf("etcd data for resource %q matches operator keyID %q", resource, expectedKeyID) + } +} + +// AssertKMSEncryptedWithWriteKey verifies raw etcd data uses the current write key for the given resource. +func AssertKMSEncryptedWithWriteKey(t testing.TB, kubeClient kubernetes.Interface, namespace, labelSelector string, raw []byte, gr schema.GroupResource) { + t.Helper() + lastMigratedKeyMeta, err := GetLastKeyMeta(t, kubeClient, namespace, labelSelector) + require.NoError(t, err) + require.NotEmpty(t, lastMigratedKeyMeta.Name, "no encryption key found to verify KMS keyID") + + keyID := OperatorKeyIDFromEncryptionKeyMeta(t, lastMigratedKeyMeta) + t.Logf("Checking if etcd data for resource %v uses write key with operator keyID %q in namespace %q", gr, keyID, namespace) + AssertEtcdValueEncryptedWithOperatorKeyID(t, raw, keyID, gr) +}