/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package certs

import (
	"bytes"
	"crypto"
	"crypto/sha256"
	"crypto/x509"
	"io/ioutil"
	"net"
	"os"
	"path"
	"path/filepath"
	"testing"

	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"

	certutil "k8s.io/client-go/util/cert"
	"k8s.io/client-go/util/keyutil"
	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
	certstestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs"
	"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
	testutil "k8s.io/kubernetes/cmd/kubeadm/test"
)

func createTestCSR(t *testing.T) (*x509.CertificateRequest, crypto.Signer) {
	csr, key, err := pkiutil.NewCSRAndKey(
		&certutil.Config{
			CommonName: "testCert",
		})
	if err != nil {
		t.Fatalf("couldn't create test cert: %v", err)
	}

	return csr, key
}

func TestWriteCertificateAuthorithyFilesIfNotExist(t *testing.T) {
	setupCert, setupKey := certstestutil.CreateCACert(t)
	caCert, caKey := certstestutil.CreateCACert(t)

	var tests = []struct {
		setupFunc     func(pkiDir string) error
		expectedError bool
		expectedCa    *x509.Certificate
	}{
		{ // ca cert does not exists > ca written
			expectedCa: caCert,
		},
		{ // ca cert exists, is ca > existing ca used
			setupFunc: func(pkiDir string) error {
				return writeCertificateAuthorithyFilesIfNotExist(pkiDir, "dummy", setupCert, setupKey)
			},
			expectedCa: setupCert,
		},
		{ // some file exists, but it is not a valid ca cert > err
			setupFunc: func(pkiDir string) error {
				testutil.SetupEmptyFiles(t, pkiDir, "dummy.crt")
				return nil
			},
			expectedError: true,
		},
		{ // cert exists, but it is not a ca > err
			setupFunc: func(pkiDir string) error {
				cert, key, config := certstestutil.CreateTestCert(t, setupCert, setupKey, certutil.AltNames{})
				return writeCertificateFilesIfNotExist(pkiDir, "dummy", setupCert, cert, key, config)
			},
			expectedError: true,
		},
	}

	for _, test := range tests {
		// Create temp folder for the test case
		tmpdir := testutil.SetupTempDir(t)
		defer os.RemoveAll(tmpdir)

		// executes setup func (if necessary)
		if test.setupFunc != nil {
			if err := test.setupFunc(tmpdir); err != nil {
				t.Errorf("error executing setupFunc: %v", err)
				continue
			}
		}

		// executes create func
		err := writeCertificateAuthorithyFilesIfNotExist(tmpdir, "dummy", caCert, caKey)

		if !test.expectedError && err != nil {
			t.Errorf("error writeCertificateAuthorithyFilesIfNotExist failed when not expected to fail: %v", err)
			continue
		} else if test.expectedError && err == nil {
			t.Error("error writeCertificateAuthorithyFilesIfNotExist didn't failed when expected")
			continue
		} else if test.expectedError {
			continue
		}

		// asserts expected files are there
		testutil.AssertFileExists(t, tmpdir, "dummy.key", "dummy.crt")

		// check created cert
		resultingCaCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(tmpdir, "dummy")
		if err != nil {
			t.Errorf("failure reading created cert: %v", err)
			continue
		}
		if !resultingCaCert.Equal(test.expectedCa) {
			t.Error("created ca cert does not match expected ca cert")
		}
	}
}

func TestWriteCertificateFilesIfNotExist(t *testing.T) {
	altNames := certutil.AltNames{
		DNSNames: []string{"example.com"},
		IPs: []net.IP{
			net.IPv4(0, 0, 0, 0),
		},
	}

	caCert, caKey := certstestutil.CreateCACert(t)
	setupCert, setupKey, _ := certstestutil.CreateTestCert(t, caCert, caKey, altNames)
	cert, key, config := certstestutil.CreateTestCert(t, caCert, caKey, altNames)

	var tests = []struct {
		setupFunc     func(pkiDir string) error
		expectedError bool
		expectedCert  *x509.Certificate
	}{
		{ // cert does not exists > cert written
			expectedCert: cert,
		},
		{ // cert exists, is signed by the same ca, missing SANs (dns name) > err
			setupFunc: func(pkiDir string) error {
				setupCert, setupKey, setupConfig := certstestutil.CreateTestCert(t, caCert, caKey, certutil.AltNames{
					IPs: []net.IP{
						net.IPv4(0, 0, 0, 0),
					},
				})
				return writeCertificateFilesIfNotExist(pkiDir, "dummy", caCert, setupCert, setupKey, setupConfig)
			},
			expectedError: true,
		},
		{ // cert exists, is signed by the same ca, missing SANs (IP address) > err
			setupFunc: func(pkiDir string) error {
				setupCert, setupKey, setupConfig := certstestutil.CreateTestCert(t, caCert, caKey, certutil.AltNames{
					DNSNames: []string{"example.com"},
				})
				return writeCertificateFilesIfNotExist(pkiDir, "dummy", caCert, setupCert, setupKey, setupConfig)
			},
			expectedError: true,
		},
		{ // cert exists, is signed by the same ca, all SANs present > existing cert used
			setupFunc: func(pkiDir string) error {
				return writeCertificateFilesIfNotExist(pkiDir, "dummy", caCert, setupCert, setupKey, config)
			},
			expectedCert: setupCert,
		},
		{ // some file exists, but it is not a valid cert > err
			setupFunc: func(pkiDir string) error {
				testutil.SetupEmptyFiles(t, pkiDir, "dummy.crt")
				return nil
			},
			expectedError: true,
		},
		{ // cert exists, is signed by another ca > err
			setupFunc: func(pkiDir string) error {
				anotherCaCert, anotherCaKey := certstestutil.CreateCACert(t)
				anotherCert, anotherKey, config := certstestutil.CreateTestCert(t, anotherCaCert, anotherCaKey, certutil.AltNames{})

				return writeCertificateFilesIfNotExist(pkiDir, "dummy", anotherCaCert, anotherCert, anotherKey, config)
			},
			expectedError: true,
		},
	}

	for _, test := range tests {
		// Create temp folder for the test case
		tmpdir := testutil.SetupTempDir(t)
		defer os.RemoveAll(tmpdir)

		// executes setup func (if necessary)
		if test.setupFunc != nil {
			if err := test.setupFunc(tmpdir); err != nil {
				t.Errorf("error executing setupFunc: %v", err)
				continue
			}
		}

		// executes create func
		err := writeCertificateFilesIfNotExist(tmpdir, "dummy", caCert, cert, key, config)

		if !test.expectedError && err != nil {
			t.Errorf("error writeCertificateFilesIfNotExist failed when not expected to fail: %v", err)
			continue
		} else if test.expectedError && err == nil {
			t.Error("error writeCertificateFilesIfNotExist didn't fail when expected")
			continue
		} else if test.expectedError {
			continue
		}

		// asserts expected files are there
		testutil.AssertFileExists(t, tmpdir, "dummy.key", "dummy.crt")

		// check created cert
		resultingCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(tmpdir, "dummy")
		if err != nil {
			t.Errorf("failure reading created cert: %v", err)
			continue
		}
		if !resultingCert.Equal(test.expectedCert) {
			t.Error("created cert does not match expected cert")
		}
	}
}

func TestWriteCSRFilesIfNotExist(t *testing.T) {
	csr, key := createTestCSR(t)
	csr2, key2 := createTestCSR(t)

	var tests = []struct {
		name          string
		setupFunc     func(csrPath string) error
		expectedError bool
		expectedCSR   *x509.CertificateRequest
	}{
		{
			name:        "no files exist",
			expectedCSR: csr,
		},
		{
			name: "other key exists",
			setupFunc: func(csrPath string) error {
				if err := pkiutil.WriteCSR(csrPath, "dummy", csr2); err != nil {
					return err
				}
				return pkiutil.WriteKey(csrPath, "dummy", key2)
			},
			expectedCSR: csr2,
		},
		{
			name: "existing CSR is garbage",
			setupFunc: func(csrPath string) error {
				return ioutil.WriteFile(path.Join(csrPath, "dummy.csr"), []byte("a--bunch--of-garbage"), os.ModePerm)
			},
			expectedError: true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			tmpdir := testutil.SetupTempDir(t)
			defer os.RemoveAll(tmpdir)

			if test.setupFunc != nil {
				if err := test.setupFunc(tmpdir); err != nil {
					t.Fatalf("couldn't set up test: %v", err)
				}
			}

			if err := writeCSRFilesIfNotExist(tmpdir, "dummy", csr, key); err != nil {
				if test.expectedError {
					return
				}
				t.Fatalf("unexpected error %v: ", err)
			}

			if test.expectedError {
				t.Fatal("Expected error, but got none")
			}

			parsedCSR, _, err := pkiutil.TryLoadCSRAndKeyFromDisk(tmpdir, "dummy")
			if err != nil {
				t.Fatalf("couldn't load csr and key: %v", err)
			}

			if sha256.Sum256(test.expectedCSR.Raw) != sha256.Sum256(parsedCSR.Raw) {
				t.Error("expected csr's fingerprint does not match ")
			}

		})
	}

}

func TestCreateServiceAccountKeyAndPublicKeyFiles(t *testing.T) {
	setupKey, err := keyutil.MakeEllipticPrivateKeyPEM()
	if err != nil {
		t.Fatalf("Can't setup test: %v", err)
	}

	tcases := []struct {
		name        string
		setupFunc   func(pkiDir string) error
		expectedErr bool
		expectedKey []byte
	}{
		{ // key does not exists > key written
			name: "generate successfully",
		},
		{ // key exists > existing key used
			name: "use existing key",
			setupFunc: func(pkiDir string) error {
				err := keyutil.WriteKey(filepath.Join(pkiDir, kubeadmconstants.ServiceAccountPrivateKeyName), setupKey)
				return err
			},
			expectedKey: setupKey,
		},
		{ // some file exists, but it is not a valid key > err
			name: "empty key",
			setupFunc: func(pkiDir string) error {
				testutil.SetupEmptyFiles(t, pkiDir, kubeadmconstants.ServiceAccountPrivateKeyName)
				return nil
			},
			expectedErr: true,
		},
	}
	for _, tt := range tcases {
		t.Run(tt.name, func(t *testing.T) {
			dir := testutil.SetupTempDir(t)
			defer os.RemoveAll(dir)

			if tt.setupFunc != nil {
				if err := tt.setupFunc(dir); err != nil {
					t.Fatalf("error executing setupFunc: %v", err)
				}
			}

			err := CreateServiceAccountKeyAndPublicKeyFiles(dir)
			if (err != nil) != tt.expectedErr {
				t.Fatalf("expected error: %v, got: %v, error: %v", tt.expectedErr, err != nil, err)
			} else if tt.expectedErr {
				return
			}

			resultingKeyPEM, wasGenerated, err := keyutil.LoadOrGenerateKeyFile(filepath.Join(dir, kubeadmconstants.ServiceAccountPrivateKeyName))
			if err != nil {
				t.Errorf("Can't load created key: %v", err)
			} else if wasGenerated {
				t.Error("The key was not created")
			} else if tt.expectedKey != nil && !bytes.Equal(resultingKeyPEM, tt.expectedKey) {
				t.Error("Non-existing key is used")
			}
		})
	}
}

func TestSharedCertificateExists(t *testing.T) {
	caCert, caKey := certstestutil.CreateCACert(t)
	_, key, _ := certstestutil.CreateTestCert(t, caCert, caKey, certutil.AltNames{})
	publicKey := key.Public()

	var tests = []struct {
		name          string
		files         certstestutil.PKIFiles
		expectedError bool
	}{
		{
			name: "success",
			files: certstestutil.PKIFiles{
				"ca.crt":             caCert,
				"ca.key":             caKey,
				"front-proxy-ca.crt": caCert,
				"front-proxy-ca.key": caKey,
				"sa.pub":             publicKey,
				"sa.key":             key,
				"etcd/ca.crt":        caCert,
				"etcd/ca.key":        caKey,
			},
		},
		{
			name: "missing ca.crt",
			files: certstestutil.PKIFiles{
				"ca.key":             caKey,
				"front-proxy-ca.crt": caCert,
				"front-proxy-ca.key": caKey,
				"sa.pub":             publicKey,
				"sa.key":             key,
				"etcd/ca.crt":        caCert,
				"etcd/ca.key":        caKey,
			},
			expectedError: true,
		},
		{
			name: "missing sa.key",
			files: certstestutil.PKIFiles{
				"ca.crt":             caCert,
				"ca.key":             caKey,
				"front-proxy-ca.crt": caCert,
				"front-proxy-ca.key": caKey,
				"sa.pub":             publicKey,
				"etcd/ca.crt":        caCert,
				"etcd/ca.key":        caKey,
			},
			expectedError: true,
		},
		{
			name: "missing front-proxy.crt",
			files: certstestutil.PKIFiles{
				"ca.crt":             caCert,
				"ca.key":             caKey,
				"front-proxy-ca.key": caKey,
				"sa.pub":             publicKey,
				"sa.key":             key,
				"etcd/ca.crt":        caCert,
				"etcd/ca.key":        caKey,
			},
			expectedError: true,
		},
		{
			name: "missing etcd/ca.crt",
			files: certstestutil.PKIFiles{
				"ca.crt":             caCert,
				"ca.key":             caKey,
				"front-proxy-ca.key": caKey,
				"sa.pub":             publicKey,
				"sa.key":             key,
				"etcd/ca.crt":        caCert,
				"etcd/ca.key":        caKey,
			},
			expectedError: true,
		},
	}

	for _, test := range tests {
		t.Run("", func(t *testing.T) {
			tmpdir := testutil.SetupTempDir(t)
			os.MkdirAll(tmpdir+"/etcd", os.ModePerm)
			defer os.RemoveAll(tmpdir)

			cfg := &kubeadmapi.ClusterConfiguration{
				CertificatesDir: tmpdir,
			}

			// created expected keys
			certstestutil.WritePKIFiles(t, tmpdir, test.files)

			// executes create func
			ret, err := SharedCertificateExists(cfg)

			switch {
			case !test.expectedError && err != nil:
				t.Errorf("error SharedCertificateExists failed when not expected to fail: %v", err)
			case test.expectedError && err == nil:
				t.Errorf("error SharedCertificateExists didn't failed when expected")
			case ret != (err == nil):
				t.Errorf("error SharedCertificateExists returned %v when expected to return %v", ret, err == nil)
			}
		})
	}
}

func TestCreatePKIAssetsWithSparseCerts(t *testing.T) {
	for _, test := range certstestutil.GetSparseCertTestCases(t) {
		t.Run(test.Name, func(t *testing.T) {
			tmpdir := testutil.SetupTempDir(t)
			defer os.RemoveAll(tmpdir)

			cfg := testutil.GetDefaultInternalConfig(t)
			cfg.ClusterConfiguration.CertificatesDir = tmpdir

			certstestutil.WritePKIFiles(t, tmpdir, test.Files)

			err := CreatePKIAssets(cfg)
			if err != nil {
				if test.ExpectError {
					return
				}
				t.Fatalf("Unexpected error: %v", err)
			}
			if test.ExpectError {
				t.Fatal("Expected error from CreatePKIAssets, got none")
			}
			assertCertsExist(t, tmpdir)
		})
	}

}

func TestUsingExternalCA(t *testing.T) {
	tests := []struct {
		name           string
		setupFuncs     []func(cfg *kubeadmapi.InitConfiguration) error
		externalCAFunc func(*kubeadmapi.ClusterConfiguration) (bool, error)
		expected       bool
		expectedErr    bool
	}{
		{
			name: "Test External CA, when complete PKI exists",
			setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{
				CreatePKIAssets,
			},
			externalCAFunc: UsingExternalCA,
			expected:       false,
		},
		{
			name: "Test External CA, when ca.key missing",
			setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{
				CreatePKIAssets,
				deleteCertOrKey(kubeadmconstants.CAKeyName),
			},
			externalCAFunc: UsingExternalCA,
			expected:       true,
		},
		{
			name: "Test External CA, when ca.key missing and signed certs are missing",
			setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{
				CreatePKIAssets,
				deleteCertOrKey(kubeadmconstants.CAKeyName),
				deleteCertOrKey(kubeadmconstants.APIServerCertName),
			},
			externalCAFunc: UsingExternalCA,
			expected:       true,
			expectedErr:    true,
		},
		{
			name: "Test External CA, when ca.key missing",
			setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{
				CreatePKIAssets,
				deleteCertOrKey(kubeadmconstants.CAKeyName),
			},
			externalCAFunc: UsingExternalCA,
			expected:       true,
		},
		{
			name: "Test External Front Proxy CA, when complete PKI exists",
			setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{
				CreatePKIAssets,
			},
			externalCAFunc: UsingExternalFrontProxyCA,
			expected:       false,
		},
		{
			name: "Test External Front Proxy CA, when front-proxy-ca.key missing",
			setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{
				CreatePKIAssets,
				deleteCertOrKey(kubeadmconstants.FrontProxyCAKeyName),
			},
			externalCAFunc: UsingExternalFrontProxyCA,
			expected:       true,
		},
		{
			name: "Test External Front Proxy CA, when front-proxy-.key missing and signed certs are missing",
			setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{
				CreatePKIAssets,
				deleteCertOrKey(kubeadmconstants.FrontProxyCAKeyName),
				deleteCertOrKey(kubeadmconstants.FrontProxyClientCertName),
			},
			externalCAFunc: UsingExternalFrontProxyCA,
			expected:       true,
			expectedErr:    true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			dir := testutil.SetupTempDir(t)
			defer os.RemoveAll(dir)

			cfg := &kubeadmapi.InitConfiguration{
				LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4"},
				ClusterConfiguration: kubeadmapi.ClusterConfiguration{
					Networking:      kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
					CertificatesDir: dir,
				},
				NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-hostname"},
			}

			for _, f := range test.setupFuncs {
				if err := f(cfg); err != nil {
					t.Errorf("error executing setup function: %v", err)
				}
			}

			val, err := test.externalCAFunc(&cfg.ClusterConfiguration)
			if val != test.expected {
				t.Errorf("UsingExternalCA did not match expected: %v", test.expected)
			}

			if (err != nil) != test.expectedErr {
				t.Errorf("UsingExternalCA returned un expected err: %v", err)
			}
		})
	}
}

func TestValidateMethods(t *testing.T) {

	caCert, caKey := certstestutil.CreateCACert(t)
	cert, key, _ := certstestutil.CreateTestCert(t, caCert, caKey, certutil.AltNames{})

	tests := []struct {
		name            string
		files           certstestutil.PKIFiles
		validateFunc    func(l certKeyLocation) error
		loc             certKeyLocation
		expectedSuccess bool
	}{
		{
			name: "validateCACert",
			files: certstestutil.PKIFiles{
				"ca.crt": caCert,
			},
			validateFunc:    validateCACert,
			loc:             certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"},
			expectedSuccess: true,
		},
		{
			name: "validateCACertAndKey (files present)",
			files: certstestutil.PKIFiles{
				"ca.crt": caCert,
				"ca.key": caKey,
			},
			validateFunc:    validateCACertAndKey,
			loc:             certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"},
			expectedSuccess: true,
		},
		{
			files: certstestutil.PKIFiles{
				"ca.crt": caCert,
			},
			name:            "validateCACertAndKey (key missing)",
			validateFunc:    validateCACertAndKey,
			loc:             certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"},
			expectedSuccess: false,
		},
		{
			name: "validateSignedCert",
			files: certstestutil.PKIFiles{
				"ca.crt":        caCert,
				"ca.key":        caKey,
				"apiserver.crt": cert,
				"apiserver.key": key,
			},
			validateFunc:    validateSignedCert,
			loc:             certKeyLocation{caBaseName: "ca", baseName: "apiserver", uxName: "apiserver"},
			expectedSuccess: true,
		},
		{
			name: "validatePrivatePublicKey",
			files: certstestutil.PKIFiles{
				"sa.pub": key.Public(),
				"sa.key": key,
			},
			validateFunc:    validatePrivatePublicKey,
			loc:             certKeyLocation{baseName: "sa", uxName: "service account"},
			expectedSuccess: true,
		},
	}

	for _, test := range tests {
		dir := testutil.SetupTempDir(t)
		defer os.RemoveAll(dir)
		test.loc.pkiDir = dir

		certstestutil.WritePKIFiles(t, dir, test.files)

		err := test.validateFunc(test.loc)
		if test.expectedSuccess && err != nil {
			t.Errorf("expected success, error executing validateFunc: %v, %v", test.name, err)
		} else if !test.expectedSuccess && err == nil {
			t.Errorf("expected failure, no error executing validateFunc: %v", test.name)
		}
	}
}

func TestNewCSR(t *testing.T) {
	kubeadmCert := KubeadmCertAPIServer
	cfg := testutil.GetDefaultInternalConfig(t)

	certConfig, err := kubeadmCert.GetConfig(cfg)
	if err != nil {
		t.Fatalf("couldn't get cert config: %v", err)
	}

	csr, _, err := NewCSR(&kubeadmCert, cfg)

	if err != nil {
		t.Errorf("invalid signature on CSR: %v", err)
	}

	assert.ElementsMatch(t, certConfig.Organization, csr.Subject.Organization, "organizations not equal")

	if csr.Subject.CommonName != certConfig.CommonName {
		t.Errorf("expected common name %q, got %q", certConfig.CommonName, csr.Subject.CommonName)
	}

	assert.ElementsMatch(t, certConfig.AltNames.DNSNames, csr.DNSNames, "dns names not equal")

	assert.Len(t, csr.IPAddresses, len(certConfig.AltNames.IPs))

	for i, ip := range csr.IPAddresses {
		if !ip.Equal(certConfig.AltNames.IPs[i]) {
			t.Errorf("[%d]: %v != %v", i, ip, certConfig.AltNames.IPs[i])
		}
	}
}

func TestCreateCertificateFilesMethods(t *testing.T) {

	var tests = []struct {
		createFunc    func(cfg *kubeadmapi.InitConfiguration) error
		expectedFiles []string
		externalEtcd  bool
	}{
		{
			createFunc: CreatePKIAssets,
			expectedFiles: []string{
				kubeadmconstants.CACertName, kubeadmconstants.CAKeyName,
				kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName,
				kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName,
				kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName,
				kubeadmconstants.EtcdServerCertName, kubeadmconstants.EtcdServerKeyName,
				kubeadmconstants.EtcdPeerCertName, kubeadmconstants.EtcdPeerKeyName,
				kubeadmconstants.EtcdHealthcheckClientCertName, kubeadmconstants.EtcdHealthcheckClientKeyName,
				kubeadmconstants.APIServerEtcdClientCertName, kubeadmconstants.APIServerEtcdClientKeyName,
				kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName,
				kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName,
				kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName,
			},
		},
		{
			createFunc:   CreatePKIAssets,
			externalEtcd: true,
			expectedFiles: []string{
				kubeadmconstants.CACertName, kubeadmconstants.CAKeyName,
				kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName,
				kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName,
				kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName,
				kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName,
				kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName,
			},
		},
	}

	for _, test := range tests {
		// Create temp folder for the test case
		tmpdir := testutil.SetupTempDir(t)
		defer os.RemoveAll(tmpdir)

		cfg := &kubeadmapi.InitConfiguration{
			LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4"},
			ClusterConfiguration: kubeadmapi.ClusterConfiguration{
				Etcd:            kubeadmapi.Etcd{Local: &kubeadmapi.LocalEtcd{}},
				Networking:      kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
				CertificatesDir: tmpdir,
			},
			NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-hostname"},
		}

		if test.externalEtcd {
			if cfg.Etcd.External == nil {
				cfg.Etcd.External = &kubeadmapi.ExternalEtcd{}
			}
			cfg.Etcd.Local = nil
			cfg.Etcd.External.Endpoints = []string{"192.168.1.1:2379"}
		}

		// executes create func
		if err := test.createFunc(cfg); err != nil {
			t.Errorf("error executing createFunc: %v", err)
			continue
		}

		// asserts expected files are there
		testutil.AssertFileExists(t, tmpdir, test.expectedFiles...)
	}
}

func deleteCertOrKey(name string) func(*kubeadmapi.InitConfiguration) error {
	return func(cfg *kubeadmapi.InitConfiguration) error {
		if err := os.Remove(filepath.Join(cfg.CertificatesDir, name)); err != nil {
			return errors.Wrapf(err, "failed removing %s", name)
		}
		return nil
	}
}

func assertCertsExist(t *testing.T, dir string) {
	tree, err := GetDefaultCertList().AsMap().CertTree()
	if err != nil {
		t.Fatalf("unexpected error getting certificates: %v", err)
	}

	for caCert, certs := range tree {
		if err := validateCACert(certKeyLocation{dir, caCert.BaseName, "", caCert.Name}); err != nil {
			t.Errorf("couldn't validate CA certificate %v: %v", caCert.Name, err)
			// Don't bother validating child certs, but do try the other CAs
			continue
		}

		for _, cert := range certs {
			if err := validateSignedCert(certKeyLocation{dir, caCert.BaseName, cert.BaseName, cert.Name}); err != nil {
				t.Errorf("couldn't validate certificate %v: %v", cert.Name, err)
			}
		}
	}
}
