Skip to content

feat: Maintain ingress serving cert for HostedCluster ingress - #9132

Open
deads2k wants to merge 5 commits into
openshift:mainfrom
deads2k:maintain-ingress-serving-cert
Open

feat: Maintain ingress serving cert for HostedCluster ingress#9132
deads2k wants to merge 5 commits into
openshift:mainfrom
deads2k:maintain-ingress-serving-cert

Conversation

@deads2k

@deads2k deads2k commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Our ingress/router is ~synonymous with OpenShift and is used to serve our webconsole. This places it in a special situation where it must logically exist on newly created clusters. Rather than try to re-orient a successful part of our product in the market, we will instead allow the serving certificate only to be managed by a service provider using the API provided by Hypershift. This change adds a field to specify a default serving certificate and have it injected into the HostedCluster kube-apiserver.

Summary by CodeRabbit

  • New Features

    • Added support for configuring a custom default TLS certificate for hosted cluster ingress.
    • Certificates can be referenced from the HostedCluster namespace and are automatically propagated to the hosted cluster’s ingress controller.
    • Certificate updates are synchronized automatically, while routes with their own certificates remain unaffected.
  • Bug Fixes

    • Improved handling when configured certificates are missing or lack required TLS data.

deads2k and others added 5 commits July 27, 2026 13:34
Add a DefaultCertificate field to IngressOperatorSpec that allows users
to reference a TLS secret in the HostedCluster namespace containing
a custom default serving certificate for the ingress controller. When
set, this certificate replaces the auto-generated wildcard certificate.

The field mirrors the upstream OpenShift IngressControllerSpec
DefaultCertificate field and follows the same contract: the referenced
secret must contain tls.crt and tls.key.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verify N-1/N+1 JSON round-trip safety of the new DefaultCertificate
field: old code ignores the unknown field, new code deserializes
old data with nil DefaultCertificate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add reconcileIngressDefaultCertSync to copy the user-provided TLS
secret from the HostedCluster namespace to the HCP namespace. The
ensureReferencedResourceAnnotation call drives re-reconciliation
when the source secret is updated, enabling certificate rotation.

Registered as a non-critical Phase 6b sync operation alongside
SSHKeySync and other optional secret syncs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When DefaultCertificate is set in IngressOperatorSpec, HCCO reads the
user-provided secret from the HCP namespace instead of the CPO-generated
wildcard cert. The data is written into the same default-ingress-cert
secret in the guest cluster's openshift-ingress namespace, so the
IngressController spec needs no changes.

The CPO continues generating the wildcard cert as a fallback and for
the CA trust chain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add v2 Ginkgo test (ValidateIngressDefaultCertificateTest) that
verifies the default-ingress-cert secret in the hosted cluster's
openshift-ingress namespace contains tls.crt and tls.key when
DefaultCertificate is configured. Skips when not configured.

Add v1 test utilities (ValidateIngressDefaultCertificate,
EnsureIngressDefaultCertificate) following the existing pattern
for EndpointPublishingStrategy validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds an optional IngressOperatorSpec.defaultCertificate secret reference. The reference flows into ingress parameters, where reconciliation selects the user-provided certificate instead of the generated wildcard certificate. The hypershift operator synchronizes and validates the source TLS secret into the control plane namespace. Unit tests cover schema compatibility, parameter propagation, synchronization outcomes, and an end-to-end test validates the resulting guest-cluster secret.

🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed All added/updated test titles are static descriptive strings; none embed generated names, dates, UUIDs, node/namespace/IP values, or other run-to-run data.
Test Structure And Quality ✅ Passed New Ginkgo tests are single-purpose, use BeforeEach, include 5m timeouts, add failure messages, and match existing e2e patterns.
Topology-Aware Scheduling Compatibility ✅ Passed No new pod placement, replicas, affinity, tolerations, or nodeSelector logic was added; the PR only plumbs and syncs ingress cert secrets.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The added Ginkgo e2e tests only read in-cluster secrets/controllers and contain no IPv4 literals, IP parsing, host/IP URL building, or external network calls.
No-Weak-Crypto ✅ Passed No MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or new secret/token comparisons appear in the added ingress-certificate code; bytes.Equal hit is unrelated preexisting code.
Container-Privileges ✅ Passed The PR only adds ingress cert API/controller/test logic; no touched code or manifests introduce privileged, hostNetwork/hostPID/hostIPC, SYS_ADMIN, or allowPrivilegeEscalation settings.
No-Sensitive-Data-In-Logs ✅ Passed No new logs expose secret contents, tokens, or other sensitive values; the added messages only reference resource names and generic status.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: supporting and maintaining a HostedCluster ingress serving certificate.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@openshift-ci

openshift-ci Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: deads2k
Once this PR has been reviewed and has the lgtm label, please assign csrwng for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the area/api Indicates the PR includes changes for the API label Jul 27, 2026
@openshift-ci
openshift-ci Bot requested review from clebs and devguyio July 27, 2026 17:40
@openshift-ci openshift-ci Bot added area/cli Indicates the PR includes changes for CLI area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/documentation Indicates the PR includes changes for documentation area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/testing Indicates the PR includes changes for e2e testing and removed do-not-merge/needs-area labels Jul 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
hypershift-operator/controllers/hostedcluster/ingress_default_cert_sync_test.go (1)

20-27: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test case for OperatorConfiguration set but IngressOperator nil.

The two no-op cases (Lines 29-38, 40-52) both exercise the same code path (OperatorConfiguration == nil), since a zero-value HostedClusterSpec already has a nil OperatorConfiguration. The intermediate nil check (hcluster.Spec.OperatorConfiguration.IngressOperator == nil, Line 2101 in hostedcluster_controller.go) is not directly exercised by any case.

♻️ Suggested additional test case
+		{
+			name: "When IngressOperator is nil, it should be a no-op",
+			hcluster: &hyperv1.HostedCluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-cluster",
+					Namespace: "clusters",
+				},
+				Spec: hyperv1.HostedClusterSpec{
+					OperatorConfiguration: &hyperv1.OperatorConfiguration{
+						IngressOperator: nil,
+					},
+				},
+			},
+			expectError: false,
+			expectSync:  false,
+		},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@hypershift-operator/controllers/hostedcluster/ingress_default_cert_sync_test.go`
around lines 20 - 27, Add a test case to TestReconcileIngressDefaultCertSync
where HostedCluster.Spec.OperatorConfiguration is initialized but
OperatorConfiguration.IngressOperator is nil, and assert the expected no-op
behavior. Keep the existing nil-OperatorConfiguration case unchanged so the
intermediate IngressOperator nil guard is exercised separately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/hypershift/v1beta1/operator.go`:
- Line 309: The defaultCertificate field in api/hypershift/v1beta1/operator.go
must use an API CEL rule requiring a non-empty name whenever the reference is
non-nil, with envtest coverage for an empty reference. In
control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go,
update the default-certificate reconciliation logic to return/report an error
for a non-nil reference with an empty name instead of serving the generated
wildcard certificate.

In
`@control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/params_test.go`:
- Around line 321-347: Rename the test case in the ingress parameters table from
“DefaultCertificate set via OperatorConfiguration” to a stable “When … it should
…” description, such as “When DefaultCertificate is configured it should
propagate it to ingress parameters.” Leave the test inputs and expected values
unchanged.

In
`@control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go`:
- Around line 1430-1432: Add focused unit tests for the certificate-source
selection logic surrounding sourceCert in the reconciliation flow: verify custom
certificate copying, fallback to the CPO-generated wildcard certificate when no
custom source is provided, and propagation of custom-secret retrieval failures.
Add these cases in the relevant resources controller test suite rather than
relying on params_test.go, which only covers parameter propagation.

In `@hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go`:
- Around line 2094-2137: Update reconcileIngressDefaultCertSync to always sync
the referenced source secret to the fixed
manifests.IngressDefaultIngressControllerCert().Name destination, and handle a
removed or changed DefaultCertificate.Name by deleting the previously synced
control-plane secret so stale TLS private key material does not remain.

---

Nitpick comments:
In
`@hypershift-operator/controllers/hostedcluster/ingress_default_cert_sync_test.go`:
- Around line 20-27: Add a test case to TestReconcileIngressDefaultCertSync
where HostedCluster.Spec.OperatorConfiguration is initialized but
OperatorConfiguration.IngressOperator is nil, and assert the expected no-op
behavior. Keep the existing nil-OperatorConfiguration case unchanged so the
intermediate IngressOperator nil guard is exercised separately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 3b7f5413-cb5d-48f0-a7f1-76ede80f12a2

📥 Commits

Reviewing files that changed from the base of the PR and between 5e4dd58 and b2e8a89.

⛔ Files ignored due to path filters (40)
  • api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !**/zz_generated*.go, !**/zz_generated*
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/EtcdSharding.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/TLSAdherence.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/EtcdSharding.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/TLSAdherence.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • client/applyconfiguration/hypershift/v1beta1/ingressoperatorspec.go is excluded by !client/**
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-Default.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
  • docs/content/reference/api.md is excluded by !docs/content/reference/api.md
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/operator.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !vendor/**, !**/vendor/**, !**/zz_generated*.go, !**/zz_generated*
📒 Files selected for processing (9)
  • api/hypershift/v1beta1/operator.go
  • api/hypershift/v1beta1/operator_test.go
  • control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/params.go
  • control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/params_test.go
  • control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go
  • hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go
  • hypershift-operator/controllers/hostedcluster/ingress_default_cert_sync_test.go
  • test/e2e/util/util_ingress_operator_configuration.go
  • test/e2e/v2/tests/hosted_cluster_ingress_test.go

// signed by the cluster's root CA.
//
// +optional
DefaultCertificate *corev1.LocalObjectReference `json:"defaultCertificate,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject an empty configured certificate reference.

defaultCertificate: {} deserializes to a non-nil reference with an empty name. The reconciliation branch then treats it as absent and serves the generated wildcard certificate, rather than rejecting the invalid custom-certificate configuration.

  • api/hypershift/v1beta1/operator.go#L309-L309: add API CEL validation requiring a non-empty name whenever defaultCertificate is present, with envtest coverage.
  • control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go#L1433-L1442: defensively report an error for a non-nil empty reference instead of falling back silently.

As per coding guidelines, “For new validation in this module, use CEL rules on the API types instead of admission webhooks.”

📍 Affects 2 files
  • api/hypershift/v1beta1/operator.go#L309-L309 (this comment)
  • control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go#L1433-L1442
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/hypershift/v1beta1/operator.go` at line 309, The defaultCertificate field
in api/hypershift/v1beta1/operator.go must use an API CEL rule requiring a
non-empty name whenever the reference is non-nil, with envtest coverage for an
empty reference. In
control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go,
update the default-certificate reconciliation logic to return/report an error
for a non-nil reference with an empty name instead of serving the generated
wildcard certificate.

Source: Coding guidelines

Comment on lines +321 to +347
{
name: "DefaultCertificate set via OperatorConfiguration",
args: args{
hcp: &hyperv1.HostedControlPlane{
Spec: hyperv1.HostedControlPlaneSpec{
OperatorConfiguration: &hyperv1.OperatorConfiguration{
IngressOperator: &hyperv1.IngressOperatorSpec{
DefaultCertificate: &corev1.LocalObjectReference{
Name: "my-custom-cert",
},
},
},
},
},
},
want: &IngressParams{
IngressSubdomain: "apps.",
Replicas: 1,
IsPrivate: false,
IBMCloudUPI: false,
AWSNLB: false,
LoadBalancerScope: v1.ExternalLoadBalancer,
DefaultCertificate: &corev1.LocalObjectReference{
Name: "my-custom-cert",
},
},
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the required test-case naming format.

Rename this case to a stable “When … it should …” description, e.g. When DefaultCertificate is configured it should propagate it to ingress parameters.

As per coding guidelines, “Always use ‘When ... it should ...’ format for describing test cases when creating unit tests.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/params_test.go`
around lines 321 - 347, Rename the test case in the ingress parameters table
from “DefaultCertificate set via OperatorConfiguration” to a stable “When … it
should …” description, such as “When DefaultCertificate is configured it should
propagate it to ingress parameters.” Leave the test inputs and expected values
unchanged.

Source: Coding guidelines

Comment on lines +1430 to +1432
// When the user provides a custom default certificate, use it as the source.
// Otherwise, fall back to the CPO-generated wildcard cert.
var sourceCert *corev1.Secret

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add focused coverage for certificate-source selection.

Cover custom-source copy, generated-wildcard fallback, and custom-secret retrieval failure. params_test.go only verifies parameter propagation, not this reconciliation behavior.

As per coding guidelines, “Unit test any code changes and additions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go`
around lines 1430 - 1432, Add focused unit tests for the certificate-source
selection logic surrounding sourceCert in the reconciliation flow: verify custom
certificate copying, fallback to the CPO-generated wildcard certificate when no
custom source is provided, and propagation of custom-secret retrieval failures.
Add these cases in the relevant resources controller test suite rather than
relying on params_test.go, which only covers parameter propagation.

Source: Coding guidelines

Comment on lines +2094 to +2137
// reconcileIngressDefaultCertSync syncs the user-provided ingress default
// certificate secret from the HostedCluster namespace to the control plane namespace.
func (r *HostedClusterReconciler) reconcileIngressDefaultCertSync(
ctx context.Context, hcluster *hyperv1.HostedCluster, createOrUpdate upsert.CreateOrUpdateFN,
controlPlaneNamespace string,
) error {
if hcluster.Spec.OperatorConfiguration == nil ||
hcluster.Spec.OperatorConfiguration.IngressOperator == nil ||
hcluster.Spec.OperatorConfiguration.IngressOperator.DefaultCertificate == nil ||
len(hcluster.Spec.OperatorConfiguration.IngressOperator.DefaultCertificate.Name) == 0 {
return nil
}
secretName := hcluster.Spec.OperatorConfiguration.IngressOperator.DefaultCertificate.Name
var src corev1.Secret
if err := r.Client.Get(ctx, client.ObjectKey{Namespace: hcluster.Namespace, Name: secretName}, &src); err != nil {
return fmt.Errorf("failed to get ingress default certificate secret %s: %w", secretName, err)
}
if err := ensureReferencedResourceAnnotation(ctx, r.Client, hcluster.Name, &src); err != nil {
return fmt.Errorf("failed to set referenced resource annotation: %w", err)
}
dest := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: controlPlaneNamespace,
Name: secretName,
},
}
_, err := createOrUpdate(ctx, r.Client, dest, func() error {
if _, ok := src.Data[corev1.TLSCertKey]; !ok {
return fmt.Errorf("ingress default certificate secret %q must have a %s key", secretName, corev1.TLSCertKey)
}
if _, ok := src.Data[corev1.TLSPrivateKeyKey]; !ok {
return fmt.Errorf("ingress default certificate secret %q must have a %s key", secretName, corev1.TLSPrivateKeyKey)
}
dest.Type = corev1.SecretTypeTLS
if dest.Data == nil {
dest.Data = map[string][]byte{}
}
dest.Data[corev1.TLSCertKey] = src.Data[corev1.TLSCertKey]
dest.Data[corev1.TLSPrivateKeyKey] = src.Data[corev1.TLSPrivateKeyKey]
return nil
})
return err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how the ingress params/HCCO layer looks up the synced default-certificate secret name in the CP namespace.
rg -n -C5 'DefaultCertificate' control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/params.go

# Check whether any generic stale-secret GC exists for referenced-resource secrets.
rg -n -C3 'referencedResourceAnnotationPrefix|PruneStale|orphan' hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go

Repository: openshift/hypershift

Length of output: 2557


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the sync function and nearby reconciliation/cleanup logic.
sed -n '2060,2165p' hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go

# Inspect how ingress parameters consume the default certificate reference.
sed -n '1,140p' control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress/params.go

# Inspect referenced-resource annotation handling around enqueue/cleanup.
sed -n '500,580p' hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go
sed -n '5610,5665p' hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go
sed -n '3988,4012p' hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go

Repository: openshift/hypershift

Length of output: 14942


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C4 'DefaultCertificate|default certificate|tls secret|TLS' control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress
rg -n -C4 'LocalObjectReference|SecretName|secretName' control-plane-operator/hostedclusterconfigoperator/controllers/resources/ingress

Repository: openshift/hypershift

Length of output: 50376


Delete stale synced ingress TLS secrets when the source reference changes
reconcileIngressDefaultCertSync copies the source secret into the control-plane namespace using DefaultCertificate.Name, but the ingress controller consumes the fixed manifests.IngressDefaultIngressControllerCert().Name secret. If the source reference changes or is removed, the old control-plane secret — including private key material — stays behind indefinitely. Sync to the fixed destination name, or delete the previous secret when the reference changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go`
around lines 2094 - 2137, Update reconcileIngressDefaultCertSync to always sync
the referenced source secret to the fixed
manifests.IngressDefaultIngressControllerCert().Name destination, and handle a
removed or changed DefaultCertificate.Name by deleting the previously synced
control-plane secret so stale TLS private key material does not remain.

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.70175% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.58%. Comparing base (144cca9) to head (b2e8a89).
⚠️ Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
...rconfigoperator/controllers/resources/resources.go 46.66% 7 Missing and 1 partial ⚠️
...trollers/hostedcluster/hostedcluster_controller.go 92.30% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9132      +/-   ##
==========================================
+ Coverage   44.51%   44.58%   +0.07%     
==========================================
  Files         774      774              
  Lines       96997    97057      +60     
==========================================
+ Hits        43179    43274      +95     
+ Misses      50830    50790      -40     
- Partials     2988     2993       +5     
Files with missing lines Coverage Δ
...igoperator/controllers/resources/ingress/params.go 94.23% <100.00%> (+6.47%) ⬆️
...trollers/hostedcluster/hostedcluster_controller.go 55.02% <92.30%> (+0.33%) ⬆️
...rconfigoperator/controllers/resources/resources.go 57.70% <46.66%> (-0.10%) ⬇️

... and 6 files with indirect coverage changes

Flag Coverage Δ
cmd-support 38.39% <ø> (ø)
cpo-hostedcontrolplane 47.22% <ø> (ø)
cpo-other 45.27% <55.55%> (+0.02%) ⬆️
hypershift-operator 54.52% <92.30%> (+0.06%) ⬆️
other 33.07% <ø> (+0.43%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@openshift-ci

openshift-ci Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

@deads2k: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@deads2k deads2k changed the title Maintain ingress serving cert for HostedCluster ingress feat: Maintain ingress serving cert for HostedCluster ingress Jul 27, 2026
EndpointPublishingStrategy *operatorv1.EndpointPublishingStrategy `json:"endpointPublishingStrategy,omitempty"`

// defaultCertificate is a reference to a secret in the HostedCluster namespace
// that contains the default certificate served by the ingress controller.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to specify it's for the default ingress controller?

Suggested change
// that contains the default certificate served by the ingress controller.
// that contains the default certificate served by the default ingress controller.

dest := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: controlPlaneNamespace,
Name: secretName,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should sync to a fixed secret name and clean it up when the reference is removed from the API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should sync to a fixed secret name and clean it up when the reference is removed from the API.

What name would you like?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. ingress-default-cert

})

report.execute("IngressDefaultCertSync", nonCritical, func() error {
return r.reconcileIngressDefaultCertSync(ctx, hcluster, createOrUpdate, controlPlaneNamespace.Name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// signed by the cluster's root CA.
//
// +optional
DefaultCertificate *corev1.LocalObjectReference `json:"defaultCertificate,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer not to use imported corev1 types for object references, please use a local dedicated reference type instead.

See for example SecretReference in etcdbackup_types.go

When doing so, make sure the name field is +required, preventing defaultCertificate: {} which is currently valid per the API definition here. Once you've done that, you won't need the pointer and can switch omitempty for omitzero

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api Indicates the PR includes changes for the API area/cli Indicates the PR includes changes for CLI area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/documentation Indicates the PR includes changes for documentation area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/testing Indicates the PR includes changes for e2e testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants