From c2e9052aead4b56a675fa145d00da0a8a00f314d Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Sat, 8 Jul 2017 14:58:11 +0200 Subject: [PATCH] fully implement kubeadm-phase-certs - stash --- cmd/kubeadm/app/cmd/BUILD | 5 +- cmd/kubeadm/app/cmd/init.go | 16 +- cmd/kubeadm/app/cmd/phases/BUILD | 20 +- cmd/kubeadm/app/cmd/phases/certs.go | 361 ++++++++++++++++-- cmd/kubeadm/app/cmd/phases/certs_test.go | 270 +++++++++++++ cmd/kubeadm/app/constants/constants.go | 3 + cmd/kubeadm/app/phases/certs/BUILD | 3 +- cmd/kubeadm/app/phases/certs/certs.go | 355 ++++++----------- cmd/kubeadm/app/phases/certs/certs_test.go | 257 ++++++------- .../app/phases/certs/pkiutil/pki_helpers.go | 10 + .../phases/certs/pkiutil/pki_helpers_test.go | 39 ++ cmd/kubeadm/app/util/BUILD | 1 + cmd/kubeadm/app/util/config/BUILD | 45 +++ .../config/masterconfig.go} | 30 +- .../config/masterconfig_test.go} | 0 15 files changed, 982 insertions(+), 433 deletions(-) create mode 100644 cmd/kubeadm/app/cmd/phases/certs_test.go create mode 100644 cmd/kubeadm/app/util/config/BUILD rename cmd/kubeadm/app/{cmd/defaults.go => util/config/masterconfig.go} (76%) rename cmd/kubeadm/app/{cmd/defaults_test.go => util/config/masterconfig_test.go} (100%) diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index a120c6ae7cd..22265d8e72a 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -13,7 +13,6 @@ go_library( srcs = [ "cmd.go", "completion.go", - "defaults.go", "init.go", "join.go", "reset.go", @@ -31,13 +30,13 @@ go_library( "//cmd/kubeadm/app/node:go_default_library", "//cmd/kubeadm/app/phases/addons:go_default_library", "//cmd/kubeadm/app/phases/apiconfig:go_default_library", - "//cmd/kubeadm/app/phases/certs:go_default_library", "//cmd/kubeadm/app/phases/controlplane:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/phases/selfhosting:go_default_library", "//cmd/kubeadm/app/phases/token:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/util:go_default_library", + "//cmd/kubeadm/app/util/config:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//cmd/kubeadm/app/util/token:go_default_library", "//pkg/api:go_default_library", @@ -55,7 +54,6 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", @@ -66,7 +64,6 @@ go_library( go_test( name = "go_default_test", srcs = [ - "defaults_test.go", "reset_test.go", "token_test.go", ], diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 555e58a0daf..0fbf7f87f8c 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -31,16 +31,17 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" + cmdphases "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" addonsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons" apiconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig" - certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/util/version" ) @@ -164,11 +165,20 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight, } // Set defaults dynamically that the API group defaulting can't (by fetching information from the internet, looking up network interfaces, etc.) - err := setInitDynamicDefaults(cfg) + err := configutil.SetInitDynamicDefaults(cfg) if err != nil { return nil, err } + fmt.Printf("[init] Using Kubernetes version: %s\n", cfg.KubernetesVersion) + fmt.Printf("[init] Using Authorization mode: %v\n", cfg.AuthorizationModes) + + // Warn about the limitations with the current cloudprovider solution. + if cfg.CloudProvider != "" { + fmt.Println("[init] WARNING: For cloudprovider integrations to work --cloud-provider must be set for all kubelets in the cluster.") + fmt.Println("\t(/etc/systemd/system/kubelet.service.d/10-kubeadm.conf should be edited for this purpose)") + } + if !skipPreFlight { fmt.Println("[preflight] Running pre-flight checks") @@ -202,7 +212,7 @@ func (i *Init) Validate(cmd *cobra.Command) error { func (i *Init) Run(out io.Writer) error { // PHASE 1: Generate certificates - err := certphase.CreatePKIAssets(i.cfg) + err := cmdphases.CreatePKIAssets(i.cfg) if err != nil { return err } diff --git a/cmd/kubeadm/app/cmd/phases/BUILD b/cmd/kubeadm/app/cmd/phases/BUILD index 98fddf940bf..c393b57b7ea 100644 --- a/cmd/kubeadm/app/cmd/phases/BUILD +++ b/cmd/kubeadm/app/cmd/phases/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -21,16 +22,31 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/validation: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/kubeconfig:go_default_library", "//cmd/kubeadm/app/phases/selfhosting:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/util:go_default_library", + "//cmd/kubeadm/app/util/config:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//pkg/api:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["certs_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//cmd/kubeadm/app/apis/kubeadm/install:go_default_library", + "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library", + "//vendor/github.com/renstrom/dedent:go_default_library", + "//vendor/github.com/spf13/cobra:go_default_library", ], ) diff --git a/cmd/kubeadm/app/cmd/phases/certs.go b/cmd/kubeadm/app/cmd/phases/certs.go index b8a90749c14..e1470131197 100644 --- a/cmd/kubeadm/app/cmd/phases/certs.go +++ b/cmd/kubeadm/app/cmd/phases/certs.go @@ -17,18 +17,20 @@ limitations under the License. package phases import ( + "crypto/rsa" + "crypto/x509" "fmt" - "net" "github.com/spf13/cobra" - netutil "k8s.io/apimachinery/pkg/util/net" - "k8s.io/apimachinery/pkg/util/validation/field" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" "k8s.io/kubernetes/pkg/api" ) @@ -40,63 +42,342 @@ func NewCmdCerts() *cobra.Command { RunE: subCmdRunE("certs"), } - cmd.AddCommand(NewCmdSelfSign()) + cmd.AddCommand(newSubCmdCerts()...) return cmd } -func NewCmdSelfSign() *cobra.Command { - // TODO: Move this into a dedicated Certificates Phase API object +// newSubCmdCerts returns sub commands for certs phase +func newSubCmdCerts() []*cobra.Command { + cfg := &kubeadmapiext.MasterConfiguration{} // Default values for the cobra help text api.Scheme.Default(cfg) - cmd := &cobra.Command{ - Use: "selfsign", - Short: "Generate the CA, APIServer signing/client cert, the ServiceAccount public/private keys and a CA and client cert for the front proxy", - Run: func(cmd *cobra.Command, args []string) { + var cfgPath string + var subCmds []*cobra.Command - // Run the defaulting once again to take passed flags into account - api.Scheme.Default(cfg) - internalcfg := &kubeadmapi.MasterConfiguration{} - api.Scheme.Convert(cfg, internalcfg, nil) - - err := RunSelfSign(internalcfg) - kubeadmutil.CheckErr(err) + subCmdProperties := []struct { + use string + short string + cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error + }{ + { + use: "all", + short: "Generate all PKI assets necessary to establish the control plane", + cmdFunc: CreatePKIAssets, + }, + { + use: "ca", + short: "Generate CA certificate and key for a Kubernetes cluster.", + cmdFunc: createOrUseCACertAndKey, + }, + { + use: "apiserver", + short: "Generate API Server serving certificate and key.", + cmdFunc: createOrUseAPIServerCertAndKey, + }, + { + use: "apiserver-kubelet-client", + short: "Generate a client certificate for the API Server to connect to the kubelets securely.", + cmdFunc: createOrUseAPIServerKubeletClientCertAndKey, + }, + { + use: "sa", + short: "Generate a private key for signing service account tokens along with its public key.", + cmdFunc: createOrUseServiceAccountKeyAndPublicKey, + }, + { + use: "front-proxy-ca", + short: "Generate front proxy CA certificate and key for a Kubernetes cluster.", + cmdFunc: createOrUseFrontProxyCACertAndKey, + }, + { + use: "front-proxy-client", + short: "Generate front proxy CA client certificate and key for a Kubernetes cluster.", + cmdFunc: createOrUseFrontProxyClientCertAndKey, }, } - cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "dns-domain", cfg.Networking.DNSDomain, "The DNS Domain for the Kubernetes cluster.") - cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, "The path where to save and store the certificates.") - cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "The subnet for the Services in the cluster.") - cmd.Flags().StringSliceVar(&cfg.APIServerCertSANs, "cert-altnames", []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 will advertise it's listening on. 0.0.0.0 means the default network interface's address.") - return cmd + for _, properties := range subCmdProperties { + // Creates the UX Command + cmd := &cobra.Command{ + Use: properties.use, + Short: properties.short, + Run: runCmdFunc(properties.cmdFunc, &cfgPath, cfg), + } + + // 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 and store the certificates") + if properties.use == "all" || properties.use == "apiserver" { + cmd.Flags().StringVar(&cfg.Networking.DNSDomain, "service-dns-domain", cfg.Networking.DNSDomain, "Use alternative domain for services, e.g. \"myorg.internal\"") + cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Use alternative range of IP address for service VIPs") + 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 will advertise it's listening on. 0.0.0.0 means the default network interface's address.") + } + + subCmds = append(subCmds, cmd) + } + + return subCmds } -// RunSelfSign generates certificate assets in the specified directory -func RunSelfSign(config *kubeadmapi.MasterConfiguration) error { - if err := validateArgs(config); err != nil { - return fmt.Errorf("The argument validation failed: %v", err) +// runCmdFunc creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of inpunt parameters) +func runCmdFunc(cmdFunc func(cfg *kubeadmapi.MasterConfiguration) error, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) { + + // the following statement build a clousure that wraps a call to a CreateCertFunc, 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 commnands and gets access to current value e.g. flags value. + + return func(cmd *cobra.Command, args []string) { + internalcfg := &kubeadmapi.MasterConfiguration{} + + // Takes passed flags into account; the defaulting is executed once again enforcing assignement of + // static default values to cfg only for values not provided with flags + api.Scheme.Default(cfg) + api.Scheme.Convert(cfg, internalcfg, nil) + + // Loads configuration from config file, if provided + // Nb. --config overrides command line flags + err := configutil.TryLoadMasterConfiguration(*cfgPath, internalcfg) + kubeadmutil.CheckErr(err) + + // Applies dynamic defaults to settings not provided with flags + err = configutil.SetInitDynamicDefaults(internalcfg) + kubeadmutil.CheckErr(err) + + // Validates cfg (flags/configs + defaults + dynamic defaults) + err = validation.ValidateMasterConfiguration(internalcfg).ToAggregate() + kubeadmutil.CheckErr(err) + + // Execute the cmdFunc + err = cmdFunc(internalcfg) + kubeadmutil.CheckErr(err) + } +} + +// CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane. +// Please note that this action is a bulk action calling all the atomic certphase actions +func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration) error { + + certActions := []func(cfg *kubeadmapi.MasterConfiguration) error{ + createOrUseCACertAndKey, + createOrUseAPIServerCertAndKey, + createOrUseAPIServerKubeletClientCertAndKey, + createOrUseServiceAccountKeyAndPublicKey, + createOrUseFrontProxyCACertAndKey, + createOrUseFrontProxyClientCertAndKey, } - // If it's possible to detect the default IP, add it to the SANs as well. Otherwise, just go with the provided ones - ip, err := netutil.ChooseBindAddress(net.ParseIP(config.API.AdvertiseAddress)) - if err == nil { - config.API.AdvertiseAddress = ip.String() + for _, action := range certActions { + err := action(cfg) + if err != nil { + return err + } } - if err = certphase.CreatePKIAssets(config); err != nil { - return err + fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n", cfg.CertificatesDir) + + return nil +} + +// createOrUseCACertAndKey create a new self signed CA, or use the existing one. +func createOrUseCACertAndKey(cfg *kubeadmapi.MasterConfiguration) error { + + return createOrUseCertificateAuthorithy( + cfg.CertificatesDir, + kubeadmconstants.CACertAndKeyBaseName, + "CA", + certphase.NewCACertAndKey, + ) +} + +// createOrUseAPIServerCertAndKey create a new CA certificate for apiserver, or use the existing one. +// It assumes the CA certificates should exists into the CertificatesDir +func createOrUseAPIServerCertAndKey(cfg *kubeadmapi.MasterConfiguration) error { + + return createOrUseSignedCertificate( + cfg.CertificatesDir, + kubeadmconstants.CACertAndKeyBaseName, + kubeadmconstants.APIServerCertAndKeyBaseName, + "API server", + func(caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { + return certphase.NewAPIServerCertAndKey(cfg, caCert, caKey) + }, + ) +} + +// create a new CA certificate for kubelets calling apiserver, or use the existing one +// It assumes the CA certificates should exists into the CertificatesDir +func createOrUseAPIServerKubeletClientCertAndKey(cfg *kubeadmapi.MasterConfiguration) error { + + return createOrUseSignedCertificate( + cfg.CertificatesDir, + kubeadmconstants.CACertAndKeyBaseName, + kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, + "API server kubelet client", + certphase.NewAPIServerKubeletClientCertAndKey, + ) +} + +// createOrUseServiceAccountKeyAndPublicKey create a new public/private key pairs for signing service account user, or use the existing one. +func createOrUseServiceAccountKeyAndPublicKey(cfg *kubeadmapi.MasterConfiguration) error { + + return createOrUseKeyAndPublicKey( + cfg.CertificatesDir, + kubeadmconstants.ServiceAccountKeyBaseName, + "service account", + certphase.NewServiceAccountSigningKey, + ) +} + +// createOrUseFrontProxyCACertAndKey create a new self signed front proxy CA, or use the existing one. +func createOrUseFrontProxyCACertAndKey(cfg *kubeadmapi.MasterConfiguration) error { + + return createOrUseCertificateAuthorithy( + cfg.CertificatesDir, + kubeadmconstants.FrontProxyCACertAndKeyBaseName, + "front-proxy CA", + certphase.NewFrontProxyCACertAndKey, + ) +} + +// createOrUseFrontProxyClientCertAndKey create a new certificate for proxy server client, or use the existing one. +// It assumes the front proxy CA certificates should exists into the CertificatesDir +func createOrUseFrontProxyClientCertAndKey(cfg *kubeadmapi.MasterConfiguration) error { + + return createOrUseSignedCertificate( + cfg.CertificatesDir, + kubeadmconstants.FrontProxyCACertAndKeyBaseName, + kubeadmconstants.FrontProxyClientCertAndKeyBaseName, + "front-proxy client", + certphase.NewFrontProxyClientCertAndKey, + ) +} + +// createOrUseCertificateAuthorithy is a generic function that will create a new certificate Authorithy using the given newFunc, +// assign file names according to the given baseName, or use the existing one already present in pkiDir. +func createOrUseCertificateAuthorithy(pkiDir string, baseName string, UXName string, newFunc func() (*x509.Certificate, *rsa.PrivateKey, error)) 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", UXName, err) + } + + // Check if the existing cert is a CA + if !caCert.IsCA { + return fmt.Errorf("certificate %s is not a CA", UXName) + } + + fmt.Printf("[certificates] Using the existing %s certificate and key.\n", UXName) + } else { + // The certificate and the key did NOT exist, let's generate them now + caCert, caKey, err := newFunc() + if err != nil { + return fmt.Errorf("failure while generating %s certificate and key: %v", UXName, err) + } + + // 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", UXName, err) + } + + fmt.Printf("[certificates] Generated %s certificate and key.\n", UXName) } return nil } -func validateArgs(config *kubeadmapi.MasterConfiguration) error { - allErrs := field.ErrorList{} - allErrs = append(allErrs, validation.ValidateNetworking(&config.Networking, field.NewPath("networking"))...) - allErrs = append(allErrs, validation.ValidateAbsolutePath(config.CertificatesDir, field.NewPath("cert-dir"))...) - allErrs = append(allErrs, validation.ValidateAPIServerCertSANs(config.APIServerCertSANs, field.NewPath("cert-altnames"))...) - allErrs = append(allErrs, validation.ValidateIPFromString(config.API.AdvertiseAddress, field.NewPath("apiserver-advertise-address"))...) +// createOrUseSignedCertificate is a generic function that will create a new signed certificate using the given newFunc, +// assign file names according to the given baseName, or use the existing one already present in pkiDir. +func createOrUseSignedCertificate(pkiDir string, CABaseName string, baseName string, UXName string, newFunc func(*x509.Certificate, *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error)) error { - return allErrs.ToAggregate() + // Checks if certificate authorithy exists in the PKI directory + if !pkiutil.CertOrKeyExist(pkiDir, CABaseName) { + return fmt.Errorf("couldn't load certificate authorithy for %s from certificate dir", UXName) + } + + // Try to load certificate authorithy .crt and .key from the PKI directory + caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, CABaseName) + if err != nil { + return fmt.Errorf("failure loading certificate authorithy for %s: %v", UXName, err) + } + + // Make sure the loaded CA cert actually is a CA + if !caCert.IsCA { + return fmt.Errorf("certificate authorithy for %s is not a CA", UXName) + } + + // 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", UXName, err) + } + + // Check if the existing cert is signed by the given CA + if err := signedCert.CheckSignatureFrom(caCert); err != nil { + return fmt.Errorf("certificate %s is not signed by corresponding CA", UXName) + } + + fmt.Printf("[certificates] Using the existing %s certificate and key.\n", UXName) + } else { + // The certificate and the key did NOT exist, let's generate them now + signedCert, signedKey, err := newFunc(caCert, caKey) + if err != nil { + return fmt.Errorf("failure while generating %s key and certificate: %v", UXName, err) + } + + // Write .crt and .key files to disk + if err = pkiutil.WriteCertAndKey(pkiDir, baseName, signedCert, signedKey); err != nil { + return fmt.Errorf("failure while saving %s certificate and key: %v", UXName, err) + } + + fmt.Printf("[certificates] Generated %s certificate and key.\n", UXName) + if pkiutil.HasServerAuth(signedCert) { + fmt.Printf("[certificates] %s serving cert is signed for DNS names %v and IPs %v\n", UXName, signedCert.DNSNames, signedCert.IPAddresses) + } + } + + return nil +} + +// createOrUseKeyAndPublicKey is a generic function that will create a new public/private key pairs using the given newFunc, +// assign file names according to the given baseName, or use the existing one already present in pkiDir. +func createOrUseKeyAndPublicKey(pkiDir string, baseName string, UXName string, newFunc func() (*rsa.PrivateKey, error)) 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 they could not be loaded properly: %v", UXName, err) + } + + fmt.Printf("[certificates] Using the existing %s key.\n", UXName) + } else { + // The key does NOT exist, let's generate it now + key, err := newFunc() + if err != nil { + return fmt.Errorf("failure while generating %s key: %v", UXName, err) + } + + // 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", UXName, err) + } + + if err = pkiutil.WritePublicKey(pkiDir, baseName, &key.PublicKey); err != nil { + return fmt.Errorf("failure while saving %s public key: %v", UXName, err) + } + fmt.Printf("[certificates] Generated %s key and public key.\n", UXName) + } + + return nil } diff --git a/cmd/kubeadm/app/cmd/phases/certs_test.go b/cmd/kubeadm/app/cmd/phases/certs_test.go new file mode 100644 index 00000000000..c6fad4b27bc --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/certs_test.go @@ -0,0 +1,270 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package phases + +import ( + "fmt" + "html/template" + "io/ioutil" + "os" + "path" + "testing" + + "github.com/renstrom/dedent" + "github.com/spf13/cobra" + + // required for triggering api machinery startup when running unit tests + _ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install" + + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" +) + +func TestSubCmdCertsCreateFiles(t *testing.T) { + + subCmds := newSubCmdCerts() + + var tests = []struct { + subCmds []string + expectedFiles []string + }{ + { + subCmds: []string{"all"}, + 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, + }, + }, + { + subCmds: []string{"ca"}, + expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName}, + }, + { + subCmds: []string{"ca", "apiserver"}, + expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName}, + }, + { + subCmds: []string{"ca", "apiserver-kubelet-client"}, + expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName, kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName}, + }, + { + subCmds: []string{"sa"}, + expectedFiles: []string{kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName}, + }, + { + subCmds: []string{"front-proxy-ca"}, + expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName}, + }, + { + subCmds: []string{"front-proxy-ca", "front-proxy-client"}, + expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName, kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName}, + }, + } + + for _, test := range tests { + // Temporary folder for the test case + tmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Couldn't create tmpdir") + } + defer os.RemoveAll(tmpdir) + + // executes given sub commands + for _, subCmdName := range test.subCmds { + subCmd := getSubCmd(t, subCmdName, subCmds) + subCmd.SetArgs([]string{fmt.Sprintf("--cert-dir=%s", tmpdir)}) + if err := subCmd.Execute(); err != nil { + t.Fatalf("Could not execute subcommand: %s", subCmdName) + } + } + + // verify expected files are there + assertFilesCount(t, tmpdir, len(test.expectedFiles)) + for _, file := range test.expectedFiles { + assertFileExists(t, tmpdir, file) + } + } +} + +func TestSubCmdApiServerFlags(t *testing.T) { + + subCmds := newSubCmdCerts() + + // Temporary folder for the test case + tmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Couldn't create tmpdir") + } + defer os.RemoveAll(tmpdir) + + // creates ca cert + subCmd := getSubCmd(t, "ca", subCmds) + subCmd.SetArgs([]string{fmt.Sprintf("--cert-dir=%s", tmpdir)}) + if err := subCmd.Execute(); err != nil { + t.Fatalf("Could not execute subcommand ca") + } + + // creates apiserver cert + subCmd = getSubCmd(t, "apiserver", subCmds) + subCmd.SetArgs([]string{ + fmt.Sprintf("--cert-dir=%s", tmpdir), + "--apiserver-cert-extra-sans=foo,boo", + "--service-cidr=10.0.0.0/24", + "--service-dns-domain=mycluster.local", + "--apiserver-advertise-address=1.2.3.4", + }) + if err := subCmd.Execute(); err != nil { + t.Fatalf("Could not execute subcommand apiserver") + } + + APIserverCert, err := pkiutil.TryLoadCertFromDisk(tmpdir, kubeadmconstants.APIServerCertAndKeyBaseName) + if err != nil { + t.Fatalf("Error loading API server certificate: %v", err) + } + + hostname, err := os.Hostname() + if err != nil { + t.Errorf("couldn't get the hostname: %v", err) + } + for i, name := range []string{hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.mycluster.local"} { + if APIserverCert.DNSNames[i] != name { + t.Errorf("APIserverCert.DNSNames[%d] is %s instead of %s", i, APIserverCert.DNSNames[i], name) + } + } + for i, ip := range []string{"10.0.0.1", "1.2.3.4"} { + if APIserverCert.IPAddresses[i].String() != ip { + t.Errorf("APIserverCert.IPAddresses[%d] is %s instead of %s", i, APIserverCert.IPAddresses[i], ip) + } + } +} + +func TestSubCmdReadsConfig(t *testing.T) { + + subCmds := newSubCmdCerts() + + var tests = []struct { + subCmds []string + expectedFileCount int + }{ + { + subCmds: []string{"sa"}, + expectedFileCount: 2, + }, + { + subCmds: []string{"front-proxy-ca", "front-proxy-client"}, + expectedFileCount: 4, + }, + { + subCmds: []string{"ca", "apiserver", "apiserver-kubelet-client"}, + expectedFileCount: 6, + }, + { + subCmds: []string{"all"}, + expectedFileCount: 12, + }, + } + + for _, test := range tests { + // Temporary folder for the test case + tmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Couldn't create tmpdir") + } + defer os.RemoveAll(tmpdir) + + configPath := saveDummyCfg(t, tmpdir) + + // executes given sub commands + for _, subCmdName := range test.subCmds { + subCmd := getSubCmd(t, subCmdName, subCmds) + subCmd.SetArgs([]string{fmt.Sprintf("--config=%s", configPath)}) + if err := subCmd.Execute(); err != nil { + t.Fatalf("Could not execute command: %s", subCmdName) + } + } + + // verify expected files are there + // NB. test.expectedFileCount + 1 because in this test case the tempdir where key/certificates + // are saved contains also the dummy configuration file + assertFilesCount(t, tmpdir, test.expectedFileCount+1) + } +} + +func getSubCmd(t *testing.T, name string, subCmds []*cobra.Command) *cobra.Command { + for _, subCmd := range subCmds { + if subCmd.Name() == name { + return subCmd + } + } + t.Fatalf("Unable to find sub command %s", name) + + return nil +} + +func assertFilesCount(t *testing.T, dirName string, count int) { + files, err := ioutil.ReadDir(dirName) + if err != nil { + t.Fatalf("Couldn't read files from tmpdir: %s", err) + } + + if len(files) != count { + t.Errorf("dir does contains %d, %d expected", len(files), count) + for _, f := range files { + t.Error(f.Name()) + } + } +} + +func assertFileExists(t *testing.T, dirName string, fileName string) { + path := path.Join(dirName, fileName) + if _, err := os.Stat(path); os.IsNotExist(err) { + t.Errorf("file %s does not exist", fileName) + } +} + +func saveDummyCfg(t *testing.T, dirName string) string { + + path := path.Join(dirName, "dummyconfig.yaml") + cfgTemplate := template.Must(template.New("init").Parse(dedent.Dedent(` + apiVersion: kubeadm.k8s.io/v1alpha1 + kind: MasterConfiguration + certificatesDir: {{.CertificatesDir}} + `))) + + f, err := os.Create(path) + if err != nil { + t.Errorf("error creating dummyconfig file %s: %v", path, err) + } + + templateData := struct { + CertificatesDir string + }{ + CertificatesDir: dirName, + } + + err = cfgTemplate.Execute(f, templateData) + if err != nil { + t.Errorf("error generating dummyconfig file %s: %v", path, err) + } + f.Close() + + return path +} diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index 3f672e224cd..8a6ef8a2f4f 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -37,10 +37,12 @@ const ( APIServerCertAndKeyBaseName = "apiserver" APIServerCertName = "apiserver.crt" APIServerKeyName = "apiserver.key" + APIServerCertCommonName = "kube-apiserver" //used as subject.commonname attribute (CN) APIServerKubeletClientCertAndKeyBaseName = "apiserver-kubelet-client" APIServerKubeletClientCertName = "apiserver-kubelet-client.crt" APIServerKubeletClientKeyName = "apiserver-kubelet-client.key" + APIServerKubeletClientCertCommonName = "kube-apiserver-kubelet-client" //used as subject.commonname attribute (CN) ServiceAccountKeyBaseName = "sa" ServiceAccountPublicKeyName = "sa.pub" @@ -53,6 +55,7 @@ const ( FrontProxyClientCertAndKeyBaseName = "front-proxy-client" FrontProxyClientCertName = "front-proxy-client.crt" FrontProxyClientKeyName = "front-proxy-client.key" + FrontProxyClientCertCommonName = "front-proxy-client" //used as subject.commonname attribute (CN) AdminKubeConfigFileName = "admin.conf" KubeletKubeConfigFileName = "kubelet.conf" diff --git a/cmd/kubeadm/app/phases/certs/BUILD b/cmd/kubeadm/app/phases/certs/BUILD index 97ceec717b4..cc99563df8a 100644 --- a/cmd/kubeadm/app/phases/certs/BUILD +++ b/cmd/kubeadm/app/phases/certs/BUILD @@ -15,7 +15,7 @@ go_test( tags = ["automanaged"], deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", - "//vendor/k8s.io/client-go/util/cert:go_default_library", + "//cmd/kubeadm/app/constants:go_default_library", ], ) @@ -31,7 +31,6 @@ go_library( "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//vendor/k8s.io/client-go/util/cert:go_default_library", ], diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index a19f7e60452..9e3718e2466 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -23,7 +23,6 @@ import ( "net" "os" - setutil "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" certutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" @@ -32,257 +31,139 @@ import ( "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" ) -// TODO: Integration test cases -// no files exist => create all four files -// valid ca.{crt,key} exists => create apiserver.{crt,key} -// valid ca.{crt,key} and apiserver.{crt,key} exists => do nothing -// invalid ca.{crt,key} exists => error -// only one of the .crt or .key file exists => error +// NewCACertAndKey will generate a self signed CA. +func NewCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) { -// CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane. -// It generates a self-signed CA certificate and a server certificate (signed by the CA) -func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration) error { - pkiDir := cfg.CertificatesDir + 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 +} + +// NewAPIServerCertAndKey generate CA certificate for apiserver, signed by the given CA. +func NewAPIServerCertAndKey(cfg *kubeadmapi.MasterConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { + + altNames, err := getAltNames(cfg) + if err != nil { + return nil, nil, fmt.Errorf("failure while composing altnames for API server: %v", err) + } + + config := certutil.Config{ + CommonName: kubeadmconstants.APIServerCertCommonName, + AltNames: *altNames, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + } + apiCert, apiKey, err := pkiutil.NewCertAndKey(caCert, caKey, config) + if err != nil { + return nil, nil, fmt.Errorf("failure while creating API server key and certificate: %v", err) + } + + return apiCert, apiKey, nil +} + +// NewAPIServerKubeletClientCertAndKey generate CA certificate for the apiservers to connect to the kubelets securely, signed by the given CA. +func NewAPIServerKubeletClientCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { + + config := certutil.Config{ + CommonName: kubeadmconstants.APIServerKubeletClientCertCommonName, + Organization: []string{kubeadmconstants.MastersGroup}, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } + apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(caCert, caKey, config) + if err != nil { + return nil, nil, fmt.Errorf("failure while creating API server kubelet client key and certificate: %v", err) + } + + return apiClientCert, apiClientKey, nil +} + +// 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 { + return nil, fmt.Errorf("failure while creating service account token signing key: %v", err) + } + + return saSigningKey, nil +} + +// 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() + if err != nil { + return nil, nil, fmt.Errorf("failure while generating front-proxy CA certificate and key: %v", err) + } + + return frontProxyCACert, frontProxyCAKey, nil +} + +// NewFrontProxyClientCertAndKey generate CA certificate for proxy server client, signed by the given front proxy CA. +func NewFrontProxyClientCertAndKey(frontProxyCACert *x509.Certificate, frontProxyCAKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) { + + config := certutil.Config{ + CommonName: kubeadmconstants.FrontProxyClientCertCommonName, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } + frontProxyClientCert, frontProxyClientKey, err := pkiutil.NewCertAndKey(frontProxyCACert, frontProxyCAKey, config) + if err != nil { + return nil, nil, fmt.Errorf("failure while creating front-proxy client key and certificate: %v", err) + } + + return frontProxyClientCert, frontProxyClientKey, nil +} + +// getAltNames builds an AltNames object for to be used when generating apiserver certificate +func getAltNames(cfg *kubeadmapi.MasterConfiguration) (*certutil.AltNames, error) { + + // host name hostname, err := os.Hostname() if err != nil { - return fmt.Errorf("couldn't get the hostname: %v", err) + return nil, fmt.Errorf("couldn't get the hostname: %v", err) } + // advertise address + advertiseAddress := net.ParseIP(cfg.API.AdvertiseAddress) + if advertiseAddress == nil { + return nil, fmt.Errorf("error parsing API AdvertiseAddress %v: is not a valid textual representation of an IP address", cfg.API.AdvertiseAddress) + } + + // internal IP address for the API server _, svcSubnet, err := net.ParseCIDR(cfg.Networking.ServiceSubnet) if err != nil { - return fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err) + return nil, fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err) } - // Build the list of SANs - altNames := getAltNames(cfg.APIServerCertSANs, hostname, cfg.Networking.DNSDomain, svcSubnet) - // Append the address the API Server is advertising - altNames.IPs = append(altNames.IPs, net.ParseIP(cfg.API.AdvertiseAddress)) - - var caCert *x509.Certificate - var caKey *rsa.PrivateKey - // If at least one of them exists, we should try to load them - // In the case that only one exists, there will most likely be an error anyway - if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.CACertAndKeyBaseName) { - // Try to load ca.crt and ca.key from the PKI directory - caCert, caKey, err = pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.CACertAndKeyBaseName) - if err != nil || caCert == nil || caKey == nil { - return fmt.Errorf("certificate and/or key existed but they could not be loaded properly") - } - - // The certificate and key could be loaded, but the certificate is not a CA - if !caCert.IsCA { - return fmt.Errorf("certificate and key could be loaded but the certificate is not a CA") - } - - fmt.Println("[certificates] Using the existing CA certificate and key.") - } else { - // The certificate and the key did NOT exist, let's generate them now - caCert, caKey, err = pkiutil.NewCertificateAuthority() - if err != nil { - return fmt.Errorf("failure while generating CA certificate and key [%v]", err) - } - - if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.CACertAndKeyBaseName, caCert, caKey); err != nil { - return fmt.Errorf("failure while saving CA certificate and key [%v]", err) - } - fmt.Println("[certificates] Generated CA certificate and key.") + internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(svcSubnet, 1) + if err != nil { + return nil, fmt.Errorf("unable to get first IP address from the given CIDR (%s): %v", svcSubnet.String(), err) } - // If at least one of them exists, we should try to load them - // In the case that only one exists, there will most likely be an error anyway - if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.APIServerCertAndKeyBaseName) { - // Try to load apiserver.crt and apiserver.key from the PKI directory - apiCert, apiKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.APIServerCertAndKeyBaseName) - if err != nil || apiCert == nil || apiKey == nil { - return fmt.Errorf("certificate and/or key existed but they could not be loaded properly") - } - - fmt.Println("[certificates] Using the existing API Server certificate and key.") - } else { - // The certificate and the key did NOT exist, let's generate them now - // TODO: Add a test case to verify that this cert has the x509.ExtKeyUsageServerAuth flag - config := certutil.Config{ - CommonName: "kube-apiserver", - AltNames: altNames, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - } - apiCert, apiKey, err := pkiutil.NewCertAndKey(caCert, caKey, config) - if err != nil { - return fmt.Errorf("failure while creating API server key and certificate [%v]", err) - } - - if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.APIServerCertAndKeyBaseName, apiCert, apiKey); err != nil { - return fmt.Errorf("failure while saving API server certificate and key [%v]", err) - } - fmt.Println("[certificates] Generated API server certificate and key.") - fmt.Printf("[certificates] API Server serving cert is signed for DNS names %v and IPs %v\n", altNames.DNSNames, altNames.IPs) - } - - // If at least one of them exists, we should try to load them - // In the case that only one exists, there will most likely be an error anyway - if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName) { - // Try to load apiserver-kubelet-client.crt and apiserver-kubelet-client.key from the PKI directory - apiCert, apiKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName) - if err != nil || apiCert == nil || apiKey == nil { - return fmt.Errorf("certificate and/or key existed but they could not be loaded properly") - } - - fmt.Println("[certificates] Using the existing API Server kubelet client certificate and key.") - } else { - // The certificate and the key did NOT exist, let's generate them now - // TODO: Add a test case to verify that this cert has the x509.ExtKeyUsageClientAuth flag - config := certutil.Config{ - CommonName: "kube-apiserver-kubelet-client", - Organization: []string{kubeadmconstants.MastersGroup}, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - } - apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(caCert, caKey, config) - if err != nil { - return fmt.Errorf("failure while creating API server kubelet client key and certificate [%v]", err) - } - - if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, apiClientCert, apiClientKey); err != nil { - return fmt.Errorf("failure while saving API server kubelet client certificate and key [%v]", err) - } - fmt.Println("[certificates] Generated API server kubelet client certificate and key.") - } - - // If the key exists, we should try to load it - if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName) { - // Try to load sa.key from the PKI directory - _, err := pkiutil.TryLoadKeyFromDisk(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName) - if err != nil { - return fmt.Errorf("certificate and/or key existed but they could not be loaded properly [%v]", err) - } - - fmt.Println("[certificates] Using the existing service account token signing key.") - } else { - // The key does NOT exist, let's generate it now - saTokenSigningKey, err := certutil.NewPrivateKey() - if err != nil { - return fmt.Errorf("failure while creating service account token signing key [%v]", err) - } - - if err = pkiutil.WriteKey(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName, saTokenSigningKey); err != nil { - return fmt.Errorf("failure while saving service account token signing key [%v]", err) - } - - if err = pkiutil.WritePublicKey(pkiDir, kubeadmconstants.ServiceAccountKeyBaseName, &saTokenSigningKey.PublicKey); err != nil { - return fmt.Errorf("failure while saving service account token signing public key [%v]", err) - } - fmt.Println("[certificates] Generated service account token signing key and public key.") - } - - // front proxy CA and client certs are used to secure a front proxy authenticator which is used to assert identity - // without the client cert, you cannot make use of the front proxy and the kube-aggregator uses this connection - // so we generate and enable it unconditionally - // This is a separte CA, so that front proxy identities cannot hit the API and normal client certs cannot be used - // as front proxies. - var frontProxyCACert *x509.Certificate - var frontProxyCAKey *rsa.PrivateKey - // If at least one of them exists, we should try to load them - // In the case that only one exists, there will most likely be an error anyway - if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName) { - // Try to load front-proxy-ca.crt and front-proxy-ca.key from the PKI directory - frontProxyCACert, frontProxyCAKey, err = pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName) - if err != nil || frontProxyCACert == nil || frontProxyCAKey == nil { - return fmt.Errorf("certificate and/or key existed but they could not be loaded properly") - } - - // The certificate and key could be loaded, but the certificate is not a CA - if !frontProxyCACert.IsCA { - return fmt.Errorf("certificate and key could be loaded but the certificate is not a CA") - } - - fmt.Println("[certificates] Using the existing front-proxy CA certificate and key.") - } else { - // The certificate and the key did NOT exist, let's generate them now - frontProxyCACert, frontProxyCAKey, err = pkiutil.NewCertificateAuthority() - if err != nil { - return fmt.Errorf("failure while generating front-proxy CA certificate and key [%v]", err) - } - - if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName, frontProxyCACert, frontProxyCAKey); err != nil { - return fmt.Errorf("failure while saving front-proxy CA certificate and key [%v]", err) - } - fmt.Println("[certificates] Generated front-proxy CA certificate and key.") - } - - // At this point we have a front proxy CA signing key. We can use that create the front proxy client cert if - // it doesn't already exist. - // If at least one of them exists, we should try to load them - // In the case that only one exists, there will most likely be an error anyway - if pkiutil.CertOrKeyExist(pkiDir, kubeadmconstants.FrontProxyClientCertAndKeyBaseName) { - // Try to load apiserver-kubelet-client.crt and apiserver-kubelet-client.key from the PKI directory - apiCert, apiKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.FrontProxyClientCertAndKeyBaseName) - if err != nil || apiCert == nil || apiKey == nil { - return fmt.Errorf("certificate and/or key existed but they could not be loaded properly") - } - - fmt.Println("[certificates] Using the existing front-proxy client certificate and key.") - } else { - // The certificate and the key did NOT exist, let's generate them now - // TODO: Add a test case to verify that this cert has the x509.ExtKeyUsageClientAuth flag - config := certutil.Config{ - CommonName: "front-proxy-client", - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - } - apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(frontProxyCACert, frontProxyCAKey, config) - if err != nil { - return fmt.Errorf("failure while creating front-proxy client key and certificate [%v]", err) - } - - if err = pkiutil.WriteCertAndKey(pkiDir, kubeadmconstants.FrontProxyClientCertAndKeyBaseName, apiClientCert, apiClientKey); err != nil { - return fmt.Errorf("failure while saving front-proxy client certificate and key [%v]", err) - } - fmt.Println("[certificates] Generated front-proxy client certificate and key.") - } - - fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n", pkiDir) - - return nil -} - -// checkAltNamesExist verifies that the cert is valid for all IPs and DNS names it should be valid for -func checkAltNamesExist(IPs []net.IP, DNSNames []string, altNames certutil.AltNames) bool { - dnsset := setutil.NewString(DNSNames...) - - for _, dnsNameThatShouldExist := range altNames.DNSNames { - if !dnsset.Has(dnsNameThatShouldExist) { - return false - } - } - - for _, ipThatShouldExist := range altNames.IPs { - found := false - for _, ip := range IPs { - if ip.Equal(ipThatShouldExist) { - found = true - break - } - } - - if !found { - return false - } - } - return true -} - -// getAltNames builds an AltNames object for the certutil to use when generating the certificates -func getAltNames(cfgAltNames []string, hostname, dnsdomain string, svcSubnet *net.IPNet) certutil.AltNames { - altNames := certutil.AltNames{ + // create AltNames with defaults DNSNames/IPs + altNames := &certutil.AltNames{ DNSNames: []string{ hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", - fmt.Sprintf("kubernetes.default.svc.%s", dnsdomain), + fmt.Sprintf("kubernetes.default.svc.%s", cfg.Networking.DNSDomain), + }, + IPs: []net.IP{ + internalAPIServerVirtualIP, + advertiseAddress, }, } - // Populate IPs/DNSNames from AltNames - for _, altname := range cfgAltNames { + // adds additional SAN + for _, altname := range cfg.APIServerCertSANs { if ip := net.ParseIP(altname); ip != nil { altNames.IPs = append(altNames.IPs, ip) } else if len(validation.IsDNS1123Subdomain(altname)) == 0 { @@ -290,11 +171,5 @@ func getAltNames(cfgAltNames []string, hostname, dnsdomain string, svcSubnet *ne } } - // and lastly, extract the internal IP address for the API server - internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(svcSubnet, 1) - if err != nil { - fmt.Printf("[certs] WARNING: Unable to get first IP address from the given CIDR (%s): %v\n", svcSubnet.String(), err) - } - altNames.IPs = append(altNames.IPs, internalAPIServerVirtualIP) - return altNames + return altNames, nil } diff --git a/cmd/kubeadm/app/phases/certs/certs_test.go b/cmd/kubeadm/app/phases/certs/certs_test.go index 8f6bdbaed0e..2592e5743b0 100644 --- a/cmd/kubeadm/app/phases/certs/certs_test.go +++ b/cmd/kubeadm/app/phases/certs/certs_test.go @@ -17,158 +17,151 @@ limitations under the License. package certs import ( - "fmt" - "io/ioutil" + "crypto/x509" "net" "os" "testing" - certutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) -func TestCreatePKIAssets(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "") +func TestNewCACertAndKey(t *testing.T) { + caCert, _, err := NewCACertAndKey() if err != nil { - t.Fatalf("Couldn't create tmpdir") + t.Fatalf("failed call NewCACertAndKey: %v", err) } - defer os.RemoveAll(tmpdir) - var tests = []struct { - cfg *kubeadmapi.MasterConfiguration - expected bool - }{ - { - cfg: &kubeadmapi.MasterConfiguration{}, - expected: false, - }, - { - // CIDR too small - cfg: &kubeadmapi.MasterConfiguration{ - API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"}, - Networking: kubeadmapi.Networking{ServiceSubnet: "10.0.0.1/1"}, - CertificatesDir: fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir), - }, - expected: false, - }, - { - // CIDR invalid - cfg: &kubeadmapi.MasterConfiguration{ - API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"}, - Networking: kubeadmapi.Networking{ServiceSubnet: "invalid"}, - CertificatesDir: fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir), - }, - expected: false, - }, - { - cfg: &kubeadmapi.MasterConfiguration{ - API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"}, - Networking: kubeadmapi.Networking{ServiceSubnet: "10.0.0.1/24"}, - CertificatesDir: fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir), - }, - expected: true, - }, + assertIsCa(t, caCert) +} + +func TestNewAPIServerCertAndKey(t *testing.T) { + hostname, err := os.Hostname() + if err != nil { + t.Errorf("couldn't get the hostname: %v", err) } - for _, rt := range tests { - actual := CreatePKIAssets(rt.cfg) - if (actual == nil) != rt.expected { - t.Errorf( - "failed CreatePKIAssets with an error:\n\texpected: %t\n\t actual: %t", - rt.expected, - (actual == nil), - ) - } + 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"}, + } + caCert, caKey, err := NewCACertAndKey() + + apiServerCert, _, err := NewAPIServerCertAndKey(cfg, caCert, caKey) + if err != nil { + 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", advertiseIP} { + assertHasIPAddresses(t, apiServerCert, net.ParseIP(IPAddress)) } } -func TestCheckAltNamesExist(t *testing.T) { - var tests = []struct { - IPs []net.IP - DNSNames []string - requiredAltNames certutil.AltNames - succeed bool - }{ - { - // equal - requiredAltNames: certutil.AltNames{IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, DNSNames: []string{"foo", "bar", "baz"}}, - IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, - DNSNames: []string{"foo", "bar", "baz"}, - succeed: true, - }, - { - // the loaded cert has more ips than required, ok - requiredAltNames: certutil.AltNames{IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, DNSNames: []string{"foo", "bar", "baz"}}, - IPs: []net.IP{net.ParseIP("192.168.2.5"), net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, - DNSNames: []string{"a", "foo", "b", "bar", "baz"}, - succeed: true, - }, - { - // the loaded cert doesn't have all ips - requiredAltNames: certutil.AltNames{IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.2.5"), net.ParseIP("192.168.1.2")}, DNSNames: []string{"foo", "bar", "baz"}}, - IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, - DNSNames: []string{"foo", "bar", "baz"}, - succeed: false, - }, - { - // the loaded cert doesn't have all ips - requiredAltNames: certutil.AltNames{IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, DNSNames: []string{"foo", "bar", "b", "baz"}}, - IPs: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("192.168.1.2")}, - DNSNames: []string{"foo", "bar", "baz"}, - succeed: false, - }, +func TestNewAPIServerKubeletClientCertAndKey(t *testing.T) { + caCert, caKey, err := NewCACertAndKey() + + apiClientCert, _, err := NewAPIServerKubeletClientCertAndKey(caCert, caKey) + if err != nil { + t.Fatalf("failed creation of cert and key: %v", err) } - for _, rt := range tests { - succeeded := checkAltNamesExist(rt.IPs, rt.DNSNames, rt.requiredAltNames) - if succeeded != rt.succeed { - t.Errorf( - "failed checkAltNamesExist:\n\texpected: %t\n\t actual: %t", - rt.succeed, - succeeded, - ) - } + assertIsSignedByCa(t, apiClientCert, caCert) + assertHasClientAuth(t, apiClientCert) + assertHasOrganization(t, apiClientCert, constants.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 TestGetAltNames(t *testing.T) { - var tests = []struct { - cfgaltnames []string - hostname string - dnsdomain string - servicecidr string - expectedIPs []string - expectedDNSNames []string - }{ - { - cfgaltnames: []string{"foo", "192.168.200.1", "bar.baz"}, - hostname: "my-node", - dnsdomain: "cluster.external", - servicecidr: "10.96.0.1/12", - expectedIPs: []string{"192.168.200.1", "10.96.0.1"}, - expectedDNSNames: []string{"my-node", "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.external", "foo", "bar.baz"}, - }, +func TestNewFrontProxyCACertAndKey(t *testing.T) { + frontProxyCACert, _, err := NewFrontProxyCACertAndKey() + if err != nil { + t.Fatalf("failed creation of cert and key: %v", err) } - for _, rt := range tests { - _, svcSubnet, _ := net.ParseCIDR(rt.servicecidr) - actual := getAltNames(rt.cfgaltnames, rt.hostname, rt.dnsdomain, svcSubnet) - for i := range actual.IPs { - if rt.expectedIPs[i] != actual.IPs[i].String() { - t.Errorf( - "failed getAltNames:\n\texpected: %s\n\t actual: %s", - rt.expectedIPs[i], - actual.IPs[i].String(), - ) - } - } - for i := range actual.DNSNames { - if rt.expectedDNSNames[i] != actual.DNSNames[i] { - t.Errorf( - "failed getAltNames:\n\texpected: %s\n\t actual: %s", - rt.expectedDNSNames[i], - actual.DNSNames[i], - ) - } - } + assertIsCa(t, frontProxyCACert) +} + +func TestNewFrontProxyClientCertAndKey(t *testing.T) { + frontProxyCACert, frontProxyCAKey, err := NewFrontProxyCACertAndKey() + + frontProxyClientCert, _, err := NewFrontProxyClientCertAndKey(frontProxyCACert, frontProxyCAKey) + if err != nil { + t.Fatalf("failed creation of cert and key: %v", err) + } + + assertIsSignedByCa(t, frontProxyClientCert, frontProxyCACert) + assertHasClientAuth(t, frontProxyClientCert) +} + +func assertIsCa(t *testing.T, cert *x509.Certificate) { + if !cert.IsCA { + t.Error("cert is not a valida CA") } } + +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") + } +} + +func assertHasClientAuth(t *testing.T, cert *x509.Certificate) { + for i := range cert.ExtKeyUsage { + if cert.ExtKeyUsage[i] == x509.ExtKeyUsageClientAuth { + return + } + } + 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 + } + } + 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 + } + } + 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 + } + } + 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/app/phases/certs/pkiutil/pki_helpers.go b/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go index d9587bf6924..fcfd07b8876 100644 --- a/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go +++ b/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go @@ -61,6 +61,16 @@ func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config certu return cert, key, nil } +// HasServerAuth returns true if the given certificate is a ServerAuth +func HasServerAuth(cert *x509.Certificate) bool { + for i := range cert.ExtKeyUsage { + if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth { + return true + } + } + return false +} + func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error { if err := WriteKey(pkiPath, name, key); err != nil { return 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 75004cc65f3..34ac26ee14c 100644 --- a/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go +++ b/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go @@ -87,6 +87,45 @@ func TestNewCertAndKey(t *testing.T) { } } +func TestHasServerAuth(t *testing.T) { + caCert, caKey, _ := NewCertificateAuthority() + + var tests = []struct { + config certutil.Config + expected bool + }{ + { + config: certutil.Config{ + CommonName: "test", + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }, + expected: true, + }, + { + config: certutil.Config{ + CommonName: "test", + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + }, + expected: false, + }, + } + + for _, rt := range tests { + cert, _, err := NewCertAndKey(caCert, caKey, rt.config) + if err != nil { + t.Fatalf("Couldn't create cert: %v", err) + } + actual := HasServerAuth(cert) + if actual != rt.expected { + t.Errorf( + "failed HasServerAuth:\n\texpected: %t\n\t actual: %t", + rt.expected, + actual, + ) + } + } +} + func TestWriteCertAndKey(t *testing.T) { tmpdir, err := ioutil.TempDir("", "") if err != nil { diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index 73731b657cf..c0872062fbc 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -52,6 +52,7 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//cmd/kubeadm/app/util/config:all-srcs", "//cmd/kubeadm/app/util/kubeconfig:all-srcs", "//cmd/kubeadm/app/util/token:all-srcs", ], diff --git a/cmd/kubeadm/app/util/config/BUILD b/cmd/kubeadm/app/util/config/BUILD new file mode 100644 index 00000000000..15ce4e50705 --- /dev/null +++ b/cmd/kubeadm/app/util/config/BUILD @@ -0,0 +1,45 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["masterconfig.go"], + tags = ["automanaged"], + deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/util:go_default_library", + "//cmd/kubeadm/app/util/token:go_default_library", + "//pkg/api:go_default_library", + "//pkg/util/version:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["masterconfig_test.go"], + library = ":go_default_library", + tags = ["automanaged"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/cmd/kubeadm/app/cmd/defaults.go b/cmd/kubeadm/app/util/config/masterconfig.go similarity index 76% rename from cmd/kubeadm/app/cmd/defaults.go rename to cmd/kubeadm/app/util/config/masterconfig.go index 937d5b8bdf9..93a19d8bbca 100644 --- a/cmd/kubeadm/app/cmd/defaults.go +++ b/cmd/kubeadm/app/util/config/masterconfig.go @@ -18,17 +18,20 @@ package cmd import ( "fmt" + "io/ioutil" "net" + "k8s.io/apimachinery/pkg/runtime" netutil "k8s.io/apimachinery/pkg/util/net" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/util/version" ) -func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { +func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { // Choose the right address for the API Server to advertise. If the advertise address is localhost or 0.0.0.0, the default interface's IP address is used // This is the same logic as the API Server uses @@ -54,15 +57,6 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { return fmt.Errorf("this version of kubeadm only supports deploying clusters with the control plane version >= %s. Current version: %s", kubeadmconstants.MinimumControlPlaneVersion.String(), cfg.KubernetesVersion) } - fmt.Printf("[init] Using Kubernetes version: %s\n", cfg.KubernetesVersion) - fmt.Printf("[init] Using Authorization modes: %v\n", cfg.AuthorizationModes) - - // Warn about the limitations with the current cloudprovider solution. - if cfg.CloudProvider != "" { - fmt.Println("[init] WARNING: For cloudprovider integrations to work --cloud-provider must be set for all kubelets in the cluster.") - fmt.Println("\t(/etc/systemd/system/kubelet.service.d/10-kubeadm.conf should be edited for this purpose)") - } - if cfg.Token == "" { var err error cfg.Token, err = tokenutil.GenerateToken() @@ -73,3 +67,19 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { return nil } + +// TryLoadMasterConfiguration tries to loads a Master configuration from the given file (if defined) +func TryLoadMasterConfiguration(cfgPath string, cfg *kubeadmapi.MasterConfiguration) error { + + if cfgPath != "" { + b, err := ioutil.ReadFile(cfgPath) + if err != nil { + return fmt.Errorf("unable to read config from %q [%v]", cfgPath, err) + } + if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), b, cfg); err != nil { + return fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err) + } + } + + return nil +} diff --git a/cmd/kubeadm/app/cmd/defaults_test.go b/cmd/kubeadm/app/util/config/masterconfig_test.go similarity index 100% rename from cmd/kubeadm/app/cmd/defaults_test.go rename to cmd/kubeadm/app/util/config/masterconfig_test.go