From 8b6f7958f07d9457231d544443186daba9648378 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 ++- .../AutoNodeKarpenter.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 ++- ...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 d99f765f090f..adf18dc406f9 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 b5222bb3375b..2da55a04ec74 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 @@ -6348,8 +6348,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/AutoNodeKarpenter.yaml b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml index 70641ae031d8..7db8f8c7e526 100644 --- a/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml +++ b/api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml @@ -6467,8 +6467,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 cc3c053f800a..bdb03fa89b26 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 @@ -6331,8 +6331,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 de1dc8aa58af..2c9a31897141 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 @@ -6351,8 +6351,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 6630d27aeb21..89baa7d755c5 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 @@ -6664,8 +6664,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 d4c1bdd7fcf2..c9851169a893 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 @@ -6804,8 +6804,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 7822b30358e7..0f00bd6e3135 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 @@ -6785,8 +6785,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 6c6fdc61639e..90009ff2cf15 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 @@ -6751,8 +6751,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 9f9425548989..45f41e7914ea 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 @@ -6396,8 +6396,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 45c638c0545a..2b498056add6 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 @@ -6353,8 +6353,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 1dd0ce8473d5..4d1b50123922 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 @@ -6349,8 +6349,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 0fbf3783689c..343d2481cc22 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 @@ -6407,8 +6407,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 a9550f13ec18..69705a2cccbf 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 @@ -6882,8 +6882,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 a3663b2d3ca9..993c070a32b2 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 @@ -8230,8 +8230,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 8e955327ae9f..827bf834e930 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 @@ -6841,8 +6841,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 72f606f53dc0..e0cadccee867 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 @@ -8141,8 +8141,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 d99f765f090f..adf18dc406f9 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 4066159e46faeb10c6caa1fa47926425c73b0480 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