-
Notifications
You must be signed in to change notification settings - Fork 64
/
ptls.go
131 lines (107 loc) · 4.45 KB
/
ptls.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package ptls
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"sync"
"k8s.io/apiserver/pkg/admission"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/transport"
)
// TODO decide if we need to expose the four TLS levels (secure, default, default-ldap, legacy) as config.
// defaultServingOptionsMinTLSVersion is the minimum tls version in the format
// expected by SecureServingOptions.MinTLSVersion from
// k8s.io/apiserver/pkg/server/options.
const defaultServingOptionsMinTLSVersion = "VersionTLS12"
type ConfigFunc func(*x509.CertPool) *tls.Config
func Legacy(rootCAs *x509.CertPool) *tls.Config {
c := Default(rootCAs)
// add all the ciphers (even the crappy ones) except the ones that Go considers to be outright broken like 3DES
c.CipherSuites = suitesToIDs(tls.CipherSuites())
return c
}
func suitesToIDs(suites []*tls.CipherSuite) []uint16 {
out := make([]uint16, 0, len(suites))
for _, suite := range suites {
suite := suite
out = append(out, suite.ID)
}
return out
}
func Merge(tlsConfigFunc ConfigFunc, tlsConfig *tls.Config) {
secureTLSConfig := tlsConfigFunc(nil)
// override the core security knobs of the TLS config
// note that these have to be kept in sync with Default / Secure above
tlsConfig.MinVersion = secureTLSConfig.MinVersion
tlsConfig.CipherSuites = secureTLSConfig.CipherSuites
// if the TLS config already states what protocols it wants to use, honor that instead of overriding
if len(tlsConfig.NextProtos) == 0 {
tlsConfig.NextProtos = secureTLSConfig.NextProtos
}
}
// RestConfigFunc allows this package to not depend on the kubeclient package.
type RestConfigFunc func(*rest.Config) (kubernetes.Interface, *rest.Config, error)
func DefaultRecommendedOptions(opts *options.RecommendedOptions, f RestConfigFunc) error {
defaultServing(opts.SecureServing)
return secureClient(opts, f)
}
func SecureRecommendedOptions(opts *options.RecommendedOptions, f RestConfigFunc) error {
secureServing(opts.SecureServing)
return secureClient(opts, f)
}
func defaultServing(opts *options.SecureServingOptionsWithLoopback) {
c := Default(nil)
cipherSuites := make([]string, 0, len(c.CipherSuites))
for _, id := range c.CipherSuites {
cipherSuites = append(cipherSuites, tls.CipherSuiteName(id))
}
opts.CipherSuites = cipherSuites
opts.MinTLSVersion = defaultServingOptionsMinTLSVersion
}
func secureClient(opts *options.RecommendedOptions, f RestConfigFunc) error {
inClusterClient, inClusterConfig, err := f(nil)
if err != nil {
return fmt.Errorf("failed to build in cluster client: %w", err)
}
if n, z := opts.Authentication.RemoteKubeConfigFile, opts.Authorization.RemoteKubeConfigFile; len(n) > 0 || len(z) > 0 {
return fmt.Errorf("delgating auth is not using in-cluster config:\nauthentication=%s\nauthorization=%s", n, z)
}
// delegated authn and authz provide easy hooks for us to set the TLS config.
// however, the underlying clients use client-go's global TLS cache with an
// in-cluster config. to make this safe, we simply do the mutation once.
wrapperFunc := wrapTransportOnce(inClusterConfig.WrapTransport)
opts.Authentication.CustomRoundTripperFn = wrapperFunc
opts.Authorization.CustomRoundTripperFn = wrapperFunc
opts.CoreAPI = nil // set this to nil to make sure our ExtraAdmissionInitializers is used
baseExtraAdmissionInitializers := opts.ExtraAdmissionInitializers
opts.ExtraAdmissionInitializers = func(c *genericapiserver.RecommendedConfig) ([]admission.PluginInitializer, error) {
// abuse this closure to rewrite how we load admission plugins
c.ClientConfig = inClusterConfig
c.SharedInformerFactory = kubeinformers.NewSharedInformerFactory(inClusterClient, 0)
// abuse this closure to rewrite our loopback config
// this is mostly future proofing for post start hooks
_, loopbackConfig, err := f(c.LoopbackClientConfig)
if err != nil {
return nil, fmt.Errorf("failed to build loopback config: %w", err)
}
c.LoopbackClientConfig = loopbackConfig
return baseExtraAdmissionInitializers(c)
}
return nil
}
func wrapTransportOnce(f transport.WrapperFunc) transport.WrapperFunc {
var once sync.Once
return func(rt http.RoundTripper) http.RoundTripper {
once.Do(func() {
_ = f(rt) // assume in-place mutation
})
return rt
}
}