github.com/jlmeeker/kismatic@v1.10.1-0.20180612190640-57f9005a1f1a/pkg/install/pki_test.go (about) 1 package install 2 3 import ( 4 "crypto/x509" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "os" 9 "path/filepath" 10 "reflect" 11 "strconv" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/apprenda/kismatic/pkg/tls" 17 "github.com/apprenda/kismatic/pkg/util" 18 "github.com/cloudflare/cfssl/helpers" 19 ) 20 21 func getPKI(t *testing.T) LocalPKI { 22 tempDir, err := ioutil.TempDir("", "pki-tests") 23 if err != nil { 24 t.Fatalf("Failed to create temp directory: %v", err) 25 } 26 pki := LocalPKI{ 27 CACsr: "test/ca-csr.json", 28 GeneratedCertsDirectory: tempDir, 29 Log: ioutil.Discard, 30 } 31 return pki 32 } 33 34 func cleanup(dir string, t *testing.T) { 35 if err := os.RemoveAll(dir); err != nil { 36 t.Fatalf("failed cleaning up temp directory: %v", err) 37 } 38 } 39 40 func mustReadCertFile(certFile string, t *testing.T) *x509.Certificate { 41 certPEM, err := ioutil.ReadFile(certFile) 42 if err != nil { 43 t.Fatalf("failed to read certificate file: %v", err) 44 } 45 cert, err := helpers.ParseCertificatePEM(certPEM) 46 if err != nil { 47 t.Fatalf("error reading host certificate: %v", err) 48 } 49 return cert 50 } 51 52 func getPlan() *Plan { 53 return &Plan{ 54 Cluster: Cluster{ 55 Name: "someName", 56 Certificates: CertsConfig{ 57 Expiry: "1h", 58 }, 59 Networking: NetworkConfig{ 60 ServiceCIDRBlock: "10.0.0.0/24", // required for DNS service 61 }, 62 }, 63 AddOns: AddOns{ 64 CNI: &CNI{}, 65 }, 66 Etcd: NodeGroup{ 67 Nodes: []Node{ 68 Node{ 69 Host: "etcd01", 70 IP: "99.99.99.99", 71 InternalIP: "88.88.88.88", 72 }, 73 Node{ 74 Host: "etcd02", 75 IP: "99.99.99.99", 76 InternalIP: "88.88.88.88", 77 }, 78 }, 79 }, 80 Master: MasterNodeGroup{ 81 Nodes: []Node{ 82 Node{ 83 Host: "master01", 84 IP: "99.99.99.99", 85 InternalIP: "88.88.88.88", 86 }, 87 Node{ 88 Host: "master02", 89 IP: "99.99.99.99", 90 InternalIP: "88.88.88.88", 91 }, 92 }, 93 LoadBalancedFQDN: "someFQDN", 94 LoadBalancedShortName: "someShortName", 95 }, 96 Worker: NodeGroup{ 97 Nodes: []Node{ 98 Node{ 99 Host: "worker01", 100 IP: "99.99.99.99", 101 InternalIP: "88.88.88.88", 102 }, 103 Node{ 104 Host: "worker02", 105 IP: "99.99.99.99", 106 InternalIP: "88.88.88.88", 107 }, 108 }, 109 }, 110 Ingress: OptionalNodeGroup{ 111 Nodes: []Node{ 112 Node{ 113 Host: "ingress01", 114 IP: "99.99.99.99", 115 InternalIP: "88.88.88.88", 116 }, 117 Node{ 118 Host: "ingress02", 119 IP: "99.99.99.99", 120 InternalIP: "88.88.88.88", 121 }, 122 }, 123 }, 124 Storage: OptionalNodeGroup{ 125 Nodes: []Node{ 126 Node{ 127 Host: "storage01", 128 IP: "99.99.99.99", 129 InternalIP: "88.88.88.88", 130 }, 131 Node{ 132 Host: "storage02", 133 IP: "99.99.99.99", 134 InternalIP: "88.88.88.88", 135 }, 136 }, 137 }, 138 } 139 } 140 141 func TestGeneratedClusterCACommonNameMatchesClusterName(t *testing.T) { 142 pki := getPKI(t) 143 defer cleanup(pki.GeneratedCertsDirectory, t) 144 145 p := getPlan() 146 ca, err := pki.GenerateClusterCA(p) 147 if err != nil { 148 t.Errorf("failed to generate cluster CA: %v", err) 149 } 150 caCert, err := helpers.ParseCertificatePEM(ca.Cert) 151 if err != nil { 152 t.Errorf("failed to parse generated cert: %v", err) 153 } 154 if !caCert.IsCA { 155 t.Errorf("generated cert is not CA") 156 } 157 158 if caCert.Subject.CommonName != p.Cluster.Name { 159 t.Errorf("common name mismatch: expected %q, got %q", p.Cluster.Name, caCert.Subject.CommonName) 160 } 161 } 162 163 func TestGeneratedClusterCAWrittenToDestinationDir(t *testing.T) { 164 pki := getPKI(t) 165 defer cleanup(pki.GeneratedCertsDirectory, t) 166 167 p := getPlan() 168 _, err := pki.GenerateClusterCA(p) 169 if err != nil { 170 t.Errorf("error generating cluster CA: %v", err) 171 } 172 destDir := pki.GeneratedCertsDirectory 173 certFile := filepath.Join(destDir, "ca.pem") 174 _, err = os.Stat(certFile) 175 if err != nil { 176 if os.IsNotExist(err) { 177 t.Error("generated certificate was not created in dest directory") 178 } else { 179 t.Errorf("error validating file existence: %v", err) 180 } 181 } 182 keyFile := filepath.Join(destDir, "ca-key.pem") 183 _, err = os.Stat(keyFile) 184 if err != nil { 185 if os.IsNotExist(err) { 186 t.Error("key not found for generated CA") 187 } else { 188 t.Errorf("error checking if CA private key exists: %v", err) 189 } 190 } 191 } 192 193 func TestClusterCAExistsGenerationSkipped(t *testing.T) { 194 pki := getPKI(t) 195 defer cleanup(pki.GeneratedCertsDirectory, t) 196 197 caFile := filepath.Join(pki.GeneratedCertsDirectory, "ca.pem") 198 if _, err := os.Create(caFile); err != nil { 199 t.Fatalf("error creating ca.pem file: %v", err) 200 } 201 202 keyFile := filepath.Join(pki.GeneratedCertsDirectory, "ca-key.pem") 203 if _, err := os.Create(keyFile); err != nil { 204 t.Fatalf("error creating ca-key.pem: %v", err) 205 } 206 207 if _, err := pki.GenerateClusterCA(&Plan{}); err != nil { 208 t.Fatalf("generate CA method returned error: %v", err) 209 } 210 211 // Verify cert file wasn't touched 212 caContents, err := ioutil.ReadFile(caFile) 213 if err != nil { 214 t.Errorf("error getting contents for %q: %v", caFile, err) 215 } 216 if len(caContents) != 0 { 217 t.Error("CA File was modified") 218 } 219 220 // Verify key file wasn't touched 221 keyContents, err := ioutil.ReadFile(keyFile) 222 if err != nil { 223 t.Fatalf("error getting stat for %q: %v", keyFile, err) 224 } 225 if len(keyContents) != 0 { 226 t.Error("Key file was modified") 227 } 228 } 229 230 func TestGenerateClusterCAPlanFileExpirationIsRespected(t *testing.T) { 231 pki := getPKI(t) 232 defer cleanup(pki.GeneratedCertsDirectory, t) 233 234 p := getPlan() 235 236 validity := 5 * 365 * 24 * time.Hour // 5 years 237 p.Cluster.Certificates.Expiry = validity.String() 238 239 ca, err := pki.GenerateClusterCA(p) 240 if err != nil { 241 t.Fatalf("error generating CA for test: %v", err) 242 } 243 caCert, err := helpers.ParseCertificatePEM(ca.Cert) 244 if err != nil { 245 t.Errorf("failed to parse generated cert: %v", err) 246 } 247 248 expirationDate := time.Now().Add(validity) 249 if caCert.NotAfter.Year() != expirationDate.Year() || caCert.NotAfter.YearDay() != expirationDate.YearDay() { 250 t.Errorf("bad expiration date on generated cert. expected %v, got %v", expirationDate, caCert.NotAfter) 251 } 252 } 253 254 func TestGenerateClusterCertificatesExistingCertsAreNotRegen(t *testing.T) { 255 pki := getPKI(t) 256 defer cleanup(pki.GeneratedCertsDirectory, t) 257 258 p := getPlan() 259 ca, err := pki.GenerateClusterCA(p) 260 if err != nil { 261 t.Fatalf("error generating CA for test: %v", err) 262 } 263 proxyClientCA, err := pki.GenerateProxyClientCA(p) 264 if err != nil { 265 t.Fatalf("error generating proxy-client CA for test: %v", err) 266 } 267 if err = pki.GenerateClusterCertificates(p, ca, proxyClientCA); err != nil { 268 t.Fatalf("error generating cluster certificates: %v", err) 269 } 270 271 // Get the mod time of all the generated files 272 files, err := ioutil.ReadDir(pki.GeneratedCertsDirectory) 273 if err != nil { 274 t.Fatalf("error listing files in generated certs dir: %v", err) 275 } 276 modTime := map[string]time.Time{} 277 for _, f := range files { 278 modTime[f.Name()] = f.ModTime() 279 } 280 281 // Run generation again. Nothing should be touched. 282 if err = pki.GenerateClusterCertificates(p, ca, proxyClientCA); err != nil { 283 t.Fatalf("error generating cluster certificates: %v", err) 284 } 285 286 files2, err := ioutil.ReadDir(pki.GeneratedCertsDirectory) 287 if err != nil { 288 t.Fatalf("error listing files in generated certs dir: %v", err) 289 } 290 modTime2 := map[string]time.Time{} 291 for _, f := range files2 { 292 modTime2[f.Name()] = f.ModTime() 293 } 294 295 for k := range modTime { 296 if modTime[k] != modTime2[k] { 297 t.Errorf("file %s was modified. modification time changed from %v to %v", k, modTime[k], modTime2[k]) 298 } 299 } 300 } 301 302 func TestNodeCertExistsSkipGeneration(t *testing.T) { 303 pki := getPKI(t) 304 defer cleanup(pki.GeneratedCertsDirectory, t) 305 306 p := getPlan() 307 node := p.Master.Nodes[0] 308 309 // Create the node cert and key file 310 ca, err := pki.GenerateClusterCA(p) 311 if err != nil { 312 t.Fatalf("error generating CA for test: %v", err) 313 } 314 if err = pki.GenerateNodeCertificate(p, node, ca); err != nil { 315 t.Fatalf("failed to generate certs: %v", err) 316 } 317 318 // Get the mod time of all the generated files 319 files, err := ioutil.ReadDir(pki.GeneratedCertsDirectory) 320 if err != nil { 321 t.Fatalf("error listing files in generated certs dir: %v", err) 322 } 323 modTime := map[string]time.Time{} 324 for _, f := range files { 325 modTime[f.Name()] = f.ModTime() 326 } 327 328 // Run generation again 329 if err = pki.GenerateNodeCertificate(p, node, ca); err != nil { 330 t.Fatalf("failed to generate certs: %v", err) 331 } 332 333 // Assert files did not change 334 files2, err := ioutil.ReadDir(pki.GeneratedCertsDirectory) 335 if err != nil { 336 t.Fatalf("error listing files in generated certs dir: %v", err) 337 } 338 modTime2 := map[string]time.Time{} 339 for _, f := range files2 { 340 modTime2[f.Name()] = f.ModTime() 341 } 342 343 for k := range modTime { 344 if modTime[k] != modTime2[k] { 345 t.Errorf("file %s was modified. modification time changed from %v to %v", k, modTime[k], modTime2[k]) 346 } 347 } 348 } 349 350 func TestGenerateClusterCertificatesValidateCertificateInformation(t *testing.T) { 351 pki := getPKI(t) 352 defer cleanup(pki.GeneratedCertsDirectory, t) 353 354 p := getPlan() 355 ca, err := pki.GenerateClusterCA(p) 356 if err != nil { 357 t.Fatalf("failed to generate cluster CA") 358 } 359 proxyClientCA, err := pki.GenerateProxyClientCA(p) 360 if err != nil { 361 t.Fatalf("failed to generate proxy-client CA") 362 } 363 etcdNode := p.Etcd.Nodes[0] 364 masterNode := p.Master.Nodes[0] 365 workerNode := p.Worker.Nodes[0] 366 ingressNode := p.Ingress.Nodes[0] 367 storageNode := p.Storage.Nodes[0] 368 369 // Generate the cluster certificates 370 err = pki.GenerateClusterCertificates(p, ca, proxyClientCA) 371 if err != nil { 372 t.Fatalf("failed to generate cluster certificates") 373 } 374 375 t.Run("etcd node: etcd server certificate", func(t *testing.T) { 376 certFilename := fmt.Sprintf("%s-etcd.pem", etcdNode.Host) 377 cert := mustReadCertFile(filepath.Join(pki.GeneratedCertsDirectory, certFilename), t) 378 379 if cert.Subject.CommonName != etcdNode.Host { 380 t.Errorf("expected common name %q but got %q", etcdNode.Host, cert.Subject.CommonName) 381 } 382 383 var found bool 384 for _, n := range cert.DNSNames { 385 if n == etcdNode.Host { 386 found = true 387 break 388 } 389 } 390 if !found { 391 t.Errorf("did not find node's hostname %q in certificate DNS names %v", etcdNode.Host, cert.DNSNames) 392 } 393 394 found = false 395 internalFound := false 396 nodeIP := net.ParseIP(etcdNode.IP) 397 internalIP := net.ParseIP(etcdNode.IP) 398 for _, ip := range cert.IPAddresses { 399 if ip.Equal(nodeIP) { 400 found = true 401 } 402 if ip.Equal(internalIP) { 403 internalFound = true 404 } 405 } 406 if !found { 407 t.Errorf("did not find node's IP address %q in cert's IP addresses %v", etcdNode.IP, cert.IPAddresses) 408 } 409 if !internalFound { 410 t.Errorf("did not find node's internal IP address %q in cert's IP address %v", etcdNode.InternalIP, cert.IPAddresses) 411 } 412 }) 413 414 // Verify the API server certificate 415 t.Run("master node: api server certificate", func(t *testing.T) { 416 certFilename := fmt.Sprintf("%s-apiserver.pem", masterNode.Host) 417 cert := mustReadCertFile(filepath.Join(pki.GeneratedCertsDirectory, certFilename), t) 418 419 // The common name should match the hostname 420 if cert.Subject.CommonName != masterNode.Host { 421 t.Errorf("expected common name: %q, but got %q", masterNode.Host, cert.Subject.CommonName) 422 } 423 424 // DNS names should contain node's hostname and load balanced names 425 for _, expected := range []string{masterNode.Host, p.Master.LoadBalancedFQDN, p.Master.LoadBalancedShortName} { 426 var found bool 427 for _, n := range cert.DNSNames { 428 if n == expected { 429 found = true 430 break 431 } 432 } 433 if !found { 434 t.Errorf("did not find name %q in the certificates DNS names: %v", masterNode.Host, cert.DNSNames) 435 } 436 } 437 438 // Node's ip and private ip should be in the cert 439 found := false 440 internalFound := false 441 nodeIP := net.ParseIP(masterNode.IP) 442 internalIP := net.ParseIP(masterNode.InternalIP) 443 for _, ip := range cert.IPAddresses { 444 if ip.Equal(nodeIP) { 445 found = true 446 } 447 if ip.Equal(internalIP) { 448 internalFound = true 449 } 450 } 451 if !found { 452 t.Errorf("did not find node's IP %q in the certificate's IPs: %v", masterNode.IP, cert.IPAddresses) 453 } 454 if !internalFound { 455 t.Errorf("did not find node's internal IP %q in the certificate's IPs: %v", masterNode.InternalIP, cert.IPAddresses) 456 } 457 }) 458 459 // Validate API server client certificates 460 tests := []struct { 461 name string 462 certFilename string 463 issuer string 464 expectedCommonName string 465 expectedSubjectAlternateNames []string 466 expectedOrganizations []string 467 }{ 468 { 469 name: "kube scheduler certificate", 470 certFilename: "kube-scheduler.pem", 471 issuer: "someName", 472 expectedCommonName: "system:kube-scheduler", 473 }, 474 { 475 name: "kube controller mgr certificate", 476 certFilename: "kube-controller-manager.pem", 477 issuer: "someName", 478 expectedCommonName: "system:kube-controller-manager", 479 }, 480 { 481 name: "master node/kubelet certificate", 482 certFilename: fmt.Sprintf("%s-kubelet.pem", masterNode.Host), 483 issuer: "someName", 484 expectedCommonName: fmt.Sprintf("system:node:%s", masterNode.Host), 485 expectedSubjectAlternateNames: []string{masterNode.Host, masterNode.InternalIP}, 486 expectedOrganizations: []string{"system:nodes"}, 487 }, 488 { 489 name: "worker node/kubelet certificate", 490 certFilename: fmt.Sprintf("%s-kubelet.pem", workerNode.Host), 491 issuer: "someName", 492 expectedCommonName: fmt.Sprintf("system:node:%s", workerNode.Host), 493 expectedSubjectAlternateNames: []string{workerNode.Host, workerNode.InternalIP}, 494 expectedOrganizations: []string{"system:nodes"}, 495 }, 496 { 497 name: "ingress node/kubelet certificate", 498 certFilename: fmt.Sprintf("%s-kubelet.pem", ingressNode.Host), 499 issuer: "someName", 500 expectedCommonName: fmt.Sprintf("system:node:%s", ingressNode.Host), 501 expectedSubjectAlternateNames: []string{ingressNode.Host, ingressNode.InternalIP}, 502 expectedOrganizations: []string{"system:nodes"}, 503 }, 504 { 505 name: "storage node/kubelet certificate", 506 certFilename: fmt.Sprintf("%s-kubelet.pem", storageNode.Host), 507 issuer: "someName", 508 expectedCommonName: fmt.Sprintf("system:node:%s", storageNode.Host), 509 expectedSubjectAlternateNames: []string{storageNode.Host, storageNode.InternalIP}, 510 expectedOrganizations: []string{"system:nodes"}, 511 }, 512 { 513 name: "admin user certificate", 514 certFilename: "admin.pem", 515 issuer: "someName", 516 expectedCommonName: "admin", 517 expectedOrganizations: []string{"system:masters"}, 518 }, 519 { 520 name: "kube-apiserver kubelet client certificate", 521 certFilename: "apiserver-kubelet-client.pem", 522 issuer: "someName", 523 expectedCommonName: "kube-apiserver-kubelet-client", 524 expectedOrganizations: []string{"system:masters"}, 525 }, 526 { 527 name: "proxy client certificate", 528 certFilename: "proxy-client.pem", 529 issuer: "proxyClientCA", 530 expectedCommonName: "aggregator", 531 expectedOrganizations: []string{"system:masters"}, 532 }, 533 } 534 535 for _, test := range tests { 536 t.Run(test.name, validateClientCertificateAndKey(pki.GeneratedCertsDirectory, 537 test.certFilename, test.issuer, test.expectedCommonName, test.expectedSubjectAlternateNames, test.expectedOrganizations...)) 538 } 539 } 540 541 func TestGenerateClusterCertificatesPlanFileExpirationIsRespected(t *testing.T) { 542 pki := getPKI(t) 543 defer cleanup(pki.GeneratedCertsDirectory, t) 544 545 p := getPlan() 546 547 validity := 5 * 365 * 24 * time.Hour // 5 years 548 p.Cluster.Certificates.Expiry = validity.String() 549 550 ca, err := pki.GenerateClusterCA(p) 551 if err != nil { 552 t.Fatalf("error generating CA for test: %v", err) 553 } 554 node := p.Master.Nodes[0] 555 if err := pki.GenerateNodeCertificate(p, node, ca); err != nil { 556 t.Fatalf("failed to generate certificate for node: %v", err) 557 } 558 certFile := filepath.Join(pki.GeneratedCertsDirectory, fmt.Sprintf("%s-apiserver.pem", node.Host)) 559 cert := mustReadCertFile(certFile, t) 560 561 expirationDate := time.Now().Add(validity) 562 if cert.NotAfter.Year() != expirationDate.Year() || cert.NotAfter.YearDay() != expirationDate.YearDay() { 563 t.Errorf("bad expiration date on generated cert. expected %v, got %v", expirationDate, cert.NotAfter) 564 } 565 } 566 567 func validateClientCertificateAndKey(certsDir, filename, expectedIssuer string, expectedCommonName string, expectedSubjectAlternateNames []string, expectedOrganizations ...string) func(t *testing.T) { 568 return func(t *testing.T) { 569 cert := mustReadCertFile(filepath.Join(certsDir, filename), t) 570 if expectedCommonName != cert.Subject.CommonName { 571 t.Errorf("Expected common name %q but got %q", expectedCommonName, cert.Subject.CommonName) 572 } 573 574 altNames := []string{} 575 altNames = append(altNames, cert.DNSNames...) 576 for _, ip := range cert.IPAddresses { 577 altNames = append(altNames, ip.String()) 578 } 579 if !util.Subset(expectedSubjectAlternateNames, altNames) { 580 t.Errorf("Expected subject alternate names: %s, but got %s", expectedSubjectAlternateNames, cert.DNSNames) 581 } 582 583 if !reflect.DeepEqual(cert.Subject.Organization, expectedOrganizations) { 584 t.Errorf("Expected organizations: %v, but got %v", expectedOrganizations, cert.Subject.Organization) 585 } 586 587 if cert.Issuer.CommonName != expectedIssuer { 588 t.Errorf("Expected issuer: %s, but got %s", expectedIssuer, cert.Issuer.CommonName) 589 } 590 } 591 } 592 593 func TestLoadBalancedNamesNotInEtcdCert(t *testing.T) { 594 pki := getPKI(t) 595 defer cleanup(pki.GeneratedCertsDirectory, t) 596 597 p := getPlan() 598 p.Worker = NodeGroup{} 599 600 ca, err := pki.GenerateClusterCA(p) 601 if err != nil { 602 t.Fatalf("error generating CA for test: %v", err) 603 } 604 if err = pki.GenerateNodeCertificate(p, p.Etcd.Nodes[0], ca); err != nil { 605 t.Fatalf("failed to generate certs: %v", err) 606 } 607 608 // Verify master node has load balanced name and FQDN 609 certFile := filepath.Join(pki.GeneratedCertsDirectory, "etcd01-etcd.pem") 610 cert := mustReadCertFile(certFile, t) 611 612 found := false 613 for _, name := range cert.DNSNames { 614 if name == p.Master.LoadBalancedFQDN { 615 found = true 616 break 617 } 618 } 619 if found { 620 t.Errorf("load balanced FQDN was found in etcd certificate") 621 } 622 623 found = false 624 for _, name := range cert.DNSNames { 625 if name == p.Master.LoadBalancedShortName { 626 found = true 627 break 628 } 629 } 630 if found { 631 t.Errorf("load balanced name was found in etcd certificate") 632 } 633 } 634 635 func TestContivProxyServerCertGenerated(t *testing.T) { 636 pki := getPKI(t) 637 defer cleanup(pki.GeneratedCertsDirectory, t) 638 639 p := getPlan() 640 p.AddOns.CNI = &CNI{Provider: cniProviderContiv} 641 642 ca, err := pki.GenerateClusterCA(p) 643 if err != nil { 644 t.Fatalf("error generating CA for test: %v", err) 645 } 646 proxyClientCA, err := pki.GenerateClusterCA(p) 647 if err != nil { 648 t.Fatalf("error generating proxy-client CA for test: %v", err) 649 } 650 if err = pki.GenerateClusterCertificates(p, ca, proxyClientCA); err != nil { 651 t.Fatalf("failed to generate certs: %v", err) 652 } 653 certFile := filepath.Join(pki.GeneratedCertsDirectory, "contiv-proxy-server.pem") 654 mustReadCertFile(certFile, t) 655 } 656 657 func TestInvalidNodeCertificateShouldFailValidation(t *testing.T) { 658 pki := getPKI(t) 659 defer cleanup(pki.GeneratedCertsDirectory, t) 660 661 p := getPlan() 662 663 ca, err := pki.GenerateClusterCA(p) 664 if err != nil { 665 t.Fatalf("error generating CA for test: %v", err) 666 } 667 proxyClientCA, err := pki.GenerateProxyClientCA(p) 668 if err != nil { 669 t.Fatalf("error generating proxy-client CA for test: %v", err) 670 } 671 if err = pki.GenerateNodeCertificate(p, p.Master.Nodes[0], ca); err != nil { 672 t.Fatalf("failed to generate certs: %v", err) 673 } 674 675 // Change the IP so that the IPs don't match the valid ones 676 p.Master.Nodes[0] = Node{ 677 Host: "master01", 678 IP: "11.12.13.14", 679 InternalIP: "22.33.44.55", 680 } 681 682 err = pki.GenerateClusterCertificates(p, ca, proxyClientCA) 683 if err == nil { 684 t.Fatalf("expected an error, got nil") 685 } 686 } 687 688 func TestAPIServerCertNoEmptyDNSNames(t *testing.T) { 689 pki := getPKI(t) 690 defer cleanup(pki.GeneratedCertsDirectory, t) 691 692 p := getPlan() 693 694 ca, err := pki.GenerateClusterCA(p) 695 if err != nil { 696 t.Fatalf("error generating CA for test: %v", err) 697 } 698 node := p.Master.Nodes[0] 699 if err := pki.GenerateNodeCertificate(p, node, ca); err != nil { 700 t.Fatalf("failed to generate certificate for node: %v", err) 701 } 702 certFile := filepath.Join(pki.GeneratedCertsDirectory, fmt.Sprintf("%s-apiserver.pem", node.Host)) 703 cert := mustReadCertFile(certFile, t) 704 for _, name := range cert.DNSNames { 705 if name == "" { 706 t.Errorf("found an empty DNS name") 707 } 708 } 709 } 710 711 func TestAPIServerCertContainsInternalIP(t *testing.T) { 712 pki := getPKI(t) 713 defer cleanup(pki.GeneratedCertsDirectory, t) 714 715 p := getPlan() 716 ca, err := pki.GenerateClusterCA(p) 717 if err != nil { 718 t.Fatalf("error generating CA for test: %v", err) 719 } 720 node := p.Master.Nodes[0] 721 if err := pki.GenerateNodeCertificate(p, node, ca); err != nil { 722 t.Fatalf("failed to generate certificate for node: %v", err) 723 } 724 certFile := filepath.Join(pki.GeneratedCertsDirectory, fmt.Sprintf("%s-apiserver.pem", node.Host)) 725 cert := mustReadCertFile(certFile, t) 726 found := false 727 internalIP := net.ParseIP(node.InternalIP) 728 for _, ip := range cert.IPAddresses { 729 if ip.Equal(internalIP) { 730 found = true 731 break 732 } 733 } 734 if !found { 735 t.Error("Node certificate does not have the internal IP as a DNS name") 736 } 737 } 738 739 func TestValidateClusterCertificatesNoExistingCerts(t *testing.T) { 740 pki := getPKI(t) 741 defer cleanup(pki.GeneratedCertsDirectory, t) 742 743 warn, err := pki.ValidateClusterCertificates(getPlan()) 744 if len(err) != 0 { 745 t.Errorf("expected no errors when validating directory with no certificates, but got: %v", err) 746 } 747 if len(warn) != 0 { 748 t.Errorf("expected no warnings when validating directory with no certificates, but got: %v", warn) 749 } 750 } 751 752 func TestValidateClusterCertificatesWithValidExistingCerts(t *testing.T) { 753 pki := getPKI(t) 754 defer cleanup(pki.GeneratedCertsDirectory, t) 755 756 p := getPlan() 757 758 ca, err := pki.GenerateClusterCA(p) 759 if err != nil { 760 t.Fatalf("error generating CA for test: %v", err) 761 } 762 proxyClientCA, err := pki.GenerateProxyClientCA(p) 763 if err != nil { 764 t.Fatalf("error generating proxy-client CA for test: %v", err) 765 } 766 if err = pki.GenerateClusterCertificates(p, ca, proxyClientCA); err != nil { 767 t.Fatalf("failed to generate certs: %v", err) 768 } 769 770 warn, errs := pki.ValidateClusterCertificates(p) 771 if len(errs) != 0 { 772 t.Errorf("expected no errors when validating certs that are valid, but got: %v", err) 773 } 774 if len(warn) != 0 { 775 t.Errorf("expected no warnings when validating certs that are valid, but got: %v", warn) 776 } 777 } 778 779 func TestValidateClusterCertificatesInvalidCerts(t *testing.T) { 780 pki := getPKI(t) 781 defer cleanup(pki.GeneratedCertsDirectory, t) 782 783 p := getPlan() 784 785 ca, err := pki.GenerateClusterCA(p) 786 if err != nil { 787 t.Fatalf("error generating CA for test: %v", err) 788 } 789 proxyClientCA, err := pki.GenerateProxyClientCA(p) 790 if err != nil { 791 t.Fatalf("error generating proxy-client CA for test: %v", err) 792 } 793 if err = pki.GenerateClusterCertificates(p, ca, proxyClientCA); err != nil { 794 t.Fatalf("failed to generate certs: %v", err) 795 } 796 797 tests := []struct { 798 description string 799 plan func(Plan) Plan 800 expectedWarnings int 801 }{ 802 { 803 description: "bad etcd certificate", 804 plan: func(p Plan) Plan { 805 etcd := p.Etcd.Nodes[0] 806 etcd.IP = "20.0.0.1" 807 p.Etcd.Nodes = []Node{etcd} 808 return p 809 }, 810 expectedWarnings: 1, 811 }, 812 { 813 description: "bad master certificates", 814 plan: func(p Plan) Plan { 815 master := p.Master.Nodes[0] 816 master.IP = "20.0.0.1" 817 p.Master.Nodes = []Node{master} 818 return p 819 }, 820 expectedWarnings: 1, 821 }, 822 } 823 824 for _, test := range tests { 825 t.Run(test.description, func(t *testing.T) { 826 plan := test.plan(*p) 827 warn, errs := pki.ValidateClusterCertificates(&plan) 828 if len(errs) != 0 { 829 t.Fatalf("unexpected error occurred validating certs: %v", err) 830 } 831 if len(warn) != test.expectedWarnings { 832 t.Errorf("expected %d warnings, but got %d. warnings were: %v", test.expectedWarnings, len(warn), warn) 833 } 834 }) 835 } 836 } 837 838 func TestCertSpecEqual(t *testing.T) { 839 tests := []struct { 840 x certificateSpec 841 y certificateSpec 842 equal bool 843 }{ 844 { 845 x: certificateSpec{}, 846 y: certificateSpec{}, 847 equal: true, 848 }, 849 { 850 x: certificateSpec{ 851 description: "foo", 852 }, 853 y: certificateSpec{ 854 description: "foo", 855 }, 856 equal: true, 857 }, 858 { 859 x: certificateSpec{ 860 description: "foo", 861 }, 862 y: certificateSpec{ 863 description: "bar", 864 }, 865 equal: false, 866 }, 867 { 868 x: certificateSpec{ 869 filename: "foo", 870 }, 871 y: certificateSpec{ 872 filename: "bar", 873 }, 874 equal: false, 875 }, 876 { 877 x: certificateSpec{ 878 filename: "foo", 879 }, 880 y: certificateSpec{ 881 filename: "foo", 882 }, 883 equal: true, 884 }, 885 { 886 x: certificateSpec{ 887 commonName: "foo", 888 }, 889 y: certificateSpec{ 890 commonName: "bar", 891 }, 892 equal: false, 893 }, 894 { 895 x: certificateSpec{ 896 subjectAlternateNames: []string{"foo"}, 897 }, 898 y: certificateSpec{ 899 subjectAlternateNames: []string{"foo"}, 900 }, 901 equal: true, 902 }, 903 { 904 x: certificateSpec{ 905 subjectAlternateNames: []string{"foo"}, 906 }, 907 y: certificateSpec{ 908 subjectAlternateNames: []string{"bar"}, 909 }, 910 equal: false, 911 }, 912 { 913 x: certificateSpec{ 914 organizations: []string{"foo"}, 915 }, 916 y: certificateSpec{ 917 organizations: []string{"foo"}, 918 }, 919 equal: true, 920 }, 921 { 922 x: certificateSpec{ 923 organizations: []string{"foo"}, 924 }, 925 y: certificateSpec{ 926 organizations: []string{"bar"}, 927 }, 928 equal: false, 929 }, 930 { 931 x: certificateSpec{ 932 description: "foo", 933 filename: "foo", 934 commonName: "foo", 935 subjectAlternateNames: []string{"foo"}, 936 organizations: []string{"foo"}, 937 }, 938 y: certificateSpec{ 939 description: "foo", 940 filename: "foo", 941 commonName: "foo", 942 subjectAlternateNames: []string{"foo"}, 943 organizations: []string{"foo"}, 944 }, 945 equal: true, 946 }, 947 { 948 x: certificateSpec{ 949 description: "foo", 950 filename: "foo", 951 commonName: "foo", 952 subjectAlternateNames: []string{"foo"}, 953 organizations: []string{"foo"}, 954 }, 955 y: certificateSpec{ 956 description: "foo", 957 filename: "bar", 958 commonName: "foo", 959 subjectAlternateNames: []string{"foo"}, 960 organizations: []string{"foo"}, 961 }, 962 equal: false, 963 }, 964 { 965 x: certificateSpec{ 966 description: "foo", 967 filename: "foo", 968 commonName: "foo", 969 subjectAlternateNames: []string{"foo"}, 970 organizations: []string{"foo"}, 971 }, 972 y: certificateSpec{ 973 description: "foo", 974 filename: "foo", 975 commonName: "bar", 976 subjectAlternateNames: []string{"foo"}, 977 organizations: []string{"foo"}, 978 }, 979 equal: false, 980 }, 981 { 982 x: certificateSpec{ 983 description: "foo", 984 filename: "foo", 985 commonName: "foo", 986 subjectAlternateNames: []string{"foo"}, 987 organizations: []string{"foo"}, 988 }, 989 y: certificateSpec{ 990 description: "foo", 991 filename: "foo", 992 commonName: "foo", 993 subjectAlternateNames: []string{"bar"}, 994 organizations: []string{"foo"}, 995 }, 996 equal: false, 997 }, 998 { 999 x: certificateSpec{ 1000 description: "foo", 1001 filename: "foo", 1002 commonName: "foo", 1003 subjectAlternateNames: []string{"foo"}, 1004 organizations: []string{"foo"}, 1005 }, 1006 y: certificateSpec{ 1007 description: "foo", 1008 filename: "foo", 1009 commonName: "foo", 1010 subjectAlternateNames: []string{"foo"}, 1011 organizations: []string{"bar"}, 1012 }, 1013 equal: false, 1014 }, 1015 { 1016 x: certificateSpec{ 1017 description: "foo", 1018 filename: "foo", 1019 commonName: "foo", 1020 subjectAlternateNames: []string{"foo"}, 1021 organizations: []string{"foo"}, 1022 }, 1023 y: certificateSpec{ 1024 description: "foo", 1025 filename: "foo", 1026 commonName: "foo", 1027 subjectAlternateNames: []string{"foo", "bar"}, 1028 organizations: []string{"foo"}, 1029 }, 1030 equal: false, 1031 }, 1032 } 1033 1034 for _, test := range tests { 1035 if test.x.equal(test.y) != test.equal { 1036 t.Errorf("expected equal = %v, but got %v. x = %+v, y = %+v", test.equal, !test.equal, test.x, test.y) 1037 } 1038 if test.y.equal(test.x) != test.equal { 1039 t.Errorf("expected equal = %v, but got %v. x = %+v, y = %+v", test.equal, !test.equal, test.x, test.y) 1040 } 1041 } 1042 } 1043 1044 func TestGenerateCertificate(t *testing.T) { 1045 pki := getPKI(t) 1046 defer cleanup(pki.GeneratedCertsDirectory, t) 1047 1048 ca, err := pki.GenerateClusterCA(getPlan()) 1049 if err != nil { 1050 t.Fatalf("error generating CA for test: %v", err) 1051 } 1052 1053 tests := []struct { 1054 name string 1055 validityPeriod string 1056 commonName string 1057 subjectAlternateNames []string 1058 organizations []string 1059 ca *tls.CA 1060 overwrite bool 1061 exists bool 1062 valid bool 1063 }{ 1064 { 1065 name: "foo", 1066 commonName: "foo", 1067 validityPeriod: "8650h", 1068 ca: ca, 1069 valid: true, 1070 }, 1071 { 1072 name: "bar", 1073 validityPeriod: "17300h", 1074 commonName: "bar-cn", 1075 subjectAlternateNames: []string{"bar-alt"}, 1076 organizations: []string{"admin"}, 1077 ca: ca, 1078 valid: true, 1079 }, 1080 { 1081 name: "foo", 1082 commonName: "foo", 1083 validityPeriod: "8650h", 1084 ca: ca, 1085 exists: true, 1086 valid: true, 1087 }, 1088 { 1089 name: "foo", 1090 validityPeriod: "8650h", 1091 commonName: "foo-cn", 1092 subjectAlternateNames: []string{"foo-alt"}, 1093 organizations: []string{"admin"}, 1094 ca: ca, 1095 overwrite: true, 1096 exists: true, 1097 valid: true, 1098 }, 1099 { 1100 name: "alice", 1101 ca: ca, 1102 valid: false, 1103 }, 1104 { 1105 validityPeriod: "8650h", 1106 ca: ca, 1107 valid: false, 1108 }, 1109 { 1110 name: "alice", 1111 validityPeriod: "8650h", 1112 valid: false, 1113 }, 1114 } 1115 for i, test := range tests { 1116 exists, err := pki.GenerateCertificate(test.name, test.validityPeriod, test.commonName, test.subjectAlternateNames, test.organizations, test.ca, test.overwrite) 1117 1118 if (err != nil) == test.valid { 1119 t.Errorf("test %d: expect valid to be %t, but got %v", i, test.valid, err) 1120 } 1121 if exists != test.exists { 1122 t.Errorf("test %d: expect exists to be %t, but got %t", i, test.exists, exists) 1123 } 1124 1125 if test.valid { 1126 cert := mustReadCertFile(filepath.Join(pki.GeneratedCertsDirectory, fmt.Sprintf("%s.pem", test.name)), t) 1127 if cert.Subject.CommonName != test.commonName { 1128 t.Errorf("test %d: expect commonName to be %s, but got %s", i, test.commonName, cert.Subject.CommonName) 1129 } 1130 if !util.Subset(cert.DNSNames, test.subjectAlternateNames) { 1131 t.Errorf("test %d: expect subjectAlternateNames to be %v, but got %v", i, test.subjectAlternateNames, cert.DNSNames) 1132 } 1133 if !util.Subset(cert.Subject.Organization, test.organizations) { 1134 t.Errorf("test %d: expect organizations to be %v, but got %v", i, test.organizations, cert.Subject.Organization) 1135 } 1136 if test.validityPeriod != "" { 1137 1138 } 1139 validity, err := strconv.Atoi(strings.TrimRight(test.validityPeriod, "h")) 1140 if err != nil { 1141 t.Errorf("test %d: could not parse validityPeriod %s", i, test.validityPeriod) 1142 } else { 1143 expirationDate := time.Now().Add(time.Duration(validity) * time.Hour) 1144 if cert.NotAfter.Year() != expirationDate.Year() || cert.NotAfter.YearDay() != expirationDate.YearDay() { 1145 t.Errorf("test %d: bad expiration date on generated cert. expected %v, got %v", i, expirationDate, cert.NotAfter) 1146 } 1147 } 1148 } 1149 } 1150 }