From 07d7d59669c179ac98055db829bb1ad47f44d4c9 Mon Sep 17 00:00:00 2001 From: Rutvik Date: Fri, 24 Apr 2026 17:28:10 +0530 Subject: [PATCH 1/2] fix(api): add missing has() guards to servingCerts CEL validation rule The CEL rule validating that APIServer loadBalancer hostname is not in servingCerts namedCertificates fails with "no such key" when servingCerts or namedCertificates are not set. This adds has() guards for servingCerts, namedCertificates, and cert.names fields to prevent the error. Fixes: OCPBUGS-77827 Signed-off-by: rutvik23 --- api/hypershift/v1beta1/hostedcluster_types.go | 2 +- .../AAA_ungated.yaml | 6 ++- .../ClusterUpdateAcceptRisks.yaml | 6 ++- .../ClusterVersionOperatorConfiguration.yaml | 6 ++- .../ExternalOIDC.yaml | 6 ++- ...ernalOIDCWithUIDAndExtraClaimMappings.yaml | 6 ++- .../ExternalOIDCWithUpstreamParity.yaml | 6 ++- .../GCPPlatform.yaml | 6 ++- .../HCPEtcdBackup.yaml | 6 ++- ...perShiftOnlyDynamicResourceAllocation.yaml | 6 ++- .../ImageStreamImportMode.yaml | 6 ++- .../KMSEncryptionProvider.yaml | 6 ++- .../OpenStack.yaml | 6 ++- .../TLSAdherence.yaml | 6 ++- ...ble.hostedclusters.services.testsuite.yaml | 43 +++++++++++++++++++ ...usters-Hypershift-CustomNoUpgrade.crd.yaml | 6 ++- ...hostedclusters-Hypershift-Default.crd.yaml | 6 ++- ...s-Hypershift-TechPreviewNoUpgrade.crd.yaml | 6 ++- .../hypershift/v1beta1/hostedcluster_types.go | 2 +- 19 files changed, 109 insertions(+), 34 deletions(-) diff --git a/api/hypershift/v1beta1/hostedcluster_types.go b/api/hypershift/v1beta1/hostedcluster_types.go index c2d63498870..20ba1b8e9a9 100644 --- a/api/hypershift/v1beta1/hostedcluster_types.go +++ b/api/hypershift/v1beta1/hostedcluster_types.go @@ -524,7 +524,7 @@ type Capabilities struct { // +kubebuilder:validation:XValidation:rule=`self.platform.type == "Azure" ? self.services.exists(s, s.service == "Konnectivity" && s.servicePublishingStrategy.type == "Route") : true`,message="Azure platform requires Konnectivity to use Route service publishing strategy" // +kubebuilder:validation:XValidation:rule=`self.platform.type == "Azure" ? self.services.exists(s, s.service == "Ignition" && s.servicePublishingStrategy.type == "Route") : true`,message="Azure platform requires Ignition to use Route service publishing strategy" // +kubebuilder:validation:XValidation:rule=`has(self.issuerURL) || !has(self.serviceAccountSigningKey)`,message="If serviceAccountSigningKey is set, issuerURL must be set" -// +kubebuilder:validation:XValidation:rule=`!self.services.exists(s, s.service == 'APIServer' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))`, message="APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[]" +// +kubebuilder:validation:XValidation:rule=`!self.services.exists(s, s.service == 'APIServer' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) && has(self.configuration.apiServer.servingCerts.namedCertificates) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))`, message="APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[]" // +kubebuilder:validation:XValidation:rule="!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) || !has(self.operatorConfiguration.clusterNetworkOperator.disableMultiNetwork) || !self.operatorConfiguration.clusterNetworkOperator.disableMultiNetwork || self.networking.networkType == 'Other'",message="disableMultiNetwork can only be set to true when networkType is 'Other'" // +kubebuilder:validation:XValidation:rule="self.networking.networkType == 'OVNKubernetes' || !has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) || !has(self.operatorConfiguration.clusterNetworkOperator.ovnKubernetesConfig)", message="ovnKubernetesConfig is forbidden when networkType is not OVNKubernetes" type HostedClusterSpec struct { diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml index 7838ef9d8f6..4fe7ae8582e 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml @@ -6490,8 +6490,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml index 29186fae176..f407c40ccdb 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml @@ -6473,8 +6473,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml index 75a647659fa..97e84e38e20 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml @@ -6493,8 +6493,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml index 9de23f8d67e..e8b7c3f7175 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml @@ -6805,8 +6805,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml index c62d6ad473f..35329bc70c6 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml @@ -6945,8 +6945,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml index 3e972abdcf7..12ecd1586b3 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml @@ -6936,8 +6936,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml index dae5c617ab6..8b20c30f114 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml @@ -6919,8 +6919,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml index d0327faa3de..cff44066c1f 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml @@ -6538,8 +6538,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml index 14c27c8efc1..6b082d402a3 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml @@ -6495,8 +6495,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml index 5dccbaf9408..c6fdb3d1736 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml @@ -6491,8 +6491,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml index 27d0b7170ef..131df805814 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml @@ -6549,8 +6549,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml index 99173632616..fa8e5050be0 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml @@ -7024,8 +7024,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/TLSAdherence.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/TLSAdherence.yaml index cb7aaf7b0b2..5825dc3c55c 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/TLSAdherence.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/TLSAdherence.yaml @@ -6513,8 +6513,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.services.testsuite.yaml b/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.services.testsuite.yaml index 1b9d301bb4d..1e7d9b6fcf4 100644 --- a/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.services.testsuite.yaml +++ b/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.services.testsuite.yaml @@ -246,3 +246,46 @@ tests: - anything - kas.duplicated.hostname.com expectedError: "loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates" + - name: When loadBalancer hostname is set and apiServer has no servingCerts it should pass + initial: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: LoadBalancer + loadBalancer: + hostname: kas.example.com + - service: Ignition + servicePublishingStrategy: + type: NodePort + nodePort: + address: "127.0.0.1" + - service: Konnectivity + servicePublishingStrategy: + type: NodePort + nodePort: + address: "fd2e:6f44:5dd8:c956::14" + - service: OAuthServer + servicePublishingStrategy: + type: NodePort + nodePort: + address: "fd2e:6f44:5dd8:c956:0000:0000:0000:0014" + configuration: + apiServer: + additionalCORSAllowedOrigins: + - "https://example.com" \ No newline at end of file diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml index 6de80f8f0a7..9aa674a6ddb 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml @@ -8311,8 +8311,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml index 8226337d326..8a59b27e05c 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml @@ -6982,8 +6982,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml index 919e3b55bbf..6b77b5236f6 100644 --- a/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml +++ b/cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml @@ -8182,8 +8182,10 @@ spec: - message: APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[] rule: '!self.services.exists(s, s.service == ''APIServer'' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) - && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, - cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' + && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) + && has(self.configuration.apiServer.servingCerts.namedCertificates) + && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, + has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))' - message: disableMultiNetwork can only be set to true when networkType is 'Other' rule: '!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) diff --git a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go index c2d63498870..20ba1b8e9a9 100644 --- a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go +++ b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go @@ -524,7 +524,7 @@ type Capabilities struct { // +kubebuilder:validation:XValidation:rule=`self.platform.type == "Azure" ? self.services.exists(s, s.service == "Konnectivity" && s.servicePublishingStrategy.type == "Route") : true`,message="Azure platform requires Konnectivity to use Route service publishing strategy" // +kubebuilder:validation:XValidation:rule=`self.platform.type == "Azure" ? self.services.exists(s, s.service == "Ignition" && s.servicePublishingStrategy.type == "Route") : true`,message="Azure platform requires Ignition to use Route service publishing strategy" // +kubebuilder:validation:XValidation:rule=`has(self.issuerURL) || !has(self.serviceAccountSigningKey)`,message="If serviceAccountSigningKey is set, issuerURL must be set" -// +kubebuilder:validation:XValidation:rule=`!self.services.exists(s, s.service == 'APIServer' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) && has(self.configuration.apiServer) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))`, message="APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[]" +// +kubebuilder:validation:XValidation:rule=`!self.services.exists(s, s.service == 'APIServer' && has(s.servicePublishingStrategy.loadBalancer) && s.servicePublishingStrategy.loadBalancer.hostname != "" && has(self.configuration) && has(self.configuration.apiServer) && has(self.configuration.apiServer.servingCerts) && has(self.configuration.apiServer.servingCerts.namedCertificates) && self.configuration.apiServer.servingCerts.namedCertificates.exists(cert, has(cert.names) && cert.names.exists(n, n == s.servicePublishingStrategy.loadBalancer.hostname)))`, message="APIServer loadBalancer hostname cannot be in ClusterConfiguration.apiserver.servingCerts.namedCertificates[]" // +kubebuilder:validation:XValidation:rule="!has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) || !has(self.operatorConfiguration.clusterNetworkOperator.disableMultiNetwork) || !self.operatorConfiguration.clusterNetworkOperator.disableMultiNetwork || self.networking.networkType == 'Other'",message="disableMultiNetwork can only be set to true when networkType is 'Other'" // +kubebuilder:validation:XValidation:rule="self.networking.networkType == 'OVNKubernetes' || !has(self.operatorConfiguration) || !has(self.operatorConfiguration.clusterNetworkOperator) || !has(self.operatorConfiguration.clusterNetworkOperator.ovnKubernetesConfig)", message="ovnKubernetesConfig is forbidden when networkType is not OVNKubernetes" type HostedClusterSpec struct { From 652a104117f67c691deccf573356f5d6a4641e4f Mon Sep 17 00:00:00 2001 From: Rutvik Date: Tue, 28 Apr 2026 17:21:29 +0530 Subject: [PATCH 2/2] test(api): add envtest cases for servingCerts CEL has() guards Add two additional envtest cases to exercise each has() guard added in the servingCerts CEL validation fix: - servingCerts present but no namedCertificates: exercises has(self.configuration.apiServer.servingCerts.namedCertificates) - namedCertificates entry with no names field: exercises has(cert.names) Signed-off-by: rutvik23 Commit-Message-Assisted-by: Claude (via Claude Code) --- ...ble.hostedclusters.services.testsuite.yaml | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.services.testsuite.yaml b/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.services.testsuite.yaml index 1e7d9b6fcf4..3e03aae164c 100644 --- a/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.services.testsuite.yaml +++ b/cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.services.testsuite.yaml @@ -288,4 +288,93 @@ tests: configuration: apiServer: additionalCORSAllowedOrigins: - - "https://example.com" \ No newline at end of file + - "https://example.com" + + - name: When loadBalancer hostname is set and apiServer has servingCerts but no namedCertificates it should pass + initial: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: LoadBalancer + loadBalancer: + hostname: kas.example.com + - service: Ignition + servicePublishingStrategy: + type: NodePort + nodePort: + address: "127.0.0.1" + - service: Konnectivity + servicePublishingStrategy: + type: NodePort + nodePort: + address: "fd2e:6f44:5dd8:c956::14" + - service: OAuthServer + servicePublishingStrategy: + type: NodePort + nodePort: + address: "fd2e:6f44:5dd8:c956:0000:0000:0000:0014" + configuration: + apiServer: + servingCerts: {} + + - name: When loadBalancer hostname is set and namedCertificates entry has no names it should pass + initial: | + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + spec: + dns: + baseDomain: example.com + platform: + type: AWS + pullSecret: + name: secret + release: + image: quay.io/openshift-release-dev/ocp-release:4.15.11-x86_64 + secretEncryption: + aescbc: + activeKey: + name: key + type: aescbc + services: + - service: APIServer + servicePublishingStrategy: + type: LoadBalancer + loadBalancer: + hostname: kas.example.com + - service: Ignition + servicePublishingStrategy: + type: NodePort + nodePort: + address: "127.0.0.1" + - service: Konnectivity + servicePublishingStrategy: + type: NodePort + nodePort: + address: "fd2e:6f44:5dd8:c956::14" + - service: OAuthServer + servicePublishingStrategy: + type: NodePort + nodePort: + address: "fd2e:6f44:5dd8:c956:0000:0000:0000:0014" + configuration: + apiServer: + servingCerts: + namedCertificates: + - servingCertificate: + name: my-cert