Skip to content

Commit c4e5612

Browse files
committed
Migrating OCP-25806 to upstream
1 parent 55d95c2 commit c4e5612

File tree

134 files changed

+12771
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+12771
-0
lines changed

cmd/openshift-apiserver-tests-ext/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
// The import below is necessary to ensure that the OAS operator tests are registered with the extension.
2424
_ "github.com/openshift/openshift-apiserver/test/extended"
25+
_ "github.com/openshift/openshift-apiserver/test/extended/apiserver"
2526
)
2627

2728
func main() {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ require (
183183
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect
184184
k8s.io/kms v0.33.3 // indirect
185185
k8s.io/kubelet v0.33.3 // indirect
186+
k8s.io/pod-security-admission v0.0.0 // indirect
186187
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
187188
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
188189
sigs.k8s.io/kustomize/api v0.19.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,8 @@ k8s.io/kubelet v0.33.3 h1:Cvy8+7Lq9saZds2ib7YBXbKvkMMJu3f5mzucmhSIJno=
570570
k8s.io/kubelet v0.33.3/go.mod h1:Q1Cfr6VQq1m9v9XsE/mDmhTxPdN6NPU6Ug0e6mAqi58=
571571
k8s.io/kubernetes v1.33.3 h1:dBx5Z2ZhR8kNzAwCoCz4j1niUbUrNUDVxeSj4/Ienu0=
572572
k8s.io/kubernetes v1.33.3/go.mod h1:nrt8sldmckKz2fCZhgRX3SKfS2e+CzXATPv6ITNkU00=
573+
k8s.io/pod-security-admission v0.33.3 h1:QjpEeaWV8hzCDq8YEtNmKOKlG6dARMK3zTZ98m3OYVI=
574+
k8s.io/pod-security-admission v0.33.3/go.mod h1:GhVxV5tSx0HlclRcEd00Y5idzagPgqYo5GvWC8QEkX4=
573575
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
574576
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
575577
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
package apiserver
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"strconv"
8+
"time"
9+
10+
g "github.com/onsi/ginkgo/v2"
11+
o "github.com/onsi/gomega"
12+
13+
configclient "github.com/openshift/client-go/config/clientset/versioned"
14+
operatorclient "github.com/openshift/client-go/operator/clientset/versioned"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/types"
17+
"k8s.io/apimachinery/pkg/util/wait"
18+
"k8s.io/client-go/kubernetes"
19+
"k8s.io/client-go/tools/clientcmd"
20+
e2e "k8s.io/kubernetes/test/e2e/framework"
21+
)
22+
23+
var _ = g.Describe("[Jira:openshift-apiserver][sig-api-machinery][openshift-apiserver][encryption]", func() {
24+
defer g.GinkgoRecover()
25+
26+
g.It("Force encryption key rotation for etcd datastore should rotate keys and update encryption prefixes [Slow][Disruptive][Suite:openshift/openshift-apiserver/conformance/serial]", func(ctx context.Context) {
27+
e2e.Logf("=== Starting Encryption Key Rotation Test ===")
28+
29+
// Get kubeconfig and create clients
30+
kubeconfig := os.Getenv("KUBECONFIG")
31+
if kubeconfig == "" {
32+
kubeconfig = clientcmd.RecommendedHomeFile
33+
}
34+
e2e.Logf("Using kubeconfig: %s", kubeconfig)
35+
36+
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
37+
o.Expect(err).NotTo(o.HaveOccurred(), "failed to load kubeconfig")
38+
e2e.Logf("Successfully loaded kubeconfig")
39+
40+
kubeClient, err := kubernetes.NewForConfig(config)
41+
o.Expect(err).NotTo(o.HaveOccurred(), "failed to create kube client")
42+
e2e.Logf("Created Kubernetes client")
43+
44+
configClient, err := configclient.NewForConfig(config)
45+
o.Expect(err).NotTo(o.HaveOccurred(), "failed to create config client")
46+
e2e.Logf("Created OpenShift config client")
47+
48+
operatorClient, err := operatorclient.NewForConfig(config)
49+
o.Expect(err).NotTo(o.HaveOccurred(), "failed to create operator client")
50+
e2e.Logf("Created OpenShift operator client")
51+
52+
g.By("1. Check if cluster is Etcd Encryption On")
53+
e2e.Logf("Checking APIServer encryption configuration...")
54+
apiserver, err := configClient.ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{})
55+
o.Expect(err).NotTo(o.HaveOccurred())
56+
57+
encryptionType := string(apiserver.Spec.Encryption.Type)
58+
e2e.Logf("Cluster encryption type: %s", encryptionType)
59+
if encryptionType != "aescbc" && encryptionType != "aesgcm" {
60+
e2e.Logf("Skipping test - encryption is not enabled (type: %s)", encryptionType)
61+
g.Skip("The cluster is Etcd Encryption Off, this case intentionally runs nothing")
62+
}
63+
e2e.Logf("Etcd Encryption is ON with type: %s", encryptionType)
64+
65+
g.By("2. Get encryption prefix before rotation")
66+
e2e.Logf("Retrieving current encryption prefixes from etcd...")
67+
oasEncValPrefix1, err := getEncryptionPrefix(ctx, config, kubeClient, "/openshift.io/routes")
68+
o.Expect(err).NotTo(o.HaveOccurred(), "fail to get encryption prefix for key routes")
69+
e2e.Logf("OpenShift API Server encryption prefix (BEFORE): %s", oasEncValPrefix1)
70+
71+
kasEncValPrefix1, err := getEncryptionPrefix(ctx, config, kubeClient, "/kubernetes.io/secrets")
72+
o.Expect(err).NotTo(o.HaveOccurred(), "fail to get encryption prefix for key secrets")
73+
e2e.Logf("Kube API Server encryption prefix (BEFORE): %s", kasEncValPrefix1)
74+
75+
e2e.Logf("Getting current encryption key numbers...")
76+
oasEncNumber, err := getEncryptionKeyNumber(ctx, kubeClient, `encryption-key-openshift-apiserver-\d+`)
77+
o.Expect(err).NotTo(o.HaveOccurred())
78+
e2e.Logf("Current OpenShift API Server encryption key number: %d", oasEncNumber)
79+
80+
kasEncNumber, err := getEncryptionKeyNumber(ctx, kubeClient, `encryption-key-openshift-kube-apiserver-\d+`)
81+
o.Expect(err).NotTo(o.HaveOccurred())
82+
e2e.Logf("Current Kube API Server encryption key number: %d", kasEncNumber)
83+
84+
t := time.Now().Format(time.RFC3339)
85+
restorePatch := []byte(`[{"op":"replace","path":"/spec/unsupportedConfigOverrides","value":null}]`)
86+
mergePatch := []byte(fmt.Sprintf(`{"spec":{"unsupportedConfigOverrides":{"encryption":{"reason":"force OAS rotation %s"}}}}`, t))
87+
88+
g.By("3. Force encryption key rotation for both openshiftapiserver and kubeapiserver")
89+
e2e.Logf("Preparing to force encryption key rotation with timestamp: %s", t)
90+
91+
// Patch OpenShift API Server
92+
defer func() {
93+
e2e.Logf("CLEANUP: Restoring openshiftapiserver/cluster's spec")
94+
_, patchErr := operatorClient.OperatorV1().OpenShiftAPIServers().Patch(ctx, "cluster", types.JSONPatchType, restorePatch, metav1.PatchOptions{})
95+
if patchErr != nil {
96+
e2e.Failf("Failed to restore openshiftapiserver: %v", patchErr)
97+
}
98+
e2e.Logf("Successfully restored openshiftapiserver/cluster")
99+
}()
100+
e2e.Logf("3.1) Patching openshiftapiserver to force encryption rotation...")
101+
_, err = operatorClient.OperatorV1().OpenShiftAPIServers().Patch(ctx, "cluster", types.MergePatchType, mergePatch, metav1.PatchOptions{})
102+
o.Expect(err).NotTo(o.HaveOccurred())
103+
e2e.Logf("Successfully patched openshiftapiserver/cluster")
104+
105+
// Patch Kube API Server
106+
defer func() {
107+
e2e.Logf("CLEANUP: Restoring kubeapiserver/cluster's spec")
108+
_, patchErr := operatorClient.OperatorV1().KubeAPIServers().Patch(ctx, "cluster", types.JSONPatchType, restorePatch, metav1.PatchOptions{})
109+
if patchErr != nil {
110+
e2e.Failf("Failed to restore kubeapiserver: %v", patchErr)
111+
}
112+
e2e.Logf("Successfully restored kubeapiserver/cluster")
113+
114+
// Wait for kube-apiserver operator to stabilize after restoration
115+
e2e.Logf("CLEANUP: Waiting for kube-apiserver operator to stabilize...")
116+
waitErr := waitForKubeAPIServerOperatorStable(ctx, kubeClient)
117+
if waitErr != nil {
118+
e2e.Failf("Kube-apiserver operator did not stabilize after cleanup: %v", waitErr)
119+
}
120+
e2e.Logf("CLEANUP: Kube-apiserver operator is stable")
121+
}()
122+
e2e.Logf("3.2) Patching kubeapiserver to force encryption rotation...")
123+
_, err = operatorClient.OperatorV1().KubeAPIServers().Patch(ctx, "cluster", types.MergePatchType, mergePatch, metav1.PatchOptions{})
124+
o.Expect(err).NotTo(o.HaveOccurred())
125+
e2e.Logf("Successfully patched kubeapiserver/cluster")
126+
127+
newOASEncSecretName := "encryption-key-openshift-apiserver-" + strconv.Itoa(oasEncNumber+1)
128+
newKASEncSecretName := "encryption-key-openshift-kube-apiserver-" + strconv.Itoa(kasEncNumber+1)
129+
130+
g.By("4. Check the new encryption key secrets appear")
131+
e2e.Logf("Waiting for new encryption key secrets to be created...")
132+
e2e.Logf("Expected new OAS secret: %s", newOASEncSecretName)
133+
e2e.Logf("Expected new KAS secret: %s", newKASEncSecretName)
134+
e2e.Logf("Polling every 5 seconds for up to 120 seconds...")
135+
136+
errKey := wait.PollUntilContextTimeout(ctx, 5*time.Second, 120*time.Second, false, func(cxt context.Context) (bool, error) {
137+
_, err1 := kubeClient.CoreV1().Secrets("openshift-config-managed").Get(cxt, newOASEncSecretName, metav1.GetOptions{})
138+
_, err2 := kubeClient.CoreV1().Secrets("openshift-config-managed").Get(cxt, newKASEncSecretName, metav1.GetOptions{})
139+
if err1 != nil || err2 != nil {
140+
e2e.Logf(" Still waiting for secrets... (OAS: %v, KAS: %v)", err1 != nil, err2 != nil)
141+
return false, nil
142+
}
143+
e2e.Logf("Found both new encryption key secrets!")
144+
return true, nil
145+
})
146+
147+
// Print openshift-apiserver and kube-apiserver secrets for debugging if time out
148+
if errKey != nil {
149+
e2e.Logf("ERROR: Timeout waiting for new encryption key secrets!")
150+
e2e.Logf("Listing all OpenShift API Server encryption secrets for debugging...")
151+
secrets, _ := kubeClient.CoreV1().Secrets("openshift-config-managed").List(ctx, metav1.ListOptions{
152+
LabelSelector: "encryption.apiserver.operator.openshift.io/component=openshift-apiserver",
153+
})
154+
if secrets != nil {
155+
e2e.Logf(" Total OpenShift API Server secrets: %d", len(secrets.Items))
156+
for i, secret := range secrets.Items {
157+
e2e.Logf(" [%d] %s (created: %v)", i+1, secret.Name, secret.CreationTimestamp)
158+
}
159+
}
160+
161+
e2e.Logf("Listing all Kube API Server encryption secrets for debugging...")
162+
secrets, _ = kubeClient.CoreV1().Secrets("openshift-config-managed").List(ctx, metav1.ListOptions{
163+
LabelSelector: "encryption.apiserver.operator.openshift.io/component=openshift-kube-apiserver",
164+
})
165+
if secrets != nil {
166+
e2e.Logf(" Total Kube API Server secrets: %d", len(secrets.Items))
167+
for i, secret := range secrets.Items {
168+
e2e.Logf(" [%d] %s (created: %v)", i+1, secret.Name, secret.CreationTimestamp)
169+
}
170+
}
171+
}
172+
o.Expect(errKey).NotTo(o.HaveOccurred(), fmt.Sprintf("new encryption key secrets %s, %s not found", newOASEncSecretName, newKASEncSecretName))
173+
174+
g.By("5. Waiting for the force encryption completion")
175+
e2e.Logf("Waiting for encryption migration to complete for secret: %s", newKASEncSecretName)
176+
e2e.Logf("This may take up to 35 minutes - checking every 30 seconds...")
177+
178+
// Check the operator status before waiting
179+
kasOperator, err := operatorClient.OperatorV1().KubeAPIServers().Get(ctx, "cluster", metav1.GetOptions{})
180+
if err == nil {
181+
e2e.Logf("KubeAPIServer operator status conditions:")
182+
for i, cond := range kasOperator.Status.Conditions {
183+
if i < 5 { // Log first 5 conditions
184+
e2e.Logf(" - %s: %s (reason: %s)", cond.Type, cond.Status, cond.Reason)
185+
}
186+
}
187+
}
188+
189+
// Only need to check kubeapiserver because kubeapiserver takes more time.
190+
completed, err := waitEncryptionKeyMigration(ctx, kubeClient, newKASEncSecretName)
191+
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("failed to complete encryption migration for %s", newKASEncSecretName))
192+
o.Expect(completed).Should(o.Equal(true))
193+
e2e.Logf("Encryption migration completed successfully!")
194+
195+
g.By("6. Get encryption prefix after force encryption completed")
196+
e2e.Logf("Retrieving new encryption prefixes from etcd...")
197+
oasEncValPrefix2, err := getEncryptionPrefix(ctx, config, kubeClient, "/openshift.io/routes")
198+
o.Expect(err).NotTo(o.HaveOccurred(), "fail to get encryption prefix for key routes")
199+
e2e.Logf("OpenShift API Server encryption prefix (AFTER): %s", oasEncValPrefix2)
200+
201+
kasEncValPrefix2, err := getEncryptionPrefix(ctx, config, kubeClient, "/kubernetes.io/secrets")
202+
o.Expect(err).NotTo(o.HaveOccurred(), "fail to get encryption prefix for key secrets")
203+
e2e.Logf("Kube API Server encryption prefix (AFTER): %s", kasEncValPrefix2)
204+
205+
e2e.Logf("Verifying encryption prefixes changed after rotation...")
206+
e2e.Logf("Expected prefix format: k8s:enc:%s:v1", encryptionType)
207+
208+
// Verify OAS prefix has correct format
209+
o.Expect(oasEncValPrefix2).Should(o.ContainSubstring(fmt.Sprintf("k8s:enc:%s:v1", encryptionType)),
210+
fmt.Sprintf("OAS encryption prefix should contain k8s:enc:%s:v1, but got: %s", encryptionType, oasEncValPrefix2))
211+
e2e.Logf("[OK] OAS prefix contains expected format: %s", oasEncValPrefix2)
212+
213+
// Verify KAS prefix has correct format
214+
o.Expect(kasEncValPrefix2).Should(o.ContainSubstring(fmt.Sprintf("k8s:enc:%s:v1", encryptionType)),
215+
fmt.Sprintf("KAS encryption prefix should contain k8s:enc:%s:v1, but got: %s", encryptionType, kasEncValPrefix2))
216+
e2e.Logf("[OK] KAS prefix contains expected format: %s", kasEncValPrefix2)
217+
218+
// Verify OAS prefix actually changed
219+
o.Expect(oasEncValPrefix2).NotTo(o.Equal(oasEncValPrefix1),
220+
fmt.Sprintf("OAS prefix should have changed after rotation, but remained: %s", oasEncValPrefix1))
221+
e2e.Logf("[OK] OAS prefix changed from %s to %s", oasEncValPrefix1, oasEncValPrefix2)
222+
223+
// Verify KAS prefix actually changed
224+
o.Expect(kasEncValPrefix2).NotTo(o.Equal(kasEncValPrefix1),
225+
fmt.Sprintf("KAS prefix should have changed after rotation, but remained: %s", kasEncValPrefix1))
226+
e2e.Logf("[OK] KAS prefix changed from %s to %s", kasEncValPrefix1, kasEncValPrefix2)
227+
228+
e2e.Logf("=== Test Completed Successfully ===")
229+
e2e.Logf("Summary:")
230+
e2e.Logf(" - Encryption Type: %s", encryptionType)
231+
e2e.Logf(" - OAS Key Rotation: %d → %d", oasEncNumber, oasEncNumber+1)
232+
e2e.Logf(" - KAS Key Rotation: %d → %d", kasEncNumber, kasEncNumber+1)
233+
e2e.Logf(" - All encryption prefixes successfully rotated")
234+
})
235+
})

0 commit comments

Comments
 (0)