From 130f8fe67b55d9e0e6882e3919e3bb7014daada8 Mon Sep 17 00:00:00 2001 From: Rohit Patil Date: Tue, 30 Jun 2026 15:15:15 +0530 Subject: [PATCH] Migrate test/e2e-encryption-perf to OpenShift Tests Extension framework Migrate encryption performance tests to support both standard Go tests and Ginkgo/OTE framework, addressing reviewer feedback to eliminate code duplication. Changes: 1. Add Ginkgo test wrapper for OTE compatibility 2. Refactor to use testing.TB interface for dual-mode support 3. Update library-go to v0.0.0-20260630085645 (includes testing.TB support) 4. Remove duplicated library-go functions (432 lines): - test/library/encryption/scenarios.go (247 lines) - test/library/encryption/perf_helpers.go (185 lines) 5. Use library.TestPerfEncryption directly instead of local copy 6. Update vendor dependencies from library-go update The test now works in both modes: - Standard Go: TestPerfEncryptionTypeAESCBC(t *testing.T) - Ginkgo/OTE: g.It(..., func(ctx) { testPerfEncryptionTypeAESCBC(ctx, g.GinkgoTB()) }) Addresses reviewer feedback from PR #926 by removing code duplication and using library-go's testing.TB-compatible functions. Depends-on: https://github.com/openshift/library-go/pull/2335 Co-Authored-By: Claude Sonnet 4.5 --- .../main.go | 11 ++ go.mod | 2 +- go.sum | 4 +- test/e2e-encryption-perf/encryption_perf.go | 112 ++++++++++++++++++ .../encryption_perf_test.go | 107 +---------------- .../test/library/encryption/assertion_auth.go | 57 +++++++++ .../test/library/encryption/assertion_oas.go | 55 +++++++++ .../test/library/encryption/helpers.go | 7 +- .../test/library/encryption/helpers_auth.go | 80 +++++++++++++ .../test/library/encryption/helpers_oas.go | 72 +++++++++++ .../test/library/encryption/perf_scenarios.go | 4 +- .../test/library/encryption/scenarios.go | 94 --------------- vendor/modules.txt | 2 +- 13 files changed, 405 insertions(+), 202 deletions(-) create mode 100644 test/e2e-encryption-perf/encryption_perf.go create mode 100644 vendor/github.com/openshift/library-go/test/library/encryption/assertion_auth.go create mode 100644 vendor/github.com/openshift/library-go/test/library/encryption/assertion_oas.go create mode 100644 vendor/github.com/openshift/library-go/test/library/encryption/helpers_auth.go create mode 100644 vendor/github.com/openshift/library-go/test/library/encryption/helpers_oas.go diff --git a/cmd/cluster-authentication-operator-tests-ext/main.go b/cmd/cluster-authentication-operator-tests-ext/main.go index fdd363a2c..be7d99777 100644 --- a/cmd/cluster-authentication-operator-tests-ext/main.go +++ b/cmd/cluster-authentication-operator-tests-ext/main.go @@ -14,6 +14,7 @@ import ( _ "github.com/openshift/cluster-authentication-operator/test/e2e" _ "github.com/openshift/cluster-authentication-operator/test/e2e-encryption-kms" + _ "github.com/openshift/cluster-authentication-operator/test/e2e-encryption-perf" "k8s.io/klog/v2" ) @@ -92,6 +93,16 @@ func prepareOperatorTestsRegistry() (*oteextension.Registry, error) { ClusterStability: oteextension.ClusterStabilityDisruptive, }) + // ClusterStability set to Disruptive: encryption perf tests trigger API server rollouts. + extension.AddSuite(oteextension.Suite{ + Name: "openshift/cluster-authentication-operator/operator-encryption-perf/serial", + Parallelism: 1, + ClusterStability: oteextension.ClusterStabilityDisruptive, + Qualifiers: []string{ + `name.contains("[Encryption]") && name.contains("[Serial]") && name.contains("Perf")`, + }, + }) + // The following suite runs KMS encryption tests. extension.AddSuite(oteextension.Suite{ Name: "openshift/cluster-authentication-operator/encryption-kms", diff --git a/go.mod b/go.mod index 2bd28ec21..65e61f981 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/openshift/api v0.0.0-20260623101811-c5eb460d04e3 github.com/openshift/build-machinery-go v0.0.0-20251023084048-5d77c1a5e5af github.com/openshift/client-go v0.0.0-20260622130833-df412d4d283e - github.com/openshift/library-go v0.0.0-20260629134817-a64e9a6e21ca + github.com/openshift/library-go v0.0.0-20260630085645-45679084a74b github.com/openshift/multi-operator-manager v0.0.0-20241205181422-20aa3906b99d github.com/openshift/oauth-apiserver v0.0.0-20260520145010-97a820bd5412 github.com/spf13/cobra v1.10.0 diff --git a/go.sum b/go.sum index 47e63ab43..60f69992f 100644 --- a/go.sum +++ b/go.sum @@ -178,8 +178,8 @@ github.com/openshift/build-machinery-go v0.0.0-20251023084048-5d77c1a5e5af h1:Ui github.com/openshift/build-machinery-go v0.0.0-20251023084048-5d77c1a5e5af/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= github.com/openshift/client-go v0.0.0-20260622130833-df412d4d283e h1:NrVmCwy3vBk6UTY+cNNFHbnB40FyusJmeUIInQod/v8= github.com/openshift/client-go v0.0.0-20260622130833-df412d4d283e/go.mod h1:lMQvYPtn6LrPO/YX2j5xdv2h6BRWgYVq1tMA3qA3N9k= -github.com/openshift/library-go v0.0.0-20260629134817-a64e9a6e21ca h1:oaM6++TO9YY9T2w1QMANcqlWY2Qc3qHkkehaFVpgPxg= -github.com/openshift/library-go v0.0.0-20260629134817-a64e9a6e21ca/go.mod h1:8d0qQEDq2kpIZmaE1tKpvZ4pLeQZT+mW2JpqAh3iZIc= +github.com/openshift/library-go v0.0.0-20260630085645-45679084a74b h1:cvaJORPgfCAZeFI+JqlaOvFosGJHd/fX2vOQZCTeTPU= +github.com/openshift/library-go v0.0.0-20260630085645-45679084a74b/go.mod h1:8d0qQEDq2kpIZmaE1tKpvZ4pLeQZT+mW2JpqAh3iZIc= github.com/openshift/multi-operator-manager v0.0.0-20241205181422-20aa3906b99d h1:Rzx23P63JFNNz5D23ubhC0FCN5rK8CeJhKcq5QKcdyU= github.com/openshift/multi-operator-manager v0.0.0-20241205181422-20aa3906b99d/go.mod h1:iVi9Bopa5cLhjG5ie9DoZVVqkH8BGb1FQVTtecOLn4I= github.com/openshift/oauth-apiserver v0.0.0-20260520145010-97a820bd5412 h1:oDB0GmUXLp8y85fWz+LGRE0hM5JqbXTfNPi5GjEqiX0= diff --git a/test/e2e-encryption-perf/encryption_perf.go b/test/e2e-encryption-perf/encryption_perf.go new file mode 100644 index 000000000..032ecbdee --- /dev/null +++ b/test/e2e-encryption-perf/encryption_perf.go @@ -0,0 +1,112 @@ +package e2e_encryption_perf + +import ( + "context" + "fmt" + "testing" + "time" + + g "github.com/onsi/ginkgo/v2" + "github.com/stretchr/testify/require" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + configv1 "github.com/openshift/api/config/v1" + oauthapiv1 "github.com/openshift/api/oauth/v1" + operatorv1 "github.com/openshift/api/operator/v1" + oauthclient "github.com/openshift/client-go/oauth/clientset/versioned/typed/oauth/v1" + operatorlibrary "github.com/openshift/cluster-authentication-operator/test/library" + operatorencryption "github.com/openshift/cluster-authentication-operator/test/library/encryption" + library "github.com/openshift/library-go/test/library/encryption" +) + +const ( + tokenStatsKey = "created oauthaccesstokens" +) + +var _ = g.Describe("[sig-auth] authentication operator", func() { + g.It("[Encryption][Serial] TestPerfEncryptionTypeAESCBC", func(ctx context.Context) { + testPerfEncryptionTypeAESCBC(ctx, g.GinkgoTB()) + }) +}) + +func testPerfEncryptionTypeAESCBC(ctx context.Context, tt testing.TB) { + ctx, cancel := context.WithTimeout(ctx, 30*time.Minute) + tt.Cleanup(cancel) + clientSet := getPerfClients(tt) + library.TestPerfEncryption(ctx, tt, library.PerfScenario{ + BasicScenario: library.BasicScenario{ + Namespace: "openshift-config-managed", + LabelSelector: "encryption.apiserver.operator.openshift.io/component" + "=" + "openshift-oauth-apiserver", + EncryptionConfigSecretName: fmt.Sprintf("encryption-config-%s", "openshift-oauth-apiserver"), + EncryptionConfigSecretNamespace: "openshift-config-managed", + OperatorNamespace: "openshift-authentication-operator", + TargetGRs: operatorencryption.DefaultTargetGRs, + AssertFunc: operatorencryption.AssertTokens, + }, + GetOperatorConditionsFunc: func(t testing.TB) ([]operatorv1.OperatorCondition, error) { + apiServerOperator, err := clientSet.OperatorClient.Get(ctx, "cluster", metav1.GetOptions{}) + if err != nil { + return nil, err + } + return apiServerOperator.Status.Conditions, nil + }, + AssertDBPopulatedFunc: func(t testing.TB, errorStore map[string]int, statStore map[string]int) { + require.Empty(t, errorStore, "db loader workers reported errors") + + tokenCount, ok := statStore[tokenStatsKey] + require.True(t, ok, "missing oauth access tokens count stats") + require.GreaterOrEqual(t, tokenCount, 14000) + t.Logf("Created %d access tokens", tokenCount) + }, + AssertMigrationTime: func(t testing.TB, migrationTime time.Duration) { + t.Logf("migration took %v", migrationTime) + expectedMigrationTime := 10 * time.Minute + if migrationTime > expectedMigrationTime { + t.Errorf("migration took too long (%v), expected it to take no more than %v", migrationTime, expectedMigrationTime) + } + }, + DBLoaderWorkers: 3, + DBLoaderFunc: library.DBLoaderRepeat(1, false, + library.DBLoaderRepeatParallel(5010, 50, false, createAccessTokenWrapper(ctx, clientSet.TokenClient), reportSecret)), + EncryptionProvider: library.EncryptionProvider{ + APIServerEncryption: configv1.APIServerEncryption{Type: configv1.EncryptionTypeAESCBC}, + }, + }) +} + +func createAccessTokenWrapper(ctx context.Context, tokenClient oauthclient.OAuthAccessTokensGetter) library.DBLoaderFuncType { + return func(_ kubernetes.Interface, namespace string, errorCollector func(error), statsCollector func(string)) error { + _, tokenNameHash := operatorlibrary.GenerateOAuthTokenPair() + token := &oauthapiv1.OAuthAccessToken{ + ObjectMeta: metav1.ObjectMeta{ + Name: tokenNameHash, + }, + RefreshToken: "I have no special talents. I am only passionately curious", + UserName: "kube:admin", + Scopes: []string{"user:full"}, + RedirectURI: "redirect.me.to.token.of.life", + ClientName: "console", + UserUID: "non-existing-user-id", + } + _, err := tokenClient.OAuthAccessTokens().Create(ctx, token, metav1.CreateOptions{}) + return err + } +} + +func reportSecret(_ kubernetes.Interface, _ string, _ func(error), statsCollector func(string)) error { + statsCollector(tokenStatsKey) + return nil +} + +func getPerfClients(t testing.TB) operatorencryption.ClientSet { + t.Helper() + + kubeConfig := operatorlibrary.NewClientConfigForTest(t) + + kubeConfig.QPS = 300 + kubeConfig.Burst = 600 + + return operatorencryption.GetClientsFor(t, kubeConfig) +} diff --git a/test/e2e-encryption-perf/encryption_perf_test.go b/test/e2e-encryption-perf/encryption_perf_test.go index 491959931..4db8d363a 100644 --- a/test/e2e-encryption-perf/encryption_perf_test.go +++ b/test/e2e-encryption-perf/encryption_perf_test.go @@ -1,109 +1,14 @@ package e2e_encryption_perf import ( - "context" - "errors" - "fmt" "testing" - "time" - - "github.com/stretchr/testify/require" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - - configv1 "github.com/openshift/api/config/v1" - oauthapiv1 "github.com/openshift/api/oauth/v1" - operatorv1 "github.com/openshift/api/operator/v1" - oauthclient "github.com/openshift/client-go/oauth/clientset/versioned/typed/oauth/v1" - operatorlibrary "github.com/openshift/cluster-authentication-operator/test/library" - operatorencryption "github.com/openshift/cluster-authentication-operator/test/library/encryption" - library "github.com/openshift/library-go/test/library/encryption" -) - -const ( - tokenStatsKey = "created oauthaccesstokens" ) +// This test calls the shared test function which +// can be called from both standard Go tests and Ginkgo tests. +// +// This situation is temporary until we verify the new e2e-aws-operator-encryption-perf-serial-ote job. +// Eventually all tests will be run only as part of the OTE framework. func TestPerfEncryptionTypeAESCBC(tt *testing.T) { - ctx := context.TODO() - clientSet := getPerfClients(tt) - library.TestPerfEncryption(tt.Context(), tt, library.PerfScenario{ - BasicScenario: library.BasicScenario{ - Namespace: "openshift-config-managed", - LabelSelector: "encryption.apiserver.operator.openshift.io/component" + "=" + "openshift-oauth-apiserver", - EncryptionConfigSecretName: fmt.Sprintf("encryption-config-%s", "openshift-oauth-apiserver"), - EncryptionConfigSecretNamespace: "openshift-config-managed", - OperatorNamespace: "openshift-authentication-operator", - TargetGRs: operatorencryption.DefaultTargetGRs, - AssertFunc: operatorencryption.AssertTokens, - }, - GetOperatorConditionsFunc: func(t testing.TB) ([]operatorv1.OperatorCondition, error) { - apiServerOperator, err := clientSet.OperatorClient.Get(ctx, "cluster", metav1.GetOptions{}) - if err != nil { - return nil, err - } - return apiServerOperator.Status.Conditions, nil - }, - AssertDBPopulatedFunc: func(t testing.TB, errorStore map[string]int, statStore map[string]int) { - tokenCount, ok := statStore[tokenStatsKey] - if !ok { - err := errors.New("missing oauth access tokens count stats, can't continue the test") - require.NoError(t, err) - } - if tokenCount < 14000 { - err := fmt.Errorf("expected to create at least 14000 tokens but %d were created", tokenCount) - require.NoError(t, err) - } - t.Logf("Created %d access tokens", tokenCount) - }, - AssertMigrationTime: func(t testing.TB, migrationTime time.Duration) { - t.Logf("migration took %v", migrationTime) - expectedMigrationTime := 10 * time.Minute - if migrationTime > expectedMigrationTime { - t.Errorf("migration took too long (%v), expected it to take no more than %v", migrationTime, expectedMigrationTime) - } - }, - DBLoaderWorkers: 3, - DBLoaderFunc: library.DBLoaderRepeat(1, false, - library.DBLoaderRepeatParallel(5010, 50, false, createAccessTokenWrapper(ctx, clientSet.TokenClient), reportSecret)), - EncryptionProvider: library.EncryptionProvider{ - APIServerEncryption: configv1.APIServerEncryption{Type: configv1.EncryptionType("aescbc")}, - }, - }) -} - -func createAccessTokenWrapper(ctx context.Context, tokenClient oauthclient.OAuthAccessTokensGetter) library.DBLoaderFuncType { - return func(_ kubernetes.Interface, namespace string, errorCollector func(error), statsCollector func(string)) error { - _, tokenNameHash := operatorlibrary.GenerateOAuthTokenPair() - token := &oauthapiv1.OAuthAccessToken{ - ObjectMeta: metav1.ObjectMeta{ - Name: tokenNameHash, - }, - RefreshToken: "I have no special talents. I am only passionately curious", - UserName: "kube:admin", - Scopes: []string{"user:full"}, - RedirectURI: "redirect.me.to.token.of.life", - ClientName: "console", - UserUID: "non-existing-user-id", - } - _, err := tokenClient.OAuthAccessTokens().Create(ctx, token, metav1.CreateOptions{}) - return err - } -} - -func reportSecret(_ kubernetes.Interface, _ string, _ func(error), statsCollector func(string)) error { - statsCollector(tokenStatsKey) - return nil -} - -func getPerfClients(t *testing.T) operatorencryption.ClientSet { - t.Helper() - - kubeConfig := operatorlibrary.NewClientConfigForTest(t) - - kubeConfig.QPS = 300 - kubeConfig.Burst = 600 - - return operatorencryption.GetClientsFor(t, kubeConfig) + testPerfEncryptionTypeAESCBC(tt.Context(), tt) } diff --git a/vendor/github.com/openshift/library-go/test/library/encryption/assertion_auth.go b/vendor/github.com/openshift/library-go/test/library/encryption/assertion_auth.go new file mode 100644 index 000000000..d32c82014 --- /dev/null +++ b/vendor/github.com/openshift/library-go/test/library/encryption/assertion_auth.go @@ -0,0 +1,57 @@ +package encryption + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + configv1 "github.com/openshift/api/config/v1" +) + +var AuthTargetGRs = []schema.GroupResource{ + {Group: "oauth.openshift.io", Resource: "oauthaccesstokens"}, + {Group: "oauth.openshift.io", Resource: "oauthauthorizetokens"}, +} + +func AssertTokenOfLifeEncrypted(t testing.TB, clientSet ClientSet, _ runtime.Object) { + t.Helper() + rawTokenValue := GetRawTokenOfLife(t, clientSet) + marker := "I have no special talents. I am only passionately curious" + if strings.Contains(rawTokenValue, marker) { + t.Errorf("access token not encrypted, etcd value contains refresh token marker in plain text") + } +} + +func AssertTokenOfLifeNotEncrypted(t testing.TB, clientSet ClientSet, _ runtime.Object) { + t.Helper() + rawTokenValue := GetRawTokenOfLife(t, clientSet) + marker := "I have no special talents. I am only passionately curious" + if !strings.Contains(rawTokenValue, marker) { + t.Errorf("access token not decrypted, etcd value does not contain refresh token marker in plain text") + } +} + +func AssertTokens(t testing.TB, clientSet ClientSet, expectedMode configv1.EncryptionType, namespace, labelSelector string) { + t.Helper() + assertAccessTokens(t, clientSet.Etcd, string(expectedMode)) + assertAuthTokens(t, clientSet.Etcd, string(expectedMode)) + AssertLastMigratedKey(t, clientSet.Kube, AuthTargetGRs, namespace, labelSelector) +} + +func assertAccessTokens(t testing.TB, etcdClient EtcdClient, expectedMode string) { + t.Logf("Checking if all OauthAccessTokens where encrypted/decrypted for %q mode", expectedMode) + totalAccessTokens, err := VerifyResources(t, etcdClient, "/openshift.io/oauth/accesstokens/", expectedMode, true) + t.Logf("Verified %d OauthAccessTokens", totalAccessTokens) + require.NoError(t, err) +} + +func assertAuthTokens(t testing.TB, etcdClient EtcdClient, expectedMode string) { + t.Logf("Checking if all OAuthAuthorizeTokens where encrypted/decrypted for %q mode", expectedMode) + totalAuthTokens, err := VerifyResources(t, etcdClient, "/openshift.io/oauth/authorizetokens/", expectedMode, true) + t.Logf("Verified %d OAuthAuthorizeTokens", totalAuthTokens) + require.NoError(t, err) +} diff --git a/vendor/github.com/openshift/library-go/test/library/encryption/assertion_oas.go b/vendor/github.com/openshift/library-go/test/library/encryption/assertion_oas.go new file mode 100644 index 000000000..6735b1d33 --- /dev/null +++ b/vendor/github.com/openshift/library-go/test/library/encryption/assertion_oas.go @@ -0,0 +1,55 @@ +package encryption + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + configv1 "github.com/openshift/api/config/v1" + routev1 "github.com/openshift/api/route/v1" +) + +var OASTargetGRs = []schema.GroupResource{ + {Group: "route.openshift.io", Resource: "routes"}, +} + +func AssertRouteOfLifeEncrypted(t testing.TB, clientSet ClientSet, resource runtime.Object) { + t.Helper() + routeOfLife, ok := resource.(*routev1.Route) + if !ok { + t.Fatalf("expected *routev1.Route, got %T", resource) + } + rawRouteValue := GetRawRouteOfLife(t, clientSet, routeOfLife.Namespace) + if strings.Contains(rawRouteValue, routeOfLife.Spec.To.Name) { + t.Errorf("route not encrypted, etcd value contains target name %q in plain text", routeOfLife.Spec.To.Name) + } +} + +func AssertRouteOfLifeNotEncrypted(t testing.TB, clientSet ClientSet, resource runtime.Object) { + t.Helper() + routeOfLife, ok := resource.(*routev1.Route) + if !ok { + t.Fatalf("expected *routev1.Route, got %T", resource) + } + rawRouteValue := GetRawRouteOfLife(t, clientSet, routeOfLife.Namespace) + if !strings.Contains(rawRouteValue, routeOfLife.Spec.To.Name) { + t.Errorf("route not decrypted, etcd value does not contain target name %q in plain text", routeOfLife.Spec.To.Name) + } +} + +func AssertRoutes(t testing.TB, clientSet ClientSet, expectedMode configv1.EncryptionType, namespace, labelSelector string) { + t.Helper() + assertRoutes(t, clientSet.Etcd, string(expectedMode)) + AssertLastMigratedKey(t, clientSet.Kube, OASTargetGRs, namespace, labelSelector) +} + +func assertRoutes(t testing.TB, etcdClient EtcdClient, expectedMode string) { + t.Logf("Checking if all Routes where encrypted/decrypted for %q mode", expectedMode) + totalRoutes, err := VerifyResources(t, etcdClient, "/openshift.io/routes/", expectedMode, false) + t.Logf("Verified %d Routes", totalRoutes) + require.NoError(t, err) +} diff --git a/vendor/github.com/openshift/library-go/test/library/encryption/helpers.go b/vendor/github.com/openshift/library-go/test/library/encryption/helpers.go index 2278ecb84..e0f836bee 100644 --- a/vendor/github.com/openshift/library-go/test/library/encryption/helpers.go +++ b/vendor/github.com/openshift/library-go/test/library/encryption/helpers.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/util/retry" @@ -47,6 +48,7 @@ type ClientSet struct { Etcd EtcdClient ApiServerConfig configv1client.APIServerInterface Kube kubernetes.Interface + DynamicClient dynamic.Interface } type EncryptionKeyMeta struct { @@ -132,7 +134,10 @@ func GetClients(t testing.TB) ClientSet { kubeClient := kubernetes.NewForConfigOrDie(kubeConfig) etcdClient := NewEtcdClient(kubeClient) - return ClientSet{Etcd: etcdClient, ApiServerConfig: apiServerConfigClient, Kube: kubeClient} + dynamicClient, err := dynamic.NewForConfig(kubeConfig) + require.NoError(t, err) + + return ClientSet{Etcd: etcdClient, ApiServerConfig: apiServerConfigClient, Kube: kubeClient, DynamicClient: dynamicClient} } func WaitForEncryptionKeyBasedOn(t testing.TB, kubeClient kubernetes.Interface, prevKeyMeta EncryptionKeyMeta, encryptionType configv1.EncryptionType, defaultTargetGRs []schema.GroupResource, namespace, labelSelector string) { diff --git a/vendor/github.com/openshift/library-go/test/library/encryption/helpers_auth.go b/vendor/github.com/openshift/library-go/test/library/encryption/helpers_auth.go new file mode 100644 index 000000000..a5c559480 --- /dev/null +++ b/vendor/github.com/openshift/library-go/test/library/encryption/helpers_auth.go @@ -0,0 +1,80 @@ +package encryption + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + oauthapiv1 "github.com/openshift/api/oauth/v1" +) + +var oauthAccessTokenGVR = schema.GroupVersionResource{Group: "oauth.openshift.io", Version: "v1", Resource: "oauthaccesstokens"} + +const tokenOfLifeName = "sha256~token-aaaaaaaa-of-aaaaaaaa-life-aaaaaaaa" + +func CreateAndStoreTokenOfLife(ctx context.Context, t testing.TB, cs ClientSet) runtime.Object { + t.Helper() + tokens := cs.DynamicClient.Resource(oauthAccessTokenGVR) + + oldToken, err := tokens.Get(ctx, tokenOfLifeName, metav1.GetOptions{}) + if err != nil && !errors.IsNotFound(err) { + t.Fatalf("Failed to check if the token already exists: %v", err) + } + if oldToken != nil && len(oldToken.GetName()) > 0 { + t.Log("The access token already exists, removing it first") + require.NoError(t, tokens.Delete(ctx, oldToken.GetName(), metav1.DeleteOptions{})) + } + + t.Logf("Creating %q at cluster scope level", tokenOfLifeName) + token := TokenOfLife(t, "") + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(token) + require.NoError(t, err) + + created, err := tokens.Create(ctx, &unstructured.Unstructured{Object: obj}, metav1.CreateOptions{}) + require.NoError(t, err) + + var result oauthapiv1.OAuthAccessToken + err = runtime.DefaultUnstructuredConverter.FromUnstructured(created.Object, &result) + require.NoError(t, err) + return &result +} + +func TokenOfLife(_ testing.TB, _ string) runtime.Object { + return &oauthapiv1.OAuthAccessToken{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "oauth.openshift.io/v1", + Kind: "OAuthAccessToken", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: tokenOfLifeName, + }, + RefreshToken: "I have no special talents. I am only passionately curious", + UserName: "kube:admin", + Scopes: []string{"user:full"}, + RedirectURI: "redirect.me.to.token.of.life", + ClientName: "console", + UserUID: "non-existing-user-id", + } +} + +func GetRawTokenOfLife(t testing.TB, clientSet ClientSet) string { + t.Helper() + timeout, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + tokenOfLifeKey := fmt.Sprintf("/openshift.io/oauth/accesstokens/%s", tokenOfLifeName) + resp, err := clientSet.Etcd.Get(timeout, tokenOfLifeKey) + require.NoError(t, err) + require.Len(t, resp.Kvs, 1, "expected exactly one key from etcd for token-of-life") + + return string(resp.Kvs[0].Value) +} diff --git a/vendor/github.com/openshift/library-go/test/library/encryption/helpers_oas.go b/vendor/github.com/openshift/library-go/test/library/encryption/helpers_oas.go new file mode 100644 index 000000000..ff2f04938 --- /dev/null +++ b/vendor/github.com/openshift/library-go/test/library/encryption/helpers_oas.go @@ -0,0 +1,72 @@ +package encryption + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/intstr" + + routev1 "github.com/openshift/api/route/v1" +) + +var routeGVR = schema.GroupVersionResource{Group: "route.openshift.io", Version: "v1", Resource: "routes"} + +func CreateAndStoreRouteOfLife(ctx context.Context, t testing.TB, cs ClientSet, ns string) runtime.Object { + t.Helper() + t.Logf("Creating %q in %q namespace", "route-of-life", ns) + + route := RouteOfLife(t, ns) + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(route) + require.NoError(t, err) + + created, err := cs.DynamicClient.Resource(routeGVR).Namespace(ns).Create(ctx, &unstructured.Unstructured{Object: obj}, metav1.CreateOptions{}) + require.NoError(t, err) + + var result routev1.Route + err = runtime.DefaultUnstructuredConverter.FromUnstructured(created.Object, &result) + require.NoError(t, err) + return &result +} + +func RouteOfLife(_ testing.TB, ns string) runtime.Object { + return &routev1.Route{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "route.openshift.io/v1", + Kind: "Route", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "route-of-life", + Namespace: ns, + }, + Spec: routev1.RouteSpec{ + Host: "devcluster.openshift.io", + Port: &routev1.RoutePort{ + TargetPort: intstr.FromInt(2014), + }, + To: routev1.RouteTargetReference{ + Name: "dummyroute", + }, + }, + } +} + +func GetRawRouteOfLife(t testing.TB, clientSet ClientSet, ns string) string { + t.Helper() + timeout, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + routeOfLifeKey := fmt.Sprintf("/openshift.io/routes/%s/%s", ns, "route-of-life") + resp, err := clientSet.Etcd.Get(timeout, routeOfLifeKey) + require.NoError(t, err) + require.Len(t, resp.Kvs, 1, "expected exactly one key from etcd for route-of-life") + + return string(resp.Kvs[0].Value) +} diff --git a/vendor/github.com/openshift/library-go/test/library/encryption/perf_scenarios.go b/vendor/github.com/openshift/library-go/test/library/encryption/perf_scenarios.go index 9e3a7cebf..0edfdc9fd 100644 --- a/vendor/github.com/openshift/library-go/test/library/encryption/perf_scenarios.go +++ b/vendor/github.com/openshift/library-go/test/library/encryption/perf_scenarios.go @@ -23,7 +23,7 @@ type PerfScenario struct { EncryptionProvider EncryptionProvider } -func TestPerfEncryption(ctx context.Context, t *testing.T, scenario PerfScenario) { +func TestPerfEncryption(ctx context.Context, t testing.TB, scenario PerfScenario) { e := NewE(t, PrintEventsOnFailure(scenario.OperatorNamespace)) migrationStartedCh := make(chan time.Time, 1) @@ -39,7 +39,7 @@ func TestPerfEncryption(ctx context.Context, t *testing.T, scenario PerfScenario } } -func runTestEncryption(ctx context.Context, tt *testing.T, scenario PerfScenario) time.Time { +func runTestEncryption(ctx context.Context, tt testing.TB, scenario PerfScenario) time.Time { var ts time.Time TestEncryptionType(ctx, tt, BasicScenario{ Namespace: scenario.Namespace, diff --git a/vendor/github.com/openshift/library-go/test/library/encryption/scenarios.go b/vendor/github.com/openshift/library-go/test/library/encryption/scenarios.go index bc923e96e..9885f4f97 100644 --- a/vendor/github.com/openshift/library-go/test/library/encryption/scenarios.go +++ b/vendor/github.com/openshift/library-go/test/library/encryption/scenarios.go @@ -354,100 +354,6 @@ func TestKMSInvalidEncryptionRecovery(ctx context.Context, t testing.TB, scenari } } -// KMSToKMSMigrationScenario tests migration between two distinct KMS configurations -// (e.g. different Vault instances or transit keys). Unlike in-place updates, changing a -// key-triggering field (transit key, vault address, etc.) causes the operator to mint a -// new encryption key and re-encrypt all resources. -type KMSToKMSMigrationScenario struct { - BasicScenario - CreateResourceFunc func(t testing.TB, clientSet ClientSet, namespace string) runtime.Object - AssertResourceEncryptedFunc func(t testing.TB, clientSet ClientSet, resource runtime.Object) - AssertResourceNotEncryptedFunc func(t testing.TB, clientSet ClientSet, resource runtime.Object) - ResourceFunc func(t testing.TB, namespace string) runtime.Object - ResourceName string - PrimaryProvider EncryptionProvider - SecondaryProvider EncryptionProvider -} - -// TestKMSToKMSMigration validates round-trip migration between two KMS providers: -// 1. Create a test resource -// 2. Encrypt with primary KMS provider and verify -// 3. Migrate to secondary KMS provider — a new key is created and resources re-encrypted -// 4. Migrate back to primary KMS provider — verifies returning to original works -// 5. Switch to identity — verify resources are decrypted -func TestKMSToKMSMigration(ctx context.Context, t testing.TB, scenario KMSToKMSMigrationScenario) { - require.NotNil(t, scenario.PrimaryProvider.Setup, "PrimaryProvider.Setup must not be nil") - require.NotNil(t, scenario.SecondaryProvider.Setup, "SecondaryProvider.Setup must not be nil") - require.Equal(t, configv1.EncryptionTypeKMS, scenario.PrimaryProvider.Type, "PrimaryProvider must use KMS encryption type") - require.Equal(t, configv1.EncryptionTypeKMS, scenario.SecondaryProvider.Type, "SecondaryProvider must use KMS encryption type") - - TestEncryptionProvidersMigration(ctx, t, ProvidersMigrationScenario{ - BasicScenario: scenario.BasicScenario, - CreateResourceFunc: scenario.CreateResourceFunc, - AssertResourceEncryptedFunc: scenario.AssertResourceEncryptedFunc, - AssertResourceNotEncryptedFunc: scenario.AssertResourceNotEncryptedFunc, - ResourceFunc: scenario.ResourceFunc, - ResourceName: scenario.ResourceName, - EncryptionProviders: []EncryptionProvider{ - scenario.PrimaryProvider, - scenario.SecondaryProvider, - scenario.PrimaryProvider, - }, - }) -} - -// TestKMSToKMSOnOff validates KMS on/off cycle using the KMS-to-KMS scenario struct: -// 1. Create a test resource -// 2. Encrypt with primary KMS → verify encrypted -// 3. Switch to identity → verify decrypted -// 4. Encrypt with secondary KMS → verify encrypted -// 5. Switch to identity → verify decrypted -func TestKMSToKMSOnOff(ctx context.Context, t testing.TB, scenario KMSToKMSMigrationScenario) { - require.NotNil(t, scenario.PrimaryProvider.Setup, "PrimaryProvider.Setup must not be nil") - require.NotNil(t, scenario.SecondaryProvider.Setup, "SecondaryProvider.Setup must not be nil") - require.Equal(t, configv1.EncryptionTypeKMS, scenario.PrimaryProvider.Type, "PrimaryProvider must use KMS encryption type") - require.Equal(t, configv1.EncryptionTypeKMS, scenario.SecondaryProvider.Type, "SecondaryProvider must use KMS encryption type") - - e := NewE(t, PrintEventsOnFailure(scenario.OperatorNamespace)) - - steps := []testStep{ - {name: "CreateResource", testFunc: func(t testing.TB) { - clientSet := GetClients(t) - scenario.CreateResourceFunc(t, clientSet, scenario.Namespace) - }}, - {name: "EncryptWithPrimaryKMS", testFunc: func(t testing.TB) { - TestEncryptionType(ctx, t, scenario.BasicScenario, scenario.PrimaryProvider) - }}, - {name: "AssertEncryptedWithPrimary", testFunc: func(t testing.TB) { - clientSet := GetClients(t) - scenario.AssertResourceEncryptedFunc(t, clientSet, scenario.ResourceFunc(t, scenario.Namespace)) - }}, - {name: "OffIdentityAfterPrimary", testFunc: func(t testing.TB) { - TestEncryptionTypeIdentity(ctx, t, scenario.BasicScenario) - }}, - {name: "AssertDecryptedAfterPrimary", testFunc: func(t testing.TB) { - clientSet := GetClients(t) - scenario.AssertResourceNotEncryptedFunc(t, clientSet, scenario.ResourceFunc(t, scenario.Namespace)) - }}, - {name: "EncryptWithSecondaryKMS", testFunc: func(t testing.TB) { - TestEncryptionType(ctx, t, scenario.BasicScenario, scenario.SecondaryProvider) - }}, - {name: "AssertEncryptedWithSecondary", testFunc: func(t testing.TB) { - clientSet := GetClients(t) - scenario.AssertResourceEncryptedFunc(t, clientSet, scenario.ResourceFunc(t, scenario.Namespace)) - }}, - } - - for _, step := range steps { - t.Logf("=== STEP: %s ===", step.name) - step.testFunc(e) - if t.Failed() { - t.Errorf("stopping the test as %q step failed", step.name) - return - } - } -} - // KMSInPlaceUpdateScenario tests that updating an in-place KMS config field // (e.g. kmsPluginImage) takes effect without creating a new encryption key. // The caller supplies Provider (initial valid config) and UpdatedProvider (same config diff --git a/vendor/modules.txt b/vendor/modules.txt index 35587d08b..77905fd71 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -411,7 +411,7 @@ github.com/openshift/client-go/user/applyconfigurations/internal github.com/openshift/client-go/user/applyconfigurations/user/v1 github.com/openshift/client-go/user/clientset/versioned/scheme github.com/openshift/client-go/user/clientset/versioned/typed/user/v1 -# github.com/openshift/library-go v0.0.0-20260629134817-a64e9a6e21ca +# github.com/openshift/library-go v0.0.0-20260630085645-45679084a74b ## explicit; go 1.25.0 github.com/openshift/library-go/pkg/apiserver/jsonpatch github.com/openshift/library-go/pkg/apps/deployment