|
1 | 1 | package server |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "crypto/ecdsa" |
5 | 6 | "crypto/elliptic" |
6 | 7 | "crypto/rand" |
@@ -35,17 +36,29 @@ func ensureCerts(dir, ip, advertiseIP string) error { |
35 | 36 | admCrtFile := filepath.Join(dir, "admin.crt") |
36 | 37 | kubeletKeyFile := filepath.Join(dir, "apiserver-kubelet-client.key") |
37 | 38 | kubeletCrtFile := filepath.Join(dir, "apiserver-kubelet-client.crt") |
| 39 | + dependentFiles := []string{ |
| 40 | + caKeyFile, caCertFile, srvKeyFile, srvCrtFile, |
| 41 | + admKeyFile, admCrtFile, kubeletKeyFile, kubeletCrtFile, |
| 42 | + filepath.Join(dir, "webhook.crt"), |
| 43 | + filepath.Join(dir, "webhook.key"), |
| 44 | + filepath.Join(dir, "authz-webhook.kubeconfig"), |
| 45 | + filepath.Join(dir, "controller-manager.kubeconfig"), |
| 46 | + filepath.Join(dir, "scheduler.kubeconfig"), |
| 47 | + } |
38 | 48 |
|
39 | 49 | // kube-apiserver fails ECDSA signature verification on self-signed ECDSA CAs. |
40 | 50 | // If the existing CA key is ECDSA, remove all derived certs so they are |
41 | 51 | // regenerated as RSA below. |
42 | 52 | if fileExists(caKeyFile) && caKeyIsECDSA(caKeyFile) { |
43 | | - for _, f := range []string{ |
44 | | - caKeyFile, caCertFile, srvKeyFile, srvCrtFile, |
45 | | - admKeyFile, admCrtFile, kubeletKeyFile, kubeletCrtFile, |
46 | | - } { |
47 | | - _ = os.Remove(f) |
48 | | - } |
| 53 | + removeAllIfExists(dependentFiles...) |
| 54 | + } |
| 55 | + |
| 56 | + // If the CA cert and key drift out of sync, every newly signed client/server |
| 57 | + // cert will be unverifiable even though all files still parse successfully. |
| 58 | + // In that case remove the CA and all dependent artifacts so startup can |
| 59 | + // regenerate a consistent chain. |
| 60 | + if !existingCACertKeyUsable(caCertFile, caKeyFile) { |
| 61 | + removeAllIfExists(dependentFiles...) |
49 | 62 | } |
50 | 63 |
|
51 | 64 | var caKey *rsa.PrivateKey |
@@ -417,6 +430,57 @@ func removeIfExists(path string) error { |
417 | 430 | return nil |
418 | 431 | } |
419 | 432 |
|
| 433 | +func removeAllIfExists(paths ...string) { |
| 434 | + for _, path := range paths { |
| 435 | + _ = os.Remove(path) |
| 436 | + } |
| 437 | +} |
| 438 | + |
| 439 | +func existingCACertKeyUsable(certFile, keyFile string) bool { |
| 440 | + if !fileExists(certFile) && !fileExists(keyFile) { |
| 441 | + return true |
| 442 | + } |
| 443 | + if !fileExists(certFile) || !fileExists(keyFile) { |
| 444 | + return false |
| 445 | + } |
| 446 | + |
| 447 | + keyPEM, err := os.ReadFile(keyFile) |
| 448 | + if err != nil { |
| 449 | + return false |
| 450 | + } |
| 451 | + keyBlock, _ := pem.Decode(keyPEM) |
| 452 | + if keyBlock == nil { |
| 453 | + return false |
| 454 | + } |
| 455 | + key, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes) |
| 456 | + if err != nil { |
| 457 | + return false |
| 458 | + } |
| 459 | + |
| 460 | + certPEM, err := os.ReadFile(certFile) |
| 461 | + if err != nil { |
| 462 | + return false |
| 463 | + } |
| 464 | + certBlock, _ := pem.Decode(certPEM) |
| 465 | + if certBlock == nil { |
| 466 | + return false |
| 467 | + } |
| 468 | + cert, err := x509.ParseCertificate(certBlock.Bytes) |
| 469 | + if err != nil || !cert.IsCA { |
| 470 | + return false |
| 471 | + } |
| 472 | + |
| 473 | + certPubDER, err := x509.MarshalPKIXPublicKey(cert.PublicKey) |
| 474 | + if err != nil { |
| 475 | + return false |
| 476 | + } |
| 477 | + keyPubDER, err := x509.MarshalPKIXPublicKey(&key.PublicKey) |
| 478 | + if err != nil { |
| 479 | + return false |
| 480 | + } |
| 481 | + return bytes.Equal(certPubDER, keyPubDER) |
| 482 | +} |
| 483 | + |
420 | 484 | func uniqueIPs(addrs ...string) []net.IP { |
421 | 485 | seen := map[string]bool{} |
422 | 486 | var result []net.IP |
|
0 commit comments