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