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 }