Skip to content

Commit 18c5d58

Browse files
Fedosinlinkvtdprotaso
authored
[release-1.21] Add network/tls package for TLS configuration (#3337)
* feat: add shared tls package for reading TLS config from environment (#3324) * feat: add shared tls package for reading TLS config from environment Extract TLS configuration parsing into a reusable knative.dev/pkg/tls package so that any Knative component (not just webhooks) can read TLS_MIN_VERSION, TLS_MAX_VERSION, TLS_CIPHER_SUITES, and TLS_CURVE_PREFERENCES from environment variables with an optional prefix. The webhook package is updated to use the new tls package, extending env var support from just WEBHOOK_TLS_MIN_VERSION to all four WEBHOOK_TLS_* variables. Programmatic Options values continue to take precedence over environment variables. Signed-off-by: Mikhail Fedosin <mfedosin@redhat.com> * fix: address review feedback on tls package Reduce the public API surface of the tls package by unexporting ParseVersion, ParseCipherSuites, and ParseCurvePreferences since they are implementation details of NewConfigFromEnv. Also validate that TLS max version is not smaller than min version in webhook.New(), document the Options TLS field precedence (programmatic > env vars > defaults), and broaden TestConfig_TLSConfig to exercise the full NewConfigFromEnv → TLSConfig path. Signed-off-by: Mikhail Fedosin <mfedosin@redhat.com> --------- Signed-off-by: Mikhail Fedosin <mfedosin@redhat.com> * Replace NewConfigFromEnv with DefaultConfigFromEnv (#3328) DefaultConfigFromEnv replaces NewConfigFromEnv by returning a full default tls.Config with overrides from env vars. This avoids specifying e.g. the TLS MinVersion explicitely. * Move tls package to network/tls, keep aliases for backward compatibility (#3331) The TLS configuration package is moved from tls/ to network/tls/ to co-locate it with the rest of the networking code. The old tls/ package now re-exports all public symbols as deprecated aliases so that existing consumers continue to compile without changes. The webhook package is updated to import from the new location directly. Signed-off-by: Mikhail Fedosin <mfedosin@redhat.com> * remove deprecated TLS package (#3333) --------- Signed-off-by: Mikhail Fedosin <mfedosin@redhat.com> Co-authored-by: Vincent Link <linkvt@users.noreply.github.com> Co-authored-by: Dave Protasowski <dprotaso@gmail.com>
1 parent 4a022ed commit 18c5d58

6 files changed

Lines changed: 760 additions & 43 deletions

File tree

network/depcheck_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ func TestNoDeps(t *testing.T) {
2626
depcheck.AssertNoDependency(t, map[string][]string{
2727
"knative.dev/pkg/network": depcheck.KnownHeavyDependencies,
2828
"knative.dev/pkg/network/handlers": depcheck.KnownHeavyDependencies,
29+
"knative.dev/pkg/network/tls": depcheck.KnownHeavyDependencies,
2930
})
3031
}

network/tls/config.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
Copyright 2026 The Knative Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package tls
18+
19+
import (
20+
cryptotls "crypto/tls"
21+
"fmt"
22+
"os"
23+
"strings"
24+
)
25+
26+
// Environment variable name suffixes for TLS configuration.
27+
// Use with a prefix to namespace them, e.g. "WEBHOOK_" + MinVersionEnvKey
28+
// reads the WEBHOOK_TLS_MIN_VERSION variable.
29+
const (
30+
MinVersionEnvKey = "TLS_MIN_VERSION"
31+
MaxVersionEnvKey = "TLS_MAX_VERSION"
32+
CipherSuitesEnvKey = "TLS_CIPHER_SUITES"
33+
CurvePreferencesEnvKey = "TLS_CURVE_PREFERENCES"
34+
)
35+
36+
// DefaultConfigFromEnv returns a tls.Config with secure defaults.
37+
// The prefix is prepended to each standard env-var suffix;
38+
// for example with prefix "WEBHOOK_" the function reads
39+
// WEBHOOK_TLS_MIN_VERSION, WEBHOOK_TLS_MAX_VERSION, etc.
40+
func DefaultConfigFromEnv(prefix string) (*cryptotls.Config, error) {
41+
cfg := &cryptotls.Config{
42+
MinVersion: cryptotls.VersionTLS13,
43+
}
44+
45+
if v := os.Getenv(prefix + MinVersionEnvKey); v != "" {
46+
ver, err := parseVersion(v)
47+
if err != nil {
48+
return nil, fmt.Errorf("invalid %s%s %q: %w", prefix, MinVersionEnvKey, v, err)
49+
}
50+
cfg.MinVersion = ver
51+
}
52+
53+
if v := os.Getenv(prefix + MaxVersionEnvKey); v != "" {
54+
ver, err := parseVersion(v)
55+
if err != nil {
56+
return nil, fmt.Errorf("invalid %s%s %q: %w", prefix, MaxVersionEnvKey, v, err)
57+
}
58+
cfg.MaxVersion = ver
59+
}
60+
61+
if v := os.Getenv(prefix + CipherSuitesEnvKey); v != "" {
62+
suites, err := parseCipherSuites(v)
63+
if err != nil {
64+
return nil, fmt.Errorf("invalid %s%s: %w", prefix, CipherSuitesEnvKey, err)
65+
}
66+
cfg.CipherSuites = suites
67+
}
68+
69+
if v := os.Getenv(prefix + CurvePreferencesEnvKey); v != "" {
70+
curves, err := parseCurvePreferences(v)
71+
if err != nil {
72+
return nil, fmt.Errorf("invalid %s%s: %w", prefix, CurvePreferencesEnvKey, err)
73+
}
74+
cfg.CurvePreferences = curves
75+
}
76+
77+
return cfg, nil
78+
}
79+
80+
// parseVersion converts a TLS version string to the corresponding
81+
// crypto/tls constant. Accepted values are "1.2" and "1.3".
82+
func parseVersion(v string) (uint16, error) {
83+
switch v {
84+
case "1.2":
85+
return cryptotls.VersionTLS12, nil
86+
case "1.3":
87+
return cryptotls.VersionTLS13, nil
88+
default:
89+
return 0, fmt.Errorf("unsupported TLS version %q: must be %q or %q", v, "1.2", "1.3")
90+
}
91+
}
92+
93+
// parseCipherSuites parses a comma-separated list of TLS cipher-suite names
94+
// (e.g. "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
95+
// into a slice of cipher-suite IDs. Names must match those returned by
96+
// crypto/tls.CipherSuiteName.
97+
func parseCipherSuites(s string) ([]uint16, error) {
98+
lookup := cipherSuiteLookup()
99+
parts := strings.Split(s, ",")
100+
suites := make([]uint16, 0, len(parts))
101+
102+
for _, name := range parts {
103+
name = strings.TrimSpace(name)
104+
if name == "" {
105+
continue
106+
}
107+
id, ok := lookup[name]
108+
if !ok {
109+
return nil, fmt.Errorf("unknown cipher suite %q", name)
110+
}
111+
suites = append(suites, id)
112+
}
113+
114+
return suites, nil
115+
}
116+
117+
// parseCurvePreferences parses a comma-separated list of elliptic-curve names
118+
// (e.g. "X25519,CurveP256") into a slice of crypto/tls.CurveID values.
119+
// Both Go constant names (CurveP256) and standard names (P-256) are accepted.
120+
func parseCurvePreferences(s string) ([]cryptotls.CurveID, error) {
121+
parts := strings.Split(s, ",")
122+
curves := make([]cryptotls.CurveID, 0, len(parts))
123+
124+
for _, name := range parts {
125+
name = strings.TrimSpace(name)
126+
if name == "" {
127+
continue
128+
}
129+
id, ok := curvesByName[name]
130+
if !ok {
131+
return nil, fmt.Errorf("unknown curve %q", name)
132+
}
133+
curves = append(curves, id)
134+
}
135+
136+
return curves, nil
137+
}
138+
139+
func cipherSuiteLookup() map[string]uint16 {
140+
m := make(map[string]uint16)
141+
for _, cs := range cryptotls.CipherSuites() {
142+
m[cs.Name] = cs.ID
143+
}
144+
return m
145+
}
146+
147+
var curvesByName = map[string]cryptotls.CurveID{
148+
"CurveP256": cryptotls.CurveP256,
149+
"CurveP384": cryptotls.CurveP384,
150+
"CurveP521": cryptotls.CurveP521,
151+
"X25519": cryptotls.X25519,
152+
"X25519MLKEM768": cryptotls.X25519MLKEM768,
153+
"P-256": cryptotls.CurveP256,
154+
"P-384": cryptotls.CurveP384,
155+
"P-521": cryptotls.CurveP521,
156+
}

0 commit comments

Comments
 (0)