Skip to content

Commit 5b51b98

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

File tree

134 files changed

+12660
-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

+12660
-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: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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("[Jira:openshift-apiserver][sig-api-machinery][openshift-apiserver][encryption] Force encryption key rotation for etcd datastore should rotate keys and update encryption prefixes[Serial][Slow][Disruptive]", 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+
_, err := operatorClient.OperatorV1().OpenShiftAPIServers().Patch(ctx, "cluster", types.JSONPatchType, restorePatch, metav1.PatchOptions{})
95+
if err != nil {
96+
e2e.Logf("WARNING: Failed to restore openshiftapiserver: %v", err)
97+
} else {
98+
e2e.Logf("✓ Successfully restored openshiftapiserver/cluster")
99+
}
100+
o.Expect(err).NotTo(o.HaveOccurred())
101+
}()
102+
e2e.Logf("3.1) Patching openshiftapiserver to force encryption rotation...")
103+
_, err = operatorClient.OperatorV1().OpenShiftAPIServers().Patch(ctx, "cluster", types.MergePatchType, mergePatch, metav1.PatchOptions{})
104+
o.Expect(err).NotTo(o.HaveOccurred())
105+
e2e.Logf("✓ Successfully patched openshiftapiserver/cluster")
106+
107+
// Patch Kube API Server
108+
defer func() {
109+
e2e.Logf("CLEANUP: Restoring kubeapiserver/cluster's spec")
110+
_, err := operatorClient.OperatorV1().KubeAPIServers().Patch(ctx, "cluster", types.JSONPatchType, restorePatch, metav1.PatchOptions{})
111+
if err != nil {
112+
e2e.Logf("WARNING: Failed to restore kubeapiserver: %v", err)
113+
} else {
114+
e2e.Logf("✓ Successfully restored kubeapiserver/cluster")
115+
}
116+
o.Expect(err).NotTo(o.HaveOccurred())
117+
}()
118+
e2e.Logf("3.2) Patching kubeapiserver to force encryption rotation...")
119+
_, err = operatorClient.OperatorV1().KubeAPIServers().Patch(ctx, "cluster", types.MergePatchType, mergePatch, metav1.PatchOptions{})
120+
o.Expect(err).NotTo(o.HaveOccurred())
121+
e2e.Logf("✓ Successfully patched kubeapiserver/cluster")
122+
123+
newOASEncSecretName := "encryption-key-openshift-apiserver-" + strconv.Itoa(oasEncNumber+1)
124+
newKASEncSecretName := "encryption-key-openshift-kube-apiserver-" + strconv.Itoa(kasEncNumber+1)
125+
126+
g.By("4. Check the new encryption key secrets appear")
127+
e2e.Logf("Waiting for new encryption key secrets to be created...")
128+
e2e.Logf("Expected new OAS secret: %s", newOASEncSecretName)
129+
e2e.Logf("Expected new KAS secret: %s", newKASEncSecretName)
130+
e2e.Logf("Polling every 5 seconds for up to 120 seconds...")
131+
132+
errKey := wait.PollUntilContextTimeout(ctx, 5*time.Second, 120*time.Second, false, func(cxt context.Context) (bool, error) {
133+
_, err1 := kubeClient.CoreV1().Secrets("openshift-config-managed").Get(cxt, newOASEncSecretName, metav1.GetOptions{})
134+
_, err2 := kubeClient.CoreV1().Secrets("openshift-config-managed").Get(cxt, newKASEncSecretName, metav1.GetOptions{})
135+
if err1 != nil || err2 != nil {
136+
e2e.Logf(" Still waiting for secrets... (OAS: %v, KAS: %v)", err1 != nil, err2 != nil)
137+
return false, nil
138+
}
139+
e2e.Logf("✓ Found both new encryption key secrets!")
140+
return true, nil
141+
})
142+
143+
// Print openshift-apiserver and kube-apiserver secrets for debugging if time out
144+
if errKey != nil {
145+
e2e.Logf("ERROR: Timeout waiting for new encryption key secrets!")
146+
e2e.Logf("Listing all OpenShift API Server encryption secrets for debugging...")
147+
secrets, _ := kubeClient.CoreV1().Secrets("openshift-config-managed").List(ctx, metav1.ListOptions{
148+
LabelSelector: "encryption.apiserver.operator.openshift.io/component=openshift-apiserver",
149+
})
150+
if secrets != nil {
151+
e2e.Logf(" Total OpenShift API Server secrets: %d", len(secrets.Items))
152+
for i, secret := range secrets.Items {
153+
e2e.Logf(" [%d] %s (created: %v)", i+1, secret.Name, secret.CreationTimestamp)
154+
}
155+
}
156+
157+
e2e.Logf("Listing all Kube API Server encryption secrets for debugging...")
158+
secrets, _ = kubeClient.CoreV1().Secrets("openshift-config-managed").List(ctx, metav1.ListOptions{
159+
LabelSelector: "encryption.apiserver.operator.openshift.io/component=openshift-kube-apiserver",
160+
})
161+
if secrets != nil {
162+
e2e.Logf(" Total Kube API Server secrets: %d", len(secrets.Items))
163+
for i, secret := range secrets.Items {
164+
e2e.Logf(" [%d] %s (created: %v)", i+1, secret.Name, secret.CreationTimestamp)
165+
}
166+
}
167+
}
168+
o.Expect(errKey).NotTo(o.HaveOccurred(), fmt.Sprintf("new encryption key secrets %s, %s not found", newOASEncSecretName, newKASEncSecretName))
169+
170+
g.By("5. Waiting for the force encryption completion")
171+
e2e.Logf("Waiting for encryption migration to complete for secret: %s", newKASEncSecretName)
172+
e2e.Logf("This may take up to 25 minutes - checking every 30 seconds...")
173+
174+
// Check the operator status before waiting
175+
kasOperator, err := operatorClient.OperatorV1().KubeAPIServers().Get(ctx, "cluster", metav1.GetOptions{})
176+
if err == nil {
177+
e2e.Logf("KubeAPIServer operator status conditions:")
178+
for i, cond := range kasOperator.Status.Conditions {
179+
if i < 5 { // Log first 5 conditions
180+
e2e.Logf(" - %s: %s (reason: %s)", cond.Type, cond.Status, cond.Reason)
181+
}
182+
}
183+
}
184+
185+
// Only need to check kubeapiserver because kubeapiserver takes more time.
186+
completed, err := waitEncryptionKeyMigration(ctx, kubeClient, newKASEncSecretName)
187+
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("saw all migrated-resources for %s", newKASEncSecretName))
188+
o.Expect(completed).Should(o.Equal(true))
189+
e2e.Logf("✓ Encryption migration completed successfully!")
190+
191+
g.By("6. Get encryption prefix after force encryption completed")
192+
e2e.Logf("Retrieving new encryption prefixes from etcd...")
193+
oasEncValPrefix2, err := getEncryptionPrefix(ctx, config, kubeClient, "/openshift.io/routes")
194+
o.Expect(err).NotTo(o.HaveOccurred(), "fail to get encryption prefix for key routes")
195+
e2e.Logf("✓ OpenShift API Server encryption prefix (AFTER): %s", oasEncValPrefix2)
196+
197+
kasEncValPrefix2, err := getEncryptionPrefix(ctx, config, kubeClient, "/kubernetes.io/secrets")
198+
o.Expect(err).NotTo(o.HaveOccurred(), "fail to get encryption prefix for key secrets")
199+
e2e.Logf("✓ Kube API Server encryption prefix (AFTER): %s", kasEncValPrefix2)
200+
201+
e2e.Logf("Verifying encryption prefixes changed after rotation...")
202+
e2e.Logf("Expected prefix format: k8s:enc:%s:v1", encryptionType)
203+
204+
o.Expect(oasEncValPrefix2).Should(o.ContainSubstring(fmt.Sprintf("k8s:enc:%s:v1", encryptionType)))
205+
e2e.Logf("✓ OAS prefix contains expected format")
206+
207+
o.Expect(kasEncValPrefix2).Should(o.ContainSubstring(fmt.Sprintf("k8s:enc:%s:v1", encryptionType)))
208+
e2e.Logf("✓ KAS prefix contains expected format")
209+
210+
o.Expect(oasEncValPrefix2).NotTo(o.Equal(oasEncValPrefix1))
211+
e2e.Logf("✓ OAS prefix changed from %s to %s", oasEncValPrefix1, oasEncValPrefix2)
212+
213+
o.Expect(kasEncValPrefix2).NotTo(o.Equal(kasEncValPrefix1))
214+
e2e.Logf("✓ KAS prefix changed from %s to %s", kasEncValPrefix1, kasEncValPrefix2)
215+
216+
e2e.Logf("=== Test Completed Successfully ===")
217+
e2e.Logf("Summary:")
218+
e2e.Logf(" - Encryption Type: %s", encryptionType)
219+
e2e.Logf(" - OAS Key Rotation: %d → %d", oasEncNumber, oasEncNumber+1)
220+
e2e.Logf(" - KAS Key Rotation: %d → %d", kasEncNumber, kasEncNumber+1)
221+
e2e.Logf(" - All encryption prefixes successfully rotated")
222+
})
223+
})

0 commit comments

Comments
 (0)