k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/certs/renewal/readwriter.go (about)

     1  /*
     2  Copyright 2019 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 renewal
    18  
    19  import (
    20  	"crypto"
    21  	"crypto/x509"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/pkg/errors"
    26  
    27  	"k8s.io/client-go/tools/clientcmd"
    28  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    29  	certutil "k8s.io/client-go/util/cert"
    30  	"k8s.io/client-go/util/keyutil"
    31  
    32  	"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
    33  )
    34  
    35  // certificateReadWriter defines the behavior of a component that
    36  // read or write a certificate stored/embedded in a file
    37  type certificateReadWriter interface {
    38  	//Exists return true if the certificate exists
    39  	Exists() (bool, error)
    40  
    41  	// Read a certificate stored/embedded in a file
    42  	Read() (*x509.Certificate, error)
    43  
    44  	// Write (update) a certificate stored/embedded in a file
    45  	Write(*x509.Certificate, crypto.Signer) error
    46  }
    47  
    48  // pkiCertificateReadWriter defines a certificateReadWriter for certificate files
    49  // in the K8s pki managed by kubeadm
    50  type pkiCertificateReadWriter struct {
    51  	baseName       string
    52  	certificateDir string
    53  }
    54  
    55  // newPKICertificateReadWriter return a new pkiCertificateReadWriter
    56  func newPKICertificateReadWriter(certificateDir string, baseName string) *pkiCertificateReadWriter {
    57  	return &pkiCertificateReadWriter{
    58  		baseName:       baseName,
    59  		certificateDir: certificateDir,
    60  	}
    61  }
    62  
    63  // Exists checks if a certificate exist
    64  func (rw *pkiCertificateReadWriter) Exists() (bool, error) {
    65  	certificatePath, _ := pkiutil.PathsForCertAndKey(rw.certificateDir, rw.baseName)
    66  	return fileExists(certificatePath)
    67  }
    68  
    69  func fileExists(filename string) (bool, error) {
    70  	info, err := os.Stat(filename)
    71  	if err != nil {
    72  		if os.IsNotExist(err) {
    73  			return false, nil
    74  		}
    75  		return false, err
    76  	}
    77  	return !info.IsDir(), nil
    78  }
    79  
    80  // Read a certificate from a file the K8s pki managed by kubeadm
    81  func (rw *pkiCertificateReadWriter) Read() (*x509.Certificate, error) {
    82  	certificatePath, _ := pkiutil.PathsForCertAndKey(rw.certificateDir, rw.baseName)
    83  	certs, err := certutil.CertsFromFile(certificatePath)
    84  	if err != nil {
    85  		return nil, errors.Wrapf(err, "failed to load existing certificate %s", rw.baseName)
    86  	}
    87  
    88  	// Safely pick the first one because the sender's certificate must come first in the list.
    89  	// For details, see: https://www.rfc-editor.org/rfc/rfc4346#section-7.4.2
    90  	return certs[0], nil
    91  }
    92  
    93  // Write a certificate to files in the K8s pki managed by kubeadm
    94  func (rw *pkiCertificateReadWriter) Write(newCert *x509.Certificate, newKey crypto.Signer) error {
    95  	if err := pkiutil.WriteCertAndKey(rw.certificateDir, rw.baseName, newCert, newKey); err != nil {
    96  		return errors.Wrapf(err, "failed to write new certificate %s", rw.baseName)
    97  	}
    98  	return nil
    99  }
   100  
   101  // kubeConfigReadWriter defines a certificateReadWriter for certificate files
   102  // embedded in the kubeConfig files managed by kubeadm, and more specifically
   103  // for the client certificate of the AuthInfo
   104  type kubeConfigReadWriter struct {
   105  	kubernetesDir      string
   106  	kubeConfigFileName string
   107  	kubeConfigFilePath string
   108  	kubeConfig         *clientcmdapi.Config
   109  	baseName           string
   110  	certificateDir     string
   111  	caCert             *x509.Certificate
   112  }
   113  
   114  // newKubeconfigReadWriter return a new kubeConfigReadWriter
   115  func newKubeconfigReadWriter(kubernetesDir string, kubeConfigFileName string, certificateDir, baseName string) *kubeConfigReadWriter {
   116  	return &kubeConfigReadWriter{
   117  		kubernetesDir:      kubernetesDir,
   118  		kubeConfigFileName: kubeConfigFileName,
   119  		kubeConfigFilePath: filepath.Join(kubernetesDir, kubeConfigFileName),
   120  		certificateDir:     certificateDir,
   121  		baseName:           baseName,
   122  	}
   123  }
   124  
   125  // Exists checks if a certificate embedded in kubeConfig file exists
   126  func (rw *kubeConfigReadWriter) Exists() (bool, error) {
   127  	return fileExists(rw.kubeConfigFilePath)
   128  }
   129  
   130  // Read a certificate embedded in kubeConfig file managed by kubeadm.
   131  // Please note that the kubeConfig file itself is kept in the ReadWriter state thus allowing
   132  // to preserve the attributes (Context, Servers, AuthInfo etc.)
   133  func (rw *kubeConfigReadWriter) Read() (*x509.Certificate, error) {
   134  	// try to load the kubeConfig file
   135  	kubeConfig, err := clientcmd.LoadFromFile(rw.kubeConfigFilePath)
   136  	if err != nil {
   137  		return nil, errors.Wrapf(err, "failed to load kubeConfig file %s", rw.kubeConfigFilePath)
   138  	}
   139  
   140  	// The CA cert is required for updating kubeconfig files.
   141  	// For local CA renewal, the local CA on disk could have changed, thus a reload is needed.
   142  	// For CSR renewal we assume the same CA on disk is mounted for usage with KCM's
   143  	// '--cluster-signing-cert-file' flag.
   144  	certificatePath, _ := pkiutil.PathsForCertAndKey(rw.certificateDir, rw.baseName)
   145  	caCerts, err := certutil.CertsFromFile(certificatePath)
   146  	if err != nil {
   147  		return nil, errors.Wrapf(err, "failed to load existing certificate %s", rw.baseName)
   148  	}
   149  
   150  	// Safely pick the first one because the sender's certificate must come first in the list.
   151  	// For details, see: https://www.rfc-editor.org/rfc/rfc4346#section-7.4.2
   152  	rw.caCert = caCerts[0]
   153  
   154  	// get current context
   155  	if _, ok := kubeConfig.Contexts[kubeConfig.CurrentContext]; !ok {
   156  		return nil, errors.Errorf("invalid kubeConfig file %s: missing context %s", rw.kubeConfigFilePath, kubeConfig.CurrentContext)
   157  	}
   158  
   159  	// get cluster info for current context and ensure a server certificate is embedded in it
   160  	clusterName := kubeConfig.Contexts[kubeConfig.CurrentContext].Cluster
   161  	if _, ok := kubeConfig.Clusters[clusterName]; !ok {
   162  		return nil, errors.Errorf("invalid kubeConfig file %s: missing cluster %s", rw.kubeConfigFilePath, clusterName)
   163  	}
   164  
   165  	cluster := kubeConfig.Clusters[clusterName]
   166  	if len(cluster.CertificateAuthorityData) == 0 {
   167  		return nil, errors.Errorf("kubeConfig file %s does not have an embedded server certificate", rw.kubeConfigFilePath)
   168  	}
   169  
   170  	// get auth info for current context and ensure a client certificate is embedded in it
   171  	authInfoName := kubeConfig.Contexts[kubeConfig.CurrentContext].AuthInfo
   172  	if _, ok := kubeConfig.AuthInfos[authInfoName]; !ok {
   173  		return nil, errors.Errorf("invalid kubeConfig file %s: missing authInfo %s", rw.kubeConfigFilePath, authInfoName)
   174  	}
   175  
   176  	authInfo := kubeConfig.AuthInfos[authInfoName]
   177  	if len(authInfo.ClientCertificateData) == 0 {
   178  		return nil, errors.Errorf("kubeConfig file %s does not have an embedded client certificate", rw.kubeConfigFilePath)
   179  	}
   180  
   181  	// parse the client certificate, retrieve the cert config and then renew it
   182  	certs, err := certutil.ParseCertsPEM(authInfo.ClientCertificateData)
   183  	if err != nil {
   184  		return nil, errors.Wrapf(err, "kubeConfig file %s does not contain a valid client certificate", rw.kubeConfigFilePath)
   185  	}
   186  
   187  	rw.kubeConfig = kubeConfig
   188  
   189  	return certs[0], nil
   190  }
   191  
   192  // Write a certificate embedded in kubeConfig file managed by kubeadm
   193  // Please note that all the other attribute of the kubeConfig file are preserved, but this
   194  // requires to call Read before Write
   195  func (rw *kubeConfigReadWriter) Write(newCert *x509.Certificate, newKey crypto.Signer) error {
   196  	// check if Read was called before Write
   197  	if rw.kubeConfig == nil {
   198  		return errors.Errorf("failed to Write kubeConfig file with renewed certs. It is necessary to call Read before Write")
   199  	}
   200  
   201  	// encodes the new key
   202  	encodedClientKey, err := keyutil.MarshalPrivateKeyToPEM(newKey)
   203  	if err != nil {
   204  		return errors.Wrapf(err, "failed to marshal private key to PEM")
   205  	}
   206  
   207  	// Update the embedded CA in the kubeconfig file.
   208  	// This assumes that the user has kept the current context to the desired one.
   209  	clusterName := rw.kubeConfig.Contexts[rw.kubeConfig.CurrentContext].Cluster
   210  	cluster := rw.kubeConfig.Clusters[clusterName]
   211  	cluster.CertificateAuthorityData = pkiutil.EncodeCertPEM(rw.caCert)
   212  
   213  	// get auth info for current context and ensure a client certificate is embedded in it
   214  	authInfoName := rw.kubeConfig.Contexts[rw.kubeConfig.CurrentContext].AuthInfo
   215  
   216  	// create a kubeConfig copy with the new client certs
   217  	newConfig := rw.kubeConfig.DeepCopy()
   218  	newConfig.AuthInfos[authInfoName].ClientKeyData = encodedClientKey
   219  	newConfig.AuthInfos[authInfoName].ClientCertificateData = pkiutil.EncodeCertPEM(newCert)
   220  
   221  	// writes the kubeConfig to disk
   222  	return clientcmd.WriteToFile(*newConfig, rw.kubeConfigFilePath)
   223  }