diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index 0fc6b5b27fa..a6b060a0da2 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -22,14 +22,164 @@ import ( "fmt" "net" - "k8s.io/apimachinery/pkg/util/validation" certutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" + "k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/validation" ) +// CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane. +// If the PKI assets already exists in the target folder, they are used only if evaluated equal; otherwise an error is returned. +func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration) error { + + certActions := []func(cfg *kubeadmapi.MasterConfiguration) error{ + CreateCACertAndKeyfiles, + CreateAPIServerCertAndKeyFiles, + CreateAPIServerKubeletClientCertAndKeyFiles, + CreateServiceAccountKeyAndPublicKeyFiles, + CreateFrontProxyCACertAndKeyFiles, + CreateFrontProxyClientCertAndKeyFiles, + } + + for _, action := range certActions { + err := action(cfg) + if err != nil { + return err + } + } + + fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n", cfg.CertificatesDir) + + return nil +} + +// CreateCACertAndKeyfiles create a new self signed CA certificate and key files. +// If the CA certificate and key files already exists in the target folder, they are used only if evaluated equal; otherwise an error is returned. +func CreateCACertAndKeyfiles(cfg *kubeadmapi.MasterConfiguration) error { + + caCert, caKey, err := NewCACertAndKey() + if err != nil { + return err + } + + return writeCertificateAuthorithyFilesIfNotExist( + cfg.CertificatesDir, + kubeadmconstants.CACertAndKeyBaseName, + caCert, + caKey, + ) +} + +// CreateAPIServerCertAndKeyFiles create a new certificate and key files for the apiserver. +// If the apiserver certificate and key files already exists in the target folder, they are used only if evaluated equal; otherwise an error is returned. +// It assumes the cluster CA certificate and key files should exists into the CertificatesDir +func CreateAPIServerCertAndKeyFiles(cfg *kubeadmapi.MasterConfiguration) error { + + caCert, caKey, err := loadCertificateAuthorithy(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) + if err != nil { + return err + } + + apiCert, apiKey, err := NewAPIServerCertAndKey(cfg, caCert, caKey) + if err != nil { + return err + } + + return writeCertificateFilesIfNotExist( + cfg.CertificatesDir, + kubeadmconstants.APIServerCertAndKeyBaseName, + caCert, + apiCert, + apiKey, + ) +} + +// CreateAPIServerKubeletClientCertAndKeyFiles create a new CA certificate for kubelets calling apiserver +// If the apiserver-kubelet-client certificate and key files already exists in the target folder, they are used only if evaluated equals; otherwise an error is returned. +// It assumes the cluster CA certificate and key files should exists into the CertificatesDir +func CreateAPIServerKubeletClientCertAndKeyFiles(cfg *kubeadmapi.MasterConfiguration) error { + + caCert, caKey, err := loadCertificateAuthorithy(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) + if err != nil { + return err + } + + apiClientCert, apiClientKey, err := NewAPIServerKubeletClientCertAndKey(caCert, caKey) + if err != nil { + return err + } + + return writeCertificateFilesIfNotExist( + cfg.CertificatesDir, + kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, + caCert, + apiClientCert, + apiClientKey, + ) +} + +// CreateServiceAccountKeyAndPublicKeyFiles create a new public/private key files for signing service account users. +// If the sa public/private key files already exists in the target folder, they are used only if evaluated equals; otherwise an error is returned. +func CreateServiceAccountKeyAndPublicKeyFiles(cfg *kubeadmapi.MasterConfiguration) error { + + saSigningKey, err := NewServiceAccountSigningKey() + if err != nil { + return err + } + + return writeKeyFilesIfNotExist( + cfg.CertificatesDir, + kubeadmconstants.ServiceAccountKeyBaseName, + saSigningKey, + ) +} + +// CreateFrontProxyCACertAndKeyFiles create a self signed front proxy CA certificate and key files. +// Front proxy CA and client certs are used to secure a front proxy authenticator which is used to assert identity +// without the client cert; This is a separte CA, so that front proxy identities cannot hit the API and normal client certs cannot be used +// as front proxies. +// If the front proxy CA certificate and key files already exists in the target folder, they are used only if evaluated equals; otherwise an error is returned. +func CreateFrontProxyCACertAndKeyFiles(cfg *kubeadmapi.MasterConfiguration) error { + + frontProxyCACert, frontProxyCAKey, err := NewFrontProxyCACertAndKey() + if err != nil { + return err + } + + return writeCertificateAuthorithyFilesIfNotExist( + cfg.CertificatesDir, + kubeadmconstants.FrontProxyCACertAndKeyBaseName, + frontProxyCACert, + frontProxyCAKey, + ) +} + +// CreateFrontProxyClientCertAndKeyFiles create a new certificate for proxy server client. +// If the front-proxy-client certificate and key files already exists in the target folder, they are used only if evaluated equals; otherwise an error is returned. +// It assumes the front proxy CAA certificate and key files should exists into the CertificatesDir +func CreateFrontProxyClientCertAndKeyFiles(cfg *kubeadmapi.MasterConfiguration) error { + + frontProxyCACert, frontProxyCAKey, err := loadCertificateAuthorithy(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName) + if err != nil { + return err + } + + frontProxyClientCert, frontProxyClientKey, err := NewFrontProxyClientCertAndKey(frontProxyCACert, frontProxyCAKey) + if err != nil { + return err + } + + return writeCertificateFilesIfNotExist( + cfg.CertificatesDir, + kubeadmconstants.FrontProxyClientCertAndKeyBaseName, + frontProxyCACert, + frontProxyClientCert, + frontProxyClientKey, + ) +} + // NewCACertAndKey will generate a self signed CA. func NewCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) { @@ -91,10 +241,6 @@ func NewServiceAccountSigningKey() (*rsa.PrivateKey, error) { } // NewFrontProxyCACertAndKey generate a self signed front proxy CA. -// Front proxy CA and client certs are used to secure a front proxy authenticator which is used to assert identity -// without the client cert. -// This is a separte CA, so that front proxy identities cannot hit the API and normal client certs cannot be used -// as front proxies. func NewFrontProxyCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) { frontProxyCACert, frontProxyCAKey, err := pkiutil.NewCertificateAuthority() @@ -120,6 +266,138 @@ func NewFrontProxyClientCertAndKey(frontProxyCACert *x509.Certificate, frontProx return frontProxyClientCert, frontProxyClientKey, nil } +// loadCertificateAuthorithy loads certificate authorithy +func loadCertificateAuthorithy(pkiDir string, baseName string) (*x509.Certificate, *rsa.PrivateKey, error) { + // Checks if certificate authorithy exists in the PKI directory + if !pkiutil.CertOrKeyExist(pkiDir, baseName) { + return nil, nil, fmt.Errorf("couldn't load %s certificate authorithy from %s", baseName, pkiDir) + } + + // Try to load certificate authorithy .crt and .key from the PKI directory + caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName) + if err != nil { + return nil, nil, fmt.Errorf("failure loading %s certificate authorithy: %v", baseName, err) + } + + // Make sure the loaded CA cert actually is a CA + if !caCert.IsCA { + return nil, nil, fmt.Errorf("%s certificate is not a certificate authorithy", baseName) + } + + return caCert, caKey, nil +} + +// writeCertificateAuthorithyFilesIfNotExist write a new certificate Authorithy to the given path. +// If there already is a certificate file at the given path; kubeadm tries to load it and check if the values in the +// existing and the expected certificate equals. If they do; kubeadm will just skip writing the file as it's up-to-date, +// otherwise this function returns an error. +func writeCertificateAuthorithyFilesIfNotExist(pkiDir string, baseName string, caCert *x509.Certificate, caKey *rsa.PrivateKey) error { + + // If cert or key exists, we should try to load them + if pkiutil.CertOrKeyExist(pkiDir, baseName) { + + // Try to load .crt and .key from the PKI directory + caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName) + if err != nil { + return fmt.Errorf("failure loading %s certificate: %v", baseName, err) + } + + // Check if the existing cert is a CA + if !caCert.IsCA { + return fmt.Errorf("certificate %s is not a CA", baseName) + } + + // kubeadm doesn't validate the existing certificate Authorithy more than this; + // Basically, if we find a certificate file with the same path; and it is a CA + // kubeadm thinks those files are equal and doesn't bother writing a new file + fmt.Printf("[certificates] Using the existing %s certificate and key.\n", baseName) + } else { + + // Write .crt and .key files to disk + if err := pkiutil.WriteCertAndKey(pkiDir, baseName, caCert, caKey); err != nil { + return fmt.Errorf("failure while saving %s certificate and key: %v", baseName, err) + } + + fmt.Printf("[certificates] Generated %s certificate and key.\n", baseName) + } + return nil +} + +// writeCertificateFilesIfNotExist write a new certificate to the given path. +// If there already is a certificate file at the given path; kubeadm tries to load it and check if the values in the +// existing and the expected certificate equals. If they do; kubeadm will just skip writing the file as it's up-to-date, +// otherwise this function returns an error. +func writeCertificateFilesIfNotExist(pkiDir string, baseName string, signingCert *x509.Certificate, cert *x509.Certificate, key *rsa.PrivateKey) error { + + // Checks if the signed certificate exists in the PKI directory + if pkiutil.CertOrKeyExist(pkiDir, baseName) { + // Try to load signed certificate .crt and .key from the PKI directory + signedCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName) + if err != nil { + return fmt.Errorf("failure loading %s certificate: %v", baseName, err) + } + + // Check if the existing cert is signed by the given CA + if err := signedCert.CheckSignatureFrom(signingCert); err != nil { + return fmt.Errorf("certificate %s is not signed by corresponding CA", baseName) + } + + // kubeadm doesn't validate the existing certificate more than this; + // Basically, if we find a certificate file with the same path; and it is signed by + // the expected certificate authorithy, kubeadm thinks those files are equal and + // doesn't bother writing a new file + fmt.Printf("[certificates] Using the existing %s certificate and key.\n", baseName) + } else { + + // Write .crt and .key files to disk + if err := pkiutil.WriteCertAndKey(pkiDir, baseName, cert, key); err != nil { + return fmt.Errorf("failure while saving %s certificate and key: %v", baseName, err) + } + + fmt.Printf("[certificates] Generated %s certificate and key.\n", baseName) + if pkiutil.HasServerAuth(cert) { + fmt.Printf("[certificates] %s serving cert is signed for DNS names %v and IPs %v\n", baseName, cert.DNSNames, cert.IPAddresses) + } + } + + return nil +} + +// writeKeyFilesIfNotExist write a new key to the given path. +// If there already is a key file at the given path; kubeadm tries to load it and check if the values in the +// existing and the expected key equals. If they do; kubeadm will just skip writing the file as it's up-to-date, +// otherwise this function returns an error. +func writeKeyFilesIfNotExist(pkiDir string, baseName string, key *rsa.PrivateKey) error { + + // Checks if the key exists in the PKI directory + if pkiutil.CertOrKeyExist(pkiDir, baseName) { + + // Try to load .key from the PKI directory + _, err := pkiutil.TryLoadKeyFromDisk(pkiDir, baseName) + if err != nil { + return fmt.Errorf("%s key existed but it could not be loaded properly: %v", baseName, err) + } + + // kubeadm doesn't validate the existing certificate key more than this; + // Basically, if we find a key file with the same path kubeadm thinks those files + // are equal and doesn't bother writing a new file + fmt.Printf("[certificates] Using the existing %s key.\n", baseName) + } else { + + // Write .key and .pub files to disk + if err := pkiutil.WriteKey(pkiDir, baseName, key); err != nil { + return fmt.Errorf("failure while saving %s key: %v", baseName, err) + } + + if err := pkiutil.WritePublicKey(pkiDir, baseName, &key.PublicKey); err != nil { + return fmt.Errorf("failure while saving %s public key: %v", baseName, err) + } + fmt.Printf("[certificates] Generated %s key and public key.\n", baseName) + } + + return nil +} + // getAltNames builds an AltNames object for to be used when generating apiserver certificate func getAltNames(cfg *kubeadmapi.MasterConfiguration) (*certutil.AltNames, error) { diff --git a/cmd/kubeadm/app/phases/certs/certs_test.go b/cmd/kubeadm/app/phases/certs/certs_test.go index 7a058373fe6..5840e648524 100644 --- a/cmd/kubeadm/app/phases/certs/certs_test.go +++ b/cmd/kubeadm/app/phases/certs/certs_test.go @@ -17,21 +17,298 @@ limitations under the License. package certs import ( + "crypto/rsa" "crypto/x509" "net" + "os" "testing" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" + + testutil "k8s.io/kubernetes/cmd/kubeadm/test" + certstestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs" ) +func TestWriteCertificateAuthorithyFilesIfNotExist(t *testing.T) { + + setupCert, setupKey, _ := NewCACertAndKey() + caCert, caKey, _ := NewCACertAndKey() + + 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, _ := NewFrontProxyClientCertAndKey(setupCert, setupKey) + return writeCertificateFilesIfNotExist(pkiDir, "dummy", setupCert, cert, key) + }, + 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) { + + caCert, caKey, _ := NewFrontProxyCACertAndKey() + setupCert, setupKey, _ := NewFrontProxyClientCertAndKey(caCert, caKey) + cert, key, _ := NewFrontProxyClientCertAndKey(caCert, caKey) + + 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 > existing cert used + setupFunc: func(pkiDir string) error { + return writeCertificateFilesIfNotExist(pkiDir, "dummy", caCert, setupCert, setupKey) + }, + 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, _ := NewFrontProxyCACertAndKey() + anotherCert, anotherKey, _ := NewFrontProxyClientCertAndKey(anotherCaCert, anotherCaKey) + + return writeCertificateFilesIfNotExist(pkiDir, "dummy", anotherCaCert, anotherCert, anotherKey) + }, + 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) + + 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 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 + 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 TestWriteKeyFilesIfNotExist(t *testing.T) { + + setupKey, _ := NewServiceAccountSigningKey() + key, _ := NewServiceAccountSigningKey() + + var tests = []struct { + setupFunc func(pkiDir string) error + expectedError bool + expectedKey *rsa.PrivateKey + }{ + { // key does not exists > key written + expectedKey: key, + }, + { // key exists > existing key used + setupFunc: func(pkiDir string) error { + return writeKeyFilesIfNotExist(pkiDir, "dummy", setupKey) + }, + expectedKey: setupKey, + }, + { // some file exists, but it is not a valid key > err + setupFunc: func(pkiDir string) error { + testutil.SetupEmptyFiles(t, pkiDir, "dummy.key") + return nil + }, + 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 := writeKeyFilesIfNotExist(tmpdir, "dummy", key) + + if !test.expectedError && err != nil { + t.Errorf("error writeKeyFilesIfNotExist failed when not expected to fail: %v", err) + continue + } else if test.expectedError && err == nil { + t.Error("error writeKeyFilesIfNotExist didn't failed when expected") + continue + } else if test.expectedError { + continue + } + + // asserts expected files are there + testutil.AssertFileExists(t, tmpdir, "dummy.key", "dummy.pub") + + // check created key + resultingKey, err := pkiutil.TryLoadKeyFromDisk(tmpdir, "dummy") + if err != nil { + t.Errorf("failure reading created key: %v", err) + continue + } + + //TODO: check if there is a better method to compare keys + if resultingKey.D == key.D { + t.Error("created key does not match expected key") + } + } +} + +func TestGetAltNames(t *testing.T) { + hostname := "valid-hostname" + advertiseIP := "1.2.3.4" + cfg := &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{AdvertiseAddress: advertiseIP}, + Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"}, + NodeName: hostname, + } + + altNames, err := getAltNames(cfg) + if err != nil { + t.Fatalf("failed calling getAltNames: %v", err) + } + + expectedDNSNames := []string{hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local"} + for _, DNSName := range expectedDNSNames { + found := false + for _, val := range altNames.DNSNames { + if val == DNSName { + found = true + break + } + } + + if !found { + t.Errorf("altNames does not contain DNSName %s", DNSName) + } + } + + expectedIPAddresses := []string{"10.96.0.1", advertiseIP} + for _, IPAddress := range expectedIPAddresses { + found := false + for _, val := range altNames.IPs { + if val.Equal(net.ParseIP(IPAddress)) { + found = true + break + } + } + + if !found { + t.Errorf("altNames does not contain IPAddress %s", IPAddress) + } + } +} + func TestNewCACertAndKey(t *testing.T) { caCert, _, err := NewCACertAndKey() if err != nil { t.Fatalf("failed call NewCACertAndKey: %v", err) } - assertIsCa(t, caCert) + certstestutil.AssertCertificateIsCa(t, caCert) } func TestNewAPIServerCertAndKey(t *testing.T) { @@ -51,15 +328,10 @@ func TestNewAPIServerCertAndKey(t *testing.T) { t.Fatalf("failed creation of cert and key: %v", err) } - assertIsSignedByCa(t, apiServerCert, caCert) - assertHasServerAuth(t, apiServerCert) - - for _, DNSName := range []string{hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local"} { - assertHasDNSNames(t, apiServerCert, DNSName) - } - for _, IPAddress := range []string{"10.96.0.1", addr} { - assertHasIPAddresses(t, apiServerCert, net.ParseIP(IPAddress)) - } + certstestutil.AssertCertificateIsSignedByCa(t, apiServerCert, caCert) + certstestutil.AssertCertificateHasServerAuthUsage(t, apiServerCert) + certstestutil.AssertCertificateHasDNSNames(t, apiServerCert, hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local") + certstestutil.AssertCertificateHasIPAddresses(t, apiServerCert, net.ParseIP("10.96.0.1"), net.ParseIP(addr)) } } @@ -71,9 +343,9 @@ func TestNewAPIServerKubeletClientCertAndKey(t *testing.T) { t.Fatalf("failed creation of cert and key: %v", err) } - assertIsSignedByCa(t, apiClientCert, caCert) - assertHasClientAuth(t, apiClientCert) - assertHasOrganization(t, apiClientCert, constants.MastersGroup) + certstestutil.AssertCertificateIsSignedByCa(t, apiClientCert, caCert) + certstestutil.AssertCertificateHasClientAuthUsage(t, apiClientCert) + certstestutil.AssertCertificateHasOrganizations(t, apiClientCert, kubeadmconstants.MastersGroup) } func TestNewNewServiceAccountSigningKey(t *testing.T) { @@ -94,7 +366,7 @@ func TestNewFrontProxyCACertAndKey(t *testing.T) { t.Fatalf("failed creation of cert and key: %v", err) } - assertIsCa(t, frontProxyCACert) + certstestutil.AssertCertificateIsCa(t, frontProxyCACert) } func TestNewFrontProxyClientCertAndKey(t *testing.T) { @@ -105,63 +377,84 @@ func TestNewFrontProxyClientCertAndKey(t *testing.T) { t.Fatalf("failed creation of cert and key: %v", err) } - assertIsSignedByCa(t, frontProxyClientCert, frontProxyCACert) - assertHasClientAuth(t, frontProxyClientCert) + certstestutil.AssertCertificateIsSignedByCa(t, frontProxyClientCert, frontProxyCACert) + certstestutil.AssertCertificateHasClientAuthUsage(t, frontProxyClientCert) } -func assertIsCa(t *testing.T, cert *x509.Certificate) { - if !cert.IsCA { - t.Error("cert is not a valida CA") +func TestCreateCertificateFilesMethods(t *testing.T) { + + var tests = []struct { + setupFunc func(cfg *kubeadmapi.MasterConfiguration) error + createFunc func(cfg *kubeadmapi.MasterConfiguration) error + expectedFiles []string + }{ + { + createFunc: CreatePKIAssets, + 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, + }, + }, + { + createFunc: CreateCACertAndKeyfiles, + expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName}, + }, + { + setupFunc: CreateCACertAndKeyfiles, + createFunc: CreateAPIServerCertAndKeyFiles, + expectedFiles: []string{kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName}, + }, + { + setupFunc: CreateCACertAndKeyfiles, + createFunc: CreateAPIServerKubeletClientCertAndKeyFiles, + expectedFiles: []string{kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName}, + }, + { + createFunc: CreateServiceAccountKeyAndPublicKeyFiles, + expectedFiles: []string{kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName}, + }, + { + createFunc: CreateFrontProxyCACertAndKeyFiles, + expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName}, + }, + { + setupFunc: CreateFrontProxyCACertAndKeyFiles, + createFunc: CreateFrontProxyClientCertAndKeyFiles, + expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName}, + }, } -} -func assertIsSignedByCa(t *testing.T, cert *x509.Certificate, ca *x509.Certificate) { - if err := cert.CheckSignatureFrom(ca); err != nil { - t.Error("cert is not signed by ca") - } -} + for _, test := range tests { + // Create temp folder for the test case + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) -func assertHasClientAuth(t *testing.T, cert *x509.Certificate) { - for i := range cert.ExtKeyUsage { - if cert.ExtKeyUsage[i] == x509.ExtKeyUsageClientAuth { - return + cfg := &kubeadmapi.MasterConfiguration{ + API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"}, + Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"}, + NodeName: "valid-hostname", + CertificatesDir: tmpdir, } - } - t.Error("cert is not a ClientAuth") -} -func assertHasServerAuth(t *testing.T, cert *x509.Certificate) { - for i := range cert.ExtKeyUsage { - if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth { - return + // executes setup func (if necessary) + if test.setupFunc != nil { + if err := test.setupFunc(cfg); err != nil { + t.Errorf("error executing setupFunc: %v", err) + continue + } } - } - t.Error("cert is not a ServerAuth") -} -func assertHasOrganization(t *testing.T, cert *x509.Certificate, OU string) { - for i := range cert.Subject.Organization { - if cert.Subject.Organization[i] == OU { - return + // executes create func + if err := test.createFunc(cfg); err != nil { + t.Errorf("error executing createFunc: %v", err) + continue } - } - t.Errorf("cert does not contain OU %s", OU) -} -func assertHasDNSNames(t *testing.T, cert *x509.Certificate, DNSName string) { - for i := range cert.DNSNames { - if cert.DNSNames[i] == DNSName { - return - } + // asserts expected files are there + testutil.AssertFileExists(t, tmpdir, test.expectedFiles...) } - t.Errorf("cert does not contain DNSName %s", DNSName) -} - -func assertHasIPAddresses(t *testing.T, cert *x509.Certificate, IPAddress net.IP) { - for i := range cert.IPAddresses { - if cert.IPAddresses[i].Equal(IPAddress) { - return - } - } - t.Errorf("cert does not contain IPAddress %s", IPAddress) } diff --git a/cmd/kubeadm/test/certs/util.go b/cmd/kubeadm/test/certs/util.go index 30ef1d303ae..4f3235010cf 100644 --- a/cmd/kubeadm/test/certs/util.go +++ b/cmd/kubeadm/test/certs/util.go @@ -19,6 +19,7 @@ package certs import ( "crypto/rsa" "crypto/x509" + "net" "testing" "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" @@ -35,6 +36,13 @@ func SetupCertificateAuthorithy(t *testing.T) (*x509.Certificate, *rsa.PrivateKe return caCert, caKey } +// AssertCertificateIsCa is a utility function for kubeadm testing that asserts if a given certificate is a CA +func AssertCertificateIsCa(t *testing.T, cert *x509.Certificate) { + if !cert.IsCA { + t.Error("cert is not a valida CA") + } +} + // AssertCertificateIsSignedByCa is a utility function for kubeadm testing that asserts if a given certificate is signed // by the expected CA func AssertCertificateIsSignedByCa(t *testing.T, cert *x509.Certificate, signingCa *x509.Certificate) { @@ -77,3 +85,50 @@ func AssertCertificateHasClientAuthUsage(t *testing.T, cert *x509.Certificate) { } t.Error("cert has not ClientAuth usage as expected") } + +// AssertCertificateHasServerAuthUsage is a utility function for kubeadm testing that asserts if a given certificate has +// the expected ExtKeyUsageServerAuth +func AssertCertificateHasServerAuthUsage(t *testing.T, cert *x509.Certificate) { + for i := range cert.ExtKeyUsage { + if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth { + return + } + } + t.Error("cert is not a ServerAuth") +} + +// AssertCertificateHasDNSNames is a utility function for kubeadm testing that asserts if a given certificate has +// the expected DNSNames +func AssertCertificateHasDNSNames(t *testing.T, cert *x509.Certificate, DNSNames ...string) { + for _, DNSName := range DNSNames { + found := false + for _, val := range cert.DNSNames { + if val == DNSName { + found = true + break + } + } + + if !found { + t.Errorf("cert does not contain DNSName %s", DNSName) + } + } +} + +// AssertCertificateHasIPAddresses is a utility function for kubeadm testing that asserts if a given certificate has +// the expected IPAddresses +func AssertCertificateHasIPAddresses(t *testing.T, cert *x509.Certificate, IPAddresses ...net.IP) { + for _, IPAddress := range IPAddresses { + found := false + for _, val := range cert.IPAddresses { + if val.Equal(IPAddress) { + found = true + break + } + } + + if !found { + t.Errorf("cert does not contain IPAddress %s", IPAddress) + } + } +} diff --git a/cmd/kubeadm/test/util.go b/cmd/kubeadm/test/util.go index fe8ea38f781..916e72bf512 100644 --- a/cmd/kubeadm/test/util.go +++ b/cmd/kubeadm/test/util.go @@ -76,6 +76,17 @@ func SetupMasterConfigurationFile(t *testing.T, tmpdir string, cfg *kubeadmapi.M return cfgPath } +// SetupEmptyFiles is a utility function for kubeadm testing that creates one or more empty files (touch) +func SetupEmptyFiles(t *testing.T, tmpdir string, fileNames ...string) { + for _, fileName := range fileNames { + newFile, err := os.Create(filepath.Join(tmpdir, fileName)) + if err != nil { + t.Fatalf("Error creating file %s in %s: %v", fileName, tmpdir, err) + } + newFile.Close() + } +} + // SetupPkiDirWithCertificateAuthorithy is a utility function for kubeadm testing that creates a // CertificateAuthorithy cert/key pair into /pki subfolder of a given temporary directory. // The funtion returns the path of the created pki.