...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/renewal/manager_test.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/renewal

     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 renewal
    18  
    19  import (
    20  	"crypto"
    21  	"crypto/x509"
    22  	"crypto/x509/pkix"
    23  	"fmt"
    24  	"net"
    25  	"os"
    26  	"path/filepath"
    27  	"reflect"
    28  	"testing"
    29  	"time"
    30  
    31  	certutil "k8s.io/client-go/util/cert"
    32  	netutils "k8s.io/utils/net"
    33  
    34  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    35  	certtestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs"
    36  	"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
    37  	testutil "k8s.io/kubernetes/cmd/kubeadm/test"
    38  )
    39  
    40  var (
    41  	testCACertCfg = &pkiutil.CertConfig{
    42  		Config: certutil.Config{CommonName: "kubernetes"},
    43  	}
    44  
    45  	testCACert, testCAKey, _ = pkiutil.NewCertificateAuthority(testCACertCfg)
    46  
    47  	testCertOrganization = []string{"sig-cluster-lifecycle"}
    48  
    49  	testCertCfg = makeTestCertConfig(testCertOrganization)
    50  )
    51  
    52  type fakecertificateReadWriter struct {
    53  	exist bool
    54  	cert  *x509.Certificate
    55  }
    56  
    57  func (cr fakecertificateReadWriter) Exists() (bool, error) {
    58  	return cr.exist, nil
    59  }
    60  
    61  func (cr fakecertificateReadWriter) Read() (*x509.Certificate, error) {
    62  	return cr.cert, nil
    63  }
    64  
    65  func (cr fakecertificateReadWriter) Write(*x509.Certificate, crypto.Signer) error {
    66  	return nil
    67  }
    68  
    69  func TestNewManager(t *testing.T) {
    70  	tests := []struct {
    71  		name                 string
    72  		cfg                  *kubeadmapi.ClusterConfiguration
    73  		expectedCertificates int
    74  	}{
    75  		{
    76  			name:                 "cluster with local etcd",
    77  			cfg:                  &kubeadmapi.ClusterConfiguration{},
    78  			expectedCertificates: 11, // [admin super-admin apiserver apiserver-etcd-client apiserver-kubelet-client controller-manager etcd/healthcheck-client etcd/peer etcd/server front-proxy-client scheduler]
    79  		},
    80  		{
    81  			name: "cluster with external etcd",
    82  			cfg: &kubeadmapi.ClusterConfiguration{
    83  				Etcd: kubeadmapi.Etcd{
    84  					External: &kubeadmapi.ExternalEtcd{},
    85  				},
    86  			},
    87  			expectedCertificates: 7, // [admin super-admin apiserver apiserver-kubelet-client controller-manager front-proxy-client scheduler]
    88  		},
    89  	}
    90  
    91  	for _, test := range tests {
    92  		t.Run(test.name, func(t *testing.T) {
    93  			rm, err := NewManager(test.cfg, "")
    94  			if err != nil {
    95  				t.Fatalf("Failed to create the certificate renewal manager: %v", err)
    96  			}
    97  
    98  			if len(rm.Certificates()) != test.expectedCertificates {
    99  				t.Errorf("Expected %d certificates, saw %d", test.expectedCertificates, len(rm.Certificates()))
   100  			}
   101  		})
   102  	}
   103  }
   104  
   105  func TestRenewUsingLocalCA(t *testing.T) {
   106  	dir := testutil.SetupTempDir(t)
   107  	defer os.RemoveAll(dir)
   108  
   109  	if err := pkiutil.WriteCertAndKey(dir, "ca", testCACert, testCAKey); err != nil {
   110  		t.Fatalf("couldn't write out CA certificate to %s", dir)
   111  	}
   112  
   113  	etcdDir := filepath.Join(dir, "etcd")
   114  	if err := pkiutil.WriteCertAndKey(etcdDir, "ca", testCACert, testCAKey); err != nil {
   115  		t.Fatalf("couldn't write out CA certificate to %s", etcdDir)
   116  	}
   117  
   118  	cfg := &kubeadmapi.ClusterConfiguration{
   119  		CertificatesDir: dir,
   120  	}
   121  	rm, err := NewManager(cfg, dir)
   122  	if err != nil {
   123  		t.Fatalf("Failed to create the certificate renewal manager: %v", err)
   124  	}
   125  
   126  	tests := []struct {
   127  		name                 string
   128  		certName             string
   129  		createCertFunc       func() *x509.Certificate
   130  		expectedOrganization []string
   131  	}{
   132  		{
   133  			name:     "Certificate renewal for a PKI certificate",
   134  			certName: "apiserver",
   135  			createCertFunc: func() *x509.Certificate {
   136  				return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey, testCertOrganization)
   137  			},
   138  			expectedOrganization: testCertOrganization,
   139  		},
   140  		{
   141  			name:     "Certificate renewal for a certificate embedded in a kubeconfig file",
   142  			certName: "admin.conf",
   143  			createCertFunc: func() *x509.Certificate {
   144  				return writeTestKubeconfig(t, dir, "admin.conf", testCACert, testCAKey)
   145  			},
   146  			expectedOrganization: testCertOrganization,
   147  		},
   148  	}
   149  
   150  	for _, test := range tests {
   151  		t.Run(test.name, func(t *testing.T) {
   152  			cert := test.createCertFunc()
   153  
   154  			time.Sleep(1 * time.Second)
   155  
   156  			_, err := rm.RenewUsingLocalCA(test.certName)
   157  			if err != nil {
   158  				t.Fatalf("error renewing certificate: %v", err)
   159  			}
   160  
   161  			newCert, err := rm.certificates[test.certName].readwriter.Read()
   162  			if err != nil {
   163  				t.Fatalf("error reading renewed certificate: %v", err)
   164  			}
   165  
   166  			if newCert.SerialNumber.Cmp(cert.SerialNumber) == 0 {
   167  				t.Fatal("expected new certificate, but renewed certificate has same serial number")
   168  			}
   169  
   170  			if !newCert.NotAfter.After(cert.NotAfter) {
   171  				t.Fatalf("expected new certificate with updated expiration, but renewed certificate has same NotAfter value: saw %s, expected greather than %s", newCert.NotAfter, cert.NotAfter)
   172  			}
   173  
   174  			certtestutil.AssertCertificateIsSignedByCa(t, newCert, testCACert)
   175  			certtestutil.AssertCertificateHasClientAuthUsage(t, newCert)
   176  			certtestutil.AssertCertificateHasOrganizations(t, newCert, test.expectedOrganization...)
   177  			certtestutil.AssertCertificateHasCommonName(t, newCert, testCertCfg.CommonName)
   178  			certtestutil.AssertCertificateHasDNSNames(t, newCert, testCertCfg.AltNames.DNSNames...)
   179  			certtestutil.AssertCertificateHasIPAddresses(t, newCert, testCertCfg.AltNames.IPs...)
   180  		})
   181  	}
   182  }
   183  
   184  func TestCreateRenewCSR(t *testing.T) {
   185  	dir := testutil.SetupTempDir(t)
   186  	defer os.RemoveAll(dir)
   187  
   188  	outdir := filepath.Join(dir, "out")
   189  
   190  	if err := os.MkdirAll(outdir, 0755); err != nil {
   191  		t.Fatalf("couldn't create %s", outdir)
   192  	}
   193  
   194  	if err := pkiutil.WriteCertAndKey(dir, "ca", testCACert, testCAKey); err != nil {
   195  		t.Fatalf("couldn't write out CA certificate to %s", dir)
   196  	}
   197  
   198  	cfg := &kubeadmapi.ClusterConfiguration{
   199  		CertificatesDir: dir,
   200  	}
   201  	rm, err := NewManager(cfg, dir)
   202  	if err != nil {
   203  		t.Fatalf("Failed to create the certificate renewal manager: %v", err)
   204  	}
   205  
   206  	tests := []struct {
   207  		name           string
   208  		certName       string
   209  		createCertFunc func() *x509.Certificate
   210  	}{
   211  		{
   212  			name:     "Creation of a CSR request for renewal of a PKI certificate",
   213  			certName: "apiserver",
   214  			createCertFunc: func() *x509.Certificate {
   215  				return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey, testCertOrganization)
   216  			},
   217  		},
   218  		{
   219  			name:     "Creation of a CSR request for renewal of a certificate embedded in a kubeconfig file",
   220  			certName: "admin.conf",
   221  			createCertFunc: func() *x509.Certificate {
   222  				return writeTestKubeconfig(t, dir, "admin.conf", testCACert, testCAKey)
   223  			},
   224  		},
   225  	}
   226  
   227  	for _, test := range tests {
   228  		t.Run(test.name, func(t *testing.T) {
   229  			test.createCertFunc()
   230  
   231  			time.Sleep(1 * time.Second)
   232  
   233  			err := rm.CreateRenewCSR(test.certName, outdir)
   234  			if err != nil {
   235  				t.Fatalf("error renewing certificate: %v", err)
   236  			}
   237  
   238  			file := fmt.Sprintf("%s.key", test.certName)
   239  			if _, err := os.Stat(filepath.Join(outdir, file)); os.IsNotExist(err) {
   240  				t.Errorf("Expected file %s does not exist", file)
   241  			}
   242  
   243  			file = fmt.Sprintf("%s.csr", test.certName)
   244  			if _, err := os.Stat(filepath.Join(outdir, file)); os.IsNotExist(err) {
   245  				t.Errorf("Expected file %s does not exist", file)
   246  			}
   247  		})
   248  	}
   249  
   250  }
   251  
   252  func TestCertToConfig(t *testing.T) {
   253  	expectedConfig := &certutil.Config{
   254  		CommonName:   "test-common-name",
   255  		Organization: testCertOrganization,
   256  		AltNames: certutil.AltNames{
   257  			IPs:      []net.IP{netutils.ParseIPSloppy("10.100.0.1")},
   258  			DNSNames: []string{"test-domain.space"},
   259  		},
   260  		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
   261  	}
   262  
   263  	cert := &x509.Certificate{
   264  		Subject: pkix.Name{
   265  			CommonName:   "test-common-name",
   266  			Organization: testCertOrganization,
   267  		},
   268  		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
   269  		DNSNames:    []string{"test-domain.space"},
   270  		IPAddresses: []net.IP{netutils.ParseIPSloppy("10.100.0.1")},
   271  	}
   272  
   273  	cfg := certToConfig(cert)
   274  
   275  	if cfg.CommonName != expectedConfig.CommonName {
   276  		t.Errorf("expected common name %q, got %q", expectedConfig.CommonName, cfg.CommonName)
   277  	}
   278  
   279  	if len(cfg.Organization) != 1 || cfg.Organization[0] != expectedConfig.Organization[0] {
   280  		t.Errorf("expected organization %v, got %v", expectedConfig.Organization, cfg.Organization)
   281  
   282  	}
   283  
   284  	if len(cfg.Usages) != 1 || cfg.Usages[0] != expectedConfig.Usages[0] {
   285  		t.Errorf("expected ext key usage %v, got %v", expectedConfig.Usages, cfg.Usages)
   286  	}
   287  
   288  	if len(cfg.AltNames.IPs) != 1 || cfg.AltNames.IPs[0].String() != expectedConfig.AltNames.IPs[0].String() {
   289  		t.Errorf("expected SAN IPs %v, got %v", expectedConfig.AltNames.IPs, cfg.AltNames.IPs)
   290  	}
   291  
   292  	if len(cfg.AltNames.DNSNames) != 1 || cfg.AltNames.DNSNames[0] != expectedConfig.AltNames.DNSNames[0] {
   293  		t.Errorf("expected SAN DNSNames %v, got %v", expectedConfig.AltNames.DNSNames, cfg.AltNames.DNSNames)
   294  	}
   295  }
   296  
   297  func makeTestCertConfig(organization []string) *pkiutil.CertConfig {
   298  	return &pkiutil.CertConfig{
   299  		Config: certutil.Config{
   300  			CommonName:   "test-common-name",
   301  			Organization: organization,
   302  			AltNames: certutil.AltNames{
   303  				IPs:      []net.IP{netutils.ParseIPSloppy("10.100.0.1")},
   304  				DNSNames: []string{"test-domain.space"},
   305  			},
   306  			Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
   307  		},
   308  	}
   309  }
   310  
   311  func TestManagerCAs(t *testing.T) {
   312  	tests := []struct {
   313  		name string
   314  		cas  map[string]*CAExpirationHandler
   315  		want []*CAExpirationHandler
   316  	}{
   317  		{
   318  			name: "CAExpirationHandler is sequential",
   319  			cas: map[string]*CAExpirationHandler{
   320  				"foo": {
   321  					Name: "1",
   322  				},
   323  				"bar": {
   324  					Name: "2",
   325  				},
   326  			},
   327  			want: []*CAExpirationHandler{
   328  				{
   329  					Name: "1",
   330  				},
   331  				{
   332  					Name: "2",
   333  				},
   334  			},
   335  		},
   336  		{
   337  			name: "CAExpirationHandler is in reverse order",
   338  			cas: map[string]*CAExpirationHandler{
   339  				"foo": {
   340  					Name: "2",
   341  				},
   342  				"bar": {
   343  					Name: "1",
   344  				},
   345  			},
   346  			want: []*CAExpirationHandler{
   347  				{
   348  					Name: "1",
   349  				},
   350  				{
   351  					Name: "2",
   352  				},
   353  			},
   354  		},
   355  	}
   356  	for _, tt := range tests {
   357  		t.Run(tt.name, func(t *testing.T) {
   358  			rm := &Manager{
   359  				cas: tt.cas,
   360  			}
   361  			if got := rm.CAs(); !reflect.DeepEqual(got, tt.want) {
   362  				t.Errorf("Manager.CAs() = %v, want %v", got, tt.want)
   363  			}
   364  		})
   365  	}
   366  }
   367  
   368  func TestManagerCAExists(t *testing.T) {
   369  	certificateReadWriterExist := fakecertificateReadWriter{
   370  		exist: true,
   371  	}
   372  	certificateReadWriterMissing := fakecertificateReadWriter{
   373  		exist: false,
   374  	}
   375  	tests := []struct {
   376  		name    string
   377  		cas     map[string]*CAExpirationHandler
   378  		caName  string
   379  		want    bool
   380  		wantErr bool
   381  	}{
   382  		{
   383  			name:    "caName does not exist in cas list",
   384  			cas:     map[string]*CAExpirationHandler{},
   385  			caName:  "foo",
   386  			want:    false,
   387  			wantErr: true,
   388  		},
   389  		{
   390  			name: "ca exists",
   391  			cas: map[string]*CAExpirationHandler{
   392  				"foo": {
   393  					Name:       "foo",
   394  					FileName:   "test",
   395  					readwriter: certificateReadWriterExist,
   396  				},
   397  			},
   398  			caName:  "foo",
   399  			want:    true,
   400  			wantErr: false,
   401  		},
   402  		{
   403  			name: "ca does not exist",
   404  			cas: map[string]*CAExpirationHandler{
   405  				"foo": {
   406  					Name:       "foo",
   407  					FileName:   "test",
   408  					readwriter: certificateReadWriterMissing,
   409  				},
   410  			},
   411  			caName:  "foo",
   412  			want:    false,
   413  			wantErr: false,
   414  		},
   415  	}
   416  	for _, tt := range tests {
   417  		t.Run(tt.name, func(t *testing.T) {
   418  			rm := &Manager{
   419  				cas: tt.cas,
   420  			}
   421  			got, err := rm.CAExists(tt.caName)
   422  			if (err != nil) != tt.wantErr {
   423  				t.Errorf("Manager.CAExists() error = %v, wantErr %v", err, tt.wantErr)
   424  				return
   425  			}
   426  			if got != tt.want {
   427  				t.Errorf("Manager.CAExists() = %v, want %v", got, tt.want)
   428  			}
   429  		})
   430  	}
   431  }
   432  
   433  func TestManagerCertificateExists(t *testing.T) {
   434  	certificateReadWriterExist := fakecertificateReadWriter{
   435  		exist: true,
   436  	}
   437  	certificateReadWriterMissing := fakecertificateReadWriter{
   438  		exist: false,
   439  	}
   440  	tests := []struct {
   441  		name         string
   442  		certificates map[string]*CertificateRenewHandler
   443  		certName     string
   444  		want         bool
   445  		wantErr      bool
   446  	}{
   447  		{
   448  			name:         "certName does not exist in certificate list",
   449  			certificates: map[string]*CertificateRenewHandler{},
   450  			certName:     "foo",
   451  			want:         false,
   452  			wantErr:      true,
   453  		},
   454  		{
   455  			name: "certificate exists",
   456  			certificates: map[string]*CertificateRenewHandler{
   457  				"foo": {
   458  					Name:       "foo",
   459  					readwriter: certificateReadWriterExist,
   460  				},
   461  			},
   462  			certName: "foo",
   463  			want:     true,
   464  			wantErr:  false,
   465  		},
   466  		{
   467  			name: "certificate does not exist",
   468  			certificates: map[string]*CertificateRenewHandler{
   469  				"foo": {
   470  					Name:       "foo",
   471  					readwriter: certificateReadWriterMissing,
   472  				},
   473  			},
   474  			certName: "foo",
   475  			want:     false,
   476  			wantErr:  false,
   477  		},
   478  	}
   479  	for _, tt := range tests {
   480  		t.Run(tt.name, func(t *testing.T) {
   481  			rm := &Manager{
   482  				certificates: tt.certificates,
   483  			}
   484  			got, err := rm.CertificateExists(tt.certName)
   485  			if (err != nil) != tt.wantErr {
   486  				t.Errorf("Manager.CertificateExists() error = %v, wantErr %v", err, tt.wantErr)
   487  				return
   488  			}
   489  			if got != tt.want {
   490  				t.Errorf("Manager.CertificateExists() = %v, want %v", got, tt.want)
   491  			}
   492  		})
   493  	}
   494  }
   495  

View as plain text