Skip to content

CNTRLPLANE-2012: Add PKI config types, validation, and CR manifest generation#10593

Open
hasbro17 wants to merge 1 commit into
openshift:mainfrom
hasbro17:pki-1-types-and-manifest
Open

CNTRLPLANE-2012: Add PKI config types, validation, and CR manifest generation#10593
hasbro17 wants to merge 1 commit into
openshift:mainfrom
hasbro17:pki-1-types-and-manifest

Conversation

@hasbro17

@hasbro17 hasbro17 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Part 1 of splitting #10396 into smaller, independently reviewable PRs.

This PR adds the configurable PKI API surface behind the ConfigurablePKI feature gate:

  • Types: PKIConfig, CertificateConfig, KeyConfig and related types on InstallConfig.PKI
  • Validation: ValidatePKIConfig() validates algorithm, key size, and curve parameters (FIPS-aware)
  • Feature gate: ConfigurablePKI registered as a TechPreview gate
  • CRD schema: install-config.yaml schema updated with pki field
  • PKI CR manifest: When the gate is active, generates a config.openshift.io/v1alpha1 PKI custom resource (cluster-pki-02-config.yaml) for day-2 operator certificate rotation
  • Defaults: DefaultPKIProfile() uses RSA-4096 until day-2 operators (CKAO, CKMO) support ECDSA rotation

No certificate generation changes — all certs remain RSA-2048. Non-TechPreview clusters are completely unaffected.

PR chain

  1. This PR — PKI types, validation, CRD, feature gate, PKI CR manifest
  2. CNTRLPLANE-2012: Refactor TLS cert generation to support configurable key algorithms #10594 —TLS engine refactoring (configurable key algorithm support)
  3. CNTRLPLANE-2012: Wire signer certs to read PKI config via SignerKeyParams #10595 — Wire signer certs + SignerKeyParams
  4. Documentation

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for configurable cluster PKI enabling customization of signer certificate cryptographic parameters. Users can now specify RSA (with configurable key sizes) or ECDSA (with configurable curves) algorithms.
  • Tests

    • Added comprehensive validation tests for PKI configuration and key parameter validation.

@openshift-ci-robot

openshift-ci-robot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

@hasbro17: This pull request references CNTRLPLANE-2012 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target either version "5.0." or "openshift-5.0.", but it targets "openshift-4.22" instead.

Details

In response to this:

Summary

Part 1 of splitting #10396 into smaller, independently reviewable PRs.

This PR adds the configurable PKI API surface behind the ConfigurablePKI feature gate:

  • Types: PKIConfig, CertificateConfig, KeyConfig and related types on InstallConfig.PKI
  • Validation: ValidatePKIConfig() validates algorithm, key size, and curve parameters (FIPS-aware)
  • Feature gate: ConfigurablePKI registered as a TechPreview gate
  • CRD schema: install-config.yaml schema updated with pki field
  • PKI CR manifest: When the gate is active, generates a config.openshift.io/v1alpha1 PKI custom resource (cluster-pki-02-config.yaml) for day-2 operator certificate rotation
  • Defaults: DefaultPKIProfile() uses RSA-4096 until day-2 operators (CKAO, CKMO) support ECDSA rotation

No certificate generation changes — all certs remain RSA-2048. Non-TechPreview clusters are completely unaffected.

PR chain

  1. This PR — PKI types, validation, CRD, feature gate, PKI CR manifest
  2. TLS engine refactoring (configurable key algorithm support)
  3. Wire signer certs + SignerKeyParams
  4. Documentation

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jun 3, 2026
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a ConfigurablePKI feature-gated pki field to InstallConfig with new PKI type contracts (PKIConfig, KeyConfig, RSAKeyConfig, ECDSAKeyConfig, KeyAlgorithm, ECDSACurve), a pkg/types/pki package with defaults (RSA-4096) and validation, feature gate wiring in install config validation, and a new PKIConfiguration manifest asset that emits cluster-pki-02-config.yaml. The InstallConfig CRD schema is updated to match.

Changes

ConfigurablePKI Feature

Layer / File(s) Summary
PKI type contracts and CRD schema
pkg/types/installconfig.go, data/data/install.openshift.io_installconfigs.yaml
Adds PKIConfig, CertificateConfig, KeyConfig, RSAKeyConfig, ECDSAKeyConfig, KeyAlgorithm, and ECDSACurve to InstallConfig with kubebuilder XValidation rules enforcing algorithm-specific subfield mutual exclusion. CRD OpenAPI schema gains the pkisignerCertificateskey structure with matching x-kubernetes-validations.
PKI defaults
pkg/types/pki/defaults.go, pkg/types/pki/defaults_test.go
Creates the pkg/types/pki package with DefaultPKIProfile() returning RSA-4096 for both the default certificate key and signer certificate key, with a unit test asserting those values.
PKI validation logic and tests
pkg/types/pki/validation.go, pkg/types/pki/validation_test.go
Adds ValidatePKIConfig and ValidateKeyConfig: nil-safe PKI config check, algorithm presence/enum check, RSA keySize range (2048–8192, multiple of 1024) and ECDSA curve (P256/P384/P521) validation with required/forbidden sub-object enforcement; table-driven tests cover valid, invalid, and mismatched configurations.
Feature gate and install config validation wiring
pkg/types/validation/featuregates.go, pkg/types/validation/installconfig.go, pkg/types/validation/featuregate_test.go, pkg/types/validation/installconfig_test.go, pkg/explain/printer_test.go
Wires ConfigurablePKI into install config validation: featuregates.go gates the pki field; installconfig.go calls pkivalidation.ValidatePKIConfig for non-nil PKI. Tests cover gate enforcement, TechPreviewNoUpgrade/CustomNoUpgrade allow cases, nil-PKI no-error, ECDSA P-384 success, EdDSA failure, and RSA-1024 failure. printer_test.go updated for pki field docs output.
PKIConfiguration manifest asset and operators wiring
pkg/asset/manifests/pki.go, pkg/asset/manifests/operators.go, pkg/asset/manifests/pki_test.go
Adds PKIConfiguration as a new WritableAsset: Generate fetches InstallConfig, short-circuits without the gate, then builds a configv1alpha1.PKI CR in default or custom mode using DefaultPKIProfile() and convertToAPICertConfig, marshaled to cluster-pki-02-config.yaml. Wired into Manifests.Dependencies() and Generate(). Tests cover gate-disabled suppression, gate-enabled default mode, and RSA/ECDSA custom signer key scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 14 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (14 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'CNTRLPLANE-2012: Add PKI config types, validation, and CR manifest generation' clearly and specifically summarizes the main changes: introducing PKI configuration types, validation logic, and custom resource manifest generation for the installer.
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 This PR contains no Ginkgo tests. All 6 test files use standard Go testing (func TestXXX(t *testing.T)), not Ginkgo (It(), Describe(), etc.), so the check does not apply.
Test Structure And Quality ✅ Passed This PR contains no Ginkgo tests. All test code uses standard Go testing (testing.T) with table-driven patterns and t.Run subtests. The check is not applicable.
Microshift Test Compatibility ✅ Passed No Ginkgo e2e tests are added in this PR. All test files use standard Go testing (testing.T), making the MicroShift compatibility check not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed This PR contains only standard Go unit tests (testing.T) with no Ginkgo e2e tests (It/Describe/Context/When patterns). The custom check for SNO compatibility only applies to Ginkgo e2e tests, not u...
Topology-Aware Scheduling Compatibility ✅ Passed This PR introduces PKI types, validation, and a config object (not workload) manifest. No deployment, pod affinity, topology constraints, or scheduling assumptions are introduced.
Ote Binary Stdout Contract ✅ Passed PR introduces new PKI-related code with no process-level stdout writes. All new files (pki.go, defaults.go, validation.go) contain only pure functions/types, no init(), main(), or logging to stdout...
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests are added in this PR. All test additions are unit tests using Go's testing.T and testify/assert. The check for IPv6 and disconnected network compatibility is not applicable.
No-Weak-Crypto ✅ Passed PR contains no weak cryptographic algorithms (MD5, SHA1, DES, RC4, 3DES, Blowfish, ECB), custom crypto implementations, or unsafe secret comparisons. Only strong algorithms used: RSA (2048-8192 bit...
Container-Privileges ✅ Passed PR adds PKI configuration types and a PKI custom resource manifest, which is a configuration object, not a workload manifest. No container privilege escalation settings (privileged, hostPID, hostNe...
No-Sensitive-Data-In-Logs ✅ Passed No logging of sensitive data (passwords, tokens, API keys, PII, credentials) found in newly added PKI configuration code, validation logic, or manifest generation functions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 requested review from barbacbd and tthvo June 3, 2026 18:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
pkg/types/pki/validation.go (1)

12-116: ⚡ Quick win

Make the fips flag meaningful or remove it for now.

fips is threaded through every PKI validator here but never read, so FIPS-enabled installs currently accept the exact same algorithms, key sizes, and curves as non-FIPS installs. That leaves the validator contract misleading and the FIPS-specific path effectively unimplemented.

🤖 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 `@pkg/types/pki/validation.go` around lines 12 - 116, The fips boolean is never
used — either remove it from the PKI validator signatures or make it enforce
FIPS-safe choices; implement the latter: use the existing fips parameter in
ValidateKeyConfig, validateRSAKeyConfig, and validateECDSAKeyConfig to tighten
accepted values when fips==true (e.g. in validateRSAKeyConfig, require a larger
minimum RSA keySize when fips is true such as >=3072 and still multiples of
1024; in validateECDSAKeyConfig, disallow non-FIPS curves like P521 when fips is
true and limit to P256/P384; and in ValidateKeyConfig ensure the
Forbidden/Required checks still apply under the fips branch), keeping the same
function names (ValidatePKIConfig, ValidateKeyConfig, validateRSAKeyConfig,
validateECDSAKeyConfig) so callers remain consistent.
pkg/asset/manifests/pki_test.go (1)

140-142: 💤 Low value

Consider verifying the Custom field is nil/empty in Default mode.

The test correctly checks that the mode is Default, but doesn't verify that the Custom field is nil or empty. While checking the mode is sufficient, explicitly asserting pkiCR.Spec.CertificateManagement.Custom is empty would make the test more thorough.

Optional test enhancement
 			if tc.expectMode != configv1alpha1.PKICertificateManagementModeCustom {
+				assert.Empty(t, pkiCR.Spec.CertificateManagement.Custom)
 				return
 			}
🤖 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 `@pkg/asset/manifests/pki_test.go` around lines 140 - 142, The test currently
returns early when tc.expectMode !=
configv1alpha1.PKICertificateManagementModeCustom; add an explicit assertion for
the Default case to verify pkiCR.Spec.CertificateManagement.Custom is nil/empty.
Locate the check around tc.expectMode and after confirming the mode is Default,
assert that pkiCR.Spec.CertificateManagement.Custom is nil or has zero length
(using t.Fatalf/t.Errorf or your test assertion helper) to make the test more
thorough.
🤖 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 `@data/data/install.openshift.io_installconfigs.yaml`:
- Around line 5088-5098: The description for installConfig.pki currently states
signerCertificates affects "all installer-generated certificate authority (CA)
certificates" which is incorrect for this PR; update the text in the YAML
properties for signerCertificates (and any parent description referencing
installConfig.pki and ConfigurablePKI) to clearly state that the PKI block is
emitted for day-2 rotation/rotation operations and does not change
installer-time certificate generation, and mention the feature gate
ConfigurablePKI as controlling emission for rotation only so oc explain
consumers are not misled.

In `@pkg/types/pki/defaults.go`:
- Around line 41-50: EffectiveSignerPKIConfig() is hardcoding RSA-4096 signer
defaults instead of deriving them from the single source of truth
DefaultPKIProfile(); update EffectiveSignerPKIConfig() to construct its returned
*types.PKIConfig by reading DefaultPKIProfile().SignerCertificates (and nested
Key/Key.RSA fields) rather than re-encoding literal RSA-4096 values so future
changes to DefaultPKIProfile() automatically propagate to the signer path.

In `@pkg/types/validation/installconfig_test.go`:
- Around line 3121-3134: The test case "invalid PKI signer with unsupported
algorithm" is missing the TechPreview feature gate for ConfigurablePKI; update
the installConfig builder (the anonymous func returning *types.InstallConfig) to
set c.FeatureSet = configv1.TechPreviewNoUpgrade so the PKI field validation
executes, or alternatively change expectedError to the feature-gate validation
message you use elsewhere (i.e., assert a feature-gate error) — adjust either
the installConfig() setup or expectedError to consistently reflect whether
ConfigurablePKI is enabled; check symbols: types.InstallConfig, c.PKI
(types.PKIConfig), and c.FeatureSet (configv1.TechPreviewNoUpgrade).
- Around line 3136-3150: The test case creates an InstallConfig via the inline
installConfig func that sets c.PKI but does not enable the tech-preview feature
gate; to match the other PKI tests and exercise the PKI validation path, update
that inline function (the one calling validInstallConfig() and setting c.PKI /
types.PKIConfig) to also set c.FeatureSet = configv1.TechPreviewNoUpgrade (same
as in the test around line 3108) so the PKI field validation is executed; update
imports if needed to reference configv1.TechPreviewNoUpgrade.

In `@pkg/types/validation/installconfig.go`:
- Around line 288-290: ValidateInstallConfig currently validates c.PKI via
pkivalidation.ValidatePKIConfig(c.PKI, field.NewPath("pki"), c.FIPS) before
feature-gate checks, causing duplicate/noisy errors when ConfigurablePKI is
disabled; change the flow so PKI content validation only runs if the
ConfigurablePKI feature is enabled (i.e. after or guarded by
ValidateFeatureSet/feature-gate check), by wrapping the existing c.PKI
validation block with the feature-gate condition or moving that call to after
ValidateFeatureSet so that ValidatePKIConfig is invoked only when the
ConfigurablePKI gate allows it.

---

Nitpick comments:
In `@pkg/asset/manifests/pki_test.go`:
- Around line 140-142: The test currently returns early when tc.expectMode !=
configv1alpha1.PKICertificateManagementModeCustom; add an explicit assertion for
the Default case to verify pkiCR.Spec.CertificateManagement.Custom is nil/empty.
Locate the check around tc.expectMode and after confirming the mode is Default,
assert that pkiCR.Spec.CertificateManagement.Custom is nil or has zero length
(using t.Fatalf/t.Errorf or your test assertion helper) to make the test more
thorough.

In `@pkg/types/pki/validation.go`:
- Around line 12-116: The fips boolean is never used — either remove it from the
PKI validator signatures or make it enforce FIPS-safe choices; implement the
latter: use the existing fips parameter in ValidateKeyConfig,
validateRSAKeyConfig, and validateECDSAKeyConfig to tighten accepted values when
fips==true (e.g. in validateRSAKeyConfig, require a larger minimum RSA keySize
when fips is true such as >=3072 and still multiples of 1024; in
validateECDSAKeyConfig, disallow non-FIPS curves like P521 when fips is true and
limit to P256/P384; and in ValidateKeyConfig ensure the Forbidden/Required
checks still apply under the fips branch), keeping the same function names
(ValidatePKIConfig, ValidateKeyConfig, validateRSAKeyConfig,
validateECDSAKeyConfig) so callers remain consistent.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1ba2c9a4-d566-448c-9f76-1b7ae60ac656

📥 Commits

Reviewing files that changed from the base of the PR and between d3fba60 and f1faa3f.

⛔ Files ignored due to path filters (1)
  • pkg/types/zz_generated.deepcopy.go is excluded by !**/zz_generated*
📒 Files selected for processing (16)
  • data/data/install.openshift.io_installconfigs.yaml
  • pkg/asset/manifests/operators.go
  • pkg/asset/manifests/pki.go
  • pkg/asset/manifests/pki_test.go
  • pkg/explain/printer_test.go
  • pkg/types/defaults/installconfig.go
  • pkg/types/installconfig.go
  • pkg/types/pki/conversion.go
  • pkg/types/pki/defaults.go
  • pkg/types/pki/defaults_test.go
  • pkg/types/pki/validation.go
  • pkg/types/pki/validation_test.go
  • pkg/types/validation/featuregate_test.go
  • pkg/types/validation/featuregates.go
  • pkg/types/validation/installconfig.go
  • pkg/types/validation/installconfig_test.go

Comment thread data/data/install.openshift.io_installconfigs.yaml
Comment thread pkg/types/pki/defaults.go Outdated
Comment thread pkg/types/validation/installconfig_test.go
Comment thread pkg/types/validation/installconfig_test.go
Comment thread pkg/types/validation/installconfig.go
Comment thread pkg/types/pki/conversion.go Outdated
Comment on lines +14 to +16
RSA: configv1alpha1.RSAKeyConfig{KeySize: local.Key.RSA.KeySize},
ECDSA: configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurve(local.Key.ECDSA.Curve)},
},

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.

Can we have a test run the output through yaml.Marshall and confirms only one is set? Or, check Algorithm before setting only one of either RSA/ECDSA? I think this is ok thanks to omitzero, but a test or explicitly check might add clarity.

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.

Makes sense, I'll add a new test in conversion_test.go to marshal the converted output and confirm only the relevant key config appears (RSA output omits ecdsa:, ECDSA output omits rsa:). So if our omitzero reliance breaks we'll know.

Comment thread pkg/types/pki/defaults.go
// ECDSA certificate rotation. Once operator support lands, switch to ECDSA P-384
// signers and ECDSA P-256 defaults to match the upstream library-go profile:
// https://github.com/openshift/library-go/blob/12d8376369b7c5b76f688d01089882ca28e351c3/pkg/pki/profile.go#L11-L26
func DefaultPKIProfile() configv1alpha1.PKIProfile {

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.

Consider removing this entire file from this PR. Just return the config below in PKIConfiguration.Generate for now. I have comments about EffectiveSignerPKIConfig , but it moke no sense if its not being called yet.

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.

For DefaultPKIProfile() the manifest generator uses it now, and PR 3 will use it via EffectiveSignerPKIConfig(). Defining it in the manifest generator and then extracting it later is just code churn for no gain.

It's the single source of truth for the default profile, shared between the manifest generator (this PR) and the TLS layer so I'd rather define it more explicitly for when the time comes to change it or migrate off from it to something defined in openshift/api as Ben alluded to. Or if we end up sharing the defaults from library-go.

Happy to move EffectiveSignerPKIConfig() and related tests to PR 3 though.

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.

Done, moved EffectiveSignerPKIConfig() to PR 3.

@hasbro17 hasbro17 force-pushed the pki-1-types-and-manifest branch from 6f6a58a to 3d195a4 Compare June 8, 2026 18:45

@tthvo tthvo left a comment

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.

Overall, looks good to me! Thank you 👍

I just have some comments; and golint/gofmt warnings need fixing :D

Comment thread pkg/types/installconfig.go Outdated
Comment on lines +274 to +276
// +union
//
//nolint:godot

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.

I think you can move the // +union up to avoid skipping lint here.

// +union
// +kubebuilder:validation:XValidation:rule="...",message="..."
// +kubebuilder:validation:XValidation:rule="...",message="..."

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.

Oh yeah that works. Will move it up.

Comment thread pkg/types/installconfig.go Outdated
// We maintain local copies so that new fields added upstream do not silently
// appear in the install-config YAML API without explicit opt-in and validation
// by the installer. Conversion to the upstream type happens at the manifest
// boundary (see pkg/types/pki/conversion.go).

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.

Suggested change
// boundary (see pkg/types/pki/conversion.go).
// We maintain local copies so that new o/api fields added do not silently
// appear in the install-config YAML API without explicit opt-in and validation
// by the installer. Conversion to the o/api type happens at the manifest

nit: In the context of installer, the word "upstream" is usually understood as upstream CAPI provider. Let's explicitly say openshift/api or o/api in short.

Comment thread pkg/types/installconfig.go Outdated
// Required when algorithm is ECDSA, and forbidden otherwise.
// +optional
// +unionMember
ECDSA ECDSAKeyConfig `json:"ecdsa,omitzero"`

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.

Since RSA and ECDSA are mutually exclusive union members, I think using pointers makes presence explicit: the installer can distinguish "not specified" from "zero value" without relying on field-level checks. Note that CEL markers are not used in the installer.

Do you envision more configurable fields being added for each algorithm in the future? If so, pointers should be future-proof. WDTY?

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.

Maybe @sanchezl can chime in on if this was raised during the upstream API review
openshift/api#2645

But just to address your points:

I think using pointers makes presence explicit: the installer can distinguish "not specified" from "zero value" without relying on field-level checks

The installer types are intentionally a local copy subset of the upstream types to keep them aligned. Adding pointers here would mean our conversion functions (CertificateConfigToUpstream) need pointer dereferencing, and we'd drift from the upstream API's design decisions.

Also on omitzero: The only scenario where value types would be a problem is if zero were a valid value for one of these fields but it isn't for either key size or curve name.

Note that CEL markers are not used in the installer

That's a fair point. But the validation in ValidateKeyConfig() already handles the mutual exclusivity check using zero value detection (which works because again zero is never a valid value for either field).
Pointers don't gain us much here imo, since zero is an explicit omission in this case.

Do you envision more configurable fields being added for each algorithm in the future?

These structs are unlikely to grow (RSA key generation needs a key size, ECDSA needs a curve) and there aren't obvious additional parameters on the horizon (@sanchezl to keep me honest).

Even if fields are added upstream we'd still have to update validation downstream to match regardless so not like it saves us too much work.
Keeping value types avoids nil-check ceremony and keeps conversion to/from upstream straightforward.

So I would lean towards keeping the types aligned with upstream even in the absence of CEL markers, unless the installer team feels strongly about this.

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.

All structs within the union MUST be pointers

https://github.com/openshift/enhancements/blob/master/dev-guide/api-conventions.md

It seems like there may be a misunderstanding. claude couldn't find any discussion of pointers in the api pr. cc @everettraven

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.

@everettraven @sanchezl I had a little more time to review this and api guidelines and git metadata are pretty clear: openshift/api#2645 should be updated to make these structs pointers

@hasbro17 it is much simpler and future proof to just check that the struct is nil rather than check all the struct's fields, which can grow over time

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.

The conventions have spoken, I must comply 🙂

@patrickdillon Fair enough, and thanks for pointing out the API conventions. I'll update my copy here for now and follow up with @sanchezl to see if we can get o/api updated as well.

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.

Update to pointer types

Comment thread pkg/types/validation/installconfig.go Outdated
}
}

if c.PKI != nil && c.Enabled(features.FeatureGateConfigurablePKI) {

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.

Suggested change
if c.PKI != nil && c.Enabled(features.FeatureGateConfigurablePKI) {
if c.PKI != nil

We can skip the feature gate check since it's already checked in pkg/types/validation/featuregates.go

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.

Yeah this isn't strictly for validation. I think this came from a code rabbit review comment earlier about adding this to reduce the error noise:
#10593 (comment)

On a non-TechPreview install, pki: {} will report both nested PKI shape errors and the later “field is protected by ConfigurablePKI” error, which makes the gated-field failure noisy and misleading

But I can remove this if you feel otherwise.

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.

Thanks for updating!

On a non-TechPreview install, pki: {} will report both nested PKI shape errors and the later “field is protected by ConfigurablePKI” error,

On this note, the 2 errors are expected (not noisy at all). It informs the user: to use pki field, they must enable the feature and provide a field pki configurations 👍

Comment thread pkg/types/pki/validation.go Outdated
// ValidatePKIConfig validates the PKI configuration.
// When pkiConfig is non-nil, signerCertificates must be fully specified.
// NOTE: All fields are value types (not pointers). Use zero-value checks.
func ValidatePKIConfig(pkiConfig *types.PKIConfig, fldPath *field.Path, fips bool) field.ErrorList {

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.

fips is being passed, but unused. Does FIPS have some additional PKI restrictions to be compliant?

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.

Good catch, I think I added this expecting more restrictions, but current validation rules (RSA 2048-8192, ECDSA P-256/P-384/P-521) are already FIPS compliant.

Removed the unused FIPS parameter from all validation functions and tests.
We can add FIPS specific restrictions if that changes in the future.

},
},
},
expectError: false,

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.

nit: highly recommend to check the error message so that we know the right "message" is shown to the user.

Comment thread pkg/types/pki/conversion.go Outdated

// CertificateConfigToUpstream converts the installer-local CertificateConfig
// to the upstream configv1alpha1.CertificateConfig for use in the PKI CR manifest.
func CertificateConfigToUpstream(local types.CertificateConfig) configv1alpha1.CertificateConfig {

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.

Suggested change
func CertificateConfigToUpstream(local types.CertificateConfig) configv1alpha1.CertificateConfig {
func ConvertToAPICertConfig(certConf types.CertificateConfig) configv1alpha1.CertificateConfig {

nit: like the other comment, we use "upstream" to mean CAPI provider here. Let's try this, WDYT?

Comment thread pkg/types/pki/conversion.go Outdated
Comment on lines +14 to +15
RSA: configv1alpha1.RSAKeyConfig{KeySize: local.Key.RSA.KeySize},
ECDSA: configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurve(local.Key.ECDSA.Curve)},

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.

This is another reason to use pointer so that we only populate the field if the corresponding RSA/ECDSA is non-nil.

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.

Same as the earlier pointer discussion.
We're mirroring the openshift/api value-type design, and conversion_test.go verifies that omitzero correctly omits the unused field in marshaled output

Comment thread pkg/types/pki/defaults.go
Comment on lines +7 to +11
// DefaultPKIProfile returns the default PKI profile for OpenShift clusters.
// Currently uses RSA-4096 until all day-2 operators (CKAO, CKMO, etc.) support
// ECDSA certificate rotation. Once operator support lands, switch to ECDSA P-384
// signers and ECDSA P-256 defaults to match the upstream library-go profile:
// https://github.com/openshift/library-go/blob/12d8376369b7c5b76f688d01089882ca28e351c3/pkg/pki/profile.go#L11-L26

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.

I'm curious 🤔. The enhancement said:

Currently, OpenShift uses hardcoded defaults (primarily RSA 2048-bit keys)

IIUC, the default should be using RSA-2048, right? What are the reasons for this func to use RSA-4026?

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.

RSA-2048 is the legacy default when the ConfigurablePKI feature gate is off (non-TechPreview). DefaultPKIProfile() defines the default for when the gate is on.
We use RSA-4096 as a stronger interim default
until day-2 operators support ECDSA rotation, at which point this switches to ECDSA P-384 to match the upstream library-go profile.

What are the reasons for this func to use RSA-4026?

The whole point of configurable PKI is to move to stronger keys than the legacy RSA-2048.
So @sanchezl can correct me but we're using the PKI feature enablement as an opportunity to move to that new default as well.

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.

Ahh right, thanks for the details! Definitely +1 for using stronger keys. Though, when we completely switch to this new default, we need to mention it in:

  • OpenShift Official Docs
  • The install-config field pki description (here) to say we default to ECDSA P-384 and This default is subject to change over time (see example). This helps customers that run command openshift-install explain installconfig.pki.

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.

Sorry forgot to update here, I'm leaving the docs as the last follow up once the implementation is settled with all 3 PRs merged.

#10396 had it all in one PR but that was hard to review.

Comment thread pkg/asset/manifests/pki_test.go Outdated
Comment on lines +150 to +151
} else {
assert.Equal(t, tc.expectDefaultsRSA, profile.Defaults.Key.RSA.KeySize)

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.

nit: we can use an if tc.expectSignerAlgo == configv1alpha1.KeyAlgorithmRSA here instead.

@hasbro17 hasbro17 force-pushed the pki-1-types-and-manifest branch from 3d195a4 to 7b88840 Compare June 17, 2026 22:43
Comment thread pkg/asset/manifests/pki.go Outdated

configData, err := yaml.Marshal(config)
if err != nil {
return errors.Wrapf(err, "failed to marshal PKI config")

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.

please use fmt.Errorf

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.

Let's use fmt.Errorf like Patrick said :D

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.

Whoops, forgot this. Will update.

Comment thread pkg/types/defaults/installconfig.go Outdated
}

if c.AdditionalTrustBundlePolicy == "" {
c.AdditionalTrustBundlePolicy = types.PolicyProxyOnly
}

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.

empty line change

Comment thread pkg/types/installconfig.go Outdated
// Required when algorithm is ECDSA, and forbidden otherwise.
// +optional
// +unionMember
ECDSA ECDSAKeyConfig `json:"ecdsa,omitzero"`

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.

All structs within the union MUST be pointers

https://github.com/openshift/enhancements/blob/master/dev-guide/api-conventions.md

It seems like there may be a misunderstanding. claude couldn't find any discussion of pointers in the api pr. cc @everettraven

Comment thread pkg/types/pki/validation.go Outdated
return allErrs
}

if config.Algorithm != types.KeyAlgorithmRSA && config.Algorithm != types.KeyAlgorithmECDSA {

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.

nit: To ease future maintenance, let's also address #10593 (comment), WDYT? On line 91, you already do the same :D

Comment thread pkg/types/pki/conversion.go Outdated

// CertificateConfigToUpstream converts the installer CertificateConfig
// to the openshift/api configv1alpha1.CertificateConfig for use in the PKI CR manifest.
func CertificateConfigToUpstream(certConf types.CertificateConfig) configv1alpha1.CertificateConfig {

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.

Sorry if I wasn't clear. We should also rename this func too to not mention "upstream", for example, ConvertToAPICertConfig (or any ya like :D).

Additionally, you can move this func into pkg/asset/manifests/pki.go as we only need to call it once to generate the manifests. This way, tests in pki_test.go will also cover it. Why? conversion.go in pkg/types usually use for converting deprecated install-config fields to new ones; thus, it also avoid potential confusion.

@hasbro17 hasbro17 force-pushed the pki-1-types-and-manifest branch from 7b88840 to 8160e36 Compare June 23, 2026 19:57
@hasbro17

Copy link
Copy Markdown
Contributor Author

Per the discussion in #10593 (comment) I've changed the union members KeyConfig.RSA and KeyConfig.ECDSA to pointer types (*RSAKeyConfig, *ECDSAKeyConfig) . Validation now uses nil checks for mutual exclusivity and presence.

Added a local convertToAPICertConfig() helper inpkg/asset/manifests/pki.go to handle the pointer-to-value conversion to openshift/api types until the upstream types are updated to pointers as well.

Comment thread pkg/asset/manifests/pki.go Outdated

configData, err := yaml.Marshal(config)
if err != nil {
return errors.Wrapf(err, "failed to marshal PKI config")

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.

Let's use fmt.Errorf like Patrick said :D

Comment thread pkg/types/pki/validation.go Outdated

validCurves := sets.New(types.ECDSACurveP256, types.ECDSACurveP384, types.ECDSACurveP521)
if !validCurves.Has(config.Curve) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("curve"), config.Curve,

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.

We should use field.UnSupported like line 37, right?

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.

Also see #10593 (comment)

Comment thread pkg/types/pki/validation.go Outdated
Comment on lines +35 to +40
validAlgorithms := sets.New(types.KeyAlgorithmRSA, types.KeyAlgorithmECDSA)
if !validAlgorithms.Has(config.Algorithm) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("algorithm"),
config.Algorithm, []string{string(types.KeyAlgorithmRSA), string(types.KeyAlgorithmECDSA)}))
return allErrs
}

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.

I think we can simplify it like:

validAlgorithms := []types.KeyAlgorithm{types.KeyAlgorithmRSA, types.KeyAlgorithmECDSA}
if !sets.New(validAlgorithms...).Has(config.Algorithm) {
	allErrs = append(allErrs, field.NotSupported(fldPath.Child("algorithm"),
		config.Algorithm, validAlgorithms))
	return allErrs
}

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.

Yeah that's much simpler. Will update.

Comment thread pkg/types/pki/validation_test.go Outdated
errs := ValidateKeyConfig(tc.config, fldPath)
if tc.expectError {
if assert.Len(t, errs, tc.errorCount, "expected %d errors, got: %v", tc.errorCount, errs) {
assert.Contains(t, errs[0].Detail, tc.errorMsg)

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.

The error may not always be the first one so let's try:

assert.Regexp(t, tc.errorMsg, errs.ToAggregate())

This is consistent with other test files.

Comment thread pkg/types/pki/validation_test.go Outdated
errs := ValidatePKIConfig(tc.pkiConfig, fldPath)
if tc.expectError {
if assert.Len(t, errs, tc.errorCount) {
assert.Contains(t, errs[0].Detail, tc.errorMsg)

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.

The error may not always be the first one so let's try:

assert.Regexp(t, tc.errorMsg, errs.ToAggregate())

Add the configurable PKI API surface to InstallConfig behind the
ConfigurablePKI feature gate. When the gate is active, the installer
generates a config.openshift.io/v1alpha1 PKI custom resource manifest
that day-2 operators use for certificate rotation parameters.

The default PKI profile uses RSA-4096 until all day-2 operators (CKAO,
CKMO, etc.) support ECDSA rotation. When pki is not specified in
install-config the PKI CR uses mode: Default. When pki is specified the
PKI CR uses mode: Custom with DefaultPKIProfile as the base and user
signerCertificates overrides layered on top.

No certificate generation changes are included — all certs remain
RSA-2048. Non-TechPreview clusters are completely unaffected.

Assisted-by: Claude Code (Opus 4.6)
@hasbro17 hasbro17 force-pushed the pki-1-types-and-manifest branch from 8160e36 to 85252ce Compare June 24, 2026 22:25

@tthvo tthvo left a comment

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.

/approve

@openshift-ci

openshift-ci Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: tthvo

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

The pull request process is described 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 approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jun 24, 2026
@tthvo

tthvo commented Jun 24, 2026

Copy link
Copy Markdown
Member

/test e2e-aws-ovn-techpreview

@tthvo

tthvo commented Jun 24, 2026

Copy link
Copy Markdown
Member

/test e2e-aws-ovn-dualstack-ipv6-primary-techpreview e2e-aws-ovn-dualstack-ipv4-primary-techpreview
/test e2e-azure-ovn-techpreview
/test e2e-gcp-ovn-techpreview
/test vsphere-ovn-techpreview

@hasbro17

Copy link
Copy Markdown
Contributor Author

/test e2e-azure-ovn-techpreview
/test e2e-gcp-ovn-techpreview
/test e2e-aws-ovn-techpreview

@openshift-ci

openshift-ci Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@hasbro17: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-gcp-ovn-techpreview 85252ce link false /test e2e-gcp-ovn-techpreview
ci/prow/e2e-aws-ovn-techpreview 85252ce link false /test e2e-aws-ovn-techpreview

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.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants