From c09d875c6c4641300a64d80b97a9b983485e7d6b Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Sun, 18 Feb 2018 16:22:42 +0100 Subject: [PATCH] refactor kubeadm join command generation --- cmd/kubeadm/app/cmd/init.go | 24 ++---- cmd/kubeadm/app/cmd/phases/bootstraptoken.go | 28 ++---- cmd/kubeadm/app/cmd/token.go | 61 +------------- cmd/kubeadm/app/cmd/util/join.go | 89 ++++++++++++++++++++ 4 files changed, 105 insertions(+), 97 deletions(-) create mode 100644 cmd/kubeadm/app/cmd/util/join.go diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index edaeef6f25..b9504d49f5 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -38,6 +38,7 @@ 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" + cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/images" @@ -46,7 +47,6 @@ import ( clusterinfophase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" nodebootstraptokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" - "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" @@ -61,7 +61,6 @@ import ( configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" "k8s.io/kubernetes/pkg/api/legacyscheme" utilsexec "k8s.io/utils/exec" ) @@ -83,7 +82,7 @@ var ( You can now join any number of machines by running the following on each node as root: - kubeadm join --token {{.Token}} {{.MasterHostPort}} --discovery-token-ca-cert-hash {{.CAPubKeyPin}} + {{.joinCommand}} `))) @@ -468,26 +467,15 @@ func (i *Init) Run(out io.Writer) error { return nil } - // Load the CA certificate from so we can pin its public key - caCert, err := pkiutil.TryLoadCertFromDisk(i.cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) + // Gets the join command + joinCommand, err := cmdutil.GetJoinCommand(kubeadmconstants.GetAdminKubeConfigPath(), i.cfg.Token, i.skipTokenPrint) if err != nil { - return fmt.Errorf("error loading ca cert from disk: %v", err) - } - - // Generate the Master host/port pair used by initDoneTempl - masterHostPort, err := kubeadmutil.GetMasterHostPort(i.cfg) - if err != nil { - return fmt.Errorf("error getting master host port: %v", err) + return fmt.Errorf("failed to get join command: %v", err) } ctx := map[string]string{ "KubeConfigPath": adminKubeConfigPath, - "Token": i.cfg.Token, - "CAPubKeyPin": pubkeypin.Hash(caCert), - "MasterHostPort": masterHostPort, - } - if i.skipTokenPrint { - ctx["Token"] = "" + "joinCommand": joinCommand, } return initDoneTempl.Execute(out, ctx) diff --git a/cmd/kubeadm/app/cmd/phases/bootstraptoken.go b/cmd/kubeadm/app/cmd/phases/bootstraptoken.go index 02e602cc03..d4f6501716 100644 --- a/cmd/kubeadm/app/cmd/phases/bootstraptoken.go +++ b/cmd/kubeadm/app/cmd/phases/bootstraptoken.go @@ -34,11 +34,9 @@ import ( kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" - "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" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/util/normalizer" ) @@ -137,7 +135,7 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command { kubeadmutil.CheckErr(err) // Creates the bootstap token - err = createBootstrapToken(client, cfgPath, cfg, description, usages, extraGroups, skipTokenPrint) + err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, usages, extraGroups, skipTokenPrint) kubeadmutil.CheckErr(err) // Create the cluster-info ConfigMap or update if it already exists @@ -194,7 +192,7 @@ func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command { client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) kubeadmutil.CheckErr(err) - err = createBootstrapToken(client, cfgPath, cfg, description, usages, extraGroups, skipTokenPrint) + err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, usages, extraGroups, skipTokenPrint) kubeadmutil.CheckErr(err) }, } @@ -288,10 +286,6 @@ func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterCon cfgPath, "config", *cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)", ) - flagSet.StringVar( - &cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, - "The path where certificates are stored", - ) flagSet.StringVar( &cfg.Token, "token", cfg.Token, "The token to use for establishing bidirectional trust between nodes and masters", @@ -318,7 +312,7 @@ func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterCon ) } -func createBootstrapToken(client clientset.Interface, cfgPath string, cfg *kubeadmapiext.MasterConfiguration, description string, usages, extraGroups []string, skipTokenPrint bool) error { +func createBootstrapToken(kubeConfigFile string, client clientset.Interface, cfgPath string, cfg *kubeadmapiext.MasterConfiguration, description string, usages, extraGroups []string, skipTokenPrint bool) error { // adding groups only makes sense for authentication usagesSet := sets.NewString(usages...) usageAuthentication := strings.TrimPrefix(bootstrapapi.BootstrapTokenUsageAuthentication, bootstrapapi.BootstrapTokenUsagePrefix) @@ -337,23 +331,19 @@ func createBootstrapToken(client clientset.Interface, cfgPath string, cfg *kubea internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) kubeadmutil.CheckErr(err) - // Load the CA certificate from so we can pin its public key - caCert, err := pkiutil.TryLoadCertFromDisk(internalcfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) - if err != nil { - return fmt.Errorf("error loading ca cert from disk: %v", err) - } - // Creates or updates the token if err := node.UpdateOrCreateToken(client, internalcfg.Token, false, internalcfg.TokenTTL.Duration, usages, extraGroups, description); err != nil { return err } fmt.Println("[bootstraptoken] Bootstrap token Created") - if skipTokenPrint { - internalcfg.Token = "{token}" - } fmt.Println("[bootstraptoken] You can now join any number of machines by running:") - fmt.Printf("[bootstraptoken] kubeadm join {master} --token %s --discovery-token-ca-cert-hash %s \n", internalcfg.Token, pubkeypin.Hash(caCert)) + + joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.Token, skipTokenPrint) + if err != nil { + return fmt.Errorf("failed to get join command: %v", err) + } + fmt.Println(joinCommand) return nil } diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index c6e3ddaa02..bacf0a94fa 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -17,15 +17,12 @@ limitations under the License. package cmd import ( - "bytes" - "crypto/x509" "fmt" "io" "os" "sort" "strings" "text/tabwriter" - "text/template" "time" "github.com/renstrom/dedent" @@ -38,8 +35,6 @@ import ( clientset "k8s.io/client-go/kubernetes" bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" bootstraputil "k8s.io/client-go/tools/bootstrap/token/util" - "k8s.io/client-go/tools/clientcmd" - clientcertutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -47,16 +42,11 @@ import ( kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/printers" ) -var joinCommandTemplate = template.Must(template.New("join").Parse(`` + - `kubeadm join --token {{.Token}} {{.MasterHostPort}}{{range $h := .CAPubKeyPins}} --discovery-token-ca-cert-hash {{$h}}{{end}}`, -)) - // NewCmdToken returns cobra.Command for token management func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { var kubeConfigFile string @@ -247,7 +237,7 @@ func RunCreateToken(out io.Writer, client clientset.Interface, token string, tok // if --print-join-command was specified, print the full `kubeadm join` command // otherwise, just print the token if printJoinCommand { - joinCommand, err := getJoinCommand(token, kubeConfigFile) + joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, token, false) if err != nil { return fmt.Errorf("failed to get join command: %v", err) } @@ -395,52 +385,3 @@ func getClientset(file string, dryRun bool) (clientset.Interface, error) { } return kubeconfigutil.ClientSetFromFile(file) } - -func getJoinCommand(token string, kubeConfigFile string) (string, error) { - // load the kubeconfig file to get the CA certificate and endpoint - config, err := clientcmd.LoadFromFile(kubeConfigFile) - if err != nil { - return "", fmt.Errorf("failed to load kubeconfig: %v", err) - } - - // load the default cluster config - clusterConfig := kubeconfigutil.GetClusterFromKubeConfig(config) - if clusterConfig == nil { - return "", fmt.Errorf("failed to get default cluster config") - } - - // load CA certificates from the kubeconfig (either from PEM data or by file path) - var caCerts []*x509.Certificate - if clusterConfig.CertificateAuthorityData != nil { - caCerts, err = clientcertutil.ParseCertsPEM(clusterConfig.CertificateAuthorityData) - if err != nil { - return "", fmt.Errorf("failed to parse CA certificate from kubeconfig: %v", err) - } - } else if clusterConfig.CertificateAuthority != "" { - caCerts, err = clientcertutil.CertsFromFile(clusterConfig.CertificateAuthority) - if err != nil { - return "", fmt.Errorf("failed to load CA certificate referenced by kubeconfig: %v", err) - } - } else { - return "", fmt.Errorf("no CA certificates found in kubeconfig") - } - - // hash all the CA certs and include their public key pins as trusted values - publicKeyPins := make([]string, 0, len(caCerts)) - for _, caCert := range caCerts { - publicKeyPins = append(publicKeyPins, pubkeypin.Hash(caCert)) - } - - ctx := map[string]interface{}{ - "Token": token, - "CAPubKeyPins": publicKeyPins, - "MasterHostPort": strings.Replace(clusterConfig.Server, "https://", "", -1), - } - - var out bytes.Buffer - err = joinCommandTemplate.Execute(&out, ctx) - if err != nil { - return "", fmt.Errorf("failed to render join command template: %v", err) - } - return out.String(), nil -} diff --git a/cmd/kubeadm/app/cmd/util/join.go b/cmd/kubeadm/app/cmd/util/join.go new file mode 100644 index 0000000000..725dd4cd11 --- /dev/null +++ b/cmd/kubeadm/app/cmd/util/join.go @@ -0,0 +1,89 @@ +/* +Copyright 2018 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 util + +import ( + "bytes" + "crypto/x509" + "fmt" + "html/template" + "strings" + + "k8s.io/client-go/tools/clientcmd" + clientcertutil "k8s.io/client-go/util/cert" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" +) + +var joinCommandTemplate = template.Must(template.New("join").Parse(`` + + `kubeadm join {{.MasterHostPort}} --token {{.Token}}{{range $h := .CAPubKeyPins}} --discovery-token-ca-cert-hash {{$h}}{{end}}`, +)) + +// GetJoinCommand returns the kubeadm join command for a given token and +// and kubernetes cluster (the current cluster in the kubeconfig file) +func GetJoinCommand(kubeConfigFile string, token string, skipTokenPrint bool) (string, error) { + // load the kubeconfig file to get the CA certificate and endpoint + config, err := clientcmd.LoadFromFile(kubeConfigFile) + if err != nil { + return "", fmt.Errorf("failed to load kubeconfig: %v", err) + } + + // load the default cluster config + clusterConfig := kubeconfigutil.GetClusterFromKubeConfig(config) + if clusterConfig == nil { + return "", fmt.Errorf("failed to get default cluster config") + } + + // load CA certificates from the kubeconfig (either from PEM data or by file path) + var caCerts []*x509.Certificate + if clusterConfig.CertificateAuthorityData != nil { + caCerts, err = clientcertutil.ParseCertsPEM(clusterConfig.CertificateAuthorityData) + if err != nil { + return "", fmt.Errorf("failed to parse CA certificate from kubeconfig: %v", err) + } + } else if clusterConfig.CertificateAuthority != "" { + caCerts, err = clientcertutil.CertsFromFile(clusterConfig.CertificateAuthority) + if err != nil { + return "", fmt.Errorf("failed to load CA certificate referenced by kubeconfig: %v", err) + } + } else { + return "", fmt.Errorf("no CA certificates found in kubeconfig") + } + + // hash all the CA certs and include their public key pins as trusted values + publicKeyPins := make([]string, 0, len(caCerts)) + for _, caCert := range caCerts { + publicKeyPins = append(publicKeyPins, pubkeypin.Hash(caCert)) + } + + ctx := map[string]interface{}{ + "Token": token, + "CAPubKeyPins": publicKeyPins, + "MasterHostPort": strings.Replace(clusterConfig.Server, "https://", "", -1), + } + + if skipTokenPrint { + ctx["Token"] = template.HTML("") + } + + var out bytes.Buffer + err = joinCommandTemplate.Execute(&out, ctx) + if err != nil { + return "", fmt.Errorf("failed to render join command template: %v", err) + } + return out.String(), nil +}