...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/phases/copycerts/copycerts.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/phases/copycerts

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

View as plain text