k8s.io/apiserver@v0.31.1/pkg/authentication/request/x509/x509.go (about) 1 /* 2 Copyright 2014 The Kubernetes 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 x509 18 19 import ( 20 "crypto/x509" 21 "crypto/x509/pkix" 22 "encoding/hex" 23 "fmt" 24 "net/http" 25 "strings" 26 "time" 27 28 utilerrors "k8s.io/apimachinery/pkg/util/errors" 29 "k8s.io/apimachinery/pkg/util/sets" 30 "k8s.io/apiserver/pkg/authentication/authenticator" 31 "k8s.io/apiserver/pkg/authentication/user" 32 "k8s.io/component-base/metrics" 33 "k8s.io/component-base/metrics/legacyregistry" 34 ) 35 36 /* 37 * By default, the following metric is defined as falling under 38 * ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes) 39 * 40 * Promoting the stability level of the metric is a responsibility of the component owner, since it 41 * involves explicitly acknowledging support for the metric across multiple releases, in accordance with 42 * the metric stability policy. 43 */ 44 var clientCertificateExpirationHistogram = metrics.NewHistogram( 45 &metrics.HistogramOpts{ 46 Namespace: "apiserver", 47 Subsystem: "client", 48 Name: "certificate_expiration_seconds", 49 Help: "Distribution of the remaining lifetime on the certificate used to authenticate a request.", 50 Buckets: []float64{ 51 0, 52 1800, // 30 minutes 53 3600, // 1 hour 54 7200, // 2 hours 55 21600, // 6 hours 56 43200, // 12 hours 57 86400, // 1 day 58 172800, // 2 days 59 345600, // 4 days 60 604800, // 1 week 61 2592000, // 1 month 62 7776000, // 3 months 63 15552000, // 6 months 64 31104000, // 1 year 65 }, 66 StabilityLevel: metrics.ALPHA, 67 }, 68 ) 69 70 func init() { 71 legacyregistry.MustRegister(clientCertificateExpirationHistogram) 72 } 73 74 // UserConversion defines an interface for extracting user info from a client certificate chain 75 type UserConversion interface { 76 User(chain []*x509.Certificate) (*authenticator.Response, bool, error) 77 } 78 79 // UserConversionFunc is a function that implements the UserConversion interface. 80 type UserConversionFunc func(chain []*x509.Certificate) (*authenticator.Response, bool, error) 81 82 // User implements x509.UserConversion 83 func (f UserConversionFunc) User(chain []*x509.Certificate) (*authenticator.Response, bool, error) { 84 return f(chain) 85 } 86 87 func columnSeparatedHex(d []byte) string { 88 h := strings.ToUpper(hex.EncodeToString(d)) 89 var sb strings.Builder 90 for i, r := range h { 91 sb.WriteRune(r) 92 if i%2 == 1 && i != len(h)-1 { 93 sb.WriteRune(':') 94 } 95 } 96 return sb.String() 97 } 98 99 func certificateIdentifier(c *x509.Certificate) string { 100 return fmt.Sprintf( 101 "SN=%d, SKID=%s, AKID=%s", 102 c.SerialNumber, 103 columnSeparatedHex(c.SubjectKeyId), 104 columnSeparatedHex(c.AuthorityKeyId), 105 ) 106 } 107 108 // VerifyOptionFunc is function which provides a shallow copy of the VerifyOptions to the authenticator. This allows 109 // for cases where the options (particularly the CAs) can change. If the bool is false, then the returned VerifyOptions 110 // are ignored and the authenticator will express "no opinion". This allows a clear signal for cases where a CertPool 111 // is eventually expected, but not currently present. 112 type VerifyOptionFunc func() (x509.VerifyOptions, bool) 113 114 // Authenticator implements request.Authenticator by extracting user info from verified client certificates 115 type Authenticator struct { 116 verifyOptionsFn VerifyOptionFunc 117 user UserConversion 118 } 119 120 // New returns a request.Authenticator that verifies client certificates using the provided 121 // VerifyOptions, and converts valid certificate chains into user.Info using the provided UserConversion 122 func New(opts x509.VerifyOptions, user UserConversion) *Authenticator { 123 return NewDynamic(StaticVerifierFn(opts), user) 124 } 125 126 // NewDynamic returns a request.Authenticator that verifies client certificates using the provided 127 // VerifyOptionFunc (which may be dynamic), and converts valid certificate chains into user.Info using the provided UserConversion 128 func NewDynamic(verifyOptionsFn VerifyOptionFunc, user UserConversion) *Authenticator { 129 return &Authenticator{verifyOptionsFn, user} 130 } 131 132 // AuthenticateRequest authenticates the request using presented client certificates 133 func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) { 134 if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 { 135 return nil, false, nil 136 } 137 138 // Use intermediates, if provided 139 optsCopy, ok := a.verifyOptionsFn() 140 // if there are intentionally no verify options, then we cannot authenticate this request 141 if !ok { 142 return nil, false, nil 143 } 144 if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 { 145 optsCopy.Intermediates = x509.NewCertPool() 146 for _, intermediate := range req.TLS.PeerCertificates[1:] { 147 optsCopy.Intermediates.AddCert(intermediate) 148 } 149 } 150 151 /* 152 kubernetes mutual (2-way) x509 between client and apiserver: 153 154 1. apiserver sending its apiserver certificate along with its publickey to client 155 2. client verifies the apiserver certificate sent against its cluster certificate authority data 156 3. client sending its client certificate along with its public key to the apiserver 157 >4. apiserver verifies the client certificate sent against its cluster certificate authority data 158 159 description: 160 here, with this function, 161 client certificate and pub key sent during the handshake process 162 are verified by apiserver against its cluster certificate authority data 163 164 normal args related to this stage: 165 --client-ca-file string If set, any request presenting a client certificate signed by 166 one of the authorities in the client-ca-file is authenticated with an identity 167 corresponding to the CommonName of the client certificate. 168 169 (retrievable from "kube-apiserver --help" command) 170 (suggested by @deads2k) 171 172 see also: 173 - for the step 1, see: staging/src/k8s.io/apiserver/pkg/server/options/serving.go 174 - for the step 2, see: staging/src/k8s.io/client-go/transport/transport.go 175 - for the step 3, see: staging/src/k8s.io/client-go/transport/transport.go 176 */ 177 178 remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now()) 179 clientCertificateExpirationHistogram.WithContext(req.Context()).Observe(remaining.Seconds()) 180 chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy) 181 if err != nil { 182 return nil, false, fmt.Errorf( 183 "verifying certificate %s failed: %w", 184 certificateIdentifier(req.TLS.PeerCertificates[0]), 185 err, 186 ) 187 } 188 189 var errlist []error 190 for _, chain := range chains { 191 user, ok, err := a.user.User(chain) 192 if err != nil { 193 errlist = append(errlist, err) 194 continue 195 } 196 197 if ok { 198 return user, ok, err 199 } 200 } 201 return nil, false, utilerrors.NewAggregate(errlist) 202 } 203 204 // Verifier implements request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth 205 type Verifier struct { 206 verifyOptionsFn VerifyOptionFunc 207 auth authenticator.Request 208 209 // allowedCommonNames contains the common names which a verified certificate is allowed to have. 210 // If empty, all verified certificates are allowed. 211 allowedCommonNames StringSliceProvider 212 } 213 214 // NewVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth 215 func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCommonNames sets.String) authenticator.Request { 216 return NewDynamicCAVerifier(StaticVerifierFn(opts), auth, StaticStringSlice(allowedCommonNames.List())) 217 } 218 219 // NewDynamicCAVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth 220 func NewDynamicCAVerifier(verifyOptionsFn VerifyOptionFunc, auth authenticator.Request, allowedCommonNames StringSliceProvider) authenticator.Request { 221 return &Verifier{verifyOptionsFn, auth, allowedCommonNames} 222 } 223 224 // AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth 225 func (a *Verifier) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) { 226 if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 { 227 return nil, false, nil 228 } 229 230 // Use intermediates, if provided 231 optsCopy, ok := a.verifyOptionsFn() 232 // if there are intentionally no verify options, then we cannot authenticate this request 233 if !ok { 234 return nil, false, nil 235 } 236 if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 { 237 optsCopy.Intermediates = x509.NewCertPool() 238 for _, intermediate := range req.TLS.PeerCertificates[1:] { 239 optsCopy.Intermediates.AddCert(intermediate) 240 } 241 } 242 243 if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil { 244 return nil, false, err 245 } 246 if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil { 247 return nil, false, err 248 } 249 return a.auth.AuthenticateRequest(req) 250 } 251 252 func (a *Verifier) verifySubject(subject pkix.Name) error { 253 // No CN restrictions 254 if len(a.allowedCommonNames.Value()) == 0 { 255 return nil 256 } 257 // Enforce CN restrictions 258 for _, allowedCommonName := range a.allowedCommonNames.Value() { 259 if allowedCommonName == subject.CommonName { 260 return nil 261 } 262 } 263 return fmt.Errorf("x509: subject with cn=%s is not in the allowed list", subject.CommonName) 264 } 265 266 // DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time, 267 // and requires certificates to be valid for client auth (x509.ExtKeyUsageClientAuth) 268 func DefaultVerifyOptions() x509.VerifyOptions { 269 return x509.VerifyOptions{ 270 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, 271 } 272 } 273 274 // CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName 275 var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (*authenticator.Response, bool, error) { 276 if len(chain[0].Subject.CommonName) == 0 { 277 return nil, false, nil 278 } 279 return &authenticator.Response{ 280 User: &user.DefaultInfo{ 281 Name: chain[0].Subject.CommonName, 282 Groups: chain[0].Subject.Organization, 283 }, 284 }, true, nil 285 })