k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/copycerts/copycerts_test.go (about) 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 }