knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/network/tls/config.go (about) 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 }