...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/phases/copycerts/copycerts_test.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/phases/copycerts

     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 copycerts
    18  
    19  import (
    20  	"context"
    21  	"encoding/hex"
    22  	"os"
    23  	"path/filepath"
    24  	"regexp"
    25  	goruntime "runtime"
    26  	"testing"
    27  
    28  	"github.com/lithammer/dedent"
    29  
    30  	v1 "k8s.io/api/core/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	fakeclient "k8s.io/client-go/kubernetes/fake"
    33  	"k8s.io/client-go/util/keyutil"
    34  
    35  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    36  	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
    37  	"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
    38  	cryptoutil "k8s.io/kubernetes/cmd/kubeadm/app/util/crypto"
    39  	testutil "k8s.io/kubernetes/cmd/kubeadm/test"
    40  )
    41  
    42  func TestGetDataFromInitConfig(t *testing.T) {
    43  	certData := []byte("cert-data")
    44  	tmpdir := testutil.SetupTempDir(t)
    45  	defer os.RemoveAll(tmpdir)
    46  	cfg := &kubeadmapi.InitConfiguration{}
    47  	cfg.CertificatesDir = tmpdir
    48  
    49  	key, err := CreateCertificateKey()
    50  	if err != nil {
    51  		t.Fatalf(dedent.Dedent("failed to create key.\nfatal error: %v"), err)
    52  	}
    53  	decodedKey, err := hex.DecodeString(key)
    54  	if err != nil {
    55  		t.Fatalf(dedent.Dedent("failed to decode key.\nfatal error: %v"), err)
    56  	}
    57  
    58  	if err := os.Mkdir(filepath.Join(tmpdir, "etcd"), 0755); err != nil {
    59  		t.Fatalf(dedent.Dedent("failed to create etcd cert dir.\nfatal error: %v"), err)
    60  	}
    61  
    62  	certs := certsToTransfer(cfg)
    63  	for name, path := range certs {
    64  		if err := os.WriteFile(path, certData, 0644); err != nil {
    65  			t.Fatalf(dedent.Dedent("failed to write cert: %s\nfatal error: %v"), name, err)
    66  		}
    67  	}
    68  
    69  	secretData, err := getDataFromDisk(cfg, decodedKey)
    70  	if err != nil {
    71  		t.Fatalf("failed to get secret data. fatal error: %v", err)
    72  	}
    73  
    74  	re := regexp.MustCompile(`[-.\w]+`)
    75  	for name, data := range secretData {
    76  		if !re.MatchString(name) {
    77  			t.Fatalf(dedent.Dedent("failed to validate secretData\n %s isn't a valid secret key"), name)
    78  		}
    79  
    80  		decryptedData, err := cryptoutil.DecryptBytes(data, decodedKey)
    81  		if string(certData) != string(decryptedData) {
    82  			t.Fatalf(dedent.Dedent("can't decrypt cert: %s\nfatal error: %v"), name, err)
    83  		}
    84  	}
    85  }
    86  
    87  func TestCertsToTransfer(t *testing.T) {
    88  	localEtcdCfg := &kubeadmapi.InitConfiguration{}
    89  	externalEtcdCfg := &kubeadmapi.InitConfiguration{}
    90  	externalEtcdCfg.Etcd = kubeadmapi.Etcd{}
    91  	externalEtcdCfg.Etcd.External = &kubeadmapi.ExternalEtcd{}
    92  
    93  	commonExpectedCerts := []string{
    94  		kubeadmconstants.CACertName,
    95  		kubeadmconstants.CAKeyName,
    96  		kubeadmconstants.FrontProxyCACertName,
    97  		kubeadmconstants.FrontProxyCAKeyName,
    98  		kubeadmconstants.ServiceAccountPublicKeyName,
    99  		kubeadmconstants.ServiceAccountPrivateKeyName,
   100  	}
   101  
   102  	tests := map[string]struct {
   103  		config        *kubeadmapi.InitConfiguration
   104  		expectedCerts []string
   105  	}{
   106  		"local etcd": {
   107  			config: localEtcdCfg,
   108  			expectedCerts: append(
   109  				[]string{kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName},
   110  				commonExpectedCerts...,
   111  			),
   112  		},
   113  		"external etcd": {
   114  			config: externalEtcdCfg,
   115  			expectedCerts: append(
   116  				[]string{externalEtcdCA, externalEtcdCert, externalEtcdKey},
   117  				commonExpectedCerts...,
   118  			),
   119  		},
   120  	}
   121  
   122  	for name, test := range tests {
   123  		t.Run(name, func(t2 *testing.T) {
   124  			certList := certsToTransfer(test.config)
   125  			for _, cert := range test.expectedCerts {
   126  				if _, found := certList[cert]; !found {
   127  					t2.Fatalf(dedent.Dedent("failed to get list of certs to upload\ncert %s not found"), cert)
   128  				}
   129  			}
   130  		})
   131  	}
   132  }
   133  
   134  func TestCertOrKeyNameToSecretName(t *testing.T) {
   135  	tests := []struct {
   136  		keyName            string
   137  		expectedSecretName string
   138  	}{
   139  		{
   140  			keyName:            "apiserver-kubelet-client.crt",
   141  			expectedSecretName: "apiserver-kubelet-client.crt",
   142  		},
   143  		{
   144  			keyName:            "etcd/ca.crt",
   145  			expectedSecretName: "etcd-ca.crt",
   146  		},
   147  		{
   148  			keyName:            "etcd/healthcheck-client.crt",
   149  			expectedSecretName: "etcd-healthcheck-client.crt",
   150  		},
   151  	}
   152  
   153  	for _, tc := range tests {
   154  		secretName := certOrKeyNameToSecretName(tc.keyName)
   155  		if secretName != tc.expectedSecretName {
   156  			t.Fatalf("secret name %s didn't match expected name %s", secretName, tc.expectedSecretName)
   157  		}
   158  	}
   159  }
   160  
   161  func TestUploadCerts(t *testing.T) {
   162  	tmpdir := testutil.SetupTempDir(t)
   163  	defer os.RemoveAll(tmpdir)
   164  
   165  	secretKey, err := CreateCertificateKey()
   166  	if err != nil {
   167  		t.Fatalf("could not create certificate key: %v", err)
   168  	}
   169  
   170  	initConfiguration := testutil.GetDefaultInternalConfig(t)
   171  	initConfiguration.ClusterConfiguration.CertificatesDir = tmpdir
   172  
   173  	if err := certs.CreatePKIAssets(initConfiguration); err != nil {
   174  		t.Fatalf("error creating PKI assets: %v", err)
   175  	}
   176  
   177  	cs := fakeclient.NewSimpleClientset()
   178  	if err := UploadCerts(cs, initConfiguration, secretKey); err != nil {
   179  		t.Fatalf("error uploading certs: %v", err)
   180  	}
   181  	rawSecretKey, err := hex.DecodeString(secretKey)
   182  	if err != nil {
   183  		t.Fatalf("error decoding key: %v", err)
   184  	}
   185  	secretMap, err := cs.CoreV1().Secrets(metav1.NamespaceSystem).Get(context.TODO(), kubeadmconstants.KubeadmCertsSecret, metav1.GetOptions{})
   186  	if err != nil {
   187  		t.Fatalf("could not fetch secret: %v", err)
   188  	}
   189  	for certName, certPath := range certsToTransfer(initConfiguration) {
   190  		secretCertData, err := cryptoutil.DecryptBytes(secretMap.Data[certOrKeyNameToSecretName(certName)], rawSecretKey)
   191  		if err != nil {
   192  			t.Fatalf("error decrypting secret data: %v", err)
   193  		}
   194  		diskCertData, err := os.ReadFile(certPath)
   195  		if err != nil {
   196  			t.Fatalf("error reading certificate from disk: %v", err)
   197  		}
   198  		// Check that the encrypted contents on the secret match the contents on disk, and that all
   199  		// the expected certificates are in the secret
   200  		if string(secretCertData) != string(diskCertData) {
   201  			t.Fatalf("cert %s does not have the expected contents. contents: %q; expected contents: %q", certName, string(secretCertData), string(diskCertData))
   202  		}
   203  	}
   204  }
   205  
   206  func TestDownloadCerts(t *testing.T) {
   207  	secretKey, err := CreateCertificateKey()
   208  	if err != nil {
   209  		t.Fatalf("could not create certificate key: %v", err)
   210  	}
   211  
   212  	// Temporary directory where certificates will be generated
   213  	tmpdir := testutil.SetupTempDir(t)
   214  	defer os.RemoveAll(tmpdir)
   215  	initConfiguration := testutil.GetDefaultInternalConfig(t)
   216  	initConfiguration.ClusterConfiguration.CertificatesDir = tmpdir
   217  
   218  	// Temporary directory where certificates will be downloaded to
   219  	targetTmpdir := testutil.SetupTempDir(t)
   220  	defer os.RemoveAll(targetTmpdir)
   221  	initForDownloadConfiguration := testutil.GetDefaultInternalConfig(t)
   222  	initForDownloadConfiguration.ClusterConfiguration.CertificatesDir = targetTmpdir
   223  
   224  	if err := certs.CreatePKIAssets(initConfiguration); err != nil {
   225  		t.Fatalf("error creating PKI assets: %v", err)
   226  	}
   227  
   228  	kubeadmCertsSecret := createKubeadmCertsSecret(t, initConfiguration, secretKey)
   229  	cs := fakeclient.NewSimpleClientset(kubeadmCertsSecret)
   230  	if err := DownloadCerts(cs, initForDownloadConfiguration, secretKey); err != nil {
   231  		t.Fatalf("error downloading certs: %v", err)
   232  	}
   233  
   234  	const keyFileMode = 0600
   235  	const certFileMode = 0644
   236  
   237  	for certName, certPath := range certsToTransfer(initForDownloadConfiguration) {
   238  		diskCertData, err := os.ReadFile(certPath)
   239  		if err != nil {
   240  			t.Errorf("error reading certificate from disk: %v", err)
   241  		}
   242  		// Check that the written files are either certificates or keys, and that they have
   243  		// the expected permissions
   244  		if _, err := keyutil.ParsePrivateKeyPEM(diskCertData); err == nil {
   245  			// File permissions are set differently on Windows, which does not match the expectation below.
   246  			if goruntime.GOOS != "windows" {
   247  				if stat, err := os.Stat(certPath); err == nil {
   248  					if stat.Mode() != keyFileMode {
   249  						t.Errorf("key %q should have mode %#o, has %#o", certName, keyFileMode, stat.Mode())
   250  					}
   251  				} else {
   252  					t.Errorf("could not stat key %q: %v", certName, err)
   253  				}
   254  			}
   255  		} else if _, err := keyutil.ParsePublicKeysPEM(diskCertData); err == nil {
   256  			// File permissions are set differently on Windows, which does not match the expectation below.
   257  			if goruntime.GOOS != "windows" {
   258  				if stat, err := os.Stat(certPath); err == nil {
   259  					if stat.Mode() != certFileMode {
   260  						t.Errorf("cert %q should have mode %#o, has %#o", certName, certFileMode, stat.Mode())
   261  					}
   262  				} else {
   263  					t.Errorf("could not stat cert %q: %v", certName, err)
   264  				}
   265  			}
   266  		} else {
   267  			t.Errorf("secret %q was not identified as a cert or as a key", certName)
   268  		}
   269  	}
   270  }
   271  
   272  func createKubeadmCertsSecret(t *testing.T, cfg *kubeadmapi.InitConfiguration, secretKey string) *v1.Secret {
   273  	decodedKey, err := hex.DecodeString(secretKey)
   274  	if err != nil {
   275  		t.Fatalf("error decoding key: %v", err)
   276  	}
   277  	secretData, err := getDataFromDisk(cfg, decodedKey)
   278  	if err != nil {
   279  		t.Fatalf("error creating secret data: %v", err)
   280  	}
   281  	return &v1.Secret{
   282  		ObjectMeta: metav1.ObjectMeta{
   283  			Name:      kubeadmconstants.KubeadmCertsSecret,
   284  			Namespace: metav1.NamespaceSystem,
   285  		},
   286  		Data: secretData,
   287  	}
   288  }
   289  

View as plain text