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 }