Skip to content

feat: implement TLS groups/curve preferences support#22

Open
damdo wants to merge 3 commits into
openshift:mainfrom
damdo:implement-curve-preferences
Open

feat: implement TLS groups/curve preferences support#22
damdo wants to merge 3 commits into
openshift:mainfrom
damdo:implement-curve-preferences

Conversation

@damdo

@damdo damdo commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

Implements TLS groups/curve preferences support in NewTLSConfigFromProfile() by leveraging the library-go crypto package.

Fixes #20

  • Bumps openshift/api to v0.0.0-20260702202555-ef71f942ef6c (includes Groups field)
  • Uses libgocrypto.CurveIDsForTLSGroups() to map TLS groups/curve preferences to Go curve IDs
  • Gracefully filters unsupported curves (post-quantum hybrids in Go 1.25)
  • Adds comprehensive test coverage for TLS groups/curve preference scenarios

Sample PR that leverages this is here: openshift/cluster-machine-approver#308

Test plan

  • Code compiles without errors
  • Tests added for supported/unsupported groups, empty groups, and predefined profiles
  • All changes isolated to proper commits (code, tests, then vendor)

TODO

@openshift-ci openshift-ci Bot requested review from mdbooth and sdodson July 3, 2026 08:27
@openshift-ci

openshift-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: damdo

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 Jul 3, 2026
@damdo damdo changed the title feat: implement TLS curve preferences support feat: implement TLS groups/curve preferences support Jul 3, 2026
@damdo

damdo commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

/hold

This PR depends on openshift/library-go#2347 merging upstream. Once that PR is merged and released, we can drop the temporary replace directive and revendor to use the official library-go version.

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 3, 2026
@damdo damdo force-pushed the implement-curve-preferences branch from d584c5e to 79a541a Compare July 3, 2026 08:32
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: 6e6e64b0-04fb-42c6-9872-9af4b8e47dd9

📥 Commits

Reviewing files that changed from the base of the PR and between 79a541a and fc0245f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • go.mod
  • pkg/tls/tls.go
  • pkg/tls/tls_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/tls/tls.go
  • go.mod

📝 Walkthrough

Walkthrough

Changes

This pull request updates go.mod dependency versions, including a bump to openshift/api, refreshed go-openapi/swag indirect dependencies, removal of josharian/intern and mailru/easyjson, and updates to various other indirect dependencies and the library-go replace directive. Separately, NewTLSConfigFromProfile in pkg/tls/tls.go now derives tls.Config.CurvePreferences from profile.Groups, setting it only when preferences exist, and returns a combined list of unsupported cipher and curve group names via a renamed return value. Corresponding test cases were added covering supported groups, unsupported groups, empty groups, and Intermediate profile behavior.

Sequence Diagram(s)

Not applicable; changes are dependency updates and configuration logic without a multi-component interaction flow warranting a sequence diagram.

Related Issues: #20

Related PRs: None identified.

Suggested labels: dependencies, enhancement

Suggested reviewers: damdo

🐰 A curve, a group, a cipher's name,
Now wired through TLS's frame.
Dependencies hop up a version or two,
Swag repackaged, easyjson bid adieu.
Tests confirm the preference chain—
A rabbit's work, not done in vain. 🥕

🚥 Pre-merge checks | ✅ 15
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding TLS groups/curve preferences support.
Description check ✅ Passed The description matches the code changes and test additions in the pull request.
Linked Issues check ✅ Passed The PR updates NewTLSConfigFromProfile to apply profile.Groups to CurvePreferences and adds coverage for supported, unsupported, and empty groups.
Out of Scope Changes check ✅ Passed The dependency and replace updates in go.mod appear tied to the TLS groups implementation and library-go support, with no clear unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Stable And Deterministic Test Names ✅ Passed The added Ginkgo titles in pkg/tls/tls_test.go are all static, descriptive strings with no timestamps, UUIDs, node/namespace names, or other run-to-run dynamic data.
Test Structure And Quality ✅ Passed The added TLS tests are focused unit tests, create no resources, use no waits, and match the package’s existing Ginkgo style.
Microshift Test Compatibility ✅ Passed Only unit tests in pkg/tls/tls_test.go were added; they use local TLS structs and no MicroShift-unsupported APIs/features.
Single Node Openshift (Sno) Test Compatibility ✅ Passed Only pkg/tls unit tests were added; no e2e/cluster-topology assumptions or SNO skip guards were present.
Topology-Aware Scheduling Compatibility ✅ Passed The PR only changes TLS config/tests; no deployment manifests or controller scheduling logic were added, so topology-aware scheduling concerns aren’t implicated.
Ote Binary Stdout Contract ✅ Passed PR only changes TLS config/tests; no process-level fmt.Print or stdout logging was added. Suite logging goes to GinkgoWriter, not stdout.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed Added Ginkgo unit tests only; no IPv4 literals, host/IP parsing, or external network calls were found in pkg/tls/tls_test.go.
No-Weak-Crypto ✅ Passed No MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or insecure secret comparisons were added; the PR only wires TLS curve preferences via library-go and tests it.
Container-Privileges ✅ Passed PR changes only go.mod/go.sum and Go test/code files; no container/K8s manifests or privilege fields were added.
No-Sensitive-Data-In-Logs ✅ Passed No new logging statements or sensitive literals were added in the touched files; the only messages are wrapped errors using the APIServer name 'cluster'.
✨ Finishing Touches
🧪 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

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
pkg/tls/tls_test.go (1)

357-371: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the conditional assertion in the Intermediate-profile Groups test.

The test only asserts CurvePreferences is set if len(profile.Groups) > 0; if the Intermediate profile definition ever ships with an empty Groups list (e.g., upstream API regression), this test silently passes without verifying anything.

✅ Proposed fix to make the test deterministic
 		It("should set CurvePreferences from the profile", func() {
 			profile := *configv1.TLSProfiles[configv1.TLSProfileIntermediateType]
+			Expect(profile.Groups).NotTo(BeEmpty(), "Intermediate profile is expected to define Groups")

 			tlsConfigFn, _ := NewTLSConfigFromProfile(profile)

 			tlsConf := &tls.Config{}
 			tlsConfigFn(tlsConf)

-			// Intermediate profile now includes Groups
-			if len(profile.Groups) > 0 {
-				Expect(tlsConf.CurvePreferences).NotTo(BeNil())
-			}
+			Expect(tlsConf.CurvePreferences).NotTo(BeNil())
 		})
🤖 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/tls/tls_test.go` around lines 357 - 371, The Intermediate profile Groups
test in NewTLSConfigFromProfile is conditional on len(profile.Groups), so it can
pass without checking anything if Groups is empty. Make the test deterministic
by asserting the expected Intermediate profile data explicitly before calling
tlsConfigFn, then always verify tlsConf.CurvePreferences is populated from the
profile’s Groups rather than skipping the assertion.
pkg/tls/tls.go (1)

119-121: 🧹 Nitpick | 🔵 Trivial

Post-quantum curve gap noted and tracked.

The TODO correctly flags that post-quantum hybrid curves (SecP256r1MLKEM768, SecP384r1MLKEM1024) are not supported in Go 1.25 and are gracefully filtered as unsupported. Since this shared TLS helper may end up protecting long-lived secrets across consuming controller-runtime projects, worth ensuring downstream consumers are aware of the current PQ gap until Go 1.26+ lands support.

As per path instructions for files matching **/*{crypt,cipher,sign,hash,tls,ssl,cert,key,token}*, "Post-quantum: flag if protecting long-lived secrets."

🤖 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/tls/tls.go` around lines 119 - 121, The TLS helper in
CurveIDsForTLSGroups already filters unsupported post-quantum hybrid curves, but
the comment needs to make the downstream exposure explicit for consumers
handling long-lived secrets. Update the TODO/documentation around curvePrefs and
unsupportedGroups in tls.go to clearly call out that SecP256r1MLKEM768 and
SecP384r1MLKEM1024 are currently unavailable in Go 1.25, that they are being
dropped by CurveIDsForTLSGroups, and that controller-runtime consumers should
not rely on PQ protection until Go 1.26+ support is available.

Source: Path instructions

🤖 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 `@pkg/tls/tls.go`:
- Around line 116-137: Normalize the unsupported group names returned from
NewTLSConfigFromProfile so they match the expected lowercase underscore format
instead of the raw enum string. Update the unsupported-group handling in
CurveIDsForTLSGroups (or immediately after its result is used in
NewTLSConfigFromProfile) to map values like SecP256r1MLKEM768 to
secp256r1_mlkem768 before appending to unsupported, while keeping
unsupportedCiphers behavior unchanged.

---

Nitpick comments:
In `@pkg/tls/tls_test.go`:
- Around line 357-371: The Intermediate profile Groups test in
NewTLSConfigFromProfile is conditional on len(profile.Groups), so it can pass
without checking anything if Groups is empty. Make the test deterministic by
asserting the expected Intermediate profile data explicitly before calling
tlsConfigFn, then always verify tlsConf.CurvePreferences is populated from the
profile’s Groups rather than skipping the assertion.

In `@pkg/tls/tls.go`:
- Around line 119-121: The TLS helper in CurveIDsForTLSGroups already filters
unsupported post-quantum hybrid curves, but the comment needs to make the
downstream exposure explicit for consumers handling long-lived secrets. Update
the TODO/documentation around curvePrefs and unsupportedGroups in tls.go to
clearly call out that SecP256r1MLKEM768 and SecP384r1MLKEM1024 are currently
unavailable in Go 1.25, that they are being dropped by CurveIDsForTLSGroups, and
that controller-runtime consumers should not rely on PQ protection until Go
1.26+ support is available.
🪄 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 thread pkg/tls/tls.go
Comment on lines +116 to +137
func NewTLSConfigFromProfile(profile configv1.TLSProfileSpec) (tlsConfig func(*tls.Config), unsupported []string) {
minVersion := libgocrypto.TLSVersionOrDie(string(profile.MinTLSVersion))
cipherSuites, unsupportedCiphers := cipherCodes(profile.Ciphers)
// TODO: post-quantum hybrid curves (SecP256r1MLKEM768, SecP384r1MLKEM1024) are not supported in Go 1.25.
// These will be returned as unsupported and gracefully filtered. Update this when Go 1.26+ support is available.
curvePrefs, unsupportedGroups := libgocrypto.CurveIDsForTLSGroups(profile.Groups)

unsupported = append(unsupportedCiphers, unsupportedGroups...)

return func(tlsConf *tls.Config) {
tlsConf.MinVersion = minVersion
// TODO: add curve preferences from profile once https://github.com/openshift/api/pull/2583 merges.
// tlsConf.CurvePreferences <<<<<< profile.Curves
if len(curvePrefs) > 0 {
tlsConf.CurvePreferences = curvePrefs
}

// TLS 1.3 cipher suites are not configurable in Go (https://github.com/golang/go/issues/29349), so only set CipherSuites accordingly.
// TODO: revisit this once we get an answer on the best way to handle this here:
// https://docs.google.com/document/d/1cMc9E8psHfnoK06ntR8kHSWB8d3rMtmldhnmM4nImjs/edit?disco=AAABu_nPcYg
if minVersion != tls.VersionTLS13 {
tlsConf.CipherSuites = cipherSuites
}
}, unsupportedCiphers
}, unsupported

Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the vendored library-go crypto package and inspect CurveIDsForTLSGroups signature
fd -a 'crypto.go' --path '*library-go/pkg/crypto*'
rg -n -A 15 'func CurveIDsForTLSGroups' -g '*.go'

Repository: openshift/controller-runtime-common

Length of output: 405


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== go.mod references =='
rg -n 'library-go|CurveIDsForTLSGroups|TLSGroup|TLSProfileSpec' go.mod go.sum vendor . || true

echo
echo '== locate tls.go =='
fd -a 'tls.go' pkg || true

echo
echo '== outline pkg/tls/tls.go =='
if command -v ast-grep >/dev/null 2>&1; then
  ast-grep outline pkg/tls/tls.go --view expanded || true
fi

echo
echo '== relevant lines in pkg/tls/tls.go =='
nl -ba pkg/tls/tls.go | sed -n '1,220p'

Repository: openshift/controller-runtime-common

Length of output: 26899


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== dependency metadata =='
rg -n 'openshift/library-go|library-go' go.mod go.sum vendor . || true

echo
echo '== search for CurveIDsForTLSGroups in repo =='
rg -n 'CurveIDsForTLSGroups|TLSGroup' . || true

echo
echo '== inspect pkg/tls tests =='
fd -a 'tls_test.go' pkg || true
if [ -f pkg/tls/tls_test.go ]; then
  nl -ba pkg/tls/tls_test.go | sed -n '1,260p'
fi

Repository: openshift/controller-runtime-common

Length of output: 9811


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== vendor CurveIDsForTLSGroups implementation =='
sed -n '392,420p' vendor/github.com/openshift/library-go/pkg/crypto/crypto.go

echo
echo '== tls tests around unsupported groups =='
sed -n '286,360p' pkg/tls/tls_test.go

Repository: openshift/controller-runtime-common

Length of output: 3714


Normalize unsupported group names
CurveIDsForTLSGroups appends string(group) for unsupported entries, so configv1.TLSGroupSecP256r1MLKEM768 is reported as SecP256r1MLKEM768, not secp256r1_mlkem768. If the lowercase underscore form is required here, normalize before returning.

🤖 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/tls/tls.go` around lines 116 - 137, Normalize the unsupported group names
returned from NewTLSConfigFromProfile so they match the expected lowercase
underscore format instead of the raw enum string. Update the unsupported-group
handling in CurveIDsForTLSGroups (or immediately after its result is used in
NewTLSConfigFromProfile) to map values like SecP256r1MLKEM768 to
secp256r1_mlkem768 before appending to unsupported, while keeping
unsupportedCiphers behavior unchanged.

@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.

🧹 Nitpick comments (1)
pkg/tls/tls_test.go (1)

357-371: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Weak assertion: test can pass without exercising anything.

The assertion is gated behind if len(profile.Groups) > 0, so if the Intermediate profile's Groups field is ever empty (e.g., due to an openshift/api regression), this test silently passes without validating any behavior.

🧪 Proposed fix to assert the precondition
 	Context("when using Intermediate profile with Groups", func() {
 		It("should set CurvePreferences from the profile", func() {
 			profile := *configv1.TLSProfiles[configv1.TLSProfileIntermediateType]
+			Expect(profile.Groups).NotTo(BeEmpty())
 
 			tlsConfigFn, _ := NewTLSConfigFromProfile(profile)
 
 			tlsConf := &tls.Config{}
 			tlsConfigFn(tlsConf)
 
-			// Intermediate profile now includes Groups
-			if len(profile.Groups) > 0 {
-				Expect(tlsConf.CurvePreferences).NotTo(BeNil())
-			}
+			Expect(tlsConf.CurvePreferences).NotTo(BeNil())
 		})
 	})
🤖 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/tls/tls_test.go` around lines 357 - 371, The test in tls_test.go has a
weak assertion because it only checks CurvePreferences when profile.Groups is
non-empty, so it can silently pass without validating behavior. In the Context
for NewTLSConfigFromProfile with configv1.TLSProfileIntermediateType, assert the
precondition on profile.Groups directly (for example, require it to be non-empty
before checking tlsConf.CurvePreferences) so the test fails if the Intermediate
profile no longer includes groups, and keep the existing CurvePreferences
expectation tied to that same setup.
🤖 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.

Nitpick comments:
In `@pkg/tls/tls_test.go`:
- Around line 357-371: The test in tls_test.go has a weak assertion because it
only checks CurvePreferences when profile.Groups is non-empty, so it can
silently pass without validating behavior. In the Context for
NewTLSConfigFromProfile with configv1.TLSProfileIntermediateType, assert the
precondition on profile.Groups directly (for example, require it to be non-empty
before checking tlsConf.CurvePreferences) so the test fails if the Intermediate
profile no longer includes groups, and keep the existing CurvePreferences
expectation tied to that same setup.

damdo added 3 commits July 3, 2026 13:09
Bump openshift/api to pick up the TLS Groups field in TLSProfileSpec
and TLSGroup type/constants added in openshift/api#2583.

Add a replace directive for library-go pointing to the damdo fork
which implements CurveIDsForTLSGroups.
Use libgocrypto.CurveIDsForTLSGroups to map TLS groups from the
profile spec to Go tls.CurveID values, and set CurvePreferences
on the TLS config when groups are specified.

Fixes openshift#20
Test supported groups, unsupported groups (post-quantum hybrids),
empty groups, and predefined Intermediate profile with groups.
@damdo damdo force-pushed the implement-curve-preferences branch from 79a541a to fc0245f Compare July 3, 2026 11:31
@openshift-ci

openshift-ci Bot commented Jul 3, 2026

Copy link
Copy Markdown

@damdo: all tests passed!

Full PR test history. Your PR dashboard.

Details

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

@damdo

damdo commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

/assign @joelanford

Comment thread pkg/tls/tls_test.go
Comment on lines +312 to +313
Expect(tlsConf.CurvePreferences).To(ContainElement(tls.X25519))
Expect(tlsConf.CurvePreferences).To(ContainElement(tls.CurveP256))

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.

Should we promise anything about the order of the groups?

Comment thread pkg/tls/tls_test.go
Comment on lines +365 to +367
if len(profile.Groups) > 0 {
Expect(tlsConf.CurvePreferences).NotTo(BeNil())
}

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.

Should we expect the lengths to be equal?

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. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement CurvePreferences support in NewTLSConfigFromProfile (openshift/api#2583 merged)

2 participants