refactor kubeadm join command generation

pull/6/head
fabriziopandini 2018-02-18 16:22:42 +01:00
parent 6775a3252d
commit c09d875c6c
4 changed files with 105 additions and 97 deletions

View File

@ -38,6 +38,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" "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" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features" "k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/kubernetes/cmd/kubeadm/app/images" "k8s.io/kubernetes/cmd/kubeadm/app/images"
@ -46,7 +47,6 @@ import (
clusterinfophase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" clusterinfophase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo"
nodebootstraptokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" nodebootstraptokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" 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" controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
@ -61,7 +61,6 @@ import (
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" 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/api/legacyscheme"
utilsexec "k8s.io/utils/exec" 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 You can now join any number of machines by running the following on each node
as root: 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 return nil
} }
// Load the CA certificate from so we can pin its public key // Gets the join command
caCert, err := pkiutil.TryLoadCertFromDisk(i.cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) joinCommand, err := cmdutil.GetJoinCommand(kubeadmconstants.GetAdminKubeConfigPath(), i.cfg.Token, i.skipTokenPrint)
if err != nil { if err != nil {
return fmt.Errorf("error loading ca cert from disk: %v", err) return fmt.Errorf("failed to get join command: %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)
} }
ctx := map[string]string{ ctx := map[string]string{
"KubeConfigPath": adminKubeConfigPath, "KubeConfigPath": adminKubeConfigPath,
"Token": i.cfg.Token, "joinCommand": joinCommand,
"CAPubKeyPin": pubkeypin.Hash(caCert),
"MasterHostPort": masterHostPort,
}
if i.skipTokenPrint {
ctx["Token"] = "<value withheld>"
} }
return initDoneTempl.Execute(out, ctx) return initDoneTempl.Execute(out, ctx)

View File

@ -34,11 +34,9 @@ import (
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" 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/clusterinfo"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" "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" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" 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/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/normalizer" "k8s.io/kubernetes/pkg/util/normalizer"
) )
@ -137,7 +135,7 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command {
kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(err)
// Creates the bootstap token // 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) kubeadmutil.CheckErr(err)
// Create the cluster-info ConfigMap or update if it already exists // 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) client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
kubeadmutil.CheckErr(err) 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) kubeadmutil.CheckErr(err)
}, },
} }
@ -288,10 +286,6 @@ func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterCon
cfgPath, "config", *cfgPath, cfgPath, "config", *cfgPath,
"Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)", "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( flagSet.StringVar(
&cfg.Token, "token", cfg.Token, &cfg.Token, "token", cfg.Token,
"The token to use for establishing bidirectional trust between nodes and masters", "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 // adding groups only makes sense for authentication
usagesSet := sets.NewString(usages...) usagesSet := sets.NewString(usages...)
usageAuthentication := strings.TrimPrefix(bootstrapapi.BootstrapTokenUsageAuthentication, bootstrapapi.BootstrapTokenUsagePrefix) 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) internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
kubeadmutil.CheckErr(err) 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 // Creates or updates the token
if err := node.UpdateOrCreateToken(client, internalcfg.Token, false, internalcfg.TokenTTL.Duration, usages, extraGroups, description); err != nil { if err := node.UpdateOrCreateToken(client, internalcfg.Token, false, internalcfg.TokenTTL.Duration, usages, extraGroups, description); err != nil {
return err return err
} }
fmt.Println("[bootstraptoken] Bootstrap token Created") 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.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 return nil
} }

View File

@ -17,15 +17,12 @@ limitations under the License.
package cmd package cmd
import ( import (
"bytes"
"crypto/x509"
"fmt" "fmt"
"io" "io"
"os" "os"
"sort" "sort"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"text/template"
"time" "time"
"github.com/renstrom/dedent" "github.com/renstrom/dedent"
@ -38,8 +35,6 @@ import (
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util" 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" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -47,16 +42,11 @@ import (
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" 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" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/printers" "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 // NewCmdToken returns cobra.Command for token management
func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
var kubeConfigFile string 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 // if --print-join-command was specified, print the full `kubeadm join` command
// otherwise, just print the token // otherwise, just print the token
if printJoinCommand { if printJoinCommand {
joinCommand, err := getJoinCommand(token, kubeConfigFile) joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, token, false)
if err != nil { if err != nil {
return fmt.Errorf("failed to get join command: %v", err) 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) 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
}

View File

@ -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("<value withheld>")
}
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
}