Skip to content

Commit 12b1648

Browse files
committed
feat: add flag to configure TLS cipher suites
This commit introduces a new command-line flag `-tls-cipher-suites` to the webhook server. This allows users to specify a comma-separated list of allowed TLS cipher suites, enhancing security configurability. This commit also enables the ability to specify the tls-min-version using the names of the constants in crypto/tls/common.go (i.e., VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13). Assisted-by: gemini-3-pro-preview
1 parent c926cd0 commit 12b1648

File tree

2 files changed

+196
-6
lines changed

2 files changed

+196
-6
lines changed

cmd/webhook/main.go

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"flag"
66
"fmt"
77
"net/http"
8+
"strings"
89

910
"github.com/open-policy-agent/cert-controller/pkg/rotator"
1011
"k8s.io/apimachinery/pkg/runtime"
@@ -42,6 +43,7 @@ const (
4243
var (
4344
audience string
4445
webhookCertDir string
46+
tlsCipherSuites string
4547
tlsMinVersion string
4648
healthAddr string
4749
metricsAddr string
@@ -73,6 +75,7 @@ func mainErr() error {
7375
flag.StringVar(&audience, "audience", "", "Audience for service account token")
7476
flag.StringVar(&webhookCertDir, "webhook-cert-dir", "/certs", "Webhook certificates dir to use. Defaults to /certs")
7577
flag.BoolVar(&disableCertRotation, "disable-cert-rotation", false, "disable automatic generation and rotation of webhook TLS certificates/keys")
78+
flag.StringVar(&tlsCipherSuites, "tls-cipher-suites", "", "Comma-separated list of TLS cipher suites")
7679
flag.StringVar(&tlsMinVersion, "tls-min-version", "1.3", "Minimum TLS version")
7780
flag.StringVar(&healthAddr, "health-addr", ":9440", "The address the health endpoint binds to")
7881
flag.StringVar(&metricsAddr, "metrics-addr", ":8095", "The address the metrics endpoint binds to")
@@ -114,10 +117,20 @@ func mainErr() error {
114117
if err != nil {
115118
return fmt.Errorf("entrypoint: unable to parse TLS version: %w", err)
116119
}
120+
tlsOpts := []func(c *tls.Config){func(c *tls.Config) { c.MinVersion = tlsVersion }}
121+
122+
cipherSuites, err := parseTLSCipherSuites(tlsCipherSuites)
123+
if err != nil {
124+
return fmt.Errorf("entrypoint: unable to parse TLS cipher suites: %w", err)
125+
}
126+
127+
if len(cipherSuites) > 0 {
128+
tlsOpts = append(tlsOpts, func(c *tls.Config) { c.CipherSuites = cipherSuites })
129+
}
117130

118131
serverOpts := webhook.Options{
119132
CertDir: webhookCertDir,
120-
TLSOpts: []func(c *tls.Config){func(c *tls.Config) { c.MinVersion = tlsVersion }},
133+
TLSOpts: tlsOpts,
121134
}
122135
mgr, err := ctrl.NewManager(config, ctrl.Options{
123136
Scheme: scheme,
@@ -207,15 +220,45 @@ func setupProbeEndpoints(mgr ctrl.Manager, setupFinished chan struct{}) {
207220

208221
func parseTLSVersion(tlsVersion string) (uint16, error) {
209222
switch tlsVersion {
210-
case "1.0":
223+
case "1.0", "VersionTLS10":
211224
return tls.VersionTLS10, nil
212-
case "1.1":
225+
case "1.1", "VersionTLS11":
213226
return tls.VersionTLS11, nil
214-
case "1.2":
227+
case "1.2", "VersionTLS12":
215228
return tls.VersionTLS12, nil
216-
case "1.3":
229+
case "1.3", "VersionTLS13":
217230
return tls.VersionTLS13, nil
218231
default:
219-
return 0, fmt.Errorf("invalid TLS version. Must be one of: 1.0, 1.1, 1.2, 1.3")
232+
return 0, fmt.Errorf("invalid TLS version. Must be one of: 1.0, 1.1, 1.2, 1.3, VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13")
233+
}
234+
}
235+
236+
func parseTLSCipherSuites(cipherSuites string) ([]uint16, error) {
237+
if cipherSuites == "" {
238+
return nil, nil
239+
}
240+
241+
// Build a map of all available cipher suites
242+
availableSuites := make(map[string]uint16)
243+
for _, s := range tls.CipherSuites() {
244+
availableSuites[s.Name] = s.ID
245+
}
246+
// Also include insecure suites just in case, though discouraged
247+
for _, s := range tls.InsecureCipherSuites() {
248+
availableSuites[s.Name] = s.ID
249+
}
250+
251+
var ids []uint16
252+
for _, name := range strings.Split(cipherSuites, ",") {
253+
name = strings.TrimSpace(name)
254+
if name == "" {
255+
continue
256+
}
257+
id, ok := availableSuites[name]
258+
if !ok {
259+
return nil, fmt.Errorf("unsupported cipher suite: %s", name)
260+
}
261+
ids = append(ids, id)
220262
}
263+
return ids, nil
221264
}

cmd/webhook/main_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package main
2+
3+
import (
4+
"crypto/tls"
5+
"reflect"
6+
"testing"
7+
)
8+
9+
func TestParseTLSVersion(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
version string
13+
want uint16
14+
wantErr bool
15+
}{
16+
{
17+
name: "TLS 1.0",
18+
version: "1.0",
19+
want: tls.VersionTLS10,
20+
wantErr: false,
21+
},
22+
{
23+
name: "VersionTLS10",
24+
version: "VersionTLS10",
25+
want: tls.VersionTLS10,
26+
wantErr: false,
27+
},
28+
{
29+
name: "TLS 1.1",
30+
version: "1.1",
31+
want: tls.VersionTLS11,
32+
wantErr: false,
33+
},
34+
{
35+
name: "VersionTLS11",
36+
version: "VersionTLS11",
37+
want: tls.VersionTLS11,
38+
wantErr: false,
39+
},
40+
{
41+
name: "TLS 1.2",
42+
version: "1.2",
43+
want: tls.VersionTLS12,
44+
wantErr: false,
45+
},
46+
{
47+
name: "VersionTLS12",
48+
version: "VersionTLS12",
49+
want: tls.VersionTLS12,
50+
wantErr: false,
51+
},
52+
{
53+
name: "TLS 1.3",
54+
version: "1.3",
55+
want: tls.VersionTLS13,
56+
wantErr: false,
57+
},
58+
{
59+
name: "VersionTLS13",
60+
version: "VersionTLS13",
61+
want: tls.VersionTLS13,
62+
wantErr: false,
63+
},
64+
{
65+
name: "Invalid version",
66+
version: "1.4",
67+
want: 0,
68+
wantErr: true,
69+
},
70+
{
71+
name: "Empty version",
72+
version: "",
73+
want: 0,
74+
wantErr: true,
75+
},
76+
}
77+
for _, tt := range tests {
78+
t.Run(tt.name, func(t *testing.T) {
79+
got, err := parseTLSVersion(tt.version)
80+
if (err != nil) != tt.wantErr {
81+
t.Errorf("parseTLSVersion() error = %v, wantErr %v", err, tt.wantErr)
82+
return
83+
}
84+
if got != tt.want {
85+
t.Errorf("parseTLSVersion() = %v, want %v", got, tt.want)
86+
}
87+
})
88+
}
89+
}
90+
91+
func TestParseTLSCipherSuites(t *testing.T) {
92+
tests := []struct {
93+
name string
94+
cipherSuites string
95+
want []uint16
96+
wantErr bool
97+
}{
98+
{
99+
name: "Empty cipher suites",
100+
cipherSuites: "",
101+
want: nil,
102+
wantErr: false,
103+
},
104+
{
105+
name: "Valid cipher suite",
106+
cipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
107+
want: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
108+
wantErr: false,
109+
},
110+
{
111+
name: "Multiple valid cipher suites",
112+
cipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
113+
want: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
114+
wantErr: false,
115+
},
116+
{
117+
name: "Valid cipher suites with spaces",
118+
cipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
119+
want: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
120+
wantErr: false,
121+
},
122+
{
123+
name: "Invalid cipher suite",
124+
cipherSuites: "INVALID_CIPHER",
125+
want: nil,
126+
wantErr: true,
127+
},
128+
{
129+
name: "Mixed valid and invalid cipher suites",
130+
cipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,INVALID_CIPHER",
131+
want: nil,
132+
wantErr: true,
133+
},
134+
}
135+
for _, tt := range tests {
136+
t.Run(tt.name, func(t *testing.T) {
137+
got, err := parseTLSCipherSuites(tt.cipherSuites)
138+
if (err != nil) != tt.wantErr {
139+
t.Errorf("parseTLSCipherSuites() error = %v, wantErr %v", err, tt.wantErr)
140+
return
141+
}
142+
if !reflect.DeepEqual(got, tt.want) {
143+
t.Errorf("parseTLSCipherSuites() = %v, want %v", got, tt.want)
144+
}
145+
})
146+
}
147+
}

0 commit comments

Comments
 (0)