kubeadm: add --print-join-command flag for token create.

This change adds a new flag `kubeadm token create --print-join-command`. When this flag is passed, kubeadm prints the full `kubeadm join [...]` command, including the CA certificate hash which is otherwise annoying to calculate.

Example:
```
$ kubeadm token create --print-join-command
kubeadm join --token 447067.20b55955bd6abe6c 192.168.99.100:8443 --discovery-token-ca-cert-hash sha256:17023a5c90b996e50c514e63e161e46f78be216fd48c0c3df3be67e008b28889
```
This commit is contained in:
Matt Moyer 2017-11-21 21:50:20 -06:00
parent 277d866111
commit b4b275d255
2 changed files with 77 additions and 3 deletions

View File

@ -74,6 +74,7 @@ go_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",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],

View File

@ -17,12 +17,15 @@ limitations under the License.
package cmd
import (
"bytes"
"crypto/x509"
"fmt"
"io"
"os"
"sort"
"strings"
"text/tabwriter"
"text/template"
"time"
"github.com/renstrom/dedent"
@ -33,6 +36,8 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes"
"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"
@ -40,12 +45,17 @@ 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"
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
"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
@ -89,6 +99,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
var extraGroups []string
var tokenDuration time.Duration
var description string
var printJoinCommand bool
createCmd := &cobra.Command{
Use: "create [token]",
Short: "Create bootstrap tokens on the server.",
@ -108,7 +119,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
client, err := getClientset(kubeConfigFile, dryRun)
kubeadmutil.CheckErr(err)
err = RunCreateToken(out, client, token, tokenDuration, usages, extraGroups, description)
err = RunCreateToken(out, client, token, tokenDuration, usages, extraGroups, description, printJoinCommand, kubeConfigFile)
kubeadmutil.CheckErr(err)
},
}
@ -121,6 +132,8 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q.", bootstrapapi.BootstrapGroupPattern))
createCmd.Flags().StringVar(&description,
"description", "", "A human friendly description of how this token is used.")
createCmd.Flags().BoolVar(&printJoinCommand,
"print-join-command", false, "Instead of printing only the token, print the full 'kubeadm join' flag needed to join the cluster using the token.")
tokenCmd.AddCommand(createCmd)
tokenCmd.AddCommand(NewCmdTokenGenerate(out))
@ -190,7 +203,7 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command {
}
// RunCreateToken generates a new bootstrap token and stores it as a secret on the server.
func RunCreateToken(out io.Writer, client clientset.Interface, token string, tokenDuration time.Duration, usages []string, extraGroups []string, description string) error {
func RunCreateToken(out io.Writer, client clientset.Interface, token string, tokenDuration time.Duration, usages []string, extraGroups []string, description string, printJoinCommand bool, kubeConfigFile string) error {
if len(token) == 0 {
var err error
token, err = tokenutil.GenerateToken()
@ -228,7 +241,18 @@ func RunCreateToken(out io.Writer, client clientset.Interface, token string, tok
return err
}
fmt.Fprintln(out, token)
// if --print-join-command was specified, print the full `kubeadm join` command
// otherwise, just print the token
if printJoinCommand {
joinCommand, err := getJoinCommand(token, kubeConfigFile)
if err != nil {
return fmt.Errorf("failed to get join command: %v", err)
}
fmt.Fprintln(out, joinCommand)
} else {
fmt.Fprintln(out, token)
}
return nil
}
@ -369,3 +393,52 @@ func getClientset(file string, dryRun bool) (clientset.Interface, error) {
client, err := kubeconfigutil.ClientSetFromFile(file)
return client, err
}
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
}