k8s.io/kubernetes@v1.29.3/pkg/kubelet/certificate/bootstrap/bootstrap.go (about)

     1  /*
     2  Copyright 2016 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 bootstrap
    18  
    19  import (
    20  	"context"
    21  	"crypto"
    22  	"crypto/rsa"
    23  	"crypto/sha512"
    24  	"crypto/x509"
    25  	"crypto/x509/pkix"
    26  	"encoding/base64"
    27  	"errors"
    28  	"fmt"
    29  	"os"
    30  	"path/filepath"
    31  	"time"
    32  
    33  	"k8s.io/klog/v2"
    34  
    35  	certificatesv1 "k8s.io/api/certificates/v1"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    38  	"k8s.io/apimachinery/pkg/util/wait"
    39  	clientset "k8s.io/client-go/kubernetes"
    40  	"k8s.io/client-go/kubernetes/scheme"
    41  	restclient "k8s.io/client-go/rest"
    42  	"k8s.io/client-go/tools/clientcmd"
    43  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    44  	"k8s.io/client-go/transport"
    45  	certutil "k8s.io/client-go/util/cert"
    46  	"k8s.io/client-go/util/certificate"
    47  	"k8s.io/client-go/util/certificate/csr"
    48  	"k8s.io/client-go/util/keyutil"
    49  )
    50  
    51  const tmpPrivateKeyFile = "kubelet-client.key.tmp"
    52  
    53  // LoadClientConfig tries to load the appropriate client config for retrieving certs and for use by users.
    54  // If bootstrapPath is empty, only kubeconfigPath is checked. If bootstrap path is set and the contents
    55  // of kubeconfigPath are valid, both certConfig and userConfig will point to that file. Otherwise the
    56  // kubeconfigPath on disk is populated based on bootstrapPath but pointing to the location of the client cert
    57  // in certDir. This preserves the historical behavior of bootstrapping where on subsequent restarts the
    58  // most recent client cert is used to request new client certs instead of the initial token.
    59  func LoadClientConfig(kubeconfigPath, bootstrapPath, certDir string) (certConfig, userConfig *restclient.Config, err error) {
    60  	if len(bootstrapPath) == 0 {
    61  		clientConfig, err := loadRESTClientConfig(kubeconfigPath)
    62  		if err != nil {
    63  			return nil, nil, fmt.Errorf("unable to load kubeconfig: %v", err)
    64  		}
    65  		klog.V(2).InfoS("No bootstrapping requested, will use kubeconfig")
    66  		return clientConfig, restclient.CopyConfig(clientConfig), nil
    67  	}
    68  
    69  	store, err := certificate.NewFileStore("kubelet-client", certDir, certDir, "", "")
    70  	if err != nil {
    71  		return nil, nil, fmt.Errorf("unable to build bootstrap cert store")
    72  	}
    73  
    74  	ok, err := isClientConfigStillValid(kubeconfigPath)
    75  	if err != nil {
    76  		return nil, nil, err
    77  	}
    78  
    79  	// use the current client config
    80  	if ok {
    81  		clientConfig, err := loadRESTClientConfig(kubeconfigPath)
    82  		if err != nil {
    83  			return nil, nil, fmt.Errorf("unable to load kubeconfig: %v", err)
    84  		}
    85  		klog.V(2).InfoS("Current kubeconfig file contents are still valid, no bootstrap necessary")
    86  		return clientConfig, restclient.CopyConfig(clientConfig), nil
    87  	}
    88  
    89  	bootstrapClientConfig, err := loadRESTClientConfig(bootstrapPath)
    90  	if err != nil {
    91  		return nil, nil, fmt.Errorf("unable to load bootstrap kubeconfig: %v", err)
    92  	}
    93  
    94  	clientConfig := restclient.AnonymousClientConfig(bootstrapClientConfig)
    95  	pemPath := store.CurrentPath()
    96  	clientConfig.KeyFile = pemPath
    97  	clientConfig.CertFile = pemPath
    98  	if err := writeKubeconfigFromBootstrapping(clientConfig, kubeconfigPath, pemPath); err != nil {
    99  		return nil, nil, err
   100  	}
   101  	klog.V(2).InfoS("Use the bootstrap credentials to request a cert, and set kubeconfig to point to the certificate dir")
   102  	return bootstrapClientConfig, clientConfig, nil
   103  }
   104  
   105  // LoadClientCert requests a client cert for kubelet if the kubeconfigPath file does not exist.
   106  // The kubeconfig at bootstrapPath is used to request a client certificate from the API server.
   107  // On success, a kubeconfig file referencing the generated key and obtained certificate is written to kubeconfigPath.
   108  // The certificate and key file are stored in certDir.
   109  func LoadClientCert(ctx context.Context, kubeconfigPath, bootstrapPath, certDir string, nodeName types.NodeName) error {
   110  	// Short-circuit if the kubeconfig file exists and is valid.
   111  	ok, err := isClientConfigStillValid(kubeconfigPath)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	if ok {
   116  		klog.V(2).InfoS("Kubeconfig exists and is valid, skipping bootstrap", "path", kubeconfigPath)
   117  		return nil
   118  	}
   119  
   120  	klog.V(2).InfoS("Using bootstrap kubeconfig to generate TLS client cert, key and kubeconfig file")
   121  
   122  	bootstrapClientConfig, err := loadRESTClientConfig(bootstrapPath)
   123  	if err != nil {
   124  		return fmt.Errorf("unable to load bootstrap kubeconfig: %v", err)
   125  	}
   126  
   127  	bootstrapClient, err := clientset.NewForConfig(bootstrapClientConfig)
   128  	if err != nil {
   129  		return fmt.Errorf("unable to create certificates signing request client: %v", err)
   130  	}
   131  
   132  	store, err := certificate.NewFileStore("kubelet-client", certDir, certDir, "", "")
   133  	if err != nil {
   134  		return fmt.Errorf("unable to build bootstrap cert store")
   135  	}
   136  
   137  	var keyData []byte
   138  	if cert, err := store.Current(); err == nil {
   139  		if cert.PrivateKey != nil {
   140  			keyData, err = keyutil.MarshalPrivateKeyToPEM(cert.PrivateKey)
   141  			if err != nil {
   142  				keyData = nil
   143  			}
   144  		}
   145  	}
   146  	// Cache the private key in a separate file until CSR succeeds. This has to
   147  	// be a separate file because store.CurrentPath() points to a symlink
   148  	// managed by the store.
   149  	privKeyPath := filepath.Join(certDir, tmpPrivateKeyFile)
   150  	if !verifyKeyData(keyData) {
   151  		klog.V(2).InfoS("No valid private key and/or certificate found, reusing existing private key or creating a new one")
   152  		// Note: always call LoadOrGenerateKeyFile so that private key is
   153  		// reused on next startup if CSR request fails.
   154  		keyData, _, err = keyutil.LoadOrGenerateKeyFile(privKeyPath)
   155  		if err != nil {
   156  			return err
   157  		}
   158  	}
   159  
   160  	if err := waitForServer(ctx, *bootstrapClientConfig, 1*time.Minute); err != nil {
   161  		klog.InfoS("Error waiting for apiserver to come up", "err", err)
   162  	}
   163  
   164  	certData, err := requestNodeCertificate(ctx, bootstrapClient, keyData, nodeName)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	if _, err := store.Update(certData, keyData); err != nil {
   169  		return err
   170  	}
   171  	if err := os.Remove(privKeyPath); err != nil && !os.IsNotExist(err) {
   172  		klog.V(2).InfoS("Failed cleaning up private key file", "path", privKeyPath, "err", err)
   173  	}
   174  
   175  	return writeKubeconfigFromBootstrapping(bootstrapClientConfig, kubeconfigPath, store.CurrentPath())
   176  }
   177  
   178  func writeKubeconfigFromBootstrapping(bootstrapClientConfig *restclient.Config, kubeconfigPath, pemPath string) error {
   179  	// Get the CA data from the bootstrap client config.
   180  	caFile, caData := bootstrapClientConfig.CAFile, []byte{}
   181  	if len(caFile) == 0 {
   182  		caData = bootstrapClientConfig.CAData
   183  	}
   184  
   185  	// Build resulting kubeconfig.
   186  	kubeconfigData := clientcmdapi.Config{
   187  		// Define a cluster stanza based on the bootstrap kubeconfig.
   188  		Clusters: map[string]*clientcmdapi.Cluster{"default-cluster": {
   189  			Server:                   bootstrapClientConfig.Host,
   190  			InsecureSkipTLSVerify:    bootstrapClientConfig.Insecure,
   191  			CertificateAuthority:     caFile,
   192  			CertificateAuthorityData: caData,
   193  		}},
   194  		// Define auth based on the obtained client cert.
   195  		AuthInfos: map[string]*clientcmdapi.AuthInfo{"default-auth": {
   196  			ClientCertificate: pemPath,
   197  			ClientKey:         pemPath,
   198  		}},
   199  		// Define a context that connects the auth info and cluster, and set it as the default
   200  		Contexts: map[string]*clientcmdapi.Context{"default-context": {
   201  			Cluster:   "default-cluster",
   202  			AuthInfo:  "default-auth",
   203  			Namespace: "default",
   204  		}},
   205  		CurrentContext: "default-context",
   206  	}
   207  
   208  	// Marshal to disk
   209  	return clientcmd.WriteToFile(kubeconfigData, kubeconfigPath)
   210  }
   211  
   212  func loadRESTClientConfig(kubeconfig string) (*restclient.Config, error) {
   213  	// Load structured kubeconfig data from the given path.
   214  	loader := &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}
   215  	loadedConfig, err := loader.Load()
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  	// Flatten the loaded data to a particular restclient.Config based on the current context.
   220  	return clientcmd.NewNonInteractiveClientConfig(
   221  		*loadedConfig,
   222  		loadedConfig.CurrentContext,
   223  		&clientcmd.ConfigOverrides{},
   224  		loader,
   225  	).ClientConfig()
   226  }
   227  
   228  // isClientConfigStillValid checks the provided kubeconfig to see if it has a valid
   229  // client certificate. It returns true if the kubeconfig is valid, or an error if bootstrapping
   230  // should stop immediately.
   231  func isClientConfigStillValid(kubeconfigPath string) (bool, error) {
   232  	_, err := os.Stat(kubeconfigPath)
   233  	if os.IsNotExist(err) {
   234  		return false, nil
   235  	}
   236  	if err != nil {
   237  		return false, fmt.Errorf("error reading existing bootstrap kubeconfig %s: %v", kubeconfigPath, err)
   238  	}
   239  	bootstrapClientConfig, err := loadRESTClientConfig(kubeconfigPath)
   240  	if err != nil {
   241  		utilruntime.HandleError(fmt.Errorf("unable to read existing bootstrap client config from %s: %v", kubeconfigPath, err))
   242  		return false, nil
   243  	}
   244  	transportConfig, err := bootstrapClientConfig.TransportConfig()
   245  	if err != nil {
   246  		utilruntime.HandleError(fmt.Errorf("unable to load transport configuration from existing bootstrap client config read from %s: %v", kubeconfigPath, err))
   247  		return false, nil
   248  	}
   249  	// has side effect of populating transport config data fields
   250  	if _, err := transport.TLSConfigFor(transportConfig); err != nil {
   251  		utilruntime.HandleError(fmt.Errorf("unable to load TLS configuration from existing bootstrap client config read from %s: %v", kubeconfigPath, err))
   252  		return false, nil
   253  	}
   254  	certs, err := certutil.ParseCertsPEM(transportConfig.TLS.CertData)
   255  	if err != nil {
   256  		utilruntime.HandleError(fmt.Errorf("unable to load TLS certificates from existing bootstrap client config read from %s: %v", kubeconfigPath, err))
   257  		return false, nil
   258  	}
   259  	if len(certs) == 0 {
   260  		utilruntime.HandleError(fmt.Errorf("unable to read TLS certificates from existing bootstrap client config read from %s: %v", kubeconfigPath, err))
   261  		return false, nil
   262  	}
   263  	now := time.Now()
   264  	for _, cert := range certs {
   265  		if now.After(cert.NotAfter) {
   266  			utilruntime.HandleError(fmt.Errorf("part of the existing bootstrap client certificate in %s is expired: %v", kubeconfigPath, cert.NotAfter))
   267  			return false, nil
   268  		}
   269  	}
   270  	return true, nil
   271  }
   272  
   273  // verifyKeyData returns true if the provided data appears to be a valid private key.
   274  func verifyKeyData(data []byte) bool {
   275  	if len(data) == 0 {
   276  		return false
   277  	}
   278  	_, err := keyutil.ParsePrivateKeyPEM(data)
   279  	return err == nil
   280  }
   281  
   282  func waitForServer(ctx context.Context, cfg restclient.Config, deadline time.Duration) error {
   283  	cfg.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
   284  	cfg.Timeout = 1 * time.Second
   285  	cli, err := restclient.UnversionedRESTClientFor(&cfg)
   286  	if err != nil {
   287  		return fmt.Errorf("couldn't create client: %v", err)
   288  	}
   289  
   290  	ctx, cancel := context.WithTimeout(ctx, deadline)
   291  	defer cancel()
   292  
   293  	var connected bool
   294  	wait.JitterUntil(func() {
   295  		if _, err := cli.Get().AbsPath("/healthz").Do(ctx).Raw(); err != nil {
   296  			klog.InfoS("Failed to connect to apiserver", "err", err)
   297  			return
   298  		}
   299  		cancel()
   300  		connected = true
   301  	}, 2*time.Second, 0.2, true, ctx.Done())
   302  
   303  	if !connected {
   304  		return errors.New("timed out waiting to connect to apiserver")
   305  	}
   306  	return nil
   307  }
   308  
   309  // requestNodeCertificate will create a certificate signing request for a node
   310  // (Organization and CommonName for the CSR will be set as expected for node
   311  // certificates) and send it to API server, then it will watch the object's
   312  // status, once approved by API server, it will return the API server's issued
   313  // certificate (pem-encoded). If there is any errors, or the watch timeouts, it
   314  // will return an error. This is intended for use on nodes (kubelet and
   315  // kubeadm).
   316  func requestNodeCertificate(ctx context.Context, client clientset.Interface, privateKeyData []byte, nodeName types.NodeName) (certData []byte, err error) {
   317  	subject := &pkix.Name{
   318  		Organization: []string{"system:nodes"},
   319  		CommonName:   "system:node:" + string(nodeName),
   320  	}
   321  
   322  	privateKey, err := keyutil.ParsePrivateKeyPEM(privateKeyData)
   323  	if err != nil {
   324  		return nil, fmt.Errorf("invalid private key for certificate request: %v", err)
   325  	}
   326  	csrData, err := certutil.MakeCSR(privateKey, subject, nil, nil)
   327  	if err != nil {
   328  		return nil, fmt.Errorf("unable to generate certificate request: %v", err)
   329  	}
   330  
   331  	usages := []certificatesv1.KeyUsage{
   332  		certificatesv1.UsageDigitalSignature,
   333  		certificatesv1.UsageClientAuth,
   334  	}
   335  	if _, ok := privateKey.(*rsa.PrivateKey); ok {
   336  		usages = append(usages, certificatesv1.UsageKeyEncipherment)
   337  	}
   338  
   339  	// The Signer interface contains the Public() method to get the public key.
   340  	signer, ok := privateKey.(crypto.Signer)
   341  	if !ok {
   342  		return nil, fmt.Errorf("private key does not implement crypto.Signer")
   343  	}
   344  
   345  	name, err := digestedName(signer.Public(), subject, usages)
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  
   350  	reqName, reqUID, err := csr.RequestCertificate(client, csrData, name, certificatesv1.KubeAPIServerClientKubeletSignerName, nil, usages, privateKey)
   351  	if err != nil {
   352  		return nil, err
   353  	}
   354  
   355  	ctx, cancel := context.WithTimeout(ctx, 3600*time.Second)
   356  	defer cancel()
   357  
   358  	klog.V(2).InfoS("Waiting for client certificate to be issued")
   359  	return csr.WaitForCertificate(ctx, client, reqName, reqUID)
   360  }
   361  
   362  // This digest should include all the relevant pieces of the CSR we care about.
   363  // We can't directly hash the serialized CSR because of random padding that we
   364  // regenerate every loop and we include usages which are not contained in the
   365  // CSR. This needs to be kept up to date as we add new fields to the node
   366  // certificates and with ensureCompatible.
   367  func digestedName(publicKey interface{}, subject *pkix.Name, usages []certificatesv1.KeyUsage) (string, error) {
   368  	hash := sha512.New512_256()
   369  
   370  	// Here we make sure two different inputs can't write the same stream
   371  	// to the hash. This delimiter is not in the base64.URLEncoding
   372  	// alphabet so there is no way to have spill over collisions. Without
   373  	// it 'CN:foo,ORG:bar' hashes to the same value as 'CN:foob,ORG:ar'
   374  	const delimiter = '|'
   375  	encode := base64.RawURLEncoding.EncodeToString
   376  
   377  	write := func(data []byte) {
   378  		hash.Write([]byte(encode(data)))
   379  		hash.Write([]byte{delimiter})
   380  	}
   381  
   382  	publicKeyData, err := x509.MarshalPKIXPublicKey(publicKey)
   383  	if err != nil {
   384  		return "", err
   385  	}
   386  	write(publicKeyData)
   387  
   388  	write([]byte(subject.CommonName))
   389  	for _, v := range subject.Organization {
   390  		write([]byte(v))
   391  	}
   392  	for _, v := range usages {
   393  		write([]byte(v))
   394  	}
   395  
   396  	return fmt.Sprintf("node-csr-%s", encode(hash.Sum(nil))), nil
   397  }