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 c2d634988708..20ba1b8e9a92 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 7838ef9d8f66..4fe7ae8582eb 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 29186fae1769..f407c40ccdb2 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 75a647659faa..97e84e38e204 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 9de23f8d67e4..e8b7c3f7175e 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 c62d6ad473fe..35329bc70c62 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 3e972abdcf76..12ecd1586b3f 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 dae5c617ab62..8b20c30f114a 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 d0327faa3dec..cff44066c1f7 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 14c27c8efc19..6b082d402a30 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 5dccbaf9408d..c6fdb3d1736b 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 27d0b7170ef2..131df805814d 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 99173632616b..fa8e5050be0f 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 cb7aaf7b0b2d..5825dc3c55c5 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 1b9d301bb4df..1e7d9b6fcf4a 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 6de80f8f0a78..9aa674a6ddb3 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 8226337d326d..8a59b27e05c1 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 919e3b55bbfd..6b77b5236f61 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 c2d634988708..20ba1b8e9a92 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 1e7d9b6fcf4a..3e03aae164cc 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