k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/util/certs/util.go (about)

     1  /*
     2  Copyright 2017 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 certs
    18  
    19  import (
    20  	"crypto"
    21  	"crypto/rsa"
    22  	"crypto/x509"
    23  	"net"
    24  	"path/filepath"
    25  	"testing"
    26  	"time"
    27  
    28  	certutil "k8s.io/client-go/util/cert"
    29  	"k8s.io/client-go/util/keyutil"
    30  
    31  	"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
    32  )
    33  
    34  // SetupCertificateAuthority is a utility function for kubeadm testing that creates a
    35  // CertificateAuthority cert/key pair
    36  func SetupCertificateAuthority(t *testing.T) (*x509.Certificate, crypto.Signer) {
    37  	caCert, caKey, err := pkiutil.NewCertificateAuthority(&pkiutil.CertConfig{
    38  		Config: certutil.Config{CommonName: "kubernetes"},
    39  	})
    40  	if err != nil {
    41  		t.Fatalf("failure while generating CA certificate and key: %v", err)
    42  	}
    43  
    44  	return caCert, caKey
    45  }
    46  
    47  // AssertCertificateIsSignedByCa is a utility function for kubeadm testing that asserts if a given certificate is signed
    48  // by the expected CA
    49  func AssertCertificateIsSignedByCa(t *testing.T, cert *x509.Certificate, signingCa *x509.Certificate) {
    50  	if err := cert.CheckSignatureFrom(signingCa); err != nil {
    51  		t.Error("cert is not signed by signing CA as expected")
    52  	}
    53  }
    54  
    55  // AssertCertificateHasNotBefore is a utility function for kubeadm testing that asserts if a given certificate has
    56  // the expected NotBefore. Truncate (round) expectedNotBefore to 1 second, since the certificate stores
    57  // with seconds as the maximum precision.
    58  func AssertCertificateHasNotBefore(t *testing.T, cert *x509.Certificate, expectedNotBefore time.Time) {
    59  	truncated := expectedNotBefore.Truncate(time.Second)
    60  	if !cert.NotBefore.Equal(truncated) {
    61  		t.Errorf("cert has NotBefore %v, expected %v", cert.NotBefore, truncated)
    62  	}
    63  }
    64  
    65  // AssertCertificateHasNotAfter is a utility function for kubeadm testing that asserts if a given certificate has
    66  // the expected NotAfter. Truncate (round) expectedNotAfter to 1 second, since the certificate stores
    67  // with seconds as the maximum precision.
    68  func AssertCertificateHasNotAfter(t *testing.T, cert *x509.Certificate, expectedNotAfter time.Time) {
    69  	truncated := expectedNotAfter.Truncate(time.Second)
    70  	if !cert.NotAfter.Equal(truncated) {
    71  		t.Errorf("cert has NotAfter %v, expected %v", cert.NotAfter, truncated)
    72  	}
    73  }
    74  
    75  // AssertCertificateHasCommonName is a utility function for kubeadm testing that asserts if a given certificate has
    76  // the expected SubjectCommonName
    77  func AssertCertificateHasCommonName(t *testing.T, cert *x509.Certificate, commonName string) {
    78  	if cert.Subject.CommonName != commonName {
    79  		t.Errorf("cert has Subject.CommonName %s, expected %s", cert.Subject.CommonName, commonName)
    80  	}
    81  }
    82  
    83  // AssertCertificateHasOrganizations is a utility function for kubeadm testing that asserts if a given certificate has
    84  // and only has the expected Subject.Organization
    85  func AssertCertificateHasOrganizations(t *testing.T, cert *x509.Certificate, organizations ...string) {
    86  	if len(cert.Subject.Organization) != len(organizations) {
    87  		t.Fatalf("cert contains a different number of Subject.Organization, expected %v, got %v", organizations, cert.Subject.Organization)
    88  	}
    89  	for _, organization := range organizations {
    90  		found := false
    91  		for i := range cert.Subject.Organization {
    92  			if cert.Subject.Organization[i] == organization {
    93  				found = true
    94  			}
    95  		}
    96  		if !found {
    97  			t.Errorf("cert does not contain Subject.Organization %s as expected", organization)
    98  		}
    99  	}
   100  }
   101  
   102  // AssertCertificateHasClientAuthUsage is a utility function for kubeadm testing that asserts if a given certificate has
   103  // the expected ExtKeyUsageClientAuth
   104  func AssertCertificateHasClientAuthUsage(t *testing.T, cert *x509.Certificate) {
   105  	for i := range cert.ExtKeyUsage {
   106  		if cert.ExtKeyUsage[i] == x509.ExtKeyUsageClientAuth {
   107  			return
   108  		}
   109  	}
   110  	t.Error("cert has not ClientAuth usage as expected")
   111  }
   112  
   113  // AssertCertificateHasServerAuthUsage is a utility function for kubeadm testing that asserts if a given certificate has
   114  // the expected ExtKeyUsageServerAuth
   115  func AssertCertificateHasServerAuthUsage(t *testing.T, cert *x509.Certificate) {
   116  	for i := range cert.ExtKeyUsage {
   117  		if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth {
   118  			return
   119  		}
   120  	}
   121  	t.Error("cert is not a ServerAuth")
   122  }
   123  
   124  // AssertCertificateHasDNSNames is a utility function for kubeadm testing that asserts if a given certificate has
   125  // the expected DNSNames
   126  func AssertCertificateHasDNSNames(t *testing.T, cert *x509.Certificate, DNSNames ...string) {
   127  	for _, DNSName := range DNSNames {
   128  		found := false
   129  		for _, val := range cert.DNSNames {
   130  			if val == DNSName {
   131  				found = true
   132  				break
   133  			}
   134  		}
   135  
   136  		if !found {
   137  			t.Errorf("cert does not contain DNSName %s", DNSName)
   138  		}
   139  	}
   140  }
   141  
   142  // AssertCertificateHasIPAddresses is a utility function for kubeadm testing that asserts if a given certificate has
   143  // the expected IPAddresses
   144  func AssertCertificateHasIPAddresses(t *testing.T, cert *x509.Certificate, IPAddresses ...net.IP) {
   145  	for _, IPAddress := range IPAddresses {
   146  		found := false
   147  		for _, val := range cert.IPAddresses {
   148  			if val.Equal(IPAddress) {
   149  				found = true
   150  				break
   151  			}
   152  		}
   153  
   154  		if !found {
   155  			t.Errorf("cert does not contain IPAddress %s", IPAddress)
   156  		}
   157  	}
   158  }
   159  
   160  // CreateCACert creates a generic CA cert.
   161  func CreateCACert(t *testing.T) (*x509.Certificate, crypto.Signer) {
   162  	certCfg := &pkiutil.CertConfig{Config: certutil.Config{CommonName: "kubernetes"}}
   163  	cert, key, err := pkiutil.NewCertificateAuthority(certCfg)
   164  	if err != nil {
   165  		t.Fatalf("couldn't create CA: %v", err)
   166  	}
   167  	return cert, key
   168  }
   169  
   170  // CreateTestCert makes a generic certificate with the given CA and alternative names.
   171  func CreateTestCert(t *testing.T, caCert *x509.Certificate, caKey crypto.Signer, altNames certutil.AltNames) (*x509.Certificate, crypto.Signer, *pkiutil.CertConfig) {
   172  	config := &pkiutil.CertConfig{
   173  		Config: certutil.Config{
   174  			CommonName: "testCert",
   175  			Usages:     []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
   176  			AltNames:   altNames,
   177  		},
   178  	}
   179  	cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, config)
   180  	if err != nil {
   181  		t.Fatalf("couldn't create test cert: %v", err)
   182  	}
   183  	return cert, key, config
   184  }
   185  
   186  // CertTestCase is a configuration of certificates and whether it's expected to work.
   187  type CertTestCase struct {
   188  	Name        string
   189  	Files       PKIFiles
   190  	ExpectError bool
   191  }
   192  
   193  // GetSparseCertTestCases produces a series of cert configurations and their intended outcomes.
   194  func GetSparseCertTestCases(t *testing.T) []CertTestCase {
   195  
   196  	caCert, caKey := CreateCACert(t)
   197  	fpCACert, fpCAKey := CreateCACert(t)
   198  	etcdCACert, etcdCAKey := CreateCACert(t)
   199  
   200  	fpCert, fpKey, _ := CreateTestCert(t, fpCACert, fpCAKey, certutil.AltNames{})
   201  
   202  	return []CertTestCase{
   203  		{
   204  			Name: "nothing present",
   205  		},
   206  		{
   207  			Name: "CAs already exist",
   208  			Files: PKIFiles{
   209  				"ca.crt":             caCert,
   210  				"ca.key":             caKey,
   211  				"front-proxy-ca.crt": fpCACert,
   212  				"front-proxy-ca.key": fpCAKey,
   213  				"etcd/ca.crt":        etcdCACert,
   214  				"etcd/ca.key":        etcdCAKey,
   215  			},
   216  		},
   217  		{
   218  			Name: "CA certs only",
   219  			Files: PKIFiles{
   220  				"ca.crt":             caCert,
   221  				"front-proxy-ca.crt": fpCACert,
   222  				"etcd/ca.crt":        etcdCACert,
   223  			},
   224  			ExpectError: true,
   225  		},
   226  		{
   227  			Name: "FrontProxyCA with certs",
   228  			Files: PKIFiles{
   229  				"ca.crt":                 caCert,
   230  				"ca.key":                 caKey,
   231  				"front-proxy-ca.crt":     fpCACert,
   232  				"front-proxy-client.crt": fpCert,
   233  				"front-proxy-client.key": fpKey,
   234  				"etcd/ca.crt":            etcdCACert,
   235  				"etcd/ca.key":            etcdCAKey,
   236  			},
   237  		},
   238  		{
   239  			Name: "FrontProxy certs missing CA",
   240  			Files: PKIFiles{
   241  				"front-proxy-client.crt": fpCert,
   242  				"front-proxy-client.key": fpKey,
   243  			},
   244  			ExpectError: true,
   245  		},
   246  	}
   247  }
   248  
   249  // PKIFiles are a list of files that should be created for a test case
   250  type PKIFiles map[string]interface{}
   251  
   252  // WritePKIFiles writes the given files out to the given directory
   253  func WritePKIFiles(t *testing.T, dir string, files PKIFiles) {
   254  	for filename, body := range files {
   255  		switch body := body.(type) {
   256  		case *x509.Certificate:
   257  			if err := certutil.WriteCert(filepath.Join(dir, filename), pkiutil.EncodeCertPEM(body)); err != nil {
   258  				t.Errorf("unable to write certificate to file %q: [%v]", dir, err)
   259  			}
   260  		case *rsa.PublicKey:
   261  			publicKeyBytes, err := pkiutil.EncodePublicKeyPEM(body)
   262  			if err != nil {
   263  				t.Errorf("unable to write public key to file %q: [%v]", filename, err)
   264  			}
   265  			if err := keyutil.WriteKey(filepath.Join(dir, filename), publicKeyBytes); err != nil {
   266  				t.Errorf("unable to write public key to file %q: [%v]", filename, err)
   267  			}
   268  		case *rsa.PrivateKey:
   269  			privateKey, err := keyutil.MarshalPrivateKeyToPEM(body)
   270  			if err != nil {
   271  				t.Errorf("unable to write private key to file %q: [%v]", filename, err)
   272  			}
   273  			if err := keyutil.WriteKey(filepath.Join(dir, filename), privateKey); err != nil {
   274  				t.Errorf("unable to write private key to file %q: [%v]", filename, err)
   275  			}
   276  		}
   277  	}
   278  }