Skip to content

Commit 0cc72e5

Browse files
authored
🌱 refactor(tls) use IANA cipher suite names instead of OpenSSL names (#218)
* refactor(tls): use IANA cipher suite names instead of OpenSSL names 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> * refactor(tls): drop cipherMap, use Go's cipher suite lists directly 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> * perf(tls): build cipher lookup maps once at init time 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> * fix(tls): address CodeRabbit review comments - 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> --------- Signed-off-by: zhujian <jiazhu@redhat.com>
1 parent 9cbb733 commit 0cc72e5

4 files changed

Lines changed: 85 additions & 72 deletions

File tree

pkg/tls/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ metadata:
1414
namespace: <component-namespace>
1515
data:
1616
minTLSVersion: VersionTLS13 # tls.ConfigMapKeyMinVersion
17-
cipherSuites: ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256
17+
cipherSuites: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
1818
# ^ tls.ConfigMapKeyCipherSuites
1919
```
2020

2121
Supported `minTLSVersion` values: `VersionTLS10`, `VersionTLS11`, `VersionTLS12` (default),
2222
`VersionTLS13`.
2323

24-
Supported cipher suite names follow the OpenSSL naming convention. TLS 1.3 cipher suites
25-
are fixed by the Go runtime and cannot be configured via `cipherSuites`.
24+
Cipher suite names use the IANA format (e.g. `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`),
25+
which matches what Go's `crypto/tls` package uses. TLS 1.3 cipher suites are fixed by
26+
the Go runtime and cannot be configured via `cipherSuites`.
2627

2728
## Use cases
2829

@@ -108,6 +109,6 @@ args := []string{
108109
| `ConfigFromFlags(minVersion, cipherSuites)` | Parse TLS config from flag strings. Returns `nil` if both are empty. |
109110
| `ConfigToFunc(tlsCfg)` | Returns a `func(*tls.Config)` for use with controller-runtime `TLSOpts`. |
110111
| `VersionToString(version)` | Convert a `crypto/tls` version constant to its string name. |
111-
| `CipherSuitesToString(suites)` | Convert cipher suite IDs back to a comma-separated OpenSSL-style string. |
112+
| `CipherSuitesToString(suites)` | Convert cipher suite IDs back to a comma-separated IANA-format string. |
112113

113114
Constants: `ConfigMapName`, `ConfigMapKeyMinVersion`, `ConfigMapKeyCipherSuites`.

pkg/tls/cipher.go

Lines changed: 0 additions & 36 deletions
This file was deleted.

pkg/tls/config.go

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"crypto/tls"
55
"fmt"
66
"strings"
7+
8+
"k8s.io/klog/v2"
79
)
810

911
const (
@@ -20,6 +22,38 @@ const (
2022
// defaultMinTLSVersion is the fallback when no TLS profile is configured
2123
const defaultMinTLSVersion = "VersionTLS12"
2224

25+
// secureCiphersByName maps IANA names → IDs for ciphers in tls.CipherSuites().
26+
var secureCiphersByName map[string]uint16
27+
28+
// insecureCiphersByName maps IANA names → IDs for ciphers in tls.InsecureCipherSuites().
29+
var insecureCiphersByName map[string]uint16
30+
31+
// cipherNamesByID maps cipher suite IDs → IANA names for all known ciphers.
32+
var cipherNamesByID map[uint16]string
33+
34+
func init() {
35+
secure := tls.CipherSuites()
36+
insecure := tls.InsecureCipherSuites()
37+
38+
secureCiphersByName = make(map[string]uint16, len(secure))
39+
for _, s := range secure {
40+
secureCiphersByName[s.Name] = s.ID
41+
}
42+
43+
insecureCiphersByName = make(map[string]uint16, len(insecure))
44+
for _, s := range insecure {
45+
insecureCiphersByName[s.Name] = s.ID
46+
}
47+
48+
cipherNamesByID = make(map[uint16]string, len(secure)+len(insecure))
49+
for _, s := range secure {
50+
cipherNamesByID[s.ID] = s.Name
51+
}
52+
for _, s := range insecure {
53+
cipherNamesByID[s.ID] = s.Name
54+
}
55+
}
56+
2357
// TLSConfig represents parsed TLS configuration
2458
type TLSConfig struct {
2559
MinVersion uint16
@@ -44,8 +78,10 @@ func parseTLSVersion(version string) (uint16, error) {
4478
}
4579
}
4680

47-
// parseCipherSuites converts OpenSSL-style cipher names to Go crypto/tls constants.
48-
// Returns a list of cipher suite IDs and a list of unsupported cipher names.
81+
// parseCipherSuites converts IANA cipher suite names to Go crypto/tls constants.
82+
// Secure ciphers (tls.CipherSuites) are accepted silently. Insecure ciphers
83+
// (tls.InsecureCipherSuites) are accepted but logged as a warning.
84+
// Returns a list of cipher suite IDs and a list of unrecognized cipher names.
4985
func parseCipherSuites(cipherString string) ([]uint16, []string) {
5086
if strings.TrimSpace(cipherString) == "" {
5187
return nil, nil
@@ -61,11 +97,16 @@ func parseCipherSuites(cipherString string) ([]uint16, []string) {
6197
continue
6298
}
6399

64-
if suite, ok := cipherMap[name]; ok {
65-
cipherSuites = append(cipherSuites, suite)
66-
} else {
67-
unsupported = append(unsupported, name)
100+
if id, ok := secureCiphersByName[name]; ok {
101+
cipherSuites = append(cipherSuites, id)
102+
continue
68103
}
104+
if id, ok := insecureCiphersByName[name]; ok {
105+
klog.Warningf("Cipher suite %s is insecure and should not be used in production", name)
106+
cipherSuites = append(cipherSuites, id)
107+
continue
108+
}
109+
unsupported = append(unsupported, name)
69110
}
70111

71112
return cipherSuites, unsupported
@@ -81,6 +122,8 @@ func GetDefaultTLSConfig() *TLSConfig {
81122

82123
// ConfigFromFlags creates TLS config from command-line flags
83124
func ConfigFromFlags(minVersion, cipherSuites string) (*TLSConfig, error) {
125+
minVersion = strings.TrimSpace(minVersion)
126+
cipherSuites = strings.TrimSpace(cipherSuites)
84127
if minVersion == "" && cipherSuites == "" {
85128
return nil, nil // No flags provided
86129
}
@@ -126,7 +169,7 @@ func VersionToString(version uint16) string {
126169
}
127170
}
128171

129-
// CipherSuitesToString converts cipher suite IDs back to OpenSSL-style names
172+
// CipherSuitesToString converts cipher suite IDs back to IANA names
130173
func CipherSuitesToString(suites []uint16) string {
131174
if len(suites) == 0 {
132175
return ""
@@ -160,12 +203,7 @@ func ConfigToFunc(tlsCfg *TLSConfig) func(*tls.Config) {
160203
}
161204
}
162205

163-
// cipherIDToName converts a cipher suite ID to its OpenSSL-style name
206+
// cipherIDToName converts a cipher suite ID to its IANA name.
164207
func cipherIDToName(id uint16) string {
165-
for name, suiteID := range cipherMap {
166-
if suiteID == id {
167-
return name
168-
}
169-
}
170-
return ""
208+
return cipherNamesByID[id]
171209
}

pkg/tls/tls_test.go

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,15 @@ func TestConfigFromFlags(t *testing.T) {
6969
{
7070
name: "valid single cipher",
7171
minVersion: "VersionTLS12",
72-
cipherSuites: "ECDHE-RSA-AES128-GCM-SHA256",
72+
cipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
7373
expectError: false,
7474
expectedMin: tls.VersionTLS12,
7575
expectedLen: 1,
7676
},
7777
{
7878
name: "valid multiple ciphers",
7979
minVersion: "VersionTLS12",
80-
cipherSuites: "ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256",
80+
cipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
8181
expectError: false,
8282
expectedMin: tls.VersionTLS12,
8383
expectedLen: 2,
@@ -91,21 +91,21 @@ func TestConfigFromFlags(t *testing.T) {
9191
{
9292
name: "mixed valid and invalid ciphers",
9393
minVersion: "VersionTLS12",
94-
cipherSuites: "ECDHE-RSA-AES128-GCM-SHA256,INVALID-CIPHER",
94+
cipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,INVALID-CIPHER",
9595
expectError: true,
9696
},
9797
{
9898
name: "only cipher suites without version defaults to TLS 1.2",
9999
minVersion: "",
100-
cipherSuites: "ECDHE-RSA-AES128-GCM-SHA256",
100+
cipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
101101
expectError: false,
102102
expectedMin: tls.VersionTLS12,
103103
expectedLen: 1,
104104
},
105105
{
106106
name: "cipher suites with whitespace",
107107
minVersion: "VersionTLS12",
108-
cipherSuites: " ECDHE-RSA-AES128-GCM-SHA256 , ECDHE-ECDSA-AES128-GCM-SHA256 ",
108+
cipherSuites: " TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 , TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ",
109109
expectError: false,
110110
expectedMin: tls.VersionTLS12,
111111
expectedLen: 2,
@@ -143,7 +143,7 @@ func TestConfigFromFlags(t *testing.T) {
143143
t.Errorf("expected MinVersion %d, got %d", tc.expectedMin, cfg.MinVersion)
144144
}
145145

146-
if tc.expectedLen > 0 && len(cfg.CipherSuites) != tc.expectedLen {
146+
if len(cfg.CipherSuites) != tc.expectedLen {
147147
t.Errorf("expected %d cipher suites, got %d", tc.expectedLen, len(cfg.CipherSuites))
148148
}
149149
})
@@ -212,15 +212,15 @@ func TestCipherSuitesToString(t *testing.T) {
212212
{
213213
name: "single cipher",
214214
suites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
215-
expected: "ECDHE-RSA-AES128-GCM-SHA256",
215+
expected: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
216216
},
217217
{
218218
name: "multiple ciphers",
219219
suites: []uint16{
220220
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
221221
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
222222
},
223-
expected: "ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256",
223+
expected: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
224224
},
225225
{
226226
name: "unknown cipher ID skipped",
@@ -229,7 +229,7 @@ func TestCipherSuitesToString(t *testing.T) {
229229
0x9999, // unknown cipher
230230
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
231231
},
232-
expected: "ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256",
232+
expected: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
233233
},
234234
}
235235

@@ -387,7 +387,7 @@ func TestLoadTLSConfigFromConfigMap(t *testing.T) {
387387
},
388388
Data: map[string]string{
389389
ConfigMapKeyMinVersion: "VersionTLS12",
390-
ConfigMapKeyCipherSuites: "ECDHE-RSA-AES128-GCM-SHA256",
390+
ConfigMapKeyCipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
391391
},
392392
}
393393
return fake.NewClientset(cm)
@@ -441,7 +441,7 @@ func TestLoadTLSConfigFromConfigMap(t *testing.T) {
441441
},
442442
Data: map[string]string{
443443
ConfigMapKeyMinVersion: "VersionTLS12",
444-
ConfigMapKeyCipherSuites: "ECDHE-RSA-AES128-GCM-SHA256,UNKNOWN-CIPHER",
444+
ConfigMapKeyCipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,UNKNOWN-CIPHER",
445445
},
446446
}
447447
return fake.NewClientset(cm)
@@ -535,7 +535,7 @@ func TestLoadTLSConfigFromConfigMap(t *testing.T) {
535535
t.Errorf("expected MinVersion %d, got %d", tc.expectedMin, cfg.MinVersion)
536536
}
537537

538-
if tc.expectedLen > 0 && len(cfg.CipherSuites) != tc.expectedLen {
538+
if len(cfg.CipherSuites) != tc.expectedLen {
539539
t.Errorf("expected %d cipher suites, got %d", tc.expectedLen, len(cfg.CipherSuites))
540540
}
541541
})
@@ -888,12 +888,12 @@ func TestParseCipherSuites(t *testing.T) {
888888
},
889889
{
890890
name: "single valid cipher",
891-
cipherString: "ECDHE-RSA-AES128-GCM-SHA256",
891+
cipherString: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
892892
expectedCount: 1,
893893
},
894894
{
895895
name: "multiple valid ciphers",
896-
cipherString: "ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256",
896+
cipherString: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
897897
expectedCount: 2,
898898
},
899899
{
@@ -904,18 +904,28 @@ func TestParseCipherSuites(t *testing.T) {
904904
},
905905
{
906906
name: "mixed valid and unsupported",
907-
cipherString: "ECDHE-RSA-AES128-GCM-SHA256,UNKNOWN-CIPHER",
907+
cipherString: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,UNKNOWN-CIPHER",
908908
expectedCount: 1,
909909
expectedUnsupported: 1,
910910
},
911911
{
912912
name: "ciphers with whitespace",
913-
cipherString: " ECDHE-RSA-AES128-GCM-SHA256 , ECDHE-ECDSA-AES128-GCM-SHA256 ",
913+
cipherString: " TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 , TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ",
914914
expectedCount: 2,
915915
},
916916
{
917917
name: "empty entries in list",
918-
cipherString: "ECDHE-RSA-AES128-GCM-SHA256,,ECDHE-ECDSA-AES128-GCM-SHA256",
918+
cipherString: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
919+
expectedCount: 2,
920+
},
921+
{
922+
name: "insecure cipher accepted with warning",
923+
cipherString: "TLS_RSA_WITH_AES_128_GCM_SHA256",
924+
expectedCount: 1,
925+
},
926+
{
927+
name: "mix of secure and insecure ciphers",
928+
cipherString: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_GCM_SHA256",
919929
expectedCount: 2,
920930
},
921931
}
@@ -944,7 +954,7 @@ func TestCipherIDToName(t *testing.T) {
944954
{
945955
name: "known cipher",
946956
id: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
947-
expected: "ECDHE-RSA-AES128-GCM-SHA256",
957+
expected: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
948958
},
949959
{
950960
name: "unknown cipher",

0 commit comments

Comments
 (0)