diff --git a/test/extended/apiserver/tls.go b/test/extended/apiserver/tls.go index 58ac08e1524d..612387341ffa 100644 --- a/test/extended/apiserver/tls.go +++ b/test/extended/apiserver/tls.go @@ -47,7 +47,13 @@ var _ = g.Describe("[sig-api-machinery][Feature:APIServer]", func() { } }) - g.It("TestTLSMinimumVersions", func() { + g.It("TestTLSMinimumVersions [FeatureGate:TLSAdherence] [apigroup:config.openshift.io]", func() { + ctx := context.Background() + enabled, err := isTLSAdherenceFeatureGateEnabled(ctx, oc) + o.Expect(err).NotTo(o.HaveOccurred()) + if !enabled { + g.Skip("TLSAdherence feature gate is not enabled on this cluster") + } g.By("Getting the APIServer configuration") config, err := oc.AdminConfigClient().ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{}) @@ -111,10 +117,15 @@ var _ = g.Describe("[sig-api-machinery][Feature:APIServer]", func() { o.Expect(err).NotTo(o.HaveOccurred()) }) - g.It("TestTLSDefaults", func() { + g.It("TestTLSDefaults [FeatureGate:TLSAdherence] [apigroup:config.openshift.io]", func() { + enabled, err := isTLSAdherenceFeatureGateEnabled(context.Background(), oc) + o.Expect(err).NotTo(o.HaveOccurred()) + if !enabled { + g.Skip("TLSAdherence feature gate is not enabled on this cluster") + } t := g.GinkgoT() - _, err := e2e.LoadClientset(true) + _, err = e2e.LoadClientset(true) o.Expect(err).NotTo(o.HaveOccurred()) g.By("Getting the APIServer config") diff --git a/test/extended/apiserver/tls_adherence.go b/test/extended/apiserver/tls_adherence.go new file mode 100644 index 000000000000..9f2d7865bb6e --- /dev/null +++ b/test/extended/apiserver/tls_adherence.go @@ -0,0 +1,111 @@ +package apiserver + +import ( + "context" + "fmt" + + g "github.com/onsi/ginkgo/v2" + o "github.com/onsi/gomega" + configv1 "github.com/openshift/api/config/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + exutil "github.com/openshift/origin/test/extended/util" +) + +// tlsAdherenceFeatureGateName is the name of the TLSAdherence feature gate as it +// appears in featuregate/cluster .status.featureGates[].enabled[].name. +const tlsAdherenceFeatureGateName = configv1.FeatureGateName("TLSAdherence") + +// isTLSAdherenceFeatureGateEnabled returns true when the TLSAdherence feature gate is +// listed as enabled in the featuregate/cluster status. +func isTLSAdherenceFeatureGateEnabled(ctx context.Context, oc *exutil.CLI) (bool, error) { + fg, err := oc.AdminConfigClient().ConfigV1().FeatureGates().Get(ctx, "cluster", metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("failed to get featuregate/cluster: %w", err) + } + for _, featureGateValues := range fg.Status.FeatureGates { + for _, enabledGate := range featureGateValues.Enabled { + if enabledGate.Name == tlsAdherenceFeatureGateName { + return true, nil + } + } + } + return false, nil +} + +// These tests verify the TLSAdherence feature gate and the spec.tlsAdherence field on +// apiservers/cluster (config.openshift.io/v1). They are gated by [OCPFeatureGate:TLSAdherence] +// for automatic pre-run filtering and include [FeatureGate:TLSAdherence] in each It description +// so the test name matches the pattern queried by the openshift/api verify-feature-promotion +// CI check in Sippy. +var _ = g.Describe("[sig-api-machinery][OCPFeatureGate:TLSAdherence][Feature:TLSAdherence] TLSAdherence apiservers/cluster", func() { + defer g.GinkgoRecover() + + oc := exutil.NewCLI("tls-adherence") + + g.BeforeEach(func(ctx context.Context) { + enabled, err := isTLSAdherenceFeatureGateEnabled(ctx, oc) + o.Expect(err).NotTo(o.HaveOccurred()) + if !enabled { + g.Skip("TLSAdherence feature gate is not enabled on this cluster") + } + }) + + // Test 1 – verify the API rejects an unrecognised spec.tlsAdherence value. + g.It("[FeatureGate:TLSAdherence] should reject an invalid spec.tlsAdherence value on apiservers/cluster [apigroup:config.openshift.io]", func(ctx context.Context) { + current, err := oc.AdminConfigClient().ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred(), "failed to get apiservers/cluster") + + desired := current.DeepCopy() + desired.Spec.TLSAdherence = configv1.TLSAdherencePolicy("InvalidValue") + + _, err = oc.AdminConfigClient().ConfigV1().APIServers().Update(ctx, desired, metav1.UpdateOptions{ + DryRun: []string{metav1.DryRunAll}, + }) + o.Expect(err).To(o.HaveOccurred(), + "apiservers/cluster should reject an invalid spec.tlsAdherence value") + o.Expect(k8serrors.IsInvalid(err)).To(o.BeTrue(), + "error should be a 422 Invalid, got: %v", err) + }) + + // Test 2 – verify the API server accepts and reflects all valid spec.tlsAdherence values via dry-run. + g.It("[FeatureGate:TLSAdherence] should accept and reflect all valid spec.tlsAdherence values on apiservers/cluster [apigroup:config.openshift.io]", func(ctx context.Context) { + validValues := []configv1.TLSAdherencePolicy{ + configv1.TLSAdherencePolicyStrictAllComponents, + configv1.TLSAdherencePolicyLegacyAdheringComponentsOnly, + } + + for _, value := range validValues { + current, err := oc.AdminConfigClient().ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred(), "failed to get apiservers/cluster") + + desired := current.DeepCopy() + desired.Spec.TLSAdherence = value + + result, err := oc.AdminConfigClient().ConfigV1().APIServers().Update(ctx, desired, metav1.UpdateOptions{ + DryRun: []string{metav1.DryRunAll}, + }) + o.Expect(err).NotTo(o.HaveOccurred(), + "apiservers/cluster should accept spec.tlsAdherence=%s", value) + o.Expect(result.Spec.TLSAdherence).To( + o.Equal(value), + "apiservers/cluster should reflect spec.tlsAdherence=%s", value) + } + }) + + // Test 3 – verify that no cluster operator is degraded when the TLSAdherence feature gate is active. + g.It("[FeatureGate:TLSAdherence] should not have any degraded cluster operators [apigroup:config.openshift.io]", func(ctx context.Context) { + coList, err := oc.AdminConfigClient().ConfigV1().ClusterOperators().List(ctx, metav1.ListOptions{}) + o.Expect(err).NotTo(o.HaveOccurred(), "failed to list clusteroperators") + + for _, co := range coList.Items { + for _, condition := range co.Status.Conditions { + if condition.Type == configv1.OperatorDegraded && condition.Status == configv1.ConditionTrue { + g.Fail(fmt.Sprintf("cluster operator %q is degraded: %s: %s", + co.Name, condition.Reason, condition.Message)) + } + } + } + }) +})