Skip to content

refactor(tls): use IANA cipher suite names instead of OpenSSL names - #1

Open
zhujian7 wants to merge 5 commits into
feat/add-tls-watcher-packagesfrom
feat/tls-iana-cipher-names
Open

refactor(tls): use IANA cipher suite names instead of OpenSSL names#1
zhujian7 wants to merge 5 commits into
feat/add-tls-watcher-packagesfrom
feat/tls-iana-cipher-names

Conversation

@zhujian7

Copy link
Copy Markdown
Owner

Summary

  • Switch cipherMap keys from OpenSSL-style names (e.g. ECDHE-RSA-AES128-GCM-SHA256) to IANA format (e.g. TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), which matches what Go's crypto/tls package uses
  • Rewrite cipherIDToName to look up s.Name from tls.CipherSuites() and tls.InsecureCipherSuites() by ID, replacing the reverse map iteration — this guarantees the returned name is always the authoritative Go/IANA name
  • Update parseCipherSuites and CipherSuitesToString doc comments; no structural changes to those functions
  • Update README ConfigMap example and CipherSuitesToString description to reference IANA format
  • Update all tests to use IANA names

Related issue(s)

Fixes #open-cluster-management-io/ocm#1443

zhujian7 and others added 3 commits March 24, 2026 13:37
…-cluster-management-io#217)

* Add pkg/tls and pkg/watcher packages

pkg/tls provides TLS configuration helpers for OCM components:
- parse TLS version and cipher suites from flags or a ConfigMap
- build crypto/tls.Config and controller-runtime TLSOpts functions
- Options type with pflag integration and priority-based config loading
  (flags > ConfigMap > defaults)

pkg/watcher provides a generic ConfigMapWatcher that triggers graceful
shutdown (via context cancellation) or a custom callback when a watched
ConfigMap is created, updated, or deleted. The initial hash is seeded
from the config in use at startup so any drift detected on first sync
also triggers a restart.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: zhujian <jiazhu@redhat.com>

* Trim pkg/tls and pkg/watcher to only what ocm and addon-framework use

Remove exported symbols that are not called by either consumer:
- options.go deleted (Options, NewOptions, AddFlags, GetTLSConfig,
  StartConfigMapWatcher, GetTLSConfigForServer — none used)
- GetSupportedCipherSuites deleted from cipher.go

Make internal-only helpers unexported:
- ParseTLSVersion → parseTLSVersion
- ParseCipherSuites → parseCipherSuites
- DefaultMinTLSVersion → defaultMinTLSVersion
- ParseTLSConfigFromConfigMap → parseTLSConfigFromConfigMap
- HashConfigMapData → hashConfigMapData (watcher package)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: zhujian <jiazhu@redhat.com>

* Add StartTLSConfigMapWatcher helper and tests for pkg/tls

StartTLSConfigMapWatcher combines ConfigMap loading, watcher seeding,
and background goroutine startup into a single call. It:
- loads the current TLS config (falling back to defaults if absent)
- seeds the watcher with the effective config so mid-startup changes
  are detected
- starts the watcher in a background goroutine
- returns the active TLSConfig so callers can apply it immediately
- rejects a nil onChangeFn to prevent silent no-ops

Add tls_test.go with 96.9% statement coverage across all exported
functions and the StartTLSConfigMapWatcher watcher integration.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: zhujian <jiazhu@redhat.com>

* Add tests for pkg/watcher

Tests cover:
- hashConfigMapData: nil/empty, single/multi-key sorted output,
  order independence, collision resistance, different values
- NewConfigMapWatcher: field and initialHash initialization
- Start AddFunc: no initData sets baseline without trigger,
  matching initData no-ops, differing initData triggers
- Start UpdateFunc: same data no-ops, changed data triggers
- Start DeleteFunc: always triggers
- Wrong CM name is ignored
- cancelFunc is called when no onChangeFunc is set
- Pre-cancelled context returns error

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: zhujian <jiazhu@redhat.com>

* Fix revive stutter lint errors in pkg/tls

Rename exported functions whose names begin with the package name
(tls.*TLS...) to avoid the revive 'exported' stutter warning:
  TLSConfigFromFlags -> ConfigFromFlags
  TLSVersionToString -> VersionToString
  TLSConfigToFunc    -> ConfigToFunc

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: zhujian <jiazhu@redhat.com>

* Address PR review comments

pkg/watcher/watcher.go:
- Use sync.Once to protect one-time initialisation of initialHash in
  AddFunc, eliminating the potential data race
- Handle cache.DeletedFinalStateUnknown tombstones in DeleteFunc to
  avoid a panic when the informer reconnects after a disconnect

pkg/watcher/watcher_test.go:
- Replace hard-coded 200ms waits with a noEventWait constant (500ms)
  for more stable CI behaviour
- cancelWrapper in TestStart_CancelFuncCalledWhenNoOnChangeFunc now
  calls the real cancel function; uses a dedicated watcher context so
  the test select does not race between cancelCalled and ctx.Done()

pkg/tls/config.go + callers:
- Rename TLSConfigFromFlags -> ConfigFromFlags
- Rename TLSVersionToString -> VersionToString
- Rename TLSConfigToFunc    -> ConfigToFunc
  to fix revive stutter lint errors (tls.TLS*)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: zhujian <jiazhu@redhat.com>

* Add README for pkg/tls

Documents the ConfigMap format, three use cases (flag-based config,
ConfigMap watcher, and operator-to-component flag forwarding), and
an API reference table for all exported functions and constants.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: zhujian <jiazhu@redhat.com>

* Address second round of PR review comments

pkg/tls/config.go:
- ConfigToFunc: guard against nil TLSConfig (returned by ConfigFromFlags
  when no flags are set) by returning a no-op func instead of panicking

pkg/tls/configmap.go:
- StartTLSConfigMapWatcher: seed watcher with raw cm.Data instead of
  re-serializing through VersionToString/CipherSuitesToString. The watcher
  hashes raw cm.Data, so normalization differences (e.g. "TLSv1.2" vs
  "VersionTLS12", or an injected empty "cipherSuites" key) produce a
  mismatched hash and trigger a spurious restart loop on every startup.
- StartTLSConfigMapWatcher: when no CM exists at startup, seed with only
  non-empty default fields (minTLSVersion only) so a CM created later with
  non-defaults triggers onChangeFn, while a CM with the same defaults does not.
- StartTLSConfigMapWatcher: call w.Start synchronously so it blocks until
  the informer cache is synced before returning, guaranteeing the watcher
  is ready when the function returns and removing the goroutine-induced race.

pkg/tls/tls_test.go:
- ConfigToFunc: add nil TLSConfig test case and negative assertions for
  CipherSuites and MaxVersion not being set when not expected
- StartTLSConfigMapWatcher: replace bool + time.Sleep (data race) with
  buffered channel + select for race-safe, deterministic event assertions
- StartTLSConfigMapWatcher: add test verifying that a CM created with
  non-default TLS version triggers restart when no CM existed at startup

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: zhujian <jiazhu@redhat.com>

* Replace pkg/watcher with base controller in pkg/tls

Replace the custom ConfigMapWatcher (pkg/watcher) with a controller
built on pkg/basecontroller/factory. The new implementation:

- Eliminates the startup race-window workaround (hash seeding via
  sync.Once) by seeding lastHash from the warm lister after
  WaitForCacheSync, before starting the controller goroutine
- Removes the custom Add/Update/Delete event handlers — Sync() reads
  from the lister and compares hashes in one place
- Deletes pkg/watcher entirely (it was only used by pkg/tls)

Signed-off-by: Jia Zhu <jiazhu@redhat.com>
Signed-off-by: zhujian <jiazhu@redhat.com>

* Fail closed when all cipher suites in ConfigMap are unsupported

If cipherSuites is non-empty but every name is unknown, the previous
code silently left cfg.CipherSuites nil, causing ConfigToFunc to fall
back to Go's default suites instead of rejecting the invalid policy.
Return an error when no supported cipher suite is found, consistent
with ConfigFromFlags behavior. Add a regression test.

Signed-off-by: zhujian <jiazhu@redhat.com>

* chore: polish constant doc comments in pkg/tls

Signed-off-by: zhujian <jiazhu@redhat.com>

---------

Signed-off-by: zhujian <jiazhu@redhat.com>
Signed-off-by: Jia Zhu <jiazhu@redhat.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Switch cipherMap keys and cipherIDToName output from OpenSSL-style
(e.g. ECDHE-RSA-AES128-GCM-SHA256) to IANA format
(e.g. TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), which matches what
Go's crypto/tls package uses.

cipherIDToName now looks up the name from tls.CipherSuites() and
tls.InsecureCipherSuites() by ID instead of reverse-iterating cipherMap,
so the returned name is always the authoritative Go/IANA name.

Update tests and README accordingly.

Signed-off-by: zhujian <jiazhu@redhat.com>
Replace the hand-maintained cipherMap allowlist with direct lookups
against tls.CipherSuites() and tls.InsecureCipherSuites():

- Secure ciphers are accepted silently
- Insecure ciphers are accepted but logged via klog.Warningf
- Unrecognized names are still rejected (existing behavior)

This removes the maintenance burden of keeping cipherMap in sync with
Go's cipher suite lists, and automatically picks up new secure ciphers
added in future Go releases. Previously excluded insecure ciphers
(RC4-based, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) are now accepted with
a warning instead of being silently rejected.

Signed-off-by: zhujian <jiazhu@redhat.com>
@zhujian7
zhujian7 force-pushed the feat/tls-iana-cipher-names branch from 0ff1f46 to 2596f85 Compare March 30, 2026 02:50
Replace per-call tls.CipherSuites()/InsecureCipherSuites() iterations
with three package-level maps built in init():

  secureCiphersByName   map[string]uint16  -- name→ID for secure suites
  insecureCiphersByName map[string]uint16  -- name→ID for insecure suites
  cipherNamesByID       map[uint16]string  -- ID→name for all suites

parseCipherSuites now does O(1) map lookups instead of O(n) linear
scans, and no longer needs the findCipherID helper.
cipherIDToName is reduced to a single map lookup with no allocation.

Signed-off-by: zhujian <jiazhu@redhat.com>
- ConfigFromFlags: trim whitespace from minVersion and cipherSuites
  before the empty check so whitespace-only flag values are treated
  as absent
- tls_test.go: remove `expectedLen > 0` guard on CipherSuites length
  assertions so zero-length expectations are also verified

Signed-off-by: zhujian <jiazhu@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant