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 }