From f5e9eb86742ef4350fdb7e5f3c3827593f06937d Mon Sep 17 00:00:00 2001 From: liz Date: Thu, 9 Aug 2018 15:14:05 -0400 Subject: [PATCH 1/2] Add certlist.go - a declarative list of all certs kubeadm requires * Sub out New*CertAndKey for functions using the new certlist --- cmd/kubeadm/app/phases/certs/certs_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmd/kubeadm/app/phases/certs/certs_test.go b/cmd/kubeadm/app/phases/certs/certs_test.go index 446f0ab35f..08122d1870 100644 --- a/cmd/kubeadm/app/phases/certs/certs_test.go +++ b/cmd/kubeadm/app/phases/certs/certs_test.go @@ -17,7 +17,9 @@ limitations under the License. package certs import ( + "crypto" "crypto/rsa" + "crypto/tls" "crypto/x509" "fmt" "net" @@ -803,3 +805,17 @@ func TestCreateCertificateFilesMethods(t *testing.T) { testutil.AssertFileExists(t, tmpdir, test.expectedFiles...) } } + +func parseCertAndKey(basePath string, t *testing.T) (*x509.Certificate, crypto.PrivateKey) { + certPair, err := tls.LoadX509KeyPair(basePath+".crt", basePath+".key") + if err != nil { + t.Fatalf("couldn't parse certificate and key: %v", err) + } + + parsedCert, err := x509.ParseCertificate(certPair.Certificate[0]) + if err != nil { + t.Fatalf("couldn't parse certificate: %v", err) + } + + return parsedCert, certPair.PrivateKey +} From 394e6b554af0c143615a6824db8864732c0ec672 Mon Sep 17 00:00:00 2001 From: liz Date: Wed, 15 Aug 2018 11:00:31 -0400 Subject: [PATCH 2/2] Yank out a bunch of manual tests and prose `phase certs` and upgrade commands now all use certslist infra --- cmd/kubeadm/app/cmd/phases/certs.go | 323 +++++------ cmd/kubeadm/app/cmd/phases/certs_test.go | 68 ++- cmd/kubeadm/app/phases/certs/certlist.go | 69 ++- cmd/kubeadm/app/phases/certs/certs.go | 358 ++----------- cmd/kubeadm/app/phases/certs/certs_test.go | 503 +++++------------- .../app/phases/certs/pkiutil/pki_helpers.go | 11 +- .../phases/certs/pkiutil/pki_helpers_test.go | 14 +- .../app/phases/kubeconfig/kubeconfig.go | 2 +- cmd/kubeadm/app/phases/upgrade/BUILD | 1 - cmd/kubeadm/app/phases/upgrade/postupgrade.go | 6 +- .../app/phases/upgrade/postupgrade_test.go | 17 +- cmd/kubeadm/app/phases/upgrade/staticpods.go | 33 +- .../app/phases/upgrade/staticpods_test.go | 27 +- cmd/kubeadm/test/certs/BUILD | 5 +- cmd/kubeadm/test/certs/util.go | 3 +- 15 files changed, 479 insertions(+), 961 deletions(-) diff --git a/cmd/kubeadm/app/cmd/phases/certs.go b/cmd/kubeadm/app/cmd/phases/certs.go index 8a3681adc9..234862b045 100644 --- a/cmd/kubeadm/app/cmd/phases/certs.go +++ b/cmd/kubeadm/app/cmd/phases/certs.go @@ -18,13 +18,13 @@ package phases import ( "fmt" + "strings" "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" - "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" @@ -38,12 +38,12 @@ var ( Generates a self-signed CA to provision identities for each component in the cluster (including nodes) and client certificates to be used by various components. - If a given certificate and private key pair both exist, kubeadm skips the generation step and + If a given certificate and private key pair both exist, kubeadm skips the generation step and existing files will be used. ` + cmdutil.AlphaDisclaimer) allCertsExample = normalizer.Examples(` - # Creates all PKI assets necessary to establish the control plane, + # Creates all PKI assets necessary to establish the control plane, # functionally equivalent to what generated by kubeadm init. kubeadm alpha phase certs all @@ -51,85 +51,17 @@ var ( kubeadm alpha phase certs all --config masterconfiguration.yaml `) - caCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the self-signed kubernetes certificate authority and related key, and saves them into %s and %s files. - - If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.CACertName, kubeadmconstants.CAKeyName) - - apiServerCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the API server serving certificate and key and saves them into %s and %s files. - - The certificate includes default subject alternative names and additional SANs provided by the user; - default SANs are: , , kubernetes, kubernetes.default, kubernetes.default.svc, - kubernetes.default.svc., (that is the .10 address in address space). - - If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName) - - apiServerKubeletCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the client certificate for the API server to connect to the kubelet securely and the respective key, - and saves them into %s and %s files. - - If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName) - - etcdCaCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the self-signed etcd certificate authority and related key and saves them into %s and %s files. - - If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName) - - etcdServerCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the etcd serving certificate and key and saves them into %s and %s files. - - The certificate includes default subject alternative names and additional SANs provided by the user; - default SANs are: localhost, 127.0.0.1. - - If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.EtcdServerCertName, kubeadmconstants.EtcdServerKeyName) - - etcdPeerCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the etcd peer certificate and key and saves them into %s and %s files. - - The certificate includes default subject alternative names and additional SANs provided by the user; - default SANs are: , . - - If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.EtcdPeerCertName, kubeadmconstants.EtcdPeerKeyName) - - etcdHealthcheckClientCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the client certificate for liveness probes to healthcheck etcd and the respective key, - and saves them into %s and %s files. - - If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.EtcdHealthcheckClientCertName, kubeadmconstants.EtcdHealthcheckClientKeyName) - - apiServerEtcdServerCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the client certificate for the API server to connect to etcd securely and the respective key, - and saves them into %s and %s files. - - If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.APIServerEtcdClientCertName, kubeadmconstants.APIServerEtcdClientKeyName) - saKeyLongDesc = fmt.Sprintf(normalizer.LongDesc(` Generates the private key for signing service account tokens along with its public key, and saves them into %s and %s files. - If both files already exist, kubeadm skips the generation step and existing files will be used. `+cmdutil.AlphaDisclaimer), kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName) - frontProxyCaCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the front proxy CA certificate and key and saves them into %s and %s files. + genericLongDesc = normalizer.LongDesc(` + Generates the %[1]s, and saves them into %[2]s.cert and %[2]s.key files.%[3]s If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName) - - frontProxyClientCertLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Generates the front proxy client certificate and key and saves them into %s and %s files. - - If both files already exist, kubeadm skips the generation step and existing files will be used. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName) + ` + cmdutil.AlphaDisclaimer) ) // NewCmdCerts returns main command for certs phase @@ -154,147 +86,146 @@ func getCertsSubCommands(defaultKubernetesVersion string) []*cobra.Command { kubeadmscheme.Scheme.Default(cfg) var cfgPath string - var subCmds []*cobra.Command - subCmdProperties := []struct { - use string - short string - long string - examples string - cmdFunc func(cfg *kubeadmapi.InitConfiguration) error - }{ - { - use: "all", - short: "Generates all PKI assets necessary to establish the control plane", - long: allCertsLongDesc, - examples: allCertsExample, - cmdFunc: certsphase.CreatePKIAssets, - }, - { - use: "ca", - short: "Generates a self-signed kubernetes CA to provision identities for components of the cluster", - long: caCertLongDesc, - cmdFunc: certsphase.CreateCACertAndKeyFiles, - }, - { - use: "apiserver", - short: "Generates an API server serving certificate and key", - long: apiServerCertLongDesc, - cmdFunc: certsphase.CreateAPIServerCertAndKeyFiles, - }, - { - use: "apiserver-kubelet-client", - short: "Generates a client certificate for the API server to connect to the kubelets securely", - long: apiServerKubeletCertLongDesc, - cmdFunc: certsphase.CreateAPIServerKubeletClientCertAndKeyFiles, - }, - { - use: "etcd-ca", - short: "Generates a self-signed CA to provision identities for etcd", - long: etcdCaCertLongDesc, - cmdFunc: certsphase.CreateEtcdCACertAndKeyFiles, - }, - { - use: "etcd-server", - short: "Generates an etcd serving certificate and key", - long: etcdServerCertLongDesc, - cmdFunc: certsphase.CreateEtcdServerCertAndKeyFiles, - }, - { - use: "etcd-peer", - short: "Generates an etcd peer certificate and key", - long: etcdPeerCertLongDesc, - cmdFunc: certsphase.CreateEtcdPeerCertAndKeyFiles, - }, - { - use: "etcd-healthcheck-client", - short: "Generates a client certificate for liveness probes to healthcheck etcd", - long: etcdHealthcheckClientCertLongDesc, - cmdFunc: certsphase.CreateEtcdHealthcheckClientCertAndKeyFiles, - }, - { - use: "apiserver-etcd-client", - short: "Generates a client certificate for the API server to connect to etcd securely", - long: apiServerEtcdServerCertLongDesc, - cmdFunc: certsphase.CreateAPIServerEtcdClientCertAndKeyFiles, - }, - { - use: "sa", - short: "Generates a private key for signing service account tokens along with its public key", - long: saKeyLongDesc, - cmdFunc: certsphase.CreateServiceAccountKeyAndPublicKeyFiles, - }, - { - use: "front-proxy-ca", - short: "Generates a front proxy CA certificate and key for a Kubernetes cluster", - long: frontProxyCaCertLongDesc, - cmdFunc: certsphase.CreateFrontProxyCACertAndKeyFiles, - }, - { - use: "front-proxy-client", - short: "Generates a front proxy CA client certificate and key for a Kubernetes cluster", - long: frontProxyClientCertLongDesc, - cmdFunc: certsphase.CreateFrontProxyClientCertAndKeyFiles, + // Special case commands + // All runs CreatePKIAssets, which isn't a particular certificate + allCmd := &cobra.Command{ + Use: "all", + Short: "Generates all PKI assets necessary to establish the control plane", + Long: allCertsLongDesc, + Example: allCertsExample, + Run: func(cmd *cobra.Command, args []string) { + internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) + kubeadmutil.CheckErr(err) + + err = certsphase.CreatePKIAssets(internalcfg) + kubeadmutil.CheckErr(err) }, } + addFlags(allCmd, &cfgPath, cfg, true) - for _, properties := range subCmdProperties { - // Creates the UX Command - cmd := &cobra.Command{ - Use: properties.use, - Short: properties.short, - Long: properties.long, - Example: properties.examples, - Run: runCmdFunc(properties.cmdFunc, &cfgPath, cfg, defaultKubernetesVersion), - } + // SA creates the private/public key pair, which doesn't use x509 at all + saCmd := &cobra.Command{ + Use: "sa", + Short: "Generates a private key for signing service account tokens along with its public key", + Long: saKeyLongDesc, + Run: func(cmd *cobra.Command, args []string) { + internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) + kubeadmutil.CheckErr(err) - // Add flags to the command - cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental") - cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where to save the certificates") - if properties.use == "all" || properties.use == "apiserver" { - cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, "Alternative domain for services, to use for the API server serving cert") - cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Alternative range of IP address for service VIPs, from which derives the internal API server VIP that will be added to the API Server serving cert") - cmd.Flags().StringSliceVar(&cfg.APIServerCertSANs, "apiserver-cert-extra-sans", []string{}, "Optional extra altnames to use for the API server serving cert. Can be both IP addresses and DNS names") - cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API server is accessible on, to use for the API server serving cert") - } + err = certsphase.CreateServiceAccountKeyAndPublicKeyFiles(internalcfg) + kubeadmutil.CheckErr(err) + }, + } + addFlags(saCmd, &cfgPath, cfg, false) - subCmds = append(subCmds, cmd) + subCmds := []*cobra.Command{allCmd, saCmd} + + certTree, err := certsphase.GetDefaultCertList().AsMap().CertTree() + kubeadmutil.CheckErr(err) + + for ca, certList := range certTree { + // Don't use pointers from for loops, they will be rewrittenb + caCmds := makeCommandsForCA(ca, certList, &cfgPath, cfg) + subCmds = append(subCmds, caCmds...) } return subCmds } -// runCmdFunc creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters) -func runCmdFunc(cmdFunc func(cfg *kubeadmapi.InitConfiguration) error, cfgPath *string, cfg *kubeadmapiv1alpha3.InitConfiguration, defaultKubernetesVersion string) func(cmd *cobra.Command, args []string) { +func makeCmd(certSpec *certsphase.KubeadmCert, cfgPath *string, cfg *kubeadmapiv1alpha3.InitConfiguration) *cobra.Command { + cmd := &cobra.Command{ + Use: certSpec.Name, + Short: fmt.Sprintf("Generates the %s", certSpec.LongName), + Long: fmt.Sprintf( + genericLongDesc, + certSpec.LongName, + certSpec.BaseName, + getSANDescription(certSpec), + ), + } + addFlags(cmd, cfgPath, cfg, certSpec.Name == "apiserver") + // Add flags to the command + return cmd +} - // the following statement build a closure that wraps a call to a cmdFunc, binding - // the function itself with the specific parameters of each sub command. - // Please note that specific parameter should be passed as value, while other parameters - passed as reference - - // are shared between sub commands and gets access to current value e.g. flags value. +func getSANDescription(certSpec *certsphase.KubeadmCert) string { + //Defaulted config we will use to get SAN certs + defaultConfig := &kubeadmapiv1alpha3.InitConfiguration{ + API: kubeadmapiv1alpha3.API{ + // GetAPIServerAltNames errors without an AdvertiseAddress; this is as good as any. + AdvertiseAddress: "127.0.0.1", + }, + } + defaultInternalConfig := &kubeadmapi.InitConfiguration{} - return func(cmd *cobra.Command, args []string) { - if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { - kubeadmutil.CheckErr(err) + kubeadmscheme.Scheme.Default(defaultConfig) + kubeadmscheme.Scheme.Convert(defaultConfig, defaultInternalConfig, nil) + + certConfig, err := certSpec.GetConfig(defaultInternalConfig) + kubeadmutil.CheckErr(err) + + if len(certConfig.AltNames.DNSNames) == 0 && len(certConfig.AltNames.IPs) == 0 { + return "" + } + // This mutates the certConfig, but we're throwing it after we construct the command anyway + sans := []string{} + + for _, dnsName := range certConfig.AltNames.DNSNames { + if dnsName != "" { + sans = append(sans, dnsName) } + } - // This is used for unit testing only... - // If we wouldn't set this to something, the code would dynamically look up the version from the internet - // By setting this explicitly for tests workarounds that - if defaultKubernetesVersion != "" { - cfg.KubernetesVersion = defaultKubernetesVersion - } else { - // KubernetesVersion is not used, but we set it explicitly to avoid the lookup - // of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig - err := SetKubernetesVersion(nil, cfg) - kubeadmutil.CheckErr(err) - } + for _, ip := range certConfig.AltNames.IPs { + sans = append(sans, ip.String()) + } + return fmt.Sprintf("\n\nDefault SANs are %s", strings.Join(sans, ", ")) +} - // This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags +func addFlags(cmd *cobra.Command, cfgPath *string, cfg *kubeadmapiv1alpha3.InitConfiguration, addAPIFlags bool) { + cmd.Flags().StringVar(cfgPath, "config", *cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental") + cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where to save the certificates") + if addAPIFlags { + cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, "Alternative domain for services, to use for the API server serving cert") + cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Alternative range of IP address for service VIPs, from which derives the internal API server VIP that will be added to the API Server serving cert") + cmd.Flags().StringSliceVar(&cfg.APIServerCertSANs, "apiserver-cert-extra-sans", []string{}, "Optional extra altnames to use for the API server serving cert. Can be both IP addresses and DNS names") + cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API server is accessible on, to use for the API server serving cert") + } +} + +func makeCommandsForCA(ca *certsphase.KubeadmCert, certList []*certsphase.KubeadmCert, cfgPath *string, cfg *kubeadmapiv1alpha3.InitConfiguration) []*cobra.Command { + subCmds := []*cobra.Command{} + + caCmd := makeCmd(ca, cfgPath, cfg) + caCmd.Run = func(cmd *cobra.Command, args []string) { internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(*cfgPath, cfg) kubeadmutil.CheckErr(err) - // Execute the cmdFunc - err = cmdFunc(internalcfg) + err = certsphase.CreateCACertAndKeyFiles(ca, internalcfg) kubeadmutil.CheckErr(err) } + + subCmds = append(subCmds, caCmd) + + for _, cert := range certList { + certCmd := makeCommandForCert(cert, ca, cfgPath, cfg) + subCmds = append(subCmds, certCmd) + } + + return subCmds +} + +func makeCommandForCert(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert, cfgPath *string, cfg *kubeadmapiv1alpha3.InitConfiguration) *cobra.Command { + certCmd := makeCmd(cert, cfgPath, cfg) + + certCmd.Run = func(cmd *cobra.Command, args []string) { + internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(*cfgPath, cfg) + kubeadmutil.CheckErr(err) + + err = certsphase.CreateCertAndKeyFilesWithCA(cert, caCert, internalcfg) + kubeadmutil.CheckErr(err) + } + + return certCmd } diff --git a/cmd/kubeadm/app/cmd/phases/certs_test.go b/cmd/kubeadm/app/cmd/phases/certs_test.go index 32d316c3e4..aa1e00df7c 100644 --- a/cmd/kubeadm/app/cmd/phases/certs_test.go +++ b/cmd/kubeadm/app/cmd/phases/certs_test.go @@ -19,6 +19,7 @@ package phases import ( "fmt" "os" + "strings" "testing" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" @@ -146,18 +147,21 @@ func TestSubCmdCertsCreateFilesWithFlags(t *testing.T) { } for _, test := range tests { - // Create temp folder for the test case - tmpdir := testutil.SetupTempDir(t) - defer os.RemoveAll(tmpdir) + t.Run(strings.Join(test.subCmds, ","), func(t *testing.T) { + // Create temp folder for the test case + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) - // executes given sub commands - for _, subCmdName := range test.subCmds { - certDirFlag := fmt.Sprintf("--cert-dir=%s", tmpdir) - cmdtestutil.RunSubCommand(t, subCmds, subCmdName, certDirFlag) - } + // executes given sub commands + for _, subCmdName := range test.subCmds { + fmt.Printf("running command %q\n", subCmdName) + certDirFlag := fmt.Sprintf("--cert-dir=%s", tmpdir) + cmdtestutil.RunSubCommand(t, subCmds, subCmdName, certDirFlag) + } - // verify expected files are there - testutil.AssertFileExists(t, tmpdir, test.expectedFiles...) + // verify expected files are there + testutil.AssertFileExists(t, tmpdir, test.expectedFiles...) + }) } } @@ -226,8 +230,12 @@ func TestSubCmdCertsCreateFilesWithConfigFile(t *testing.T) { }, }, { - subCmds: []string{"ca", "apiserver", "apiserver-kubelet-client"}, - expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName, kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName}, + subCmds: []string{"ca", "apiserver", "apiserver-kubelet-client"}, + expectedFiles: []string{ + kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, + kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName, + kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName, + }, }, { subCmds: []string{"etcd-ca", "etcd-server", "etcd-peer", "etcd-healthcheck-client", "apiserver-etcd-client"}, @@ -250,26 +258,28 @@ func TestSubCmdCertsCreateFilesWithConfigFile(t *testing.T) { } for _, test := range tests { - // Create temp folder for the test case - tmpdir := testutil.SetupTempDir(t) - defer os.RemoveAll(tmpdir) + t.Run(strings.Join(test.subCmds, ","), func(t *testing.T) { + // Create temp folder for the test case - certdir := tmpdir + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) - cfg := &kubeadmapi.InitConfiguration{ - API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4", BindPort: 1234}, - CertificatesDir: certdir, - NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-node-name"}, - } - configPath := testutil.SetupInitConfigurationFile(t, tmpdir, cfg) + cfg := &kubeadmapi.InitConfiguration{ + API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4", BindPort: 1234}, + CertificatesDir: tmpdir, + NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-node-name"}, + } + configPath := testutil.SetupInitConfigurationFile(t, tmpdir, cfg) - // executes given sub commands - for _, subCmdName := range test.subCmds { - configFlag := fmt.Sprintf("--config=%s", configPath) - cmdtestutil.RunSubCommand(t, subCmds, subCmdName, configFlag) - } + // executes given sub commands + for _, subCmdName := range test.subCmds { + t.Logf("running subcommand %q", subCmdName) + configFlag := fmt.Sprintf("--config=%s", configPath) + cmdtestutil.RunSubCommand(t, subCmds, subCmdName, configFlag) + } - // verify expected files are there - testutil.AssertFileExists(t, tmpdir, test.expectedFiles...) + // verify expected files are there + testutil.AssertFileExists(t, tmpdir, test.expectedFiles...) + }) } } diff --git a/cmd/kubeadm/app/phases/certs/certlist.go b/cmd/kubeadm/app/phases/certs/certlist.go index fc85252fed..4874c2e10d 100644 --- a/cmd/kubeadm/app/phases/certs/certlist.go +++ b/cmd/kubeadm/app/phases/certs/certlist.go @@ -32,6 +32,7 @@ type configMutatorsFunc func(*kubeadmapi.InitConfiguration, *certutil.Config) er // KubeadmCert represents a certificate that Kubeadm will create to function properly. type KubeadmCert struct { Name string + LongName string BaseName string CAName string // Some attributes will depend on the InitConfiguration, only known at runtime. @@ -57,13 +58,14 @@ func (k *KubeadmCert) CreateFromCA(ic *kubeadmapi.InitConfiguration, caCert *x50 if err != nil { return fmt.Errorf("couldn't create %q certificate: %v", k.Name, err) } - cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, *cfg) + cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, cfg) if err != nil { return err } - writeCertificateAuthorithyFilesIfNotExist( + writeCertificateFilesIfNotExist( ic.CertificatesDir, k.BaseName, + caCert, cert, key, ) @@ -71,14 +73,42 @@ func (k *KubeadmCert) CreateFromCA(ic *kubeadmapi.InitConfiguration, caCert *x50 return nil } +// CreateAsCA creates a certificate authority, writing the files to disk and also returning the created CA so it can be used to sign child certs. +func (k *KubeadmCert) CreateAsCA(ic *kubeadmapi.InitConfiguration) (*x509.Certificate, *rsa.PrivateKey, error) { + cfg, err := k.GetConfig(ic) + if err != nil { + return nil, nil, fmt.Errorf("couldn't get configuration for %q CA certificate: %v", k.Name, err) + } + caCert, caKey, err := NewCACertAndKey(cfg) + if err != nil { + return nil, nil, fmt.Errorf("couldn't generate %q CA certificate: %v", k.Name, err) + } + + err = writeCertificateAuthorithyFilesIfNotExist( + ic.CertificatesDir, + k.BaseName, + caCert, + caKey, + ) + if err != nil { + return nil, nil, fmt.Errorf("couldn't write out %q CA certificate: %v", k.Name, err) + } + + return caCert, caKey, nil +} + // CertificateTree is represents a one-level-deep tree, mapping a CA to the certs that depend on it. type CertificateTree map[*KubeadmCert]Certificates // CreateTree creates the CAs, certs signed by the CAs, and writes them all to disk. func (t CertificateTree) CreateTree(ic *kubeadmapi.InitConfiguration) error { for ca, leaves := range t { - // TODO: NewCACertAndKey should take an ic - caCert, caKey, err := NewCACertAndKey() + cfg, err := ca.GetConfig(ic) + if err != nil { + return err + } + + caCert, caKey, err := NewCACertAndKey(cfg) if err != nil { return err } @@ -89,12 +119,13 @@ func (t CertificateTree) CreateTree(ic *kubeadmapi.InitConfiguration) error { } } - if err := writeCertificateAuthorithyFilesIfNotExist( + err = writeCertificateAuthorithyFilesIfNotExist( ic.CertificatesDir, ca.BaseName, caCert, caKey, - ); err != nil { + ) + if err != nil { return err } } @@ -171,7 +202,8 @@ func GetCertsWithoutEtcd() Certificates { var ( // KubeadmCertRootCA is the definition of the Kubernetes Root CA for the API Server and kubelet. KubeadmCertRootCA = KubeadmCert{ - Name: "root-ca", + Name: "ca", + LongName: "self-signed kubernetes CA to provision identities for other kuberenets components", BaseName: kubeadmconstants.CACertAndKeyBaseName, config: certutil.Config{ CommonName: "kubernetes", @@ -179,9 +211,10 @@ var ( } // KubeadmCertAPIServer is the definition of the cert used to serve the kubernetes API. KubeadmCertAPIServer = KubeadmCert{ - Name: "api-server", + Name: "apiserver", + LongName: "certificate for serving the kubernetes API", BaseName: kubeadmconstants.APIServerCertAndKeyBaseName, - CAName: "root-ca", + CAName: "ca", config: certutil.Config{ CommonName: kubeadmconstants.APIServerCertCommonName, Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, @@ -192,9 +225,10 @@ var ( } // KubeadmCertKubeletClient is the definition of the cert used by the API server to access the kubelet. KubeadmCertKubeletClient = KubeadmCert{ - Name: "api-server-kubelet-client", + Name: "apiserver-kubelet-client", + LongName: "Client certificate for the API server to connect to kubelet", BaseName: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, - CAName: "root-ca", + CAName: "ca", config: certutil.Config{ CommonName: kubeadmconstants.APIServerKubeletClientCertCommonName, Organization: []string{kubeadmconstants.MastersGroup}, @@ -205,6 +239,7 @@ var ( // KubeadmCertFrontProxyCA is the definition of the CA used for the front end proxy. KubeadmCertFrontProxyCA = KubeadmCert{ Name: "front-proxy-ca", + LongName: "self-signed CA to provision identities for front proxy", BaseName: kubeadmconstants.FrontProxyCACertAndKeyBaseName, config: certutil.Config{ CommonName: "front-proxy-ca", @@ -215,6 +250,7 @@ var ( KubeadmCertFrontProxyClient = KubeadmCert{ Name: "front-proxy-client", BaseName: kubeadmconstants.FrontProxyClientCertAndKeyBaseName, + LongName: "client for the front proxy", CAName: "front-proxy-ca", config: certutil.Config{ CommonName: kubeadmconstants.FrontProxyClientCertCommonName, @@ -225,6 +261,7 @@ var ( // KubeadmCertEtcdCA is the definition of the root CA used by the hosted etcd server. KubeadmCertEtcdCA = KubeadmCert{ Name: "etcd-ca", + LongName: "self-signed CA to provision identities for etcd", BaseName: kubeadmconstants.EtcdCACertAndKeyBaseName, config: certutil.Config{ CommonName: "etcd-ca", @@ -233,6 +270,7 @@ var ( // KubeadmCertEtcdServer is the definition of the cert used to serve etcd to clients. KubeadmCertEtcdServer = KubeadmCert{ Name: "etcd-server", + LongName: "certificate for serving etcd", BaseName: kubeadmconstants.EtcdServerCertAndKeyBaseName, CAName: "etcd-ca", config: certutil.Config{ @@ -250,6 +288,7 @@ var ( // KubeadmCertEtcdPeer is the definition of the cert used by etcd peers to access each other. KubeadmCertEtcdPeer = KubeadmCert{ Name: "etcd-peer", + LongName: "credentials for etcd nodes to communicate with each other", BaseName: kubeadmconstants.EtcdPeerCertAndKeyBaseName, CAName: "etcd-ca", config: certutil.Config{ @@ -262,7 +301,8 @@ var ( } // KubeadmCertEtcdHealthcheck is the definition of the cert used by Kubernetes to check the health of the etcd server. KubeadmCertEtcdHealthcheck = KubeadmCert{ - Name: "etcd-healthcheck", + Name: "etcd-healthcheck-client", + LongName: "client certificate for liveness probes to healtcheck etcd", BaseName: kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName, CAName: "etcd-ca", config: certutil.Config{ @@ -273,7 +313,8 @@ var ( } // KubeadmCertEtcdAPIClient is the definition of the cert used by the API server to access etcd. KubeadmCertEtcdAPIClient = KubeadmCert{ - Name: "etcd-api-client", + Name: "apiserver-etcd-client", + LongName: "client apiserver uses to access etcd", BaseName: kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName, CAName: "etcd-ca", config: certutil.Config{ @@ -288,7 +329,7 @@ func makeAltNamesMutator(f func(*kubeadmapi.InitConfiguration) (*certutil.AltNam return func(mc *kubeadmapi.InitConfiguration, cc *certutil.Config) error { altNames, err := f(mc) if err != nil { - return nil + return err } cc.AltNames = *altNames return nil diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index b65d2682a5..511cba6179 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -66,186 +66,6 @@ func CreatePKIAssets(cfg *kubeadmapi.InitConfiguration) error { return nil } -// CreateCACertAndKeyFiles create a new self signed cluster 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.InitConfiguration) error { - glog.V(1).Infoln("create a new self signed cluster CA certificate and key files") - 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 exist in the CertificatesDir. -func CreateAPIServerCertAndKeyFiles(cfg *kubeadmapi.InitConfiguration) error { - glog.V(1).Infoln("creating a new certificate and key files for the apiserver") - caCert, caKey, err := loadCertificateAuthority(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 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 exist in the CertificatesDir. -func CreateAPIServerKubeletClientCertAndKeyFiles(cfg *kubeadmapi.InitConfiguration) error { - glog.V(1).Infoln("creating a new certificate for kubelets calling apiserver") - caCert, caKey, err := loadCertificateAuthority(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) - if err != nil { - return err - } - - apiKubeletClientCert, apiKubeletClientKey, err := NewAPIServerKubeletClientCertAndKey(cfg, caCert, caKey) - if err != nil { - return err - } - - return writeCertificateFilesIfNotExist( - cfg.CertificatesDir, - kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, - caCert, - apiKubeletClientCert, - apiKubeletClientKey, - ) -} - -// CreateEtcdCACertAndKeyFiles create a self signed etcd CA certificate and key files. -// The etcd CA and client certs are used to secure communication between etcd peers and connections to etcd from the API server. -// This is a separate CA, so that kubernetes client identities cannot connect to etcd directly or peer with the etcd cluster. -// If the etcd CA certificate and key files already exists in the target folder, they are used only if evaluated equals; otherwise an error is returned. -func CreateEtcdCACertAndKeyFiles(cfg *kubeadmapi.InitConfiguration) error { - glog.V(1).Infoln("creating a self signed etcd CA certificate and key files") - etcdCACert, etcdCAKey, err := NewEtcdCACertAndKey() - if err != nil { - return err - } - - return writeCertificateAuthorithyFilesIfNotExist( - cfg.CertificatesDir, - kubeadmconstants.EtcdCACertAndKeyBaseName, - etcdCACert, - etcdCAKey, - ) -} - -// CreateEtcdServerCertAndKeyFiles create a new certificate and key file for etcd. -// If the etcd serving certificate and key file already exist in the target folder, they are used only if evaluated equal; otherwise an error is returned. -// It assumes the etcd CA certificate and key file exist in the CertificatesDir -func CreateEtcdServerCertAndKeyFiles(cfg *kubeadmapi.InitConfiguration) error { - glog.V(1).Infoln("creating a new server certificate and key files for etcd") - etcdCACert, etcdCAKey, err := loadCertificateAuthority(cfg.CertificatesDir, kubeadmconstants.EtcdCACertAndKeyBaseName) - if err != nil { - return err - } - - etcdServerCert, etcdServerKey, err := NewEtcdServerCertAndKey(cfg, etcdCACert, etcdCAKey) - if err != nil { - return err - } - - return writeCertificateFilesIfNotExist( - cfg.CertificatesDir, - kubeadmconstants.EtcdServerCertAndKeyBaseName, - etcdCACert, - etcdServerCert, - etcdServerKey, - ) -} - -// CreateEtcdPeerCertAndKeyFiles create a new certificate and key file for etcd peering. -// If the etcd peer certificate and key file already exist in the target folder, they are used only if evaluated equal; otherwise an error is returned. -// It assumes the etcd CA certificate and key file exist in the CertificatesDir -func CreateEtcdPeerCertAndKeyFiles(cfg *kubeadmapi.InitConfiguration) error { - glog.V(1).Infoln("creating a new certificate and key files for etcd peering") - etcdCACert, etcdCAKey, err := loadCertificateAuthority(cfg.CertificatesDir, kubeadmconstants.EtcdCACertAndKeyBaseName) - if err != nil { - return err - } - - etcdPeerCert, etcdPeerKey, err := NewEtcdPeerCertAndKey(cfg, etcdCACert, etcdCAKey) - if err != nil { - return err - } - - return writeCertificateFilesIfNotExist( - cfg.CertificatesDir, - kubeadmconstants.EtcdPeerCertAndKeyBaseName, - etcdCACert, - etcdPeerCert, - etcdPeerKey, - ) -} - -// CreateEtcdHealthcheckClientCertAndKeyFiles create a new client certificate for liveness probes to healthcheck etcd -// If the etcd-healthcheck-client certificate and key file already exist in the target folder, they are used only if evaluated equal; otherwise an error is returned. -// It assumes the etcd CA certificate and key file exist in the CertificatesDir -func CreateEtcdHealthcheckClientCertAndKeyFiles(cfg *kubeadmapi.InitConfiguration) error { - - etcdCACert, etcdCAKey, err := loadCertificateAuthority(cfg.CertificatesDir, kubeadmconstants.EtcdCACertAndKeyBaseName) - if err != nil { - return err - } - - etcdHealthcheckClientCert, etcdHealthcheckClientKey, err := NewEtcdHealthcheckClientCertAndKey(cfg, etcdCACert, etcdCAKey) - if err != nil { - return err - } - - return writeCertificateFilesIfNotExist( - cfg.CertificatesDir, - kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName, - etcdCACert, - etcdHealthcheckClientCert, - etcdHealthcheckClientKey, - ) -} - -// CreateAPIServerEtcdClientCertAndKeyFiles create a new client certificate for the apiserver calling etcd -// If the apiserver-etcd-client certificate and key file already exist in the target folder, they are used only if evaluated equal; otherwise an error is returned. -// It assumes the etcd CA certificate and key file exist in the CertificatesDir -func CreateAPIServerEtcdClientCertAndKeyFiles(cfg *kubeadmapi.InitConfiguration) error { - glog.V(1).Infoln("creating a new client certificate for the apiserver calling etcd") - etcdCACert, etcdCAKey, err := loadCertificateAuthority(cfg.CertificatesDir, kubeadmconstants.EtcdCACertAndKeyBaseName) - if err != nil { - return err - } - - apiEtcdClientCert, apiEtcdClientKey, err := NewAPIServerEtcdClientCertAndKey(cfg, etcdCACert, etcdCAKey) - if err != nil { - return err - } - - return writeCertificateFilesIfNotExist( - cfg.CertificatesDir, - kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName, - etcdCACert, - apiEtcdClientCert, - apiEtcdClientKey, - ) -} - // 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.InitConfiguration) error { @@ -262,118 +82,8 @@ func CreateServiceAccountKeyAndPublicKeyFiles(cfg *kubeadmapi.InitConfiguration) ) } -// 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 separate 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.InitConfiguration) error { - glog.V(1).Infoln("creating a self signed front proxy CA certificate and key files") - 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 CA certificate and key files exist in the CertificatesDir. -func CreateFrontProxyClientCertAndKeyFiles(cfg *kubeadmapi.InitConfiguration) error { - glog.V(1).Infoln("creating a new certificate for proxy server client") - frontProxyCACert, frontProxyCAKey, err := loadCertificateAuthority(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName) - if err != nil { - return err - } - - frontProxyClientCert, frontProxyClientKey, err := NewFrontProxyClientCertAndKey(cfg, 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) { - - caCert, caKey, err := pkiutil.NewCertificateAuthority() - if err != nil { - return nil, nil, fmt.Errorf("failure while generating CA certificate and key: %v", err) - } - - return caCert, caKey, nil -} - -func newCertAndKeyFromSpec(certSpec *KubeadmCert, cfg *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { - certConfig, err := certSpec.GetConfig(cfg) - if err != nil { - return nil, nil, fmt.Errorf("failure while creating certificate %s: %v", certSpec.Name, err) - } - cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, *certConfig) - if err != nil { - return nil, nil, fmt.Errorf("failure while creating %s key and certificate: %v", certSpec.Name, err) - } - - return cert, key, err -} - -// NewAPIServerCertAndKey generate certificate for apiserver, signed by the given CA. -func NewAPIServerCertAndKey(cfg *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { - return newCertAndKeyFromSpec(&KubeadmCertAPIServer, cfg, caCert, caKey) -} - -// NewAPIServerKubeletClientCertAndKey generate certificate for the apiservers to connect to the kubelets securely, signed by the given CA. -func NewAPIServerKubeletClientCertAndKey(cfg *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { - return newCertAndKeyFromSpec(&KubeadmCertKubeletClient, cfg, caCert, caKey) -} - -// NewEtcdCACertAndKey generate a self signed etcd CA. -func NewEtcdCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) { - - etcdCACert, etcdCAKey, err := pkiutil.NewCertificateAuthority() - if err != nil { - return nil, nil, fmt.Errorf("failure while generating etcd CA certificate and key: %v", err) - } - - return etcdCACert, etcdCAKey, nil -} - -// NewEtcdServerCertAndKey generate certificate for etcd, signed by the given CA. -func NewEtcdServerCertAndKey(cfg *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { - return newCertAndKeyFromSpec(&KubeadmCertEtcdServer, cfg, caCert, caKey) -} - -// NewEtcdPeerCertAndKey generate certificate for etcd peering, signed by the given CA. -func NewEtcdPeerCertAndKey(cfg *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { - return newCertAndKeyFromSpec(&KubeadmCertEtcdPeer, cfg, caCert, caKey) -} - -// NewEtcdHealthcheckClientCertAndKey generate certificate for liveness probes to healthcheck etcd, signed by the given CA. -func NewEtcdHealthcheckClientCertAndKey(cfg *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { - return newCertAndKeyFromSpec(&KubeadmCertEtcdHealthcheck, cfg, caCert, caKey) -} - -// NewAPIServerEtcdClientCertAndKey generate certificate for the apiservers to connect to etcd securely, signed by the given CA. -func NewAPIServerEtcdClientCertAndKey(cfg *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { - return newCertAndKeyFromSpec(&KubeadmCertEtcdHealthcheck, cfg, caCert, caKey) -} - // NewServiceAccountSigningKey generate public/private key pairs for signing service account tokens. func NewServiceAccountSigningKey() (*rsa.PrivateKey, error) { - // The key does NOT exist, let's generate it now saSigningKey, err := certutil.NewPrivateKey() if err != nil { @@ -383,23 +93,71 @@ func NewServiceAccountSigningKey() (*rsa.PrivateKey, error) { return saSigningKey, nil } -// NewFrontProxyCACertAndKey generate a self signed front proxy CA. -func NewFrontProxyCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) { +// NewCACertAndKey will generate a self signed CA. +func NewCACertAndKey(certSpec *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) { - frontProxyCACert, frontProxyCAKey, err := pkiutil.NewCertificateAuthority() + caCert, caKey, err := pkiutil.NewCertificateAuthority(certSpec) if err != nil { - return nil, nil, fmt.Errorf("failure while generating front-proxy CA certificate and key: %v", err) + return nil, nil, fmt.Errorf("failure while generating CA certificate and key: %v", err) } - return frontProxyCACert, frontProxyCAKey, nil + return caCert, caKey, nil } -// NewFrontProxyClientCertAndKey generate certificate for proxy server client, signed by the given front proxy CA. -func NewFrontProxyClientCertAndKey(cfg *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { - return newCertAndKeyFromSpec(&KubeadmCertFrontProxyClient, cfg, caCert, caKey) +// CreateCACertAndKeyFiles generates and writes out a given certificate authority. +// The certSpec should be one of the variables from this package. +func CreateCACertAndKeyFiles(certSpec *KubeadmCert, cfg *kubeadmapi.InitConfiguration) error { + if certSpec.CAName != "" { + return fmt.Errorf("This function should only be used for CAs, but cert %s has CA %s", certSpec.Name, certSpec.CAName) + } + glog.V(1).Infoln("creating a new certificate authority for %s", certSpec.Name) + + certConfig, err := certSpec.GetConfig(cfg) + if err != nil { + return err + } + + caCert, caKey, err := NewCACertAndKey(certConfig) + if err != nil { + return err + } + + return writeCertificateAuthorithyFilesIfNotExist( + cfg.CertificatesDir, + certSpec.BaseName, + caCert, + caKey, + ) +} + +// CreateCertAndKeyFilesWithCA loads the given certificate authority from disk, then generates and writes out the given certificate and key. +// The certSpec and caCertSpec should both be one of the variables from this package. +func CreateCertAndKeyFilesWithCA(certSpec *KubeadmCert, caCertSpec *KubeadmCert, cfg *kubeadmapi.InitConfiguration) error { + if certSpec.CAName != caCertSpec.Name { + return fmt.Errorf("Expected CAname for %s to be %q, but was %s", certSpec.Name, certSpec.CAName, caCertSpec.Name) + } + + caCert, caKey, err := loadCertificateAuthority(cfg.CertificatesDir, caCertSpec.BaseName) + if err != nil { + return fmt.Errorf("Couldn't load CA certificate %s: %v", caCertSpec.Name, err) + } + + return certSpec.CreateFromCA(cfg, caCert, caKey) +} + +func newCertAndKeyFromSpec(certSpec *KubeadmCert, cfg *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { + certConfig, err := certSpec.GetConfig(cfg) + if err != nil { + return nil, nil, fmt.Errorf("failure while creating certificate %s: %v", certSpec.Name, err) + } + cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, certConfig) + if err != nil { + return nil, nil, fmt.Errorf("failure while creating %s key and certificate: %v", certSpec.Name, err) + } + + return cert, key, err } -// loadCertificateAuthority loads certificate authority func loadCertificateAuthority(pkiDir string, baseName string) (*x509.Certificate, *rsa.PrivateKey, error) { // Checks if certificate authority exists in the PKI directory if !pkiutil.CertOrKeyExist(pkiDir, baseName) { @@ -422,7 +180,7 @@ func loadCertificateAuthority(pkiDir string, baseName string) (*x509.Certificate // writeCertificateAuthorithyFilesIfNotExist write a new certificate Authority 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, +// existing and the eexpected 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 { diff --git a/cmd/kubeadm/app/phases/certs/certs_test.go b/cmd/kubeadm/app/phases/certs/certs_test.go index 08122d1870..f476086d5b 100644 --- a/cmd/kubeadm/app/phases/certs/certs_test.go +++ b/cmd/kubeadm/app/phases/certs/certs_test.go @@ -17,16 +17,15 @@ limitations under the License. package certs import ( - "crypto" "crypto/rsa" - "crypto/tls" "crypto/x509" "fmt" - "net" "os" + "path" "path/filepath" "testing" + 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" @@ -34,10 +33,30 @@ import ( certstestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs" ) -func TestWriteCertificateAuthorithyFilesIfNotExist(t *testing.T) { +func createCACert(t *testing.T) (*x509.Certificate, *rsa.PrivateKey) { + certCfg := &certutil.Config{CommonName: "kubernetes"} + cert, key, err := NewCACertAndKey(certCfg) + if err != nil { + t.Fatalf("couldn't create CA: %v", err) + } + return cert, key +} - setupCert, setupKey, _ := NewCACertAndKey() - caCert, caKey, _ := NewCACertAndKey() +func createTestCert(t *testing.T, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey) { + cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, + &certutil.Config{ + CommonName: "testCert", + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + }) + if err != nil { + t.Fatalf("couldn't create test cert: %v", err) + } + return cert, key +} + +func TestWriteCertificateAuthorithyFilesIfNotExist(t *testing.T) { + setupCert, setupKey := createCACert(t) + caCert, caKey := createCACert(t) var tests = []struct { setupFunc func(pkiDir string) error @@ -62,7 +81,7 @@ func TestWriteCertificateAuthorithyFilesIfNotExist(t *testing.T) { }, { // cert exists, but it is not a ca > err setupFunc: func(pkiDir string) error { - cert, key, _ := NewFrontProxyClientCertAndKey(&kubeadmapi.InitConfiguration{}, setupCert, setupKey) + cert, key := createTestCert(t, setupCert, setupKey) return writeCertificateFilesIfNotExist(pkiDir, "dummy", setupCert, cert, key) }, expectedError: true, @@ -112,9 +131,9 @@ func TestWriteCertificateAuthorithyFilesIfNotExist(t *testing.T) { func TestWriteCertificateFilesIfNotExist(t *testing.T) { - caCert, caKey, _ := NewFrontProxyCACertAndKey() - setupCert, setupKey, _ := NewFrontProxyClientCertAndKey(&kubeadmapi.InitConfiguration{}, caCert, caKey) - cert, key, _ := NewFrontProxyClientCertAndKey(&kubeadmapi.InitConfiguration{}, caCert, caKey) + caCert, caKey := createCACert(t) + setupCert, setupKey := createTestCert(t, caCert, caKey) + cert, key := createTestCert(t, caCert, caKey) var tests = []struct { setupFunc func(pkiDir string) error @@ -139,8 +158,8 @@ func TestWriteCertificateFilesIfNotExist(t *testing.T) { }, { // cert exists, is signed by another ca > err setupFunc: func(pkiDir string) error { - anotherCaCert, anotherCaKey, _ := NewFrontProxyCACertAndKey() - anotherCert, anotherKey, _ := NewFrontProxyClientCertAndKey(&kubeadmapi.InitConfiguration{}, anotherCaCert, anotherCaKey) + anotherCaCert, anotherCaKey := createCACert(t) + anotherCert, anotherKey := createTestCert(t, anotherCaCert, anotherCaKey) return writeCertificateFilesIfNotExist(pkiDir, "dummy", anotherCaCert, anotherCert, anotherKey) }, @@ -261,7 +280,8 @@ func TestWriteKeyFilesIfNotExist(t *testing.T) { } func TestNewCACertAndKey(t *testing.T) { - caCert, _, err := NewCACertAndKey() + certCfg := &certutil.Config{CommonName: "kubernetes"} + caCert, _, err := NewCACertAndKey(certCfg) if err != nil { t.Fatalf("failed call NewCACertAndKey: %v", err) } @@ -269,269 +289,90 @@ func TestNewCACertAndKey(t *testing.T) { certstestutil.AssertCertificateIsCa(t, caCert) } -func TestNewAPIServerCertAndKey(t *testing.T) { - hostname := "valid-hostname" - - advertiseAddresses := []string{"1.2.3.4", "1:2:3::4"} - for _, addr := range advertiseAddresses { - cfg := &kubeadmapi.InitConfiguration{ - API: kubeadmapi.API{AdvertiseAddress: addr}, - Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"}, - NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: hostname}, - } - caCert, caKey, err := NewCACertAndKey() - if err != nil { - t.Fatalf("failed creation of ca cert and key: %v", err) - } - - apiServerCert, _, err := NewAPIServerCertAndKey(cfg, caCert, caKey) - if err != nil { - t.Fatalf("failed creation of cert and key: %v", err) - } - - 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)) - } -} - -func TestNewAPIServerKubeletClientCertAndKey(t *testing.T) { - caCert, caKey, err := NewCACertAndKey() - if err != nil { - t.Fatalf("failed creation of ca cert and key: %v", err) - } - - apiKubeletClientCert, _, err := NewAPIServerKubeletClientCertAndKey(&kubeadmapi.InitConfiguration{}, caCert, caKey) - if err != nil { - t.Fatalf("failed creation of cert and key: %v", err) - } - - certstestutil.AssertCertificateIsSignedByCa(t, apiKubeletClientCert, caCert) - certstestutil.AssertCertificateHasClientAuthUsage(t, apiKubeletClientCert) - certstestutil.AssertCertificateHasOrganizations(t, apiKubeletClientCert, kubeadmconstants.MastersGroup) -} - -func TestNewEtcdCACertAndKey(t *testing.T) { - etcdCACert, _, err := NewEtcdCACertAndKey() - if err != nil { - t.Fatalf("failed creation of cert and key: %v", err) - } - - certstestutil.AssertCertificateIsCa(t, etcdCACert) -} - -func TestNewEtcdServerCertAndKey(t *testing.T) { - proxy := "user-etcd-proxy" - proxyIP := "10.10.10.100" - - cfg := &kubeadmapi.InitConfiguration{ - NodeRegistration: kubeadmapi.NodeRegistrationOptions{ - Name: "etcd-server-cert", - }, - Etcd: kubeadmapi.Etcd{ - Local: &kubeadmapi.LocalEtcd{ - ServerCertSANs: []string{ - proxy, - proxyIP, - }, - }, - }, - } - caCert, caKey, err := NewCACertAndKey() - if err != nil { - t.Fatalf("failed creation of ca cert and key: %v", err) - } - - etcdServerCert, _, err := NewEtcdServerCertAndKey(cfg, caCert, caKey) - if err != nil { - t.Fatalf("failed creation of cert and key: %v", err) - } - - certstestutil.AssertCertificateIsSignedByCa(t, etcdServerCert, caCert) - certstestutil.AssertCertificateHasServerAuthUsage(t, etcdServerCert) - certstestutil.AssertCertificateHasDNSNames(t, etcdServerCert, "localhost", proxy) - certstestutil.AssertCertificateHasIPAddresses(t, etcdServerCert, net.ParseIP("127.0.0.1"), net.ParseIP(proxyIP)) -} - -func TestNewEtcdPeerCertAndKey(t *testing.T) { - hostname := "valid-hostname" - proxy := "user-etcd-proxy" - proxyIP := "10.10.10.100" - - advertiseAddresses := []string{"1.2.3.4", "1:2:3::4"} - for _, addr := range advertiseAddresses { - cfg := &kubeadmapi.InitConfiguration{ - API: kubeadmapi.API{AdvertiseAddress: addr}, - NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: hostname}, - Etcd: kubeadmapi.Etcd{ - Local: &kubeadmapi.LocalEtcd{ - PeerCertSANs: []string{ - proxy, - proxyIP, - }, - }, - }, - } - caCert, caKey, err := NewCACertAndKey() - if err != nil { - t.Fatalf("failed creation of ca cert and key: %v", err) - } - - etcdPeerCert, _, err := NewEtcdPeerCertAndKey(cfg, caCert, caKey) - if err != nil { - t.Fatalf("failed creation of cert and key: %v", err) - } - - certstestutil.AssertCertificateIsSignedByCa(t, etcdPeerCert, caCert) - certstestutil.AssertCertificateHasServerAuthUsage(t, etcdPeerCert) - certstestutil.AssertCertificateHasClientAuthUsage(t, etcdPeerCert) - certstestutil.AssertCertificateHasDNSNames(t, etcdPeerCert, hostname, proxy) - certstestutil.AssertCertificateHasIPAddresses(t, etcdPeerCert, net.ParseIP(addr), net.ParseIP(proxyIP)) - } -} - -func TestNewEtcdHealthcheckClientCertAndKey(t *testing.T) { - caCert, caKey, err := NewCACertAndKey() - if err != nil { - t.Fatalf("failed creation of ca cert and key: %v", err) - } - - etcdHealthcheckClientCert, _, err := NewEtcdHealthcheckClientCertAndKey(&kubeadmapi.InitConfiguration{}, caCert, caKey) - if err != nil { - t.Fatalf("failed creation of cert and key: %v", err) - } - - certstestutil.AssertCertificateIsSignedByCa(t, etcdHealthcheckClientCert, caCert) - certstestutil.AssertCertificateHasClientAuthUsage(t, etcdHealthcheckClientCert) - certstestutil.AssertCertificateHasOrganizations(t, etcdHealthcheckClientCert, kubeadmconstants.MastersGroup) -} - -func TestNewAPIServerEtcdClientCertAndKey(t *testing.T) { - caCert, caKey, err := NewCACertAndKey() - if err != nil { - t.Fatalf("failed creation of ca cert and key: %v", err) - } - - apiEtcdClientCert, _, err := NewAPIServerEtcdClientCertAndKey(&kubeadmapi.InitConfiguration{}, caCert, caKey) - if err != nil { - t.Fatalf("failed creation of cert and key: %v", err) - } - - certstestutil.AssertCertificateIsSignedByCa(t, apiEtcdClientCert, caCert) - certstestutil.AssertCertificateHasClientAuthUsage(t, apiEtcdClientCert) - certstestutil.AssertCertificateHasOrganizations(t, apiEtcdClientCert, kubeadmconstants.MastersGroup) -} - -func TestNewNewServiceAccountSigningKey(t *testing.T) { - - key, err := NewServiceAccountSigningKey() - if err != nil { - t.Fatalf("failed creation of key: %v", err) - } - - if key.N.BitLen() < 2048 { - t.Error("Service account signing key has less than 2048 bits size") - } -} - -func TestNewFrontProxyCACertAndKey(t *testing.T) { - frontProxyCACert, _, err := NewFrontProxyCACertAndKey() - if err != nil { - t.Fatalf("failed creation of cert and key: %v", err) - } - - certstestutil.AssertCertificateIsCa(t, frontProxyCACert) -} - -func TestNewFrontProxyClientCertAndKey(t *testing.T) { - frontProxyCACert, frontProxyCAKey, err := NewFrontProxyCACertAndKey() - if err != nil { - t.Fatalf("failed creation of ca cert and key: %v", err) - } - - frontProxyClientCert, _, err := NewFrontProxyClientCertAndKey(&kubeadmapi.InitConfiguration{}, frontProxyCACert, frontProxyCAKey) - if err != nil { - t.Fatalf("failed creation of cert and key: %v", err) - } - - certstestutil.AssertCertificateIsSignedByCa(t, frontProxyClientCert, frontProxyCACert) - certstestutil.AssertCertificateHasClientAuthUsage(t, frontProxyClientCert) -} - func TestSharedCertificateExists(t *testing.T) { + caCert, caKey := createCACert(t) + _, key := createTestCert(t, caCert, caKey) + publicKey := &key.PublicKey var tests = []struct { - setupFunc func(cfg *kubeadmapi.InitConfiguration) + name string + files pkiFiles expectedError bool }{ - { // expected certs exist, pass - setupFunc: func(cfg *kubeadmapi.InitConfiguration) { - CreateCACertAndKeyFiles(cfg) - CreateServiceAccountKeyAndPublicKeyFiles(cfg) - CreateFrontProxyCACertAndKeyFiles(cfg) + { + name: "success", + files: pkiFiles{ + "ca.crt": caCert, + "ca.key": caKey, + "front-proxy-ca.crt": caCert, + "front-proxy-ca.key": caKey, + "sa.pub": publicKey, + "sa.key": key, }, - expectedError: false, }, - { // expected ca.crt missing - setupFunc: func(cfg *kubeadmapi.InitConfiguration) { - // start from the condition created by the previous tests - os.Remove(filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName)) + { + name: "missing ca.crt", + files: pkiFiles{ + "ca.key": caKey, + "front-proxy-ca.crt": caCert, + "front-proxy-ca.key": caKey, + "sa.pub": publicKey, + "sa.key": key, }, expectedError: true, }, - { // expected sa.key missing - setupFunc: func(cfg *kubeadmapi.InitConfiguration) { - // start from the condition created by the previous tests - CreateCACertAndKeyFiles(cfg) - os.Remove(filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName)) + { + name: "missing sa.key", + files: pkiFiles{ + "ca.crt": caCert, + "ca.key": caKey, + "front-proxy-ca.crt": caCert, + "front-proxy-ca.key": caKey, + "sa.pub": publicKey, }, expectedError: true, }, - { // expected front-proxy.crt missing - setupFunc: func(cfg *kubeadmapi.InitConfiguration) { - // start from the condition created by the previous tests - CreateServiceAccountKeyAndPublicKeyFiles(cfg) - os.Remove(filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName)) + { + name: "expected front-proxy.crt missing", + files: pkiFiles{ + "ca.crt": caCert, + "ca.key": caKey, + "front-proxy-ca.key": caKey, + "sa.pub": publicKey, + "sa.key": key, }, expectedError: true, }, } - tmpdir := testutil.SetupTempDir(t) - defer os.RemoveAll(tmpdir) - - cfg := &kubeadmapi.InitConfiguration{ - CertificatesDir: tmpdir, - } - for _, test := range tests { - // executes setup func (if necessary) - if test.setupFunc != nil { - test.setupFunc(cfg) - } + t.Run("", func(t *testing.T) { + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) - // executes create func - ret, err := SharedCertificateExists(cfg) + cfg := &kubeadmapi.InitConfiguration{ + CertificatesDir: tmpdir, + } - if !test.expectedError && err != nil { - t.Errorf("error SharedCertificateExists failed when not expected to fail: %v", err) - continue - } else if test.expectedError && err == nil { - t.Error("error SharedCertificateExists didn't failed when expected") - continue - } else if test.expectedError { - continue - } + // created expected keys + writePKIFiles(t, tmpdir, test.files) - if ret != (err == nil) { - t.Errorf("error SharedCertificateExists returned %v when expected to return %v", ret, err == nil) - } + // 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 TestUsingExternalCA(t *testing.T) { - tests := []struct { setupFuncs []func(cfg *kubeadmapi.InitConfiguration) error expected bool @@ -577,17 +418,20 @@ func TestUsingExternalCA(t *testing.T) { func TestValidateMethods(t *testing.T) { + caCert, caKey := createCACert(t) + cert, key := createTestCert(t, caCert, caKey) + tests := []struct { name string - setupFuncs []func(cfg *kubeadmapi.InitConfiguration) error + files pkiFiles validateFunc func(l certKeyLocation) error loc certKeyLocation expectedSuccess bool }{ { name: "validateCACert", - setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{ - CreateCACertAndKeyFiles, + files: pkiFiles{ + "ca.crt": caCert, }, validateFunc: validateCACert, loc: certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"}, @@ -595,28 +439,30 @@ func TestValidateMethods(t *testing.T) { }, { name: "validateCACertAndKey (files present)", - setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{ - CreateCACertAndKeyFiles, + files: pkiFiles{ + "ca.crt": caCert, + "ca.key": caKey, }, validateFunc: validateCACertAndKey, loc: certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"}, expectedSuccess: true, }, { - name: "validateCACertAndKey (key missing)", - setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{ - CreatePKIAssets, - deleteCAKey, + files: pkiFiles{ + "ca.crt": caCert, }, + name: "validateCACertAndKey (key missing)", validateFunc: validateCACertAndKey, loc: certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"}, expectedSuccess: false, }, { name: "validateSignedCert", - setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{ - CreateCACertAndKeyFiles, - CreateAPIServerCertAndKeyFiles, + files: pkiFiles{ + "ca.crt": caCert, + "ca.key": caKey, + "apiserver.crt": cert, + "apiserver.key": key, }, validateFunc: validateSignedCert, loc: certKeyLocation{caBaseName: "ca", baseName: "apiserver", uxName: "apiserver"}, @@ -624,8 +470,9 @@ func TestValidateMethods(t *testing.T) { }, { name: "validatePrivatePublicKey", - setupFuncs: []func(cfg *kubeadmapi.InitConfiguration) error{ - CreateServiceAccountKeyAndPublicKeyFiles, + files: pkiFiles{ + "sa.pub": &key.PublicKey, + "sa.key": key, }, validateFunc: validatePrivatePublicKey, loc: certKeyLocation{baseName: "sa", uxName: "service account"}, @@ -634,25 +481,11 @@ func TestValidateMethods(t *testing.T) { } for _, test := range tests { - dir := testutil.SetupTempDir(t) defer os.RemoveAll(dir) test.loc.pkiDir = dir - cfg := &kubeadmapi.InitConfiguration{ - API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"}, - Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"}, - NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "valid-hostname"}, - CertificatesDir: dir, - } - - fmt.Println("Testing", test.name) - - for _, f := range test.setupFuncs { - if err := f(cfg); err != nil { - t.Errorf("error executing setup function: %v", err) - } - } + writePKIFiles(t, dir, test.files) err := test.validateFunc(test.loc) if test.expectedSuccess && err != nil { @@ -663,18 +496,29 @@ func TestValidateMethods(t *testing.T) { } } -func deleteCAKey(cfg *kubeadmapi.InitConfiguration) error { - if err := os.Remove(filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName)); err != nil { - return fmt.Errorf("failed removing %s: %v", kubeadmconstants.CAKeyName, err) - } - return nil -} +type pkiFiles map[string]interface{} -func deleteFrontProxyCAKey(cfg *kubeadmapi.InitConfiguration) error { - if err := os.Remove(filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCAKeyName)); err != nil { - return fmt.Errorf("failed removing %s: %v", kubeadmconstants.FrontProxyCAKeyName, err) +func writePKIFiles(t *testing.T, dir string, files pkiFiles) { + for filename, body := range files { + switch body := body.(type) { + case *x509.Certificate: + if err := certutil.WriteCert(path.Join(dir, filename), certutil.EncodeCertPEM(body)); err != nil { + t.Errorf("unable to write certificate to file %q: [%v]", dir, err) + } + case *rsa.PublicKey: + publicKeyBytes, err := certutil.EncodePublicKeyPEM(body) + if err != nil { + t.Errorf("unable to write public key to file %q: [%v]", filename, err) + } + if err := certutil.WriteKey(path.Join(dir, filename), publicKeyBytes); err != nil { + t.Errorf("unable to write public key to file %q: [%v]", filename, err) + } + case *rsa.PrivateKey: + if err := certutil.WriteKey(path.Join(dir, filename), certutil.EncodePrivateKeyPEM(body)); err != nil { + t.Errorf("unable to write private key to file %q: [%v]", filename, err) + } + } } - return nil } func TestCreateCertificateFilesMethods(t *testing.T) { @@ -713,57 +557,6 @@ func TestCreateCertificateFilesMethods(t *testing.T) { 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: CreateEtcdCACertAndKeyFiles, - expectedFiles: []string{kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName}, - }, - { - setupFunc: CreateEtcdCACertAndKeyFiles, - createFunc: CreateEtcdServerCertAndKeyFiles, - expectedFiles: []string{kubeadmconstants.EtcdServerCertName, kubeadmconstants.EtcdServerKeyName}, - }, - { - setupFunc: CreateEtcdCACertAndKeyFiles, - createFunc: CreateEtcdPeerCertAndKeyFiles, - expectedFiles: []string{kubeadmconstants.EtcdPeerCertName, kubeadmconstants.EtcdPeerKeyName}, - }, - { - setupFunc: CreateEtcdCACertAndKeyFiles, - createFunc: CreateEtcdHealthcheckClientCertAndKeyFiles, - expectedFiles: []string{kubeadmconstants.EtcdHealthcheckClientCertName, kubeadmconstants.EtcdHealthcheckClientKeyName}, - }, - { - setupFunc: CreateEtcdCACertAndKeyFiles, - createFunc: CreateAPIServerEtcdClientCertAndKeyFiles, - expectedFiles: []string{kubeadmconstants.APIServerEtcdClientCertName, kubeadmconstants.APIServerEtcdClientKeyName}, - }, - { - 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}, - }, } for _, test := range tests { @@ -787,14 +580,6 @@ func TestCreateCertificateFilesMethods(t *testing.T) { cfg.Etcd.External.Endpoints = []string{"192.168.1.1:2379"} } - // executes setup func (if necessary) - if test.setupFunc != nil { - if err := test.setupFunc(cfg); err != nil { - t.Errorf("error executing setupFunc: %v", err) - continue - } - } - // executes create func if err := test.createFunc(cfg); err != nil { t.Errorf("error executing createFunc: %v", err) @@ -806,16 +591,16 @@ func TestCreateCertificateFilesMethods(t *testing.T) { } } -func parseCertAndKey(basePath string, t *testing.T) (*x509.Certificate, crypto.PrivateKey) { - certPair, err := tls.LoadX509KeyPair(basePath+".crt", basePath+".key") - if err != nil { - t.Fatalf("couldn't parse certificate and key: %v", err) +func deleteCAKey(cfg *kubeadmapi.InitConfiguration) error { + if err := os.Remove(filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName)); err != nil { + return fmt.Errorf("failed removing %s: %v", kubeadmconstants.CAKeyName, err) } - - parsedCert, err := x509.ParseCertificate(certPair.Certificate[0]) - if err != nil { - t.Fatalf("couldn't parse certificate: %v", err) - } - - return parsedCert, certPair.PrivateKey + return nil +} + +func deleteFrontProxyCAKey(cfg *kubeadmapi.InitConfiguration) error { + if err := os.Remove(filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCAKeyName)); err != nil { + return fmt.Errorf("failed removing %s: %v", kubeadmconstants.FrontProxyCAKeyName, err) + } + return nil } diff --git a/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go b/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go index eb55d45aa3..e9d9f301b8 100644 --- a/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go +++ b/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go @@ -34,16 +34,13 @@ import ( ) // NewCertificateAuthority creates new certificate and private key for the certificate authority -func NewCertificateAuthority() (*x509.Certificate, *rsa.PrivateKey, error) { +func NewCertificateAuthority(config *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) { key, err := certutil.NewPrivateKey() if err != nil { return nil, nil, fmt.Errorf("unable to create private key [%v]", err) } - config := certutil.Config{ - CommonName: "kubernetes", - } - cert, err := certutil.NewSelfSignedCACert(config, key) + cert, err := certutil.NewSelfSignedCACert(*config, key) if err != nil { return nil, nil, fmt.Errorf("unable to create self-signed certificate [%v]", err) } @@ -52,13 +49,13 @@ func NewCertificateAuthority() (*x509.Certificate, *rsa.PrivateKey, error) { } // NewCertAndKey creates new certificate and key by passing the certificate authority certificate and key -func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) { +func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) { key, err := certutil.NewPrivateKey() if err != nil { return nil, nil, fmt.Errorf("unable to create private key [%v]", err) } - cert, err := certutil.NewSignedCert(config, key, caCert, caKey) + cert, err := certutil.NewSignedCert(*config, key, caCert, caKey) if err != nil { return nil, nil, fmt.Errorf("unable to sign certificate [%v]", err) } diff --git a/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go b/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go index 46358cc85e..6e3464b9fb 100644 --- a/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go +++ b/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go @@ -30,7 +30,7 @@ import ( ) func TestNewCertificateAuthority(t *testing.T) { - cert, key, err := NewCertificateAuthority() + cert, key, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) if cert == nil { t.Errorf( @@ -73,7 +73,7 @@ func TestNewCertAndKey(t *testing.T) { t.Fatalf("Couldn't create rsa Private Key") } caCert := &x509.Certificate{} - config := certutil.Config{ + config := &certutil.Config{ CommonName: "test", Organization: []string{"test"}, Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, @@ -90,7 +90,7 @@ func TestNewCertAndKey(t *testing.T) { } func TestHasServerAuth(t *testing.T) { - caCert, caKey, _ := NewCertificateAuthority() + caCert, caKey, _ := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) var tests = []struct { config certutil.Config @@ -113,7 +113,7 @@ func TestHasServerAuth(t *testing.T) { } for _, rt := range tests { - cert, _, err := NewCertAndKey(caCert, caKey, rt.config) + cert, _, err := NewCertAndKey(caCert, caKey, &rt.config) if err != nil { t.Fatalf("Couldn't create cert: %v", err) } @@ -261,7 +261,7 @@ func TestTryLoadCertAndKeyFromDisk(t *testing.T) { } defer os.RemoveAll(tmpdir) - caCert, caKey, err := NewCertificateAuthority() + caCert, caKey, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) if err != nil { t.Errorf( "failed to create cert and key with an error: %v", @@ -311,7 +311,7 @@ func TestTryLoadCertFromDisk(t *testing.T) { } defer os.RemoveAll(tmpdir) - caCert, _, err := NewCertificateAuthority() + caCert, _, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) if err != nil { t.Errorf( "failed to create cert and key with an error: %v", @@ -361,7 +361,7 @@ func TestTryLoadKeyFromDisk(t *testing.T) { } defer os.RemoveAll(tmpdir) - _, caKey, err := NewCertificateAuthority() + _, caKey, err := NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) if err != nil { t.Errorf( "failed to create cert and key with an error: %v", diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index ccc3e4a9ac..f994859b40 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -222,7 +222,7 @@ func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string) (*clientc Organization: spec.ClientCertAuth.Organizations, Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, } - clientCert, clientKey, err := pkiutil.NewCertAndKey(spec.CACert, spec.ClientCertAuth.CAKey, clientCertConfig) + clientCert, clientKey, err := pkiutil.NewCertAndKey(spec.CACert, spec.ClientCertAuth.CAKey, &clientCertConfig) if err != nil { return nil, fmt.Errorf("failure while creating %s client certificate: %v", spec.ClientName, err) } diff --git a/cmd/kubeadm/app/phases/upgrade/BUILD b/cmd/kubeadm/app/phases/upgrade/BUILD index acce46ca7d..f7fd5a520c 100644 --- a/cmd/kubeadm/app/phases/upgrade/BUILD +++ b/cmd/kubeadm/app/phases/upgrade/BUILD @@ -80,7 +80,6 @@ go_test( "//cmd/kubeadm/app/apis/kubeadm/v1alpha3:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/phases/certs:go_default_library", - "//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library", "//cmd/kubeadm/app/phases/controlplane:go_default_library", "//cmd/kubeadm/app/phases/etcd:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", diff --git a/cmd/kubeadm/app/phases/upgrade/postupgrade.go b/cmd/kubeadm/app/phases/upgrade/postupgrade.go index b5a8829d55..dd51e745b9 100644 --- a/cmd/kubeadm/app/phases/upgrade/postupgrade.go +++ b/cmd/kubeadm/app/phases/upgrade/postupgrade.go @@ -195,7 +195,11 @@ func backupAPIServerCertIfNeeded(cfg *kubeadmapi.InitConfiguration, dryRun bool) if err := backupAPIServerCertAndKey(certAndKeyDir); err != nil { fmt.Printf("[postupgrade] WARNING: failed to backup kube-apiserver cert and key: %v", err) } - return certsphase.CreateAPIServerCertAndKeyFiles(cfg) + return certsphase.CreateCertAndKeyFilesWithCA( + &certsphase.KubeadmCertAPIServer, + &certsphase.KubeadmCertRootCA, + cfg, + ) } func writeKubeletConfigFiles(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, newK8sVer *version.Version, dryRun bool) error { diff --git a/cmd/kubeadm/app/phases/upgrade/postupgrade_test.go b/cmd/kubeadm/app/phases/upgrade/postupgrade_test.go index 7ed1db8f42..5fd1763f92 100644 --- a/cmd/kubeadm/app/phases/upgrade/postupgrade_test.go +++ b/cmd/kubeadm/app/phases/upgrade/postupgrade_test.go @@ -27,7 +27,6 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/constants" certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" - "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" testutil "k8s.io/kubernetes/cmd/kubeadm/test" ) @@ -148,23 +147,21 @@ func TestShouldBackupAPIServerCertAndKey(t *testing.T) { expected: true, }, } { - caCert, caKey, err := certsphase.NewCACertAndKey() + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + cfg.CertificatesDir = tmpdir + + caCert, caKey, err := certsphase.KubeadmCertRootCA.CreateAsCA(cfg) if err != nil { t.Fatalf("failed creation of ca cert and key: %v", err) } caCert.NotBefore = caCert.NotBefore.Add(-test.adjustedExpiry).UTC() - apiCert, apiKey, err := certsphase.NewAPIServerCertAndKey(cfg, caCert, caKey) + + err = certsphase.KubeadmCertAPIServer.CreateFromCA(cfg, caCert, caKey) if err != nil { t.Fatalf("Test %s: failed creation of cert and key: %v", desc, err) } - tmpdir := testutil.SetupTempDir(t) - defer os.RemoveAll(tmpdir) - - if err := pkiutil.WriteCertAndKey(tmpdir, constants.APIServerCertAndKeyBaseName, apiCert, apiKey); err != nil { - t.Fatalf("Test %s: failure while saving %s certificate and key: %v", desc, constants.APIServerCertAndKeyBaseName, err) - } - certAndKey := []string{filepath.Join(tmpdir, constants.APIServerCertName), filepath.Join(tmpdir, constants.APIServerKeyName)} for _, path := range certAndKey { if _, err := os.Stat(path); os.IsNotExist(err) { diff --git a/cmd/kubeadm/app/phases/upgrade/staticpods.go b/cmd/kubeadm/app/phases/upgrade/staticpods.go index d96e94c8c5..200327f194 100644 --- a/cmd/kubeadm/app/phases/upgrade/staticpods.go +++ b/cmd/kubeadm/app/phases/upgrade/staticpods.go @@ -187,24 +187,27 @@ func upgradeComponent(component string, waiter apiclient.Waiter, pathMgr StaticP // ensure etcd certs are generated for etcd and kube-apiserver if component == constants.Etcd || component == constants.KubeAPIServer { - if err := certsphase.CreateEtcdCACertAndKeyFiles(cfg); err != nil { + + caCert, caKey, err := certsphase.KubeadmCertEtcdCA.CreateAsCA(cfg) + if err != nil { return fmt.Errorf("failed to upgrade the %s CA certificate and key: %v", constants.Etcd, err) } - } - if component == constants.Etcd { - if err := certsphase.CreateEtcdServerCertAndKeyFiles(cfg); err != nil { - return fmt.Errorf("failed to upgrade the %s certificate and key: %v", constants.Etcd, err) + + if component == constants.Etcd { + if err := certsphase.KubeadmCertEtcdServer.CreateFromCA(cfg, caCert, caKey); err != nil { + return fmt.Errorf("failed to upgrade the %s certificate and key: %v", constants.Etcd, err) + } + if err := certsphase.KubeadmCertEtcdPeer.CreateFromCA(cfg, caCert, caKey); err != nil { + return fmt.Errorf("failed to upgrade the %s peer certificate and key: %v", constants.Etcd, err) + } + if err := certsphase.KubeadmCertEtcdHealthcheck.CreateFromCA(cfg, caCert, caKey); err != nil { + return fmt.Errorf("failed to upgrade the %s healthcheck certificate and key: %v", constants.Etcd, err) + } } - if err := certsphase.CreateEtcdPeerCertAndKeyFiles(cfg); err != nil { - return fmt.Errorf("failed to upgrade the %s peer certificate and key: %v", constants.Etcd, err) - } - if err := certsphase.CreateEtcdHealthcheckClientCertAndKeyFiles(cfg); err != nil { - return fmt.Errorf("failed to upgrade the %s healthcheck certificate and key: %v", constants.Etcd, err) - } - } - if component == constants.KubeAPIServer { - if err := certsphase.CreateAPIServerEtcdClientCertAndKeyFiles(cfg); err != nil { - return fmt.Errorf("failed to upgrade the %s %s-client certificate and key: %v", constants.KubeAPIServer, constants.Etcd, err) + if component == constants.KubeAPIServer { + if err := certsphase.KubeadmCertEtcdAPIClient.CreateFromCA(cfg, caCert, caKey); err != nil { + return fmt.Errorf("failed to upgrade the %s %s-client certificate and key: %v", constants.KubeAPIServer, constants.Etcd, err) + } } } diff --git a/cmd/kubeadm/app/phases/upgrade/staticpods_test.go b/cmd/kubeadm/app/phases/upgrade/staticpods_test.go index 4f0ec82aa7..197a56664d 100644 --- a/cmd/kubeadm/app/phases/upgrade/staticpods_test.go +++ b/cmd/kubeadm/app/phases/upgrade/staticpods_test.go @@ -420,27 +420,16 @@ func TestStaticPodControlPlane(t *testing.T) { t.Fatalf("couldn't create config: %v", err) } - // Initialize PKI minus any etcd certificates to simulate etcd PKI upgrade - certActions := []func(cfg *kubeadmapi.InitConfiguration) error{ - certsphase.CreateCACertAndKeyFiles, - certsphase.CreateAPIServerCertAndKeyFiles, - certsphase.CreateAPIServerKubeletClientCertAndKeyFiles, - // certsphase.CreateEtcdCACertAndKeyFiles, - // certsphase.CreateEtcdServerCertAndKeyFiles, - // certsphase.CreateEtcdPeerCertAndKeyFiles, - // certsphase.CreateEtcdHealthcheckClientCertAndKeyFiles, - // certsphase.CreateAPIServerEtcdClientCertAndKeyFiles, - certsphase.CreateServiceAccountKeyAndPublicKeyFiles, - certsphase.CreateFrontProxyCACertAndKeyFiles, - certsphase.CreateFrontProxyClientCertAndKeyFiles, + tree, err := certsphase.GetCertsWithoutEtcd().AsMap().CertTree() + if err != nil { + t.Fatalf("couldn't get cert tree: %v", err) } - for _, action := range certActions { - err := action(oldcfg) - if err != nil { - t.Fatalf("couldn't initialize pre-upgrade certificate: %v", err) - } + + if err := tree.CreateTree(oldcfg); err != nil { + t.Fatalf("couldn't get create cert tree: %v", err) } - fmt.Printf("Wrote certs to %s\n", oldcfg.CertificatesDir) + + t.Logf("Wrote certs to %s\n", oldcfg.CertificatesDir) // Initialize the directory with v1.7 manifests; should then be upgraded to v1.8 using the method err = controlplanephase.CreateInitStaticPodManifestFiles(pathMgr.RealManifestDir(), oldcfg) diff --git a/cmd/kubeadm/test/certs/BUILD b/cmd/kubeadm/test/certs/BUILD index 52ead0d15b..1211e65531 100644 --- a/cmd/kubeadm/test/certs/BUILD +++ b/cmd/kubeadm/test/certs/BUILD @@ -9,7 +9,10 @@ go_library( name = "go_default_library", srcs = ["util.go"], importpath = "k8s.io/kubernetes/cmd/kubeadm/test/certs", - deps = ["//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library"], + deps = [ + "//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library", + "//staging/src/k8s.io/client-go/util/cert:go_default_library", + ], ) filegroup( diff --git a/cmd/kubeadm/test/certs/util.go b/cmd/kubeadm/test/certs/util.go index 4f3235010c..1cd8be17db 100644 --- a/cmd/kubeadm/test/certs/util.go +++ b/cmd/kubeadm/test/certs/util.go @@ -22,13 +22,14 @@ import ( "net" "testing" + certutil "k8s.io/client-go/util/cert" "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" ) // SetupCertificateAuthorithy is a utility function for kubeadm testing that creates a // CertificateAuthorithy cert/key pair func SetupCertificateAuthorithy(t *testing.T) (*x509.Certificate, *rsa.PrivateKey) { - caCert, caKey, err := pkiutil.NewCertificateAuthority() + caCert, caKey, err := pkiutil.NewCertificateAuthority(&certutil.Config{CommonName: "kubernetes"}) if err != nil { t.Fatalf("failure while generating CA certificate and key: %v", err) }