k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/copycerts/copycerts.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 copycerts 18 19 import ( 20 "context" 21 "encoding/hex" 22 "fmt" 23 "os" 24 "path/filepath" 25 "strings" 26 27 "github.com/pkg/errors" 28 29 v1 "k8s.io/api/core/v1" 30 rbac "k8s.io/api/rbac/v1" 31 apierrors "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 clientset "k8s.io/client-go/kubernetes" 35 certutil "k8s.io/client-go/util/cert" 36 "k8s.io/client-go/util/keyutil" 37 bootstraputil "k8s.io/cluster-bootstrap/token/util" 38 "k8s.io/klog/v2" 39 40 bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1" 41 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" 42 kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" 43 nodebootstraptokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" 44 "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" 45 cryptoutil "k8s.io/kubernetes/cmd/kubeadm/app/util/crypto" 46 ) 47 48 const ( 49 externalEtcdCA = "external-etcd-ca.crt" 50 externalEtcdCert = "external-etcd.crt" 51 externalEtcdKey = "external-etcd.key" 52 ) 53 54 // createShortLivedBootstrapToken creates the token used to manager kubeadm-certs 55 // and return the tokenID 56 func createShortLivedBootstrapToken(client clientset.Interface) (string, error) { 57 tokenStr, err := bootstraputil.GenerateBootstrapToken() 58 if err != nil { 59 return "", errors.Wrap(err, "error generating token to upload certs") 60 } 61 token, err := bootstraptokenv1.NewBootstrapTokenString(tokenStr) 62 if err != nil { 63 return "", errors.Wrap(err, "error creating upload certs token") 64 } 65 tokens := []bootstraptokenv1.BootstrapToken{{ 66 Token: token, 67 Description: "Proxy for managing TTL for the kubeadm-certs secret", 68 TTL: &metav1.Duration{ 69 Duration: kubeadmconstants.DefaultCertTokenDuration, 70 }, 71 }} 72 73 if err := nodebootstraptokenphase.CreateNewTokens(client, tokens); err != nil { 74 return "", errors.Wrap(err, "error creating token") 75 } 76 return tokens[0].Token.ID, nil 77 } 78 79 // CreateCertificateKey returns a cryptographically secure random key 80 func CreateCertificateKey() (string, error) { 81 randBytes, err := cryptoutil.CreateRandBytes(kubeadmconstants.CertificateKeySize) 82 if err != nil { 83 return "", err 84 } 85 return hex.EncodeToString(randBytes), nil 86 } 87 88 // UploadCerts save certs needs to join a new control-plane on kubeadm-certs sercret. 89 func UploadCerts(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, key string) error { 90 fmt.Printf("[upload-certs] Storing the certificates in Secret %q in the %q Namespace\n", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem) 91 decodedKey, err := hex.DecodeString(key) 92 if err != nil { 93 return errors.Wrap(err, "error decoding certificate key") 94 } 95 tokenID, err := createShortLivedBootstrapToken(client) 96 if err != nil { 97 return err 98 } 99 100 secretData, err := getDataFromDisk(cfg, decodedKey) 101 if err != nil { 102 return err 103 } 104 ref, err := getSecretOwnerRef(client, tokenID) 105 if err != nil { 106 return err 107 } 108 109 err = apiclient.CreateOrUpdateSecret(client, &v1.Secret{ 110 ObjectMeta: metav1.ObjectMeta{ 111 Name: kubeadmconstants.KubeadmCertsSecret, 112 Namespace: metav1.NamespaceSystem, 113 OwnerReferences: ref, 114 }, 115 Data: secretData, 116 }) 117 if err != nil { 118 return err 119 } 120 121 return createRBAC(client) 122 } 123 124 func createRBAC(client clientset.Interface) error { 125 err := apiclient.CreateOrUpdateRole(client, &rbac.Role{ 126 ObjectMeta: metav1.ObjectMeta{ 127 Name: kubeadmconstants.KubeadmCertsClusterRoleName, 128 Namespace: metav1.NamespaceSystem, 129 }, 130 Rules: []rbac.PolicyRule{ 131 { 132 Verbs: []string{"get"}, 133 APIGroups: []string{""}, 134 Resources: []string{"secrets"}, 135 ResourceNames: []string{kubeadmconstants.KubeadmCertsSecret}, 136 }, 137 }, 138 }) 139 if err != nil { 140 return err 141 } 142 143 return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{ 144 ObjectMeta: metav1.ObjectMeta{ 145 Name: kubeadmconstants.KubeadmCertsClusterRoleName, 146 Namespace: metav1.NamespaceSystem, 147 }, 148 RoleRef: rbac.RoleRef{ 149 APIGroup: rbac.GroupName, 150 Kind: "Role", 151 Name: kubeadmconstants.KubeadmCertsClusterRoleName, 152 }, 153 Subjects: []rbac.Subject{ 154 { 155 Kind: rbac.GroupKind, 156 Name: kubeadmconstants.NodeBootstrapTokenAuthGroup, 157 }, 158 }, 159 }) 160 } 161 162 func getSecretOwnerRef(client clientset.Interface, tokenID string) ([]metav1.OwnerReference, error) { 163 secretName := bootstraputil.BootstrapTokenSecretName(tokenID) 164 secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(context.TODO(), secretName, metav1.GetOptions{}) 165 if err != nil { 166 return nil, errors.Wrap(err, "error to get token reference") 167 } 168 169 gvk := schema.GroupVersionKind{Version: "v1", Kind: "Secret"} 170 ref := metav1.NewControllerRef(secret, gvk) 171 return []metav1.OwnerReference{*ref}, nil 172 } 173 174 func loadAndEncryptCert(certPath string, key []byte) ([]byte, error) { 175 cert, err := os.ReadFile(certPath) 176 if err != nil { 177 return nil, err 178 } 179 return cryptoutil.EncryptBytes(cert, key) 180 } 181 182 func certsToTransfer(cfg *kubeadmapi.InitConfiguration) map[string]string { 183 certsDir := cfg.CertificatesDir 184 certs := map[string]string{ 185 kubeadmconstants.CACertName: filepath.Join(certsDir, kubeadmconstants.CACertName), 186 kubeadmconstants.CAKeyName: filepath.Join(certsDir, kubeadmconstants.CAKeyName), 187 kubeadmconstants.FrontProxyCACertName: filepath.Join(certsDir, kubeadmconstants.FrontProxyCACertName), 188 kubeadmconstants.FrontProxyCAKeyName: filepath.Join(certsDir, kubeadmconstants.FrontProxyCAKeyName), 189 kubeadmconstants.ServiceAccountPublicKeyName: filepath.Join(certsDir, kubeadmconstants.ServiceAccountPublicKeyName), 190 kubeadmconstants.ServiceAccountPrivateKeyName: filepath.Join(certsDir, kubeadmconstants.ServiceAccountPrivateKeyName), 191 } 192 193 if cfg.Etcd.External == nil { 194 certs[kubeadmconstants.EtcdCACertName] = filepath.Join(certsDir, kubeadmconstants.EtcdCACertName) 195 certs[kubeadmconstants.EtcdCAKeyName] = filepath.Join(certsDir, kubeadmconstants.EtcdCAKeyName) 196 } else { 197 certs[externalEtcdCA] = cfg.Etcd.External.CAFile 198 certs[externalEtcdCert] = cfg.Etcd.External.CertFile 199 certs[externalEtcdKey] = cfg.Etcd.External.KeyFile 200 } 201 202 return certs 203 } 204 205 func getDataFromDisk(cfg *kubeadmapi.InitConfiguration, key []byte) (map[string][]byte, error) { 206 secretData := map[string][]byte{} 207 for certName, certPath := range certsToTransfer(cfg) { 208 cert, err := loadAndEncryptCert(certPath, key) 209 if err == nil || os.IsNotExist(err) { 210 secretData[certOrKeyNameToSecretName(certName)] = cert 211 } else { 212 return nil, err 213 } 214 } 215 return secretData, nil 216 } 217 218 // DownloadCerts downloads the certificates needed to join a new control plane. 219 func DownloadCerts(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, key string) error { 220 fmt.Printf("[download-certs] Downloading the certificates in Secret %q in the %q Namespace\n", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem) 221 222 decodedKey, err := hex.DecodeString(key) 223 if err != nil { 224 return errors.Wrap(err, "error decoding certificate key") 225 } 226 227 secret, err := getSecret(client) 228 if err != nil { 229 return errors.Wrap(err, "error downloading the secret") 230 } 231 232 secretData, err := getDataFromSecret(secret, decodedKey) 233 if err != nil { 234 return errors.Wrap(err, "error decoding secret data with provided key") 235 } 236 237 fmt.Printf("[download-certs] Saving the certificates to the folder: %q\n", cfg.CertificatesDir) 238 239 for certOrKeyName, certOrKeyPath := range certsToTransfer(cfg) { 240 certOrKeyData, found := secretData[certOrKeyNameToSecretName(certOrKeyName)] 241 if !found { 242 return errors.Errorf("the Secret does not include the required certificate or key - name: %s, path: %s", certOrKeyName, certOrKeyPath) 243 } 244 if len(certOrKeyData) == 0 { 245 klog.V(1).Infof("[download-certs] Not saving %q to disk, since it is empty in the %q Secret\n", certOrKeyName, kubeadmconstants.KubeadmCertsSecret) 246 continue 247 } 248 if err := writeCertOrKey(certOrKeyPath, certOrKeyData); err != nil { 249 return err 250 } 251 } 252 253 return nil 254 } 255 256 func writeCertOrKey(certOrKeyPath string, certOrKeyData []byte) error { 257 if _, err := keyutil.ParsePrivateKeyPEM(certOrKeyData); err == nil { 258 return keyutil.WriteKey(certOrKeyPath, certOrKeyData) 259 } else if _, err := keyutil.ParsePublicKeysPEM(certOrKeyData); err == nil { 260 return certutil.WriteCert(certOrKeyPath, certOrKeyData) 261 } 262 return errors.New("unknown data found in Secret entry") 263 } 264 265 func getSecret(client clientset.Interface) (*v1.Secret, error) { 266 secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(context.TODO(), kubeadmconstants.KubeadmCertsSecret, metav1.GetOptions{}) 267 if err != nil { 268 if apierrors.IsNotFound(err) { 269 return nil, errors.Errorf("Secret %q was not found in the %q Namespace. This Secret might have expired. Please, run `kubeadm init phase upload-certs --upload-certs` on a control plane to generate a new one", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem) 270 } 271 return nil, err 272 } 273 return secret, nil 274 } 275 276 func getDataFromSecret(secret *v1.Secret, key []byte) (map[string][]byte, error) { 277 secretData := map[string][]byte{} 278 for secretName, encryptedSecret := range secret.Data { 279 // In some cases the secret might have empty data if the secrets were not present on disk 280 // when uploading. This can specially happen with external insecure etcd (no certs) 281 if len(encryptedSecret) > 0 { 282 cert, err := cryptoutil.DecryptBytes(encryptedSecret, key) 283 if err != nil { 284 // If any of the decrypt operations fail do not return a partial result, 285 // return an empty result immediately 286 return map[string][]byte{}, err 287 } 288 secretData[secretName] = cert 289 } else { 290 secretData[secretName] = []byte{} 291 } 292 } 293 return secretData, nil 294 } 295 296 func certOrKeyNameToSecretName(certOrKeyName string) string { 297 return strings.Replace(certOrKeyName, "/", "-", -1) 298 }