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  }