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 }