|
| 1 | +// Package tlsconfig converts OpenShift TLS security profiles |
| 2 | +// (configv1.TLSSecurityProfile) into Go crypto/tls configuration. |
| 3 | +package tlsconfig |
| 4 | + |
| 5 | +import ( |
| 6 | + "context" |
| 7 | + "crypto/tls" |
| 8 | + "fmt" |
| 9 | + "strings" |
| 10 | + |
| 11 | + configv1 "github.com/openshift/api/config/v1" |
| 12 | + "sigs.k8s.io/controller-runtime/pkg/client" |
| 13 | + logf "sigs.k8s.io/controller-runtime/pkg/log" |
| 14 | +) |
| 15 | + |
| 16 | +var log = logf.Log.WithName("tlsconfig") |
| 17 | + |
| 18 | +// TODO: Once openshift/api is bumped to a version that includes the Groups |
| 19 | +// field on TLSProfileSpec (added upstream for OCP 4.20), wire up |
| 20 | +// tls.Config.CurvePreferences from the profile's Groups to support |
| 21 | +// post-quantum key agreement (e.g. X25519MLKEM768). |
| 22 | + |
| 23 | +// openSSLToGoCipherSuiteID maps OpenSSL cipher suite names used by OpenShift |
| 24 | +// TLS profiles to Go crypto/tls cipher suite IDs. |
| 25 | +// |
| 26 | +// TLS 1.3 ciphers (TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, |
| 27 | +// TLS_CHACHA20_POLY1305_SHA256) are not included because Go's crypto/tls |
| 28 | +// always enables them when TLS 1.3 is negotiated — the CipherSuites field |
| 29 | +// on tls.Config only controls TLS 1.2 and below. |
| 30 | +// |
| 31 | +// Ciphers not supported by Go (e.g., DHE-RSA-*) are omitted from this map |
| 32 | +// and logged as warnings at startup. |
| 33 | +var openSSLToGoCipherSuiteID = map[string]uint16{ |
| 34 | + "ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, |
| 35 | + "ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, |
| 36 | + "ECDHE-ECDSA-AES256-GCM-SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, |
| 37 | + "ECDHE-RSA-AES256-GCM-SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, |
| 38 | + "ECDHE-ECDSA-CHACHA20-POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, |
| 39 | + "ECDHE-RSA-CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, |
| 40 | + "ECDHE-ECDSA-AES128-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, |
| 41 | + "ECDHE-RSA-AES128-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, |
| 42 | + "ECDHE-ECDSA-AES128-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, |
| 43 | + "ECDHE-RSA-AES128-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, |
| 44 | + "ECDHE-ECDSA-AES256-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, |
| 45 | + "ECDHE-RSA-AES256-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, |
| 46 | + "AES128-GCM-SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256, |
| 47 | + "AES256-GCM-SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, |
| 48 | + "AES128-SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256, |
| 49 | + "AES128-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, |
| 50 | + "AES256-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, |
| 51 | + "DES-CBC3-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, |
| 52 | +} |
| 53 | + |
| 54 | +// tlsVersionMap maps OpenShift TLS protocol version constants to Go crypto/tls version constants. |
| 55 | +var tlsVersionMap = map[configv1.TLSProtocolVersion]uint16{ |
| 56 | + configv1.VersionTLS10: tls.VersionTLS10, |
| 57 | + configv1.VersionTLS11: tls.VersionTLS11, |
| 58 | + configv1.VersionTLS12: tls.VersionTLS12, |
| 59 | + configv1.VersionTLS13: tls.VersionTLS13, |
| 60 | +} |
| 61 | + |
| 62 | +// EffectiveProfile resolves a TLS security profile to a concrete TLSProfileSpec. |
| 63 | +// Returns the Intermediate profile when the input is nil or references an unknown type. |
| 64 | +func EffectiveProfile(profile *configv1.TLSSecurityProfile) *configv1.TLSProfileSpec { |
| 65 | + if profile == nil { |
| 66 | + return configv1.TLSProfiles[configv1.TLSProfileIntermediateType] |
| 67 | + } |
| 68 | + if profile.Type == configv1.TLSProfileCustomType && profile.Custom != nil { |
| 69 | + return &profile.Custom.TLSProfileSpec |
| 70 | + } |
| 71 | + if spec, ok := configv1.TLSProfiles[profile.Type]; ok { |
| 72 | + return spec |
| 73 | + } |
| 74 | + return configv1.TLSProfiles[configv1.TLSProfileIntermediateType] |
| 75 | +} |
| 76 | + |
| 77 | +// FromProfile returns a function that configures a tls.Config based on an |
| 78 | +// OpenShift TLS security profile. Cipher suites not supported by Go's |
| 79 | +// crypto/tls are skipped with a warning log. |
| 80 | +func FromProfile(profile *configv1.TLSSecurityProfile) func(*tls.Config) { |
| 81 | + spec := EffectiveProfile(profile) |
| 82 | + |
| 83 | + minVersion := uint16(tls.VersionTLS12) |
| 84 | + if v, ok := tlsVersionMap[spec.MinTLSVersion]; ok { |
| 85 | + minVersion = v |
| 86 | + } |
| 87 | + |
| 88 | + var cipherSuites []uint16 |
| 89 | + for _, cipher := range spec.Ciphers { |
| 90 | + if id, ok := openSSLToGoCipherSuiteID[cipher]; ok { |
| 91 | + cipherSuites = append(cipherSuites, id) |
| 92 | + } else if !strings.HasPrefix(cipher, "TLS_") { |
| 93 | + // TLS 1.3 ciphers (TLS_* prefix) are always enabled by Go and |
| 94 | + // don't need to be in CipherSuites. Anything else is genuinely |
| 95 | + // unsupported by Go's crypto/tls. |
| 96 | + log.Info("Skipping cipher suite not supported by Go's crypto/tls", "cipher", cipher) |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + return func(cfg *tls.Config) { |
| 101 | + cfg.MinVersion = minVersion |
| 102 | + if len(cipherSuites) > 0 { |
| 103 | + cfg.CipherSuites = cipherSuites |
| 104 | + } |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +// FetchAPIServerProfile reads the TLS security profile from the cluster's |
| 109 | +// ApiServer configuration (apiserver.config.openshift.io/cluster). |
| 110 | +func FetchAPIServerProfile(ctx context.Context, reader client.Reader) (*configv1.TLSSecurityProfile, error) { |
| 111 | + apiServer := &configv1.APIServer{} |
| 112 | + if err := reader.Get(ctx, client.ObjectKey{Name: "cluster"}, apiServer); err != nil { |
| 113 | + return nil, fmt.Errorf("getting apiserver config: %w", err) |
| 114 | + } |
| 115 | + return apiServer.Spec.TLSSecurityProfile, nil |
| 116 | +} |
0 commit comments