Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions pkg/console/subresource/deployment/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,83 @@ func TestWithConsoleAnnotations(t *testing.T) {
}
}

func TestServingCertAnnotationChangesOnRotation(t *testing.T) {
consoleConfigMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ResourceVersion: "1"},
}
serviceCAConfigMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ResourceVersion: "2"},
}
trustedCAConfigMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{ResourceVersion: "3"},
}
oAuthClientSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ResourceVersion: "4"},
}
proxyConfig := &configv1.Proxy{
ObjectMeta: metav1.ObjectMeta{ResourceVersion: "5"},
}
infrastructureConfig := &configv1.Infrastructure{
ObjectMeta: metav1.ObjectMeta{ResourceVersion: "6"},
}

makeDeployment := func() *appsv1.Deployment {
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
},
},
}
}

oldCert := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ResourceVersion: "111111"},
}
newCert := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ResourceVersion: "222222"},
}

depBefore := makeDeployment()
withConsoleAnnotations(depBefore, consoleConfigMap, serviceCAConfigMap, nil, trustedCAConfigMap, oAuthClientSecret, nil, oldCert, proxyConfig, infrastructureConfig)

depAfter := makeDeployment()
withConsoleAnnotations(depAfter, consoleConfigMap, serviceCAConfigMap, nil, trustedCAConfigMap, oAuthClientSecret, nil, newCert, proxyConfig, infrastructureConfig)

oldVal := depBefore.ObjectMeta.Annotations[servingCertSecretResourceVersionAnnotation]
newVal := depAfter.ObjectMeta.Annotations[servingCertSecretResourceVersionAnnotation]

if oldVal != "111111" {
t.Errorf("expected annotation value '111111' for old cert, got %q", oldVal)
}
if newVal != "222222" {
t.Errorf("expected annotation value '222222' for new cert, got %q", newVal)
}
if oldVal == newVal {
t.Error("annotation value did not change after cert rotation")
}

oldPodVal := depBefore.Spec.Template.ObjectMeta.Annotations[servingCertSecretResourceVersionAnnotation]
newPodVal := depAfter.Spec.Template.ObjectMeta.Annotations[servingCertSecretResourceVersionAnnotation]
if oldPodVal == newPodVal {
t.Error("pod template annotation value did not change after cert rotation — rollout would not be triggered")
}
}

func TestServingCertAnnotationInResourceAnnotations(t *testing.T) {
found := false
for _, a := range resourceAnnotations {
if a == servingCertSecretResourceVersionAnnotation {
found = true
break
}
}
if !found {
t.Errorf("servingCertSecretResourceVersionAnnotation (%q) is not in resourceAnnotations — LogDeploymentAnnotationChanges will not detect cert rotation", servingCertSecretResourceVersionAnnotation)
}
}

func TestWithReplicas(t *testing.T) {
var (
singleNodeReplicaCount int32 = SingleNodeConsoleReplicas
Expand Down
106 changes: 106 additions & 0 deletions test/e2e/cert_rotation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package e2e

import (
"context"
"testing"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"

"github.com/openshift/console-operator/pkg/api"
"github.com/openshift/console-operator/test/e2e/framework"
)

const servingCertAnnotation = "console.openshift.io/serving-cert-secret-version"

// TestCertRotationTriggersRollout verifies that deleting the console-serving-cert
// secret (simulating a service-CA cert rotation) causes the operator to detect
// the new secret and roll out a new console deployment with the updated
// resource version annotation.
func TestCertRotationTriggersRollout(t *testing.T) {
client, _ := framework.StandardSetup(t)
defer framework.StandardCleanup(t, client)

// 1. Record the current state of the deployment and secret.
deployment, err := framework.GetConsoleDeployment(client)
if err != nil {
t.Fatalf("failed to get console deployment: %v", err)
}
oldAnnotation := deployment.Spec.Template.ObjectMeta.Annotations[servingCertAnnotation]
oldGeneration := deployment.ObjectMeta.Generation
t.Logf("before rotation: annotation=%q, generation=%d", oldAnnotation, oldGeneration)

oldSecret, err := client.Core.Secrets(api.TargetNamespace).Get(context.TODO(), api.ConsoleServingCertName, metav1.GetOptions{})
if err != nil {
t.Fatalf("failed to get console-serving-cert secret: %v", err)
}
oldSecretRV := oldSecret.ResourceVersion
t.Logf("before rotation: secret resourceVersion=%q", oldSecretRV)

// 2. Delete the secret to trigger service-CA to regenerate it with a new resourceVersion.
t.Log("deleting console-serving-cert secret to simulate cert rotation...")
err = client.Core.Secrets(api.TargetNamespace).Delete(context.TODO(), api.ConsoleServingCertName, metav1.DeleteOptions{})
if err != nil {
t.Fatalf("failed to delete console-serving-cert secret: %v", err)
}

// 3. Wait for the secret to be recreated by service-CA with a new resourceVersion.
t.Log("waiting for service-CA to recreate the secret...")
var newSecretRV string
err = wait.PollImmediate(2*time.Second, framework.AsyncOperationTimeout, func() (bool, error) {
secret, err := client.Core.Secrets(api.TargetNamespace).Get(context.TODO(), api.ConsoleServingCertName, metav1.GetOptions{})
if err != nil {
return false, nil
}
if secret.ResourceVersion != oldSecretRV {
newSecretRV = secret.ResourceVersion
return true, nil
}
return false, nil
})
if err != nil {
t.Fatalf("timed out waiting for console-serving-cert secret to be recreated: %v", err)
}
t.Logf("secret recreated with new resourceVersion=%q", newSecretRV)

// 4. Wait for the operator to reconcile and update the deployment annotation.
t.Log("waiting for operator to update deployment annotation...")
err = wait.PollImmediate(2*time.Second, framework.AsyncOperationTimeout, func() (bool, error) {
dep, err := framework.GetConsoleDeployment(client)
if err != nil {
return false, nil
}
currentAnnotation := dep.Spec.Template.ObjectMeta.Annotations[servingCertAnnotation]
return currentAnnotation != oldAnnotation && currentAnnotation != "", nil
})
if err != nil {
t.Fatalf("timed out waiting for deployment annotation to update after cert rotation: %v", err)
}

// 5. Verify the new annotation matches the new secret's resourceVersion.
updatedDeployment, err := framework.GetConsoleDeployment(client)
if err != nil {
t.Fatalf("failed to get updated console deployment: %v", err)
}
newAnnotation := updatedDeployment.Spec.Template.ObjectMeta.Annotations[servingCertAnnotation]
t.Logf("after rotation: annotation=%q, generation=%d", newAnnotation, updatedDeployment.ObjectMeta.Generation)

if newAnnotation != newSecretRV {
t.Errorf("expected deployment annotation %q to match new secret resourceVersion %q", newAnnotation, newSecretRV)
}

if updatedDeployment.ObjectMeta.Generation <= oldGeneration {
t.Errorf("expected deployment generation to increase after cert rotation: old=%d, new=%d", oldGeneration, updatedDeployment.ObjectMeta.Generation)
}

// 6. Wait for operator to settle after the rollout.
t.Log("waiting for operator to reach settled state...")
settled, err := framework.WaitForSettledState(t, client, "cert-rotation")
if err != nil {
t.Fatalf("operator did not settle after cert rotation: %v", err)
}
if !settled {
t.Error("operator did not reach settled state after cert rotation")
}
}