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  })