k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/certs/certlist_test.go (about)

     1  /*
     2  Copyright 2018 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 certs
    18  
    19  import (
    20  	"crypto"
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"os"
    24  	"path/filepath"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	certutil "k8s.io/client-go/util/cert"
    31  
    32  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    33  	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
    34  	"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
    35  )
    36  
    37  func TestCertListOrder(t *testing.T) {
    38  	tests := []struct {
    39  		certs Certificates
    40  		name  string
    41  	}{
    42  		{
    43  			name:  "Default Certificate List",
    44  			certs: GetDefaultCertList(),
    45  		},
    46  		{
    47  			name:  "Cert list less etcd",
    48  			certs: GetCertsWithoutEtcd(),
    49  		},
    50  	}
    51  
    52  	for _, test := range tests {
    53  		t.Run(test.name, func(t *testing.T) {
    54  			var lastCA *KubeadmCert
    55  			for i, cert := range test.certs {
    56  				if i > 0 && lastCA == nil {
    57  					t.Fatalf("CA not present in list before certificate %q", cert.Name)
    58  				}
    59  				if cert.CAName == "" {
    60  					lastCA = cert
    61  				} else {
    62  					if cert.CAName != lastCA.Name {
    63  						t.Fatalf("expected CA name %q, got %q, for certificate %q", lastCA.Name, cert.CAName, cert.Name)
    64  					}
    65  				}
    66  			}
    67  		})
    68  	}
    69  }
    70  
    71  func TestCAPointersValid(t *testing.T) {
    72  	tests := []struct {
    73  		certs Certificates
    74  		name  string
    75  	}{
    76  		{
    77  			name:  "Default Certificate List",
    78  			certs: GetDefaultCertList(),
    79  		},
    80  		{
    81  			name:  "Cert list less etcd",
    82  			certs: GetCertsWithoutEtcd(),
    83  		},
    84  	}
    85  
    86  	for _, test := range tests {
    87  		t.Run(test.name, func(t *testing.T) {
    88  
    89  			certMap := test.certs.AsMap()
    90  
    91  			for _, cert := range test.certs {
    92  				if cert.CAName != "" && certMap[cert.CAName] == nil {
    93  					t.Errorf("Certificate %q references nonexistent CA %q", cert.Name, cert.CAName)
    94  				}
    95  			}
    96  		})
    97  	}
    98  }
    99  
   100  func TestMakeCertTree(t *testing.T) {
   101  	rootCert := &KubeadmCert{
   102  		Name: "root",
   103  	}
   104  	leaf0 := &KubeadmCert{
   105  		Name:   "leaf0",
   106  		CAName: "root",
   107  	}
   108  	leaf1 := &KubeadmCert{
   109  		Name:   "leaf1",
   110  		CAName: "root",
   111  	}
   112  	selfSigned := &KubeadmCert{
   113  		Name: "self-signed",
   114  	}
   115  
   116  	certMap := CertificateMap{
   117  		"root":        rootCert,
   118  		"leaf0":       leaf0,
   119  		"leaf1":       leaf1,
   120  		"self-signed": selfSigned,
   121  	}
   122  
   123  	orphanCertMap := CertificateMap{
   124  		"leaf0": leaf0,
   125  	}
   126  
   127  	if _, err := orphanCertMap.CertTree(); err == nil {
   128  		t.Error("expected orphan cert map to error, but got nil")
   129  	}
   130  
   131  	certTree, err := certMap.CertTree()
   132  	t.Logf("cert tree: %v", certTree)
   133  	if err != nil {
   134  		t.Errorf("expected no error, but got %v", err)
   135  	}
   136  
   137  	if len(certTree) != 2 {
   138  		t.Errorf("Expected tree to have 2 roots, got %d", len(certTree))
   139  	}
   140  
   141  	if len(certTree[rootCert]) != 2 {
   142  		t.Errorf("Expected root to have 2 leaves, got %d", len(certTree[rootCert]))
   143  	}
   144  
   145  	if _, ok := certTree[selfSigned]; !ok {
   146  		t.Error("Expected selfSigned to be present in tree, but missing")
   147  	}
   148  }
   149  
   150  func TestCreateCertificateChain(t *testing.T) {
   151  	dir, err := os.MkdirTemp("", t.Name())
   152  	if err != nil {
   153  		t.Fatal(err)
   154  	}
   155  	defer os.RemoveAll(dir)
   156  
   157  	ic := &kubeadmapi.InitConfiguration{
   158  		NodeRegistration: kubeadmapi.NodeRegistrationOptions{
   159  			Name: "test-node",
   160  		},
   161  		ClusterConfiguration: kubeadmapi.ClusterConfiguration{
   162  			CertificatesDir: dir,
   163  		},
   164  	}
   165  
   166  	caCfg := Certificates{
   167  		{
   168  			config:   pkiutil.CertConfig{},
   169  			Name:     "test-ca",
   170  			BaseName: "test-ca",
   171  		},
   172  		{
   173  			config: pkiutil.CertConfig{
   174  				Config: certutil.Config{
   175  					AltNames: certutil.AltNames{
   176  						DNSNames: []string{"test-domain.space"},
   177  					},
   178  					Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
   179  				},
   180  			},
   181  			configMutators: []configMutatorsFunc{
   182  				setCommonNameToNodeName(),
   183  			},
   184  			CAName:   "test-ca",
   185  			Name:     "test-daughter",
   186  			BaseName: "test-daughter",
   187  		},
   188  	}
   189  
   190  	certTree, err := caCfg.AsMap().CertTree()
   191  	if err != nil {
   192  		t.Fatalf("unexpected error getting tree: %v", err)
   193  	}
   194  
   195  	if err := certTree.CreateTree(ic); err != nil {
   196  		t.Fatal(err)
   197  	}
   198  
   199  	caCert, _ := parseCertAndKey(filepath.Join(dir, "test-ca"), t)
   200  	daughterCert, _ := parseCertAndKey(filepath.Join(dir, "test-daughter"), t)
   201  
   202  	pool := x509.NewCertPool()
   203  	pool.AddCert(caCert)
   204  
   205  	_, err = daughterCert.Verify(x509.VerifyOptions{
   206  		DNSName:   "test-domain.space",
   207  		Roots:     pool,
   208  		KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
   209  	})
   210  	if err != nil {
   211  		t.Errorf("couldn't verify daughter cert: %v", err)
   212  	}
   213  
   214  }
   215  
   216  func parseCertAndKey(basePath string, t *testing.T) (*x509.Certificate, crypto.PrivateKey) {
   217  	certPair, err := tls.LoadX509KeyPair(basePath+".crt", basePath+".key")
   218  	if err != nil {
   219  		t.Fatalf("couldn't parse certificate and key: %v", err)
   220  	}
   221  
   222  	parsedCert, err := x509.ParseCertificate(certPair.Certificate[0])
   223  	if err != nil {
   224  		t.Fatalf("couldn't parse certificate: %v", err)
   225  	}
   226  
   227  	return parsedCert, certPair.PrivateKey
   228  }
   229  
   230  func TestCreateKeyAndCSR(t *testing.T) {
   231  	dir, err := os.MkdirTemp("", t.Name())
   232  	if err != nil {
   233  		t.Fatal(err)
   234  	}
   235  	defer os.RemoveAll(dir)
   236  
   237  	validKubeadmConfig := &kubeadmapi.InitConfiguration{
   238  		NodeRegistration: kubeadmapi.NodeRegistrationOptions{
   239  			Name: "test-node",
   240  		},
   241  		ClusterConfiguration: kubeadmapi.ClusterConfiguration{
   242  			CertificatesDir: dir,
   243  		},
   244  	}
   245  	validKubeadmCert := &KubeadmCert{
   246  		Name:     "ca",
   247  		LongName: "self-signed Kubernetes CA to provision identities for other Kubernetes components",
   248  		BaseName: kubeadmconstants.CACertAndKeyBaseName,
   249  		config: pkiutil.CertConfig{
   250  			Config: certutil.Config{
   251  				CommonName: "kubernetes",
   252  			},
   253  		},
   254  	}
   255  
   256  	type args struct {
   257  		kubeadmConfig *kubeadmapi.InitConfiguration
   258  		cert          *KubeadmCert
   259  	}
   260  	tests := []struct {
   261  		name       string
   262  		args       args
   263  		wantErr    bool
   264  		createfile bool
   265  	}{
   266  		{
   267  			name: "kubeadmConfig is nil",
   268  			args: args{
   269  				kubeadmConfig: nil,
   270  				cert:          validKubeadmCert,
   271  			},
   272  			wantErr: true,
   273  		},
   274  		{
   275  			name: "cert is nil",
   276  			args: args{
   277  				kubeadmConfig: validKubeadmConfig,
   278  				cert:          nil,
   279  			},
   280  			wantErr: true,
   281  		},
   282  		{
   283  			name: "key and CSR do not exist",
   284  			args: args{
   285  				kubeadmConfig: validKubeadmConfig,
   286  				cert:          validKubeadmCert,
   287  			},
   288  			wantErr: false,
   289  		},
   290  		{
   291  			name: "key or CSR already exist",
   292  			args: args{
   293  				kubeadmConfig: validKubeadmConfig,
   294  				cert:          validKubeadmCert,
   295  			},
   296  			wantErr: true,
   297  		},
   298  	}
   299  	for _, tt := range tests {
   300  		t.Run(tt.name, func(t *testing.T) {
   301  			if err := createKeyAndCSR(tt.args.kubeadmConfig, tt.args.cert); (err != nil) != tt.wantErr {
   302  				t.Errorf("createKeyAndCSR() error = %v, wantErr %v", err, tt.wantErr)
   303  			}
   304  		})
   305  	}
   306  }
   307  
   308  func TestGetConfig(t *testing.T) {
   309  	var (
   310  		now      = time.Now()
   311  		backdate = kubeadmconstants.CertificateBackdate
   312  	)
   313  
   314  	tests := []struct {
   315  		name           string
   316  		cert           *KubeadmCert
   317  		cfg            *kubeadmapi.InitConfiguration
   318  		expectedConfig *pkiutil.CertConfig
   319  	}{
   320  		{
   321  			name: "encryption algorithm is set",
   322  			cert: &KubeadmCert{
   323  				creationTime: now,
   324  			},
   325  			cfg: &kubeadmapi.InitConfiguration{
   326  				ClusterConfiguration: kubeadmapi.ClusterConfiguration{
   327  					EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmECDSAP256,
   328  				},
   329  			},
   330  			expectedConfig: &pkiutil.CertConfig{
   331  				Config: certutil.Config{
   332  					NotBefore: now.Add(-backdate),
   333  				},
   334  				EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmECDSAP256,
   335  			},
   336  		},
   337  		{
   338  			name: "cert validity is set",
   339  			cert: &KubeadmCert{
   340  				CAName:       "some-ca",
   341  				creationTime: now,
   342  			},
   343  			cfg: &kubeadmapi.InitConfiguration{
   344  				ClusterConfiguration: kubeadmapi.ClusterConfiguration{
   345  					CertificateValidityPeriod: &metav1.Duration{
   346  						Duration: time.Hour * 1,
   347  					},
   348  				},
   349  			},
   350  			expectedConfig: &pkiutil.CertConfig{
   351  				Config: certutil.Config{
   352  					NotBefore: now.Add(-backdate),
   353  				},
   354  				NotAfter: now.Add(time.Hour * 1)},
   355  		},
   356  		{
   357  			name: "CA cert validity is set",
   358  			cert: &KubeadmCert{
   359  				CAName:       "",
   360  				creationTime: now,
   361  			},
   362  			cfg: &kubeadmapi.InitConfiguration{
   363  				ClusterConfiguration: kubeadmapi.ClusterConfiguration{
   364  					CACertificateValidityPeriod: &metav1.Duration{
   365  						Duration: time.Hour * 10,
   366  					},
   367  				},
   368  			},
   369  			expectedConfig: &pkiutil.CertConfig{
   370  				Config: certutil.Config{
   371  					NotBefore: now.Add(-backdate),
   372  				},
   373  				NotAfter: now.Add(time.Hour * 10),
   374  			},
   375  		},
   376  	}
   377  	for _, tt := range tests {
   378  		t.Run(tt.name, func(t *testing.T) {
   379  			actual, err := tt.cert.GetConfig(tt.cfg)
   380  			if err != nil {
   381  				t.Fatalf("unexpected error: %v", err)
   382  			}
   383  			if diff := cmp.Diff(actual, tt.expectedConfig); diff != "" {
   384  				t.Fatalf("GetConfig() returned diff (-want,+got):\n%s", diff)
   385  			}
   386  		})
   387  	}
   388  }