sigs.k8s.io/cluster-api/bootstrap/kubeadm@v0.0.0-20191016155141-23a891785b60/internal/cluster/certificates.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 cluster
    18  
    19  import (
    20  	"context"
    21  	"crypto/rand"
    22  	"crypto/rsa"
    23  	"crypto/sha256"
    24  	"crypto/x509"
    25  	"crypto/x509/pkix"
    26  	"encoding/hex"
    27  	"math/big"
    28  	"path/filepath"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/pkg/errors"
    33  	corev1 "k8s.io/api/core/v1"
    34  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/client-go/util/cert"
    37  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha2"
    38  	"sigs.k8s.io/cluster-api/bootstrap/kubeadm/kubeadm/v1beta1"
    39  	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha2"
    40  	"sigs.k8s.io/cluster-api/util/certs"
    41  	"sigs.k8s.io/cluster-api/util/secret"
    42  	"sigs.k8s.io/controller-runtime/pkg/client"
    43  )
    44  
    45  const (
    46  	rootOwnerValue = "root:root"
    47  
    48  	// EtcdCA is the secret name suffix for the Etcd CA
    49  	EtcdCA secret.Purpose = "etcd"
    50  
    51  	// ServiceAccount is the secret name suffix for the Service Account keys
    52  	ServiceAccount secret.Purpose = "sa"
    53  
    54  	// FrontProxyCA is the secret name suffix for Front Proxy CA
    55  	FrontProxyCA secret.Purpose = "proxy"
    56  
    57  	// APIServerEtcdClient is the secret name of user-supplied secret containing the apiserver-etcd-client key/cert
    58  	APIServerEtcdClient secret.Purpose = "apiserver-etcd-client"
    59  
    60  	defaultCertificatesDir = "/etc/kubernetes/pki"
    61  )
    62  
    63  var (
    64  	// ErrMissingCertificate is an error indicating a certificate is entirely missing
    65  	ErrMissingCertificate = errors.New("missing certificate")
    66  
    67  	// ErrMissingCrt is an error indicating the crt file is missing from the certificate
    68  	ErrMissingCrt = errors.New("missing crt data")
    69  
    70  	// ErrMissingKey is an error indicating the key file is missing from the certificate
    71  	ErrMissingKey = errors.New("missing key data")
    72  )
    73  
    74  // Certificates are the certificates necessary to bootstrap a cluster.
    75  type Certificates []*Certificate
    76  
    77  // NewCertificatesForInitialControlPlane returns a list of certificates configured for a control plane node
    78  func NewCertificatesForInitialControlPlane(config *v1beta1.ClusterConfiguration) Certificates {
    79  	if config.CertificatesDir == "" {
    80  		config.CertificatesDir = defaultCertificatesDir
    81  	}
    82  
    83  	certificates := Certificates{
    84  		&Certificate{
    85  			Purpose:  secret.ClusterCA,
    86  			CertFile: filepath.Join(config.CertificatesDir, "ca.crt"),
    87  			KeyFile:  filepath.Join(config.CertificatesDir, "ca.key"),
    88  		},
    89  		&Certificate{
    90  			Purpose:  ServiceAccount,
    91  			CertFile: filepath.Join(config.CertificatesDir, "sa.pub"),
    92  			KeyFile:  filepath.Join(config.CertificatesDir, "sa.key"),
    93  		},
    94  		&Certificate{
    95  			Purpose:  FrontProxyCA,
    96  			CertFile: filepath.Join(config.CertificatesDir, "front-proxy-ca.crt"),
    97  			KeyFile:  filepath.Join(config.CertificatesDir, "front-proxy-ca.key"),
    98  		},
    99  	}
   100  
   101  	etcdCert := &Certificate{
   102  		Purpose:  EtcdCA,
   103  		CertFile: filepath.Join(config.CertificatesDir, "etcd", "ca.crt"),
   104  		KeyFile:  filepath.Join(config.CertificatesDir, "etcd", "ca.key"),
   105  	}
   106  
   107  	// TODO make sure all the fields are actually defined and return an error if not
   108  	if config.Etcd.External != nil {
   109  		etcdCert = &Certificate{
   110  			Purpose:  EtcdCA,
   111  			CertFile: config.Etcd.External.CAFile,
   112  		}
   113  		apiserverEtcdClientCert := &Certificate{
   114  			Purpose:  APIServerEtcdClient,
   115  			CertFile: config.Etcd.External.CertFile,
   116  			KeyFile:  config.Etcd.External.KeyFile,
   117  		}
   118  		certificates = append(certificates, apiserverEtcdClientCert)
   119  	}
   120  
   121  	certificates = append(certificates, etcdCert)
   122  	return certificates
   123  }
   124  
   125  // NewCertificatesForJoiningControlPlane gets any certs that exist and writes them to disk
   126  func NewCertificatesForJoiningControlPlane() Certificates {
   127  	return Certificates{
   128  		&Certificate{
   129  			Purpose:  secret.ClusterCA,
   130  			CertFile: filepath.Join(defaultCertificatesDir, "ca.crt"),
   131  			KeyFile:  filepath.Join(defaultCertificatesDir, "ca.key"),
   132  		},
   133  		&Certificate{
   134  			Purpose:  ServiceAccount,
   135  			CertFile: filepath.Join(defaultCertificatesDir, "sa.pub"),
   136  			KeyFile:  filepath.Join(defaultCertificatesDir, "sa.key"),
   137  		},
   138  		&Certificate{
   139  			Purpose:  FrontProxyCA,
   140  			CertFile: filepath.Join(defaultCertificatesDir, "front-proxy-ca.crt"),
   141  			KeyFile:  filepath.Join(defaultCertificatesDir, "front-proxy-ca.key"),
   142  		},
   143  		&Certificate{
   144  			Purpose:  EtcdCA,
   145  			CertFile: filepath.Join(defaultCertificatesDir, "etcd", "ca.crt"),
   146  			KeyFile:  filepath.Join(defaultCertificatesDir, "etcd", "ca.key"),
   147  		},
   148  	}
   149  }
   150  
   151  // NewCertificatesForWorker return an initialized but empty set of CA certificates needed to bootstrap a cluster.
   152  func NewCertificatesForWorker(caCertPath string) Certificates {
   153  	if caCertPath == "" {
   154  		caCertPath = filepath.Join(defaultCertificatesDir, "ca.crt")
   155  	}
   156  
   157  	return Certificates{
   158  		&Certificate{
   159  			Purpose:  secret.ClusterCA,
   160  			CertFile: caCertPath,
   161  		},
   162  	}
   163  }
   164  
   165  // GetByPurpose returns a certificate by the given name.
   166  // This could be removed if we use a map instead of a slice to hold certificates, however other code becomes more complex.
   167  func (c Certificates) GetByPurpose(purpose secret.Purpose) *Certificate {
   168  	for _, certificate := range c {
   169  		if certificate.Purpose == purpose {
   170  			return certificate
   171  		}
   172  	}
   173  	return nil
   174  }
   175  
   176  // Lookup looks up each certificate from secrets and populates the certificate with the secret data.
   177  func (c Certificates) Lookup(ctx context.Context, ctrlclient client.Client, cluster *clusterv1.Cluster) error {
   178  	// Look up each certificate as a secret and populate the certificate/key
   179  	for _, certificate := range c {
   180  		s := &corev1.Secret{}
   181  		key := client.ObjectKey{
   182  			Name:      secret.Name(cluster.Name, certificate.Purpose),
   183  			Namespace: cluster.Namespace,
   184  		}
   185  		if err := ctrlclient.Get(ctx, key, s); err != nil {
   186  			if apierrors.IsNotFound(err) {
   187  				continue
   188  			}
   189  			return errors.WithStack(err)
   190  		}
   191  		// If a user has a badly formatted secret it will prevent the cluster from working.
   192  		kp, err := secretToKeyPair(s)
   193  		if err != nil {
   194  			return err
   195  		}
   196  		certificate.KeyPair = kp
   197  	}
   198  	return nil
   199  }
   200  
   201  // EnsureAllExist ensure that there is some data present for every certificate
   202  func (c Certificates) EnsureAllExist() error {
   203  	for _, certificate := range c {
   204  		if certificate.KeyPair == nil {
   205  			return ErrMissingCertificate
   206  		}
   207  		if len(certificate.KeyPair.Cert) == 0 {
   208  			return errors.Wrapf(ErrMissingCrt, "for certificate: %s", certificate.Purpose)
   209  		}
   210  		if len(certificate.KeyPair.Key) == 0 {
   211  			return errors.Wrapf(ErrMissingKey, "for certificate: %s", certificate.Purpose)
   212  		}
   213  	}
   214  	return nil
   215  }
   216  
   217  // TODO: consider moving a generating function into the Certificate object itself?
   218  type certGenerator func() (*certs.KeyPair, error)
   219  
   220  // Generate will generate any certificates that do not have KeyPair data.
   221  func (c Certificates) Generate() error {
   222  	for _, certificate := range c {
   223  		if certificate.KeyPair == nil {
   224  			var generator certGenerator
   225  			switch certificate.Purpose {
   226  			case APIServerEtcdClient: // Do not generate the APIServerEtcdClient key pair. It is user supplied
   227  				continue
   228  			case ServiceAccount:
   229  				generator = generateServiceAccountKeys
   230  			default:
   231  				generator = generateCACert
   232  			}
   233  
   234  			kp, err := generator()
   235  			if err != nil {
   236  				return err
   237  			}
   238  			certificate.KeyPair = kp
   239  			certificate.Generated = true
   240  		}
   241  	}
   242  	return nil
   243  }
   244  
   245  // SaveGenerated will save any certificates that have been generated as Kubernetes secrets.
   246  func (c Certificates) SaveGenerated(ctx context.Context, ctrlclient client.Client, cluster *clusterv1.Cluster, config *bootstrapv1.KubeadmConfig) error {
   247  	for _, certificate := range c {
   248  		if !certificate.Generated {
   249  			continue
   250  		}
   251  		s := certificate.AsSecret(cluster, config)
   252  		if err := ctrlclient.Create(ctx, s); err != nil {
   253  			return errors.WithStack(err)
   254  		}
   255  	}
   256  	return nil
   257  }
   258  
   259  // LookupOrGenerate is a convenience function that wraps cluster bootstrap certificate behavior.
   260  func (c Certificates) LookupOrGenerate(ctx context.Context, ctrlclient client.Client, cluster *clusterv1.Cluster, config *bootstrapv1.KubeadmConfig) error {
   261  	// Find the certificates that exist
   262  	if err := c.Lookup(ctx, ctrlclient, cluster); err != nil {
   263  		return err
   264  	}
   265  
   266  	// Generate the certificates that don't exist
   267  	if err := c.Generate(); err != nil {
   268  		return err
   269  	}
   270  
   271  	// Save any certificates that have been generated
   272  	if err := c.SaveGenerated(ctx, ctrlclient, cluster, config); err != nil {
   273  		return err
   274  	}
   275  
   276  	return nil
   277  }
   278  
   279  // Certificate represents a single certificate CA.
   280  type Certificate struct {
   281  	Generated         bool
   282  	Purpose           secret.Purpose
   283  	KeyPair           *certs.KeyPair
   284  	CertFile, KeyFile string
   285  }
   286  
   287  // Hashes hashes all the certificates stored in a CA certificate.
   288  func (c *Certificate) Hashes() ([]string, error) {
   289  	certificates, err := cert.ParseCertsPEM(c.KeyPair.Cert)
   290  	if err != nil {
   291  		return nil, errors.Wrapf(err, "unable to parse %s certificate", c.Purpose)
   292  	}
   293  	out := make([]string, 0)
   294  	for _, c := range certificates {
   295  		out = append(out, hashCert(c))
   296  	}
   297  	return out, nil
   298  }
   299  
   300  // hashCert calculates the sha256 of certificate.
   301  func hashCert(certificate *x509.Certificate) string {
   302  	spkiHash := sha256.Sum256(certificate.RawSubjectPublicKeyInfo)
   303  	return "sha256:" + strings.ToLower(hex.EncodeToString(spkiHash[:]))
   304  }
   305  
   306  // AsSecret converts a single certificate into a Kubernetes secret.
   307  func (c *Certificate) AsSecret(cluster *clusterv1.Cluster, config *bootstrapv1.KubeadmConfig) *corev1.Secret {
   308  	s := &corev1.Secret{
   309  		ObjectMeta: metav1.ObjectMeta{
   310  			Namespace: cluster.Namespace,
   311  			Name:      secret.Name(cluster.Name, c.Purpose),
   312  			Labels: map[string]string{
   313  				clusterv1.MachineClusterLabelName: cluster.Name,
   314  			},
   315  		},
   316  		Data: map[string][]byte{
   317  			secret.TLSKeyDataName: c.KeyPair.Key,
   318  			secret.TLSCrtDataName: c.KeyPair.Cert,
   319  		},
   320  	}
   321  
   322  	if c.Generated {
   323  		s.OwnerReferences = []metav1.OwnerReference{
   324  			{
   325  				APIVersion: bootstrapv1.GroupVersion.String(),
   326  				Kind:       "KubeadmConfig",
   327  				Name:       config.Name,
   328  				UID:        config.UID,
   329  			},
   330  		}
   331  	}
   332  	return s
   333  }
   334  
   335  // AsFiles converts the certificate to a slice of Files that may have 0, 1 or 2 Files.
   336  func (c *Certificate) AsFiles() []bootstrapv1.File {
   337  	out := make([]bootstrapv1.File, 0)
   338  	if len(c.KeyPair.Cert) > 0 {
   339  		out = append(out, bootstrapv1.File{
   340  			Path:        c.CertFile,
   341  			Owner:       rootOwnerValue,
   342  			Permissions: "0640",
   343  			Content:     string(c.KeyPair.Cert),
   344  		})
   345  	}
   346  	if len(c.KeyPair.Key) > 0 {
   347  		out = append(out, bootstrapv1.File{
   348  			Path:        c.KeyFile,
   349  			Owner:       rootOwnerValue,
   350  			Permissions: "0600",
   351  			Content:     string(c.KeyPair.Key),
   352  		})
   353  	}
   354  	return out
   355  }
   356  
   357  // AsFiles converts a slice of certificates into bootstrap files.
   358  func (c Certificates) AsFiles() []bootstrapv1.File {
   359  	clusterCA := c.GetByPurpose(secret.ClusterCA)
   360  	etcdCA := c.GetByPurpose(EtcdCA)
   361  	frontProxyCA := c.GetByPurpose(FrontProxyCA)
   362  	serviceAccountKey := c.GetByPurpose(ServiceAccount)
   363  
   364  	certFiles := make([]bootstrapv1.File, 0)
   365  	if clusterCA != nil {
   366  		certFiles = append(certFiles, clusterCA.AsFiles()...)
   367  	}
   368  	if etcdCA != nil {
   369  		certFiles = append(certFiles, etcdCA.AsFiles()...)
   370  	}
   371  	if frontProxyCA != nil {
   372  		certFiles = append(certFiles, frontProxyCA.AsFiles()...)
   373  	}
   374  	if serviceAccountKey != nil {
   375  		certFiles = append(certFiles, serviceAccountKey.AsFiles()...)
   376  	}
   377  
   378  	// these will only exist if external etcd was defined and supplied by the user
   379  	apiserverEtcdClientCert := c.GetByPurpose(APIServerEtcdClient)
   380  	if apiserverEtcdClientCert != nil {
   381  		certFiles = append(certFiles, apiserverEtcdClientCert.AsFiles()...)
   382  	}
   383  
   384  	return certFiles
   385  }
   386  
   387  func secretToKeyPair(s *corev1.Secret) (*certs.KeyPair, error) {
   388  	c, exists := s.Data[secret.TLSCrtDataName]
   389  	if !exists {
   390  		return nil, errors.Errorf("missing data for key %s", secret.TLSCrtDataName)
   391  	}
   392  
   393  	// In some cases (external etcd) it's ok if the etcd.key does not exist.
   394  	// TODO: some other function should ensure that the certificates we need exist.
   395  	key, exists := s.Data[secret.TLSKeyDataName]
   396  	if !exists {
   397  		key = []byte("")
   398  	}
   399  
   400  	return &certs.KeyPair{
   401  		Cert: c,
   402  		Key:  key,
   403  	}, nil
   404  }
   405  
   406  func generateCACert() (*certs.KeyPair, error) {
   407  	x509Cert, privKey, err := newCertificateAuthority()
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  	return &certs.KeyPair{
   412  		Cert: certs.EncodeCertPEM(x509Cert),
   413  		Key:  certs.EncodePrivateKeyPEM(privKey),
   414  	}, nil
   415  }
   416  
   417  func generateServiceAccountKeys() (*certs.KeyPair, error) {
   418  	saCreds, err := certs.NewPrivateKey()
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  	saPub, err := certs.EncodePublicKeyPEM(&saCreds.PublicKey)
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  	return &certs.KeyPair{
   427  		Cert: saPub,
   428  		Key:  certs.EncodePrivateKeyPEM(saCreds),
   429  	}, nil
   430  }
   431  
   432  // newCertificateAuthority creates new certificate and private key for the certificate authority
   433  func newCertificateAuthority() (*x509.Certificate, *rsa.PrivateKey, error) {
   434  	key, err := certs.NewPrivateKey()
   435  	if err != nil {
   436  		return nil, nil, err
   437  	}
   438  
   439  	c, err := newSelfSignedCACert(key)
   440  	if err != nil {
   441  		return nil, nil, err
   442  	}
   443  
   444  	return c, key, nil
   445  }
   446  
   447  // newSelfSignedCACert creates a CA certificate.
   448  func newSelfSignedCACert(key *rsa.PrivateKey) (*x509.Certificate, error) {
   449  	cfg := certs.Config{
   450  		CommonName: "kubernetes",
   451  	}
   452  
   453  	now := time.Now().UTC()
   454  
   455  	tmpl := x509.Certificate{
   456  		SerialNumber: new(big.Int).SetInt64(0),
   457  		Subject: pkix.Name{
   458  			CommonName:   cfg.CommonName,
   459  			Organization: cfg.Organization,
   460  		},
   461  		NotBefore:             now.Add(time.Minute * -5),
   462  		NotAfter:              now.Add(time.Hour * 24 * 365 * 10), // 10 years
   463  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   464  		MaxPathLenZero:        true,
   465  		BasicConstraintsValid: true,
   466  		MaxPathLen:            0,
   467  		IsCA:                  true,
   468  	}
   469  
   470  	b, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, key.Public(), key)
   471  	if err != nil {
   472  		return nil, errors.Wrapf(err, "failed to create self signed CA certificate: %+v", tmpl)
   473  	}
   474  
   475  	c, err := x509.ParseCertificate(b)
   476  	return c, errors.WithStack(err)
   477  }