Merge pull request #36462 from mikedanese/discovery

Automatic merge from submit-queue

kubeadm: refactor discovery behind an interface

This adds support for alternative discovery methods using discovery urls. It is a breaking change. This is a WIP.

Example usage:
```
$ kubeadm init --discovery token://
$ kubeadm join --discovery token://c05de9:ab224260fb3cd718@192.168.0.1:6555,191.168.0.2:6443
$ kubeadm join --discovery file:///etc/kubernetes/cluster.json
$ kubeadm join --discovery https://storage.google.apis.com/kube-discovery/98ea6e4/kubeconfig.json
```

@kubernetes/sig-cluster-lifecycle
pull/6/head
Kubernetes Submit Queue 2016-12-15 15:35:26 -08:00 committed by GitHub
commit 8d71970dcd
40 changed files with 807 additions and 506 deletions

View File

@ -33,7 +33,6 @@ type EnvParams struct {
type MasterConfiguration struct { type MasterConfiguration struct {
metav1.TypeMeta metav1.TypeMeta
Secrets Secrets
API API API API
Discovery Discovery Discovery Discovery
Etcd Etcd Etcd Etcd
@ -45,11 +44,27 @@ type MasterConfiguration struct {
type API struct { type API struct {
AdvertiseAddresses []string AdvertiseAddresses []string
ExternalDNSNames []string ExternalDNSNames []string
BindPort int32 Port int32
} }
type Discovery struct { type Discovery struct {
BindPort int32 HTTPS *HTTPSDiscovery
File *FileDiscovery
Token *TokenDiscovery
}
type HTTPSDiscovery struct {
URL string
}
type FileDiscovery struct {
Path string
}
type TokenDiscovery struct {
ID string
Secret string
Addresses []string
} }
type Networking struct { type Networking struct {
@ -65,20 +80,10 @@ type Etcd struct {
KeyFile string KeyFile string
} }
type Secrets struct {
GivenToken string // dot-separated `<TokenID>.<Token>` set by the user
TokenID string // optional on master side, will be generated if not specified
Token []byte // optional on master side, will be generated if not specified
BearerToken string // set based on Token
}
type NodeConfiguration struct { type NodeConfiguration struct {
metav1.TypeMeta metav1.TypeMeta
MasterAddresses []string Discovery Discovery
Secrets Secrets
APIPort int32
DiscoveryPort int32
} }
// ClusterInfo TODO add description // ClusterInfo TODO add description

View File

@ -33,7 +33,6 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error {
RegisterDefaults(scheme) RegisterDefaults(scheme)
return scheme.AddDefaultingFuncs( return scheme.AddDefaultingFuncs(
SetDefaults_MasterConfiguration, SetDefaults_MasterConfiguration,
SetDefaults_NodeConfiguration,
) )
} }
@ -42,12 +41,8 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
obj.KubernetesVersion = DefaultKubernetesVersion obj.KubernetesVersion = DefaultKubernetesVersion
} }
if obj.API.BindPort == 0 { if obj.API.Port == 0 {
obj.API.BindPort = DefaultAPIBindPort obj.API.Port = DefaultAPIBindPort
}
if obj.Discovery.BindPort == 0 {
obj.Discovery.BindPort = DefaultDiscoveryBindPort
} }
if obj.Networking.ServiceSubnet == "" { if obj.Networking.ServiceSubnet == "" {
@ -58,13 +53,3 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
obj.Networking.DNSDomain = DefaultServiceDNSDomain obj.Networking.DNSDomain = DefaultServiceDNSDomain
} }
} }
func SetDefaults_NodeConfiguration(obj *NodeConfiguration) {
if obj.APIPort == 0 {
obj.APIPort = DefaultAPIBindPort
}
if obj.DiscoveryPort == 0 {
obj.DiscoveryPort = DefaultDiscoveryBindPort
}
}

View File

@ -23,10 +23,9 @@ import (
type MasterConfiguration struct { type MasterConfiguration struct {
metav1.TypeMeta `json:",inline"` metav1.TypeMeta `json:",inline"`
Secrets Secrets `json:"secrets"`
API API `json:"api"` API API `json:"api"`
Etcd Etcd `json:"etcd"`
Discovery Discovery `json:"discovery"` Discovery Discovery `json:"discovery"`
Etcd Etcd `json:"etcd"`
Networking Networking `json:"networking"` Networking Networking `json:"networking"`
KubernetesVersion string `json:"kubernetesVersion"` KubernetesVersion string `json:"kubernetesVersion"`
CloudProvider string `json:"cloudProvider"` CloudProvider string `json:"cloudProvider"`
@ -35,11 +34,27 @@ type MasterConfiguration struct {
type API struct { type API struct {
AdvertiseAddresses []string `json:"advertiseAddresses"` AdvertiseAddresses []string `json:"advertiseAddresses"`
ExternalDNSNames []string `json:"externalDNSNames"` ExternalDNSNames []string `json:"externalDNSNames"`
BindPort int32 `json:"bindPort"` Port int32 `json:"port"`
} }
type Discovery struct { type Discovery struct {
BindPort int32 `json:"bindPort"` HTTPS *HTTPSDiscovery `json:"https"`
File *FileDiscovery `json:"file"`
Token *TokenDiscovery `json:"token"`
}
type HTTPSDiscovery struct {
URL string `json:"url"`
}
type FileDiscovery struct {
Path string `json:"path"`
}
type TokenDiscovery struct {
ID string `json:"id"`
Secret string `json:"secret"`
Addresses []string `json:"addresses"`
} }
type Networking struct { type Networking struct {
@ -65,10 +80,7 @@ type Secrets struct {
type NodeConfiguration struct { type NodeConfiguration struct {
metav1.TypeMeta `json:",inline"` metav1.TypeMeta `json:",inline"`
MasterAddresses []string `json:"masterAddresses"` Discovery Discovery `json:"discovery"`
Secrets Secrets `json:"secrets"`
APIPort int32 `json:"apiPort"`
DiscoveryPort int32 `json:"discoveryPort"`
} }
// ClusterInfo TODO add description // ClusterInfo TODO add description

View File

@ -29,14 +29,9 @@ import (
// All generated defaulters are covering - they call all nested defaulters. // All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error { func RegisterDefaults(scheme *runtime.Scheme) error {
scheme.AddTypeDefaultingFunc(&MasterConfiguration{}, func(obj interface{}) { SetObjectDefaults_MasterConfiguration(obj.(*MasterConfiguration)) }) scheme.AddTypeDefaultingFunc(&MasterConfiguration{}, func(obj interface{}) { SetObjectDefaults_MasterConfiguration(obj.(*MasterConfiguration)) })
scheme.AddTypeDefaultingFunc(&NodeConfiguration{}, func(obj interface{}) { SetObjectDefaults_NodeConfiguration(obj.(*NodeConfiguration)) })
return nil return nil
} }
func SetObjectDefaults_MasterConfiguration(in *MasterConfiguration) { func SetObjectDefaults_MasterConfiguration(in *MasterConfiguration) {
SetDefaults_MasterConfiguration(in) SetDefaults_MasterConfiguration(in)
} }
func SetObjectDefaults_NodeConfiguration(in *NodeConfiguration) {
SetDefaults_NodeConfiguration(in)
}

View File

@ -0,0 +1,18 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["validation.go"],
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//pkg/util/validation/field:go_default_library",
],
)

View File

@ -0,0 +1,70 @@
/*
Copyright 2016 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 validation
import (
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/pkg/util/validation/field"
)
func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateDiscovery(&c.Discovery, field.NewPath("discovery"))...)
return allErrs
}
func ValidateNodeConfiguration(c *kubeadm.NodeConfiguration) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateDiscovery(&c.Discovery, field.NewPath("discovery"))...)
return allErrs
}
func ValidateDiscovery(c *kubeadm.Discovery, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
var count int
if c.Token != nil {
allErrs = append(allErrs, ValidateTokenDiscovery(c.Token, fldPath)...)
count++
}
if c.File != nil {
allErrs = append(allErrs, ValidateFileDiscovery(c.File, fldPath)...)
count++
}
if c.HTTPS != nil {
allErrs = append(allErrs, ValidateHTTPSDiscovery(c.HTTPS, fldPath)...)
count++
}
if count != 1 {
allErrs = append(allErrs, field.Invalid(fldPath, nil, "exactly one discovery strategy can be provided"))
}
return allErrs
}
func ValidateFileDiscovery(c *kubeadm.FileDiscovery, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
return allErrs
}
func ValidateHTTPSDiscovery(c *kubeadm.HTTPSDiscovery, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
return allErrs
}
func ValidateTokenDiscovery(c *kubeadm.TokenDiscovery, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
return allErrs
}

View File

@ -22,12 +22,15 @@ go_library(
deps = [ deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/cmd/flags:go_default_library", "//cmd/kubeadm/app/cmd/flags:go_default_library",
"//cmd/kubeadm/app/discovery:go_default_library",
"//cmd/kubeadm/app/master:go_default_library", "//cmd/kubeadm/app/master:go_default_library",
"//cmd/kubeadm/app/node:go_default_library", "//cmd/kubeadm/app/node:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library",
"//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util:go_default_library",
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/client/unversioned/clientcmd/api:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util:go_default_library",
"//pkg/runtime:go_default_library", "//pkg/runtime:go_default_library",
"//pkg/util/flag:go_default_library", "//pkg/util/flag:go_default_library",

View File

@ -17,9 +17,7 @@ limitations under the License.
package cmd package cmd
import ( import (
"bytes"
"fmt" "fmt"
"html/template"
"io" "io"
"io/ioutil" "io/ioutil"
@ -28,7 +26,9 @@ 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/cmd/flags" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/flags"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery"
kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master" kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight" "k8s.io/kubernetes/cmd/kubeadm/app/preflight"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
@ -37,18 +37,6 @@ import (
netutil "k8s.io/kubernetes/pkg/util/net" netutil "k8s.io/kubernetes/pkg/util/net"
) )
const (
joinArgsTemplateLiteral = `--token={{.Cfg.Secrets.GivenToken -}}
{{if ne .Cfg.API.BindPort .DefaultAPIBindPort -}}
{{" --api-port="}}{{.Cfg.API.BindPort -}}
{{end -}}
{{if ne .Cfg.Discovery.BindPort .DefaultDiscoveryBindPort -}}
{{" --discovery-port="}}{{.Cfg.Discovery.BindPort -}}
{{end -}}
{{" "}}{{index .Cfg.API.AdvertiseAddresses 0 -}}
`
)
var ( var (
initDoneMsgf = dedent.Dedent(` initDoneMsgf = dedent.Dedent(`
Your Kubernetes master has initialized successfully! Your Kubernetes master has initialized successfully!
@ -59,7 +47,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:
kubeadm join %s kubeadm join --discovery %s
`) `)
) )
@ -78,14 +66,11 @@ func NewCmdInit(out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
i, err := NewInit(cfgPath, &cfg, skipPreFlight) i, err := NewInit(cfgPath, &cfg, skipPreFlight)
kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(i.Validate())
kubeadmutil.CheckErr(i.Run(out)) kubeadmutil.CheckErr(i.Run(out))
}, },
} }
cmd.PersistentFlags().StringVar(
&cfg.Secrets.GivenToken, "token", cfg.Secrets.GivenToken,
"Shared secret used to secure cluster bootstrap; if none is provided, one will be generated for you",
)
cmd.PersistentFlags().StringSliceVar( cmd.PersistentFlags().StringSliceVar(
&cfg.API.AdvertiseAddresses, "api-advertise-addresses", cfg.API.AdvertiseAddresses, &cfg.API.AdvertiseAddresses, "api-advertise-addresses", cfg.API.AdvertiseAddresses,
"The IP addresses to advertise, in case autodetection fails", "The IP addresses to advertise, in case autodetection fails",
@ -148,14 +133,9 @@ func NewCmdInit(out io.Writer) *cobra.Command {
"skip preflight checks normally run before modifying the system", "skip preflight checks normally run before modifying the system",
) )
cmd.PersistentFlags().Int32Var( cmd.PersistentFlags().Var(
&cfg.API.BindPort, "api-port", cfg.API.BindPort, discovery.NewDiscoveryValue(&cfg.Discovery), "discovery",
"Port for API to bind to", "The discovery method kubeadm will use for connecting nodes to the master",
)
cmd.PersistentFlags().Int32Var(
&cfg.Discovery.BindPort, "discovery-port", cfg.Discovery.BindPort,
"Port for JWS discovery service to bind to",
) )
return cmd return cmd
@ -228,18 +208,21 @@ func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight
return &Init{cfg: cfg}, nil return &Init{cfg: cfg}, nil
} }
// joinArgsData denotes a data object which is needed by function generateJoinArgs to generate kubeadm join arguments. func (i *Init) Validate() error {
type joinArgsData struct { return validation.ValidateMasterConfiguration(i.cfg).ToAggregate()
Cfg *kubeadmapi.MasterConfiguration
DefaultAPIBindPort int32
DefaultDiscoveryBindPort int32
} }
// Run executes master node provisioning, including certificates, needed static pod manifests, etc. // Run executes master node provisioning, including certificates, needed static pod manifests, etc.
func (i *Init) Run(out io.Writer) error { func (i *Init) Run(out io.Writer) error {
if err := kubemaster.CreateTokenAuthFile(&i.cfg.Secrets); err != nil {
if i.cfg.Discovery.Token != nil {
if err := kubemaster.PrepareTokenDiscovery(i.cfg.Discovery.Token); err != nil {
return err return err
} }
if err := kubemaster.CreateTokenAuthFile(kubeadmutil.BearerToken(i.cfg.Discovery.Token)); err != nil {
return err
}
}
if err := kubemaster.WriteStaticPodManifests(i.cfg); err != nil { if err := kubemaster.WriteStaticPodManifests(i.cfg); err != nil {
return err return err
@ -275,34 +258,25 @@ func (i *Init) Run(out io.Writer) error {
return err return err
} }
schedulePodsOnMaster := false if err := kubemaster.UpdateMasterRoleLabelsAndTaints(client, false); err != nil {
if err := kubemaster.UpdateMasterRoleLabelsAndTaints(client, schedulePodsOnMaster); err != nil {
return err return err
} }
if i.cfg.Discovery.Token != nil {
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client, caCert); err != nil { if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client, caCert); err != nil {
return err return err
} }
}
if err := kubemaster.CreateEssentialAddons(i.cfg, client); err != nil { if err := kubemaster.CreateEssentialAddons(i.cfg, client); err != nil {
return err return err
} }
data := joinArgsData{i.cfg, kubeadmapiext.DefaultAPIBindPort, kubeadmapiext.DefaultDiscoveryBindPort} fmt.Fprintf(out, initDoneMsgf, generateJoinArgs(i.cfg))
if joinArgs, err := generateJoinArgs(data); err != nil {
return err
} else {
fmt.Fprintf(out, initDoneMsgf, joinArgs)
}
return nil return nil
} }
// generateJoinArgs generates kubeadm join arguments // generateJoinArgs generates kubeadm join arguments
func generateJoinArgs(data joinArgsData) (string, error) { func generateJoinArgs(cfg *kubeadmapi.MasterConfiguration) string {
joinArgsTemplate := template.Must(template.New("joinArgsTemplate").Parse(joinArgsTemplateLiteral)) return discovery.NewDiscoveryValue(&cfg.Discovery).String()
var b bytes.Buffer
if err := joinArgsTemplate.Execute(&b, data); err != nil {
return "", err
}
return b.String(), nil
} }

View File

@ -26,10 +26,13 @@ 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/discovery"
kubenode "k8s.io/kubernetes/cmd/kubeadm/app/node" kubenode "k8s.io/kubernetes/cmd/kubeadm/app/node"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight" "k8s.io/kubernetes/cmd/kubeadm/app/preflight"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
) )
@ -60,15 +63,11 @@ func NewCmdJoin(out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
j, err := NewJoin(cfgPath, args, &cfg, skipPreFlight) j, err := NewJoin(cfgPath, args, &cfg, skipPreFlight)
kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(j.Validate())
kubeadmutil.CheckErr(j.Run(out)) kubeadmutil.CheckErr(j.Run(out))
}, },
} }
cmd.PersistentFlags().StringVar(
&cfg.Secrets.GivenToken, "token", cfg.Secrets.GivenToken,
"(required) Shared secret used to secure bootstrap. Must match the output of 'kubeadm init'",
)
cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file") cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file")
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
@ -76,14 +75,9 @@ func NewCmdJoin(out io.Writer) *cobra.Command {
"skip preflight checks normally run before modifying the system", "skip preflight checks normally run before modifying the system",
) )
cmd.PersistentFlags().Int32Var( cmd.PersistentFlags().Var(
&cfg.APIPort, "api-port", cfg.APIPort, discovery.NewDiscoveryValue(&cfg.Discovery), "discovery",
"(optional) API server port on the master", "The discovery method kubeadm will use for connecting nodes to the master",
)
cmd.PersistentFlags().Int32Var(
&cfg.DiscoveryPort, "discovery-port", cfg.DiscoveryPort,
"(optional) Discovery port on the master",
) )
return cmd return cmd
@ -107,14 +101,6 @@ func NewJoin(cfgPath string, args []string, cfg *kubeadmapi.NodeConfiguration, s
} }
} }
if len(args) == 0 && len(cfg.MasterAddresses) == 0 {
return nil, fmt.Errorf("must specify master address (see --help)")
}
cfg.MasterAddresses = append(cfg.MasterAddresses, args...)
if len(cfg.MasterAddresses) > 1 {
return nil, fmt.Errorf("must not specify more than one master address (see --help)")
}
if !skipPreFlight { if !skipPreFlight {
fmt.Println("[preflight] Running pre-flight checks") fmt.Println("[preflight] Running pre-flight checks")
@ -134,40 +120,46 @@ func NewJoin(cfgPath string, args []string, cfg *kubeadmapi.NodeConfiguration, s
// Try to start the kubelet service in case it's inactive // Try to start the kubelet service in case it's inactive
preflight.TryStartKubelet() preflight.TryStartKubelet()
ok, err := kubeadmutil.UseGivenTokenIfValid(&cfg.Secrets)
if !ok {
if err != nil {
return nil, fmt.Errorf("%v (see --help)", err)
}
return nil, fmt.Errorf("Must specify --token (see --help)")
}
return &Join{cfg: cfg}, nil return &Join{cfg: cfg}, nil
} }
func (j *Join) Validate() error {
return validation.ValidateNodeConfiguration(j.cfg).ToAggregate()
}
// Run executes worked node provisioning and tries to join an existing cluster. // Run executes worked node provisioning and tries to join an existing cluster.
func (j *Join) Run(out io.Writer) error { func (j *Join) Run(out io.Writer) error {
clusterInfo, err := kubenode.RetrieveTrustedClusterInfo(j.cfg) clusterInfo, err := kubenode.RetrieveTrustedClusterInfo(j.cfg.Discovery.Token)
if err != nil { if err != nil {
return err return err
} }
connectionDetails, err := kubenode.EstablishMasterConnection(j.cfg, clusterInfo) var cfg *clientcmdapi.Config
// TODO: delete this first block when we move Token to the discovery interface
if j.cfg.Discovery.Token != nil {
connectionDetails, err := kubenode.EstablishMasterConnection(j.cfg.Discovery.Token, clusterInfo)
if err != nil { if err != nil {
return err return err
} }
err = kubenode.CheckForNodeNameDuplicates(connectionDetails) err = kubenode.CheckForNodeNameDuplicates(connectionDetails)
if err != nil { if err != nil {
return err return err
} }
cfg, err = kubenode.PerformTLSBootstrapDeprecated(connectionDetails)
kubeconfig, err := kubenode.PerformTLSBootstrap(connectionDetails)
if err != nil { if err != nil {
return err return err
} }
} else {
cfg, err = discovery.For(j.cfg.Discovery)
if err != nil {
return err
}
if err := kubenode.PerformTLSBootstrap(cfg); err != nil {
return err
}
}
err = kubeadmutil.WriteKubeconfigIfNotExists("kubelet", kubeconfig) err = kubeadmutil.WriteKubeconfigIfNotExists("kubelet", cfg)
if err != nil { if err != nil {
return err return err
} }

View File

@ -75,12 +75,12 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command {
} }
func RunGenerateToken(out io.Writer) error { func RunGenerateToken(out io.Writer) error {
s := &kubeadmapi.Secrets{} d := &kubeadmapi.TokenDiscovery{}
err := util.GenerateToken(s) err := util.GenerateToken(d)
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintln(out, s.GivenToken) fmt.Fprintln(out, util.BearerToken(d))
return nil return nil
} }

View File

@ -0,0 +1,37 @@
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 = [
"discovery.go",
"flags.go",
],
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/discovery/file:go_default_library",
"//cmd/kubeadm/app/discovery/https:go_default_library",
"//cmd/kubeadm/app/discovery/token:go_default_library",
"//pkg/client/unversioned/clientcmd/api:go_default_library",
"//vendor:github.com/spf13/pflag",
],
)
go_test(
name = "go_default_test",
srcs = ["flags_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//vendor:github.com/davecgh/go-spew/spew",
],
)

View File

@ -0,0 +1,31 @@
/*
Copyright 2016 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 discovery
import (
"fmt"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
func For(c kubeadmapi.Discovery) (*clientcmdapi.Config, error) {
switch {
default:
return nil, fmt.Errorf("unimplemented")
}
}

View File

@ -0,0 +1,15 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["file.go"],
tags = ["automanaged"],
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
)

View File

@ -0,0 +1,30 @@
/*
Copyright 2016 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 file
import (
"net/url"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func Parse(u *url.URL, c *kubeadm.Discovery) error {
c.File = &kubeadm.FileDiscovery{
Path: u.Path,
}
return nil
}

View File

@ -0,0 +1,83 @@
/*
Copyright 2016 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 discovery
import (
"fmt"
"net/url"
"strings"
"github.com/spf13/pflag"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/file"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/https"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/token"
)
type discoveryValue struct {
v *kubeadm.Discovery
}
func NewDiscoveryValue(d *kubeadm.Discovery) pflag.Value {
return &discoveryValue{
v: d,
}
}
func (d *discoveryValue) String() string {
switch {
case d.v.HTTPS != nil:
return d.v.HTTPS.URL
case d.v.File != nil:
return "file://" + d.v.File.Path
case d.v.Token != nil:
return fmt.Sprintf("token://%s:%s@%s", d.v.Token.ID, d.v.Token.Secret, strings.Join(d.v.Token.Addresses, ","))
default:
return "unknown"
}
}
func (d *discoveryValue) Set(s string) error {
var kd kubeadm.Discovery
if err := ParseURL(&kd, s); err != nil {
return err
}
*d.v = kd
return nil
}
func (d *discoveryValue) Type() string {
return "discovery"
}
func ParseURL(d *kubeadm.Discovery, s string) error {
u, err := url.Parse(s)
if err != nil {
return err
}
switch u.Scheme {
case "https":
return https.Parse(u, d)
case "file":
return file.Parse(u, d)
case "token":
return token.Parse(u, d)
default:
return fmt.Errorf("unknown discovery scheme")
}
}

View File

@ -0,0 +1,83 @@
/*
Copyright 2016 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 discovery
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func TestParseURL(t *testing.T) {
cases := []struct {
url string
expect kubeadm.Discovery
expectErr bool
}{
{
url: "token://",
expect: kubeadm.Discovery{
Token: &kubeadm.TokenDiscovery{},
},
},
{
url: "token://c05de9:ab224260fb3cd718@192.168.0.1:6555,191.168.0.2:6443",
expect: kubeadm.Discovery{
Token: &kubeadm.TokenDiscovery{
ID: "c05de9",
Secret: "ab224260fb3cd718",
Addresses: []string{
"192.168.0.1:6555",
"191.168.0.2:6443",
},
},
},
},
{
url: "file:///foo/bar/baz",
expect: kubeadm.Discovery{
File: &kubeadm.FileDiscovery{
Path: "/foo/bar/baz",
},
},
},
{
url: "https://storage.googleapis.com/kubeadm-disco/clusters/217651295213",
expect: kubeadm.Discovery{
HTTPS: &kubeadm.HTTPSDiscovery{
URL: "https://storage.googleapis.com/kubeadm-disco/clusters/217651295213",
},
},
},
}
for _, c := range cases {
var d kubeadm.Discovery
if err := ParseURL(&d, c.url); err != nil {
if !c.expectErr {
t.Errorf("unexpected error parsing discovery url: %v", err)
}
continue
}
if !reflect.DeepEqual(d, c.expect) {
t.Errorf("expected discovery config to be equeal but got:\n\ta: %s\n\tb: %s", spew.Sdump(d), spew.Sdump(c.expect))
}
}
}

View File

@ -0,0 +1,15 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["https.go"],
tags = ["automanaged"],
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
)

View File

@ -0,0 +1,30 @@
/*
Copyright 2016 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 https
import (
"net/url"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func Parse(u *url.URL, c *kubeadm.Discovery) error {
c.HTTPS = &kubeadm.HTTPSDiscovery{
URL: u.String(),
}
return nil
}

View File

@ -0,0 +1,15 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["token.go"],
tags = ["automanaged"],
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
)

View File

@ -0,0 +1,46 @@
/*
Copyright 2016 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 token
import (
"net/url"
"strings"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func Parse(u *url.URL, c *kubeadm.Discovery) error {
var (
hosts []string
tokenID, token string
)
if u.Host != "" {
hosts = strings.Split(u.Host, ",")
}
if u.User != nil {
if p, ok := u.User.Password(); ok {
tokenID = u.User.Username()
token = p
}
}
c.Token = &kubeadm.TokenDiscovery{
ID: tokenID,
Secret: token,
Addresses: hosts,
}
return nil
}

View File

@ -38,6 +38,7 @@ go_library(
"//pkg/registry/core/service/ipallocator:go_default_library", "//pkg/registry/core/service/ipallocator:go_default_library",
"//pkg/util/cert:go_default_library", "//pkg/util/cert:go_default_library",
"//pkg/util/intstr:go_default_library", "//pkg/util/intstr:go_default_library",
"//pkg/util/net:go_default_library",
"//pkg/util/uuid:go_default_library", "//pkg/util/uuid:go_default_library",
"//pkg/util/wait:go_default_library", "//pkg/util/wait:go_default_library",
"//vendor:github.com/blang/semver", "//vendor:github.com/blang/semver",

View File

@ -24,6 +24,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"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1"
extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" extensions "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
@ -43,18 +44,18 @@ const (
kubeDiscoverySecretName = "clusterinfo" kubeDiscoverySecretName = "clusterinfo"
) )
func encodeKubeDiscoverySecretData(cfg *kubeadmapi.MasterConfiguration, caCert *x509.Certificate) map[string][]byte { func encodeKubeDiscoverySecretData(dcfg *kubeadmapi.TokenDiscovery, apicfg kubeadmapi.API, caCert *x509.Certificate) map[string][]byte {
var ( var (
data = map[string][]byte{} data = map[string][]byte{}
endpointList = []string{} endpointList = []string{}
tokenMap = map[string]string{} tokenMap = map[string]string{}
) )
for _, addr := range cfg.API.AdvertiseAddresses { for _, addr := range apicfg.AdvertiseAddresses {
endpointList = append(endpointList, fmt.Sprintf("https://%s:%d", addr, cfg.API.BindPort)) endpointList = append(endpointList, fmt.Sprintf("https://%s:%d", addr, apicfg.Port))
} }
tokenMap[cfg.Secrets.TokenID] = cfg.Secrets.BearerToken tokenMap[dcfg.ID] = dcfg.Secret
data["endpoint-list.json"], _ = json.Marshal(endpointList) data["endpoint-list.json"], _ = json.Marshal(endpointList)
data["token-map.json"], _ = json.Marshal(tokenMap) data["token-map.json"], _ = json.Marshal(tokenMap)
@ -83,7 +84,7 @@ func newKubeDiscoveryPodSpec(cfg *kubeadmapi.MasterConfiguration) v1.PodSpec {
Ports: []v1.ContainerPort{ Ports: []v1.ContainerPort{
// TODO when CNI issue (#31307) is resolved, we should consider adding // TODO when CNI issue (#31307) is resolved, we should consider adding
// `HostIP: s.API.AdvertiseAddrs[0]`, if there is only one address` // `HostIP: s.API.AdvertiseAddrs[0]`, if there is only one address`
{Name: "http", ContainerPort: kubeadmapiext.DefaultDiscoveryBindPort, HostPort: cfg.Discovery.BindPort}, {Name: "http", ContainerPort: kubeadmapiext.DefaultDiscoveryBindPort, HostPort: kubeadmutil.DiscoveryPort(cfg.Discovery.Token)},
}, },
SecurityContext: &v1.SecurityContext{ SecurityContext: &v1.SecurityContext{
SELinuxOptions: &v1.SELinuxOptions{ SELinuxOptions: &v1.SELinuxOptions{
@ -110,7 +111,7 @@ func newKubeDiscovery(cfg *kubeadmapi.MasterConfiguration, caCert *x509.Certific
Secret: &v1.Secret{ Secret: &v1.Secret{
ObjectMeta: v1.ObjectMeta{Name: kubeDiscoverySecretName}, ObjectMeta: v1.ObjectMeta{Name: kubeDiscoverySecretName},
Type: v1.SecretTypeOpaque, Type: v1.SecretTypeOpaque,
Data: encodeKubeDiscoverySecretData(cfg, caCert), Data: encodeKubeDiscoverySecretData(cfg.Discovery.Token, cfg.API, caCert),
}, },
} }

View File

@ -23,62 +23,6 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
) )
func TestEncodeKubeDiscoverySecretData(t *testing.T) {
var tests = []struct {
cfg *kubeadmapi.MasterConfiguration
expected bool
}{
{
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{BindPort: 123, AdvertiseAddresses: []string{"10.0.0.1"}},
Networking: kubeadmapi.Networking{ServiceSubnet: "10.0.0.1/1"},
},
expected: true,
},
}
for _, rt := range tests {
caCert := &x509.Certificate{}
actual := encodeKubeDiscoverySecretData(rt.cfg, caCert)
if (actual != nil) != rt.expected {
t.Errorf(
"failed encodeKubeDiscoverySecretData, return map[string][]byte was nil",
)
}
}
}
func TestNewKubeDiscoveryPodSpec(t *testing.T) {
var tests = []struct {
cfg *kubeadmapi.MasterConfiguration
p int32
expected bool
}{
{
cfg: &kubeadmapi.MasterConfiguration{
Discovery: kubeadmapi.Discovery{BindPort: 123},
},
p: 123,
},
{
cfg: &kubeadmapi.MasterConfiguration{
Discovery: kubeadmapi.Discovery{BindPort: 456},
},
p: 456,
},
}
for _, rt := range tests {
actual := newKubeDiscoveryPodSpec(rt.cfg)
if actual.Containers[0].Ports[0].HostPort != rt.p {
t.Errorf(
"failed newKubeDiscoveryPodSpec:\n\texpected: %d\n\t actual: %d",
rt.p,
actual.Containers[0].Ports[0].HostPort,
)
}
}
}
func TestNewKubeDiscovery(t *testing.T) { func TestNewKubeDiscovery(t *testing.T) {
var tests = []struct { var tests = []struct {
cfg *kubeadmapi.MasterConfiguration cfg *kubeadmapi.MasterConfiguration
@ -87,8 +31,9 @@ func TestNewKubeDiscovery(t *testing.T) {
}{ }{
{ {
cfg: &kubeadmapi.MasterConfiguration{ cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{BindPort: 123, AdvertiseAddresses: []string{"10.0.0.1"}}, API: kubeadmapi.API{Port: 123, AdvertiseAddresses: []string{"10.0.0.1"}},
Networking: kubeadmapi.Networking{ServiceSubnet: "10.0.0.1/1"}, Networking: kubeadmapi.Networking{ServiceSubnet: "10.0.0.1/1"},
Discovery: kubeadmapi.Discovery{Token: &kubeadmapi.TokenDiscovery{}},
}, },
caCert: &x509.Certificate{}, caCert: &x509.Certificate{},
}, },

View File

@ -34,7 +34,7 @@ func CreateCertsAndConfigForClients(cfg kubeadmapi.API, clientNames []string, ca
// TODO this is not great, but there is only one address we can use here // TODO this is not great, but there is only one address we can use here
// so we'll pick the first one, there is much of chance to have an empty // so we'll pick the first one, there is much of chance to have an empty
// slice by the time this gets called // slice by the time this gets called
fmt.Sprintf("https://%s:%d", cfg.AdvertiseAddresses[0], cfg.BindPort), fmt.Sprintf("https://%s:%d", cfg.AdvertiseAddresses[0], cfg.Port),
certutil.EncodeCertPEM(caCert), certutil.EncodeCertPEM(caCert),
) )

View File

@ -292,7 +292,7 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration) []string {
"--tls-cert-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/apiserver.pem", "--tls-cert-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/apiserver.pem",
"--tls-private-key-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/apiserver-key.pem", "--tls-private-key-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/apiserver-key.pem",
"--token-auth-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/tokens.csv", "--token-auth-file="+kubeadmapi.GlobalEnvParams.HostPKIPath+"/tokens.csv",
fmt.Sprintf("--secure-port=%d", cfg.API.BindPort), fmt.Sprintf("--secure-port=%d", cfg.API.Port),
"--allow-privileged", "--allow-privileged",
) )

View File

@ -364,7 +364,7 @@ func TestGetAPIServerCommand(t *testing.T) {
}{ }{
{ {
cfg: &kubeadmapi.MasterConfiguration{ cfg: &kubeadmapi.MasterConfiguration{
API: kubeadm.API{BindPort: 123}, API: kubeadm.API{Port: 123},
Networking: kubeadm.Networking{ServiceSubnet: "bar"}, Networking: kubeadm.Networking{ServiceSubnet: "bar"},
}, },
expected: []string{ expected: []string{
@ -384,7 +384,7 @@ func TestGetAPIServerCommand(t *testing.T) {
}, },
{ {
cfg: &kubeadmapi.MasterConfiguration{ cfg: &kubeadmapi.MasterConfiguration{
API: kubeadm.API{BindPort: 123, AdvertiseAddresses: []string{"foo"}}, API: kubeadm.API{Port: 123, AdvertiseAddresses: []string{"foo"}},
Networking: kubeadm.Networking{ServiceSubnet: "bar"}, Networking: kubeadm.Networking{ServiceSubnet: "bar"},
}, },
expected: []string{ expected: []string{
@ -405,7 +405,7 @@ func TestGetAPIServerCommand(t *testing.T) {
}, },
{ {
cfg: &kubeadmapi.MasterConfiguration{ cfg: &kubeadmapi.MasterConfiguration{
API: kubeadm.API{BindPort: 123}, API: kubeadm.API{Port: 123},
Networking: kubeadm.Networking{ServiceSubnet: "bar"}, Networking: kubeadm.Networking{ServiceSubnet: "bar"},
Etcd: kubeadm.Etcd{CertFile: "fiz", KeyFile: "faz"}, Etcd: kubeadm.Etcd{CertFile: "fiz", KeyFile: "faz"},
}, },
@ -429,7 +429,7 @@ func TestGetAPIServerCommand(t *testing.T) {
// Make sure --kubelet-preferred-address-types // Make sure --kubelet-preferred-address-types
{ {
cfg: &kubeadmapi.MasterConfiguration{ cfg: &kubeadmapi.MasterConfiguration{
API: kubeadm.API{BindPort: 123, AdvertiseAddresses: []string{"foo"}}, API: kubeadm.API{Port: 123, AdvertiseAddresses: []string{"foo"}},
Networking: kubeadm.Networking{ServiceSubnet: "bar"}, Networking: kubeadm.Networking{ServiceSubnet: "bar"},
KubernetesVersion: "v1.5.3", KubernetesVersion: "v1.5.3",
}, },

View File

@ -21,41 +21,52 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"strconv"
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"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
netutil "k8s.io/kubernetes/pkg/util/net"
"k8s.io/kubernetes/pkg/util/uuid" "k8s.io/kubernetes/pkg/util/uuid"
) )
func generateTokenIfNeeded(s *kubeadmapi.Secrets) error { func generateTokenIfNeeded(d *kubeadmapi.TokenDiscovery) error {
ok, err := kubeadmutil.UseGivenTokenIfValid(s) ok, err := kubeadmutil.IsTokenValid(d)
// TODO(phase1+) @krousey: I know it won't happen with the way it is currently implemented, but this doesn't handle case where ok is true and err is non-nil.
if !ok {
if err != nil { if err != nil {
return err return err
} }
err = kubeadmutil.GenerateToken(s) if ok {
if err != nil {
return err
}
fmt.Printf("[tokens] Generated token: %q\n", s.GivenToken)
} else {
fmt.Println("[tokens] Accepted provided token") fmt.Println("[tokens] Accepted provided token")
return nil
} }
if err := kubeadmutil.GenerateToken(d); err != nil {
return err
}
fmt.Printf("[tokens] Generated token: %q\n", kubeadmutil.BearerToken(d))
return nil return nil
} }
func CreateTokenAuthFile(s *kubeadmapi.Secrets) error { func PrepareTokenDiscovery(d *kubeadmapi.TokenDiscovery) error {
tokenAuthFilePath := path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, "tokens.csv") if len(d.Addresses) == 0 {
if err := generateTokenIfNeeded(s); err != nil { ip, err := netutil.ChooseHostInterface()
if err != nil {
return err
}
d.Addresses = []string{ip.String() + ":" + strconv.Itoa(kubeadmapiext.DefaultDiscoveryBindPort)}
}
if err := generateTokenIfNeeded(d); err != nil {
return fmt.Errorf("failed to generate token(s) [%v]", err) return fmt.Errorf("failed to generate token(s) [%v]", err)
} }
return nil
}
func CreateTokenAuthFile(bt string) error {
tokenAuthFilePath := path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, "tokens.csv")
if err := os.MkdirAll(kubeadmapi.GlobalEnvParams.HostPKIPath, 0700); err != nil { if err := os.MkdirAll(kubeadmapi.GlobalEnvParams.HostPKIPath, 0700); err != nil {
return fmt.Errorf("failed to create directory %q [%v]", kubeadmapi.GlobalEnvParams.HostPKIPath, err) return fmt.Errorf("failed to create directory %q [%v]", kubeadmapi.GlobalEnvParams.HostPKIPath, err)
} }
serialized := []byte(fmt.Sprintf("%s,kubeadm-node-csr,%s,system:kubelet-bootstrap\n", s.BearerToken, uuid.NewUUID())) serialized := []byte(fmt.Sprintf("%s,kubeadm-node-csr,%s,system:kubelet-bootstrap\n", bt, uuid.NewUUID()))
// DumpReaderToFile create a file with mode 0600 // DumpReaderToFile create a file with mode 0600
if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), tokenAuthFilePath); err != nil { if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), tokenAuthFilePath); err != nil {
return fmt.Errorf("failed to save token auth file (%q) [%v]", tokenAuthFilePath, err) return fmt.Errorf("failed to save token auth file (%q) [%v]", tokenAuthFilePath, err)

View File

@ -17,67 +17,44 @@ limitations under the License.
package master package master
import ( import (
"fmt"
"io/ioutil"
"os"
"testing" "testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
) )
func TestGenerateTokenIfNeeded(t *testing.T) { func TestValidTokenPopulatesSecrets(t *testing.T) {
var tests = []struct { t.Run("provided", func(t *testing.T) {
s kubeadmapi.Secrets expectedID := "123456"
expected bool expectedSecret := "0123456789abcdef"
}{ s := &kubeadmapi.TokenDiscovery{
{kubeadmapi.Secrets{GivenToken: "noperiod"}, false}, // not 2-part '.' format ID: expectedID,
{kubeadmapi.Secrets{GivenToken: "abcd.a"}, false}, // len(tokenID) != 6 Secret: expectedSecret,
{kubeadmapi.Secrets{GivenToken: "abcdef.a"}, true},
{kubeadmapi.Secrets{GivenToken: ""}, true},
} }
for _, rt := range tests { err := generateTokenIfNeeded(s)
actual := generateTokenIfNeeded(&rt.s)
if (actual == nil) != rt.expected {
t.Errorf(
"failed UseGivenTokenIfValid:\n\texpected: %t\n\t actual: %t\n\t token:%s",
rt.expected,
(actual == nil),
rt.s.GivenToken,
)
}
}
}
func TestCreateTokenAuthFile(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil { if err != nil {
t.Fatalf("Couldn't create tmpdir") t.Errorf("generateTokenIfNeeded gave an error for a valid token: %v", err)
} }
defer os.Remove(tmpdir) if s.ID != expectedID {
t.Errorf("generateTokenIfNeeded did not populate the TokenID correctly; expected [%s] but got [%s]", expectedID, s.ID)
}
if s.Secret != expectedSecret {
t.Errorf("generateTokenIfNeeded did not populate the Token correctly; expected %v but got %v", expectedSecret, s.Secret)
}
})
// set up tmp GlobalEnvParams values for testing t.Run("not provided", func(t *testing.T) {
oldEnv := kubeadmapi.GlobalEnvParams s := &kubeadmapi.TokenDiscovery{}
kubeadmapi.GlobalEnvParams.HostPKIPath = fmt.Sprintf("%s/etc/kubernetes/pki", tmpdir)
defer func() { kubeadmapi.GlobalEnvParams = oldEnv }()
var tests = []struct { err := generateTokenIfNeeded(s)
s kubeadmapi.Secrets if err != nil {
expected bool t.Errorf("generateTokenIfNeeded gave an error for a valid token: %v", err)
}{
{kubeadmapi.Secrets{GivenToken: "noperiod"}, false}, // not 2-part '.' format
{kubeadmapi.Secrets{GivenToken: "abcd.a"}, false}, // len(tokenID) != 6
{kubeadmapi.Secrets{GivenToken: "abcdef.a"}, true},
{kubeadmapi.Secrets{GivenToken: ""}, true},
} }
for _, rt := range tests { if s.ID == "" {
actual := CreateTokenAuthFile(&rt.s) t.Errorf("generateTokenIfNeeded did not populate the TokenID correctly; expected ID to be non-empty")
if (actual == nil) != rt.expected {
t.Errorf(
"failed WriteKubeconfigIfNotExists with an error:\n\texpected: %t\n\t actual: %t",
rt.expected,
(actual == nil),
)
} }
if s.Secret == "" {
t.Errorf("generateTokenIfNeeded did not populate the Token correctly; expected Secret to be non-empty")
} }
})
} }

View File

@ -50,7 +50,7 @@ const retryTimeout = 5
// The function builds a client for every endpoint and concurrently keeps trying to connect to any one // The function builds a client for every endpoint and concurrently keeps trying to connect to any one
// of the provided endpoints. Blocks until at least one connection is established, then it stops the // of the provided endpoints. Blocks until at least one connection is established, then it stops the
// connection attempts for other endpoints. // connection attempts for other endpoints.
func EstablishMasterConnection(s *kubeadmapi.NodeConfiguration, clusterInfo *kubeadmapi.ClusterInfo) (*ConnectionDetails, error) { func EstablishMasterConnection(c *kubeadmapi.TokenDiscovery, clusterInfo *kubeadmapi.ClusterInfo) (*ConnectionDetails, error) {
hostName, err := os.Hostname() hostName, err := os.Hostname()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get node hostname [%v]", err) return nil, fmt.Errorf("failed to get node hostname [%v]", err)
@ -65,7 +65,7 @@ func EstablishMasterConnection(s *kubeadmapi.NodeConfiguration, clusterInfo *kub
result := make(chan *ConnectionDetails) result := make(chan *ConnectionDetails)
var wg sync.WaitGroup var wg sync.WaitGroup
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
clientSet, err := createClients(caCert, endpoint, s.Secrets.BearerToken, nodeName) clientSet, err := createClients(caCert, endpoint, kubeadmutil.BearerToken(c), nodeName)
if err != nil { if err != nil {
fmt.Printf("[bootstrap] Warning: %s. Skipping endpoint %s\n", err, endpoint) fmt.Printf("[bootstrap] Warning: %s. Skipping endpoint %s\n", err, endpoint)
continue continue

View File

@ -107,7 +107,7 @@ func TestEstablishMasterConnection(t *testing.T) {
}, },
} }
for _, rt := range tests { for _, rt := range tests {
s := &kubeadmapi.NodeConfiguration{} s := &kubeadmapi.TokenDiscovery{}
c := &kubeadmapi.ClusterInfo{Endpoints: []string{rt.e}, CertificateAuthorities: []string{rt.c}} c := &kubeadmapi.ClusterInfo{Endpoints: []string{rt.e}, CertificateAuthorities: []string{rt.c}}
_, actual := EstablishMasterConnection(s, c) _, actual := EstablishMasterConnection(s, c)
if (actual == nil) != rt.expect { if (actual == nil) != rt.expect {

View File

@ -18,16 +18,18 @@ package node
import ( import (
"fmt" "fmt"
"os"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/kubelet/util/csr" "k8s.io/kubernetes/pkg/kubelet/util/csr"
"k8s.io/kubernetes/pkg/types"
certutil "k8s.io/kubernetes/pkg/util/cert" certutil "k8s.io/kubernetes/pkg/util/cert"
) )
// PerformTLSBootstrap executes a certificate signing request with the func PerformTLSBootstrapDeprecated(connection *ConnectionDetails) (*clientcmdapi.Config, error) {
// provided connection details.
func PerformTLSBootstrap(connection *ConnectionDetails) (*clientcmdapi.Config, error) {
csrClient := connection.CertClient.CertificateSigningRequests() csrClient := connection.CertClient.CertificateSigningRequests()
fmt.Println("[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request") fmt.Println("[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request")
@ -55,3 +57,49 @@ func PerformTLSBootstrap(connection *ConnectionDetails) (*clientcmdapi.Config, e
return finalConfig, nil return finalConfig, nil
} }
// PerformTLSBootstrap executes a certificate signing request with the
// provided connection details.
func PerformTLSBootstrap(cfg *clientcmdapi.Config) error {
hostName, err := os.Hostname()
if err != nil {
return err
}
name := types.NodeName(hostName)
rc, err := clientcmd.NewDefaultClientConfig(*cfg, nil).ClientConfig()
if err != nil {
return err
}
c, err := clientset.NewForConfig(rc)
if err != nil {
return err
}
fmt.Println("<node/csr> created API client to obtain unique certificate for this node, generating keys and certificate signing request")
key, err := certutil.MakeEllipticPrivateKeyPEM()
if err != nil {
return fmt.Errorf("<node/csr> failed to generating private key [%v]", err)
}
cert, err := csr.RequestNodeCertificate(c.Certificates().CertificateSigningRequests(), key, name)
if err != nil {
return fmt.Errorf("<node/csr> failed to request signed certificate from the API server [%v]", err)
}
fmtCert, err := certutil.FormatBytesCert(cert)
if err != nil {
return fmt.Errorf("<node/csr> failed to format certificate [%v]", err)
}
fmt.Printf("<node/csr> received signed certificate from the API server:\n%s\n", fmtCert)
fmt.Println("<node/csr> generating kubelet configuration")
cfg.AuthInfos["kubelet"] = &clientcmdapi.AuthInfo{
ClientKeyData: key,
ClientCertificateData: []byte(fmtCert),
}
cfg.Contexts["kubelet"] = &clientcmdapi.Context{
AuthInfo: "kubelet",
Cluster: cfg.Contexts[cfg.CurrentContext].Cluster,
}
cfg.CurrentContext = "kubelet"
return nil
}

View File

@ -67,7 +67,7 @@ func TestPerformTLSBootstrap(t *testing.T) {
t.Fatalf("encountered an error while trying to get New Cert Client: %v", err) t.Fatalf("encountered an error while trying to get New Cert Client: %v", err)
} }
cd.CertClient = tmpConfig cd.CertClient = tmpConfig
_, actual := PerformTLSBootstrap(cd) _, actual := PerformTLSBootstrapDeprecated(cd)
if (actual == nil) != rt.expect { if (actual == nil) != rt.expect {
t.Errorf( t.Errorf(
"failed createClients:\n\texpected: %t\n\t actual: %t", "failed createClients:\n\texpected: %t\n\t actual: %t",

View File

@ -32,9 +32,8 @@ import (
// the amount of time to wait between each request to the discovery API // the amount of time to wait between each request to the discovery API
const discoveryRetryTimeout = 5 * time.Second const discoveryRetryTimeout = 5 * time.Second
func RetrieveTrustedClusterInfo(s *kubeadmapi.NodeConfiguration) (*kubeadmapi.ClusterInfo, error) { func RetrieveTrustedClusterInfo(d *kubeadmapi.TokenDiscovery) (*kubeadmapi.ClusterInfo, error) {
host, port := s.MasterAddresses[0], s.DiscoveryPort requestURL := fmt.Sprintf("http://%s/cluster-info/v1/?token-id=%s", d.Addresses[0], d.ID)
requestURL := fmt.Sprintf("http://%s:%d/cluster-info/v1/?token-id=%s", host, port, s.Secrets.TokenID)
req, err := http.NewRequest("GET", requestURL, nil) req, err := http.NewRequest("GET", requestURL, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to consturct an HTTP request [%v]", err) return nil, fmt.Errorf("failed to consturct an HTTP request [%v]", err)
@ -63,7 +62,7 @@ func RetrieveTrustedClusterInfo(s *kubeadmapi.NodeConfiguration) (*kubeadmapi.Cl
fmt.Println("[discovery] Cluster info object received, verifying signature using given token") fmt.Println("[discovery] Cluster info object received, verifying signature using given token")
output, err := object.Verify(s.Secrets.Token) output, err := object.Verify([]byte(d.Secret))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object [%v]", err) return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object [%v]", err)
} }

View File

@ -67,26 +67,26 @@ func TestRetrieveTrustedClusterInfo(t *testing.T) {
} }
tests := []struct { tests := []struct {
h string h string
p int32 p int
payload string payload string
expect bool expect bool
}{ }{
{ {
h: host, h: host,
p: int32(iPort), p: iPort,
payload: "", payload: "",
expect: false, expect: false,
}, },
{ {
h: host, h: host,
p: int32(iPort), p: iPort,
payload: "foo", payload: "foo",
expect: false, expect: false,
}, },
} }
for _, rt := range tests { for _, rt := range tests {
j.Payload = rt.payload j.Payload = rt.payload
nc := &kubeadmapi.NodeConfiguration{MasterAddresses: []string{host}, DiscoveryPort: int32(iPort)} nc := &kubeadmapi.TokenDiscovery{Addresses: []string{rt.h + ":" + strconv.Itoa(rt.p)}}
_, actual := RetrieveTrustedClusterInfo(nc) _, actual := RetrieveTrustedClusterInfo(nc)
if (actual == nil) != rt.expect { if (actual == nil) != rt.expect {
t.Errorf( t.Errorf(

View File

@ -280,14 +280,13 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error {
HostnameCheck{}, HostnameCheck{},
ServiceCheck{Service: "kubelet", CheckIfActive: false}, ServiceCheck{Service: "kubelet", CheckIfActive: false},
ServiceCheck{Service: "docker", CheckIfActive: true}, ServiceCheck{Service: "docker", CheckIfActive: true},
FirewalldCheck{ports: []int{int(cfg.API.BindPort), int(cfg.Discovery.BindPort), 10250}}, FirewalldCheck{ports: []int{int(cfg.API.Port), 10250}},
PortOpenCheck{port: int(cfg.API.BindPort)}, PortOpenCheck{port: int(cfg.API.Port)},
PortOpenCheck{port: 8080}, PortOpenCheck{port: 8080},
PortOpenCheck{port: int(cfg.Discovery.BindPort)},
PortOpenCheck{port: 10250}, PortOpenCheck{port: 10250},
PortOpenCheck{port: 10251}, PortOpenCheck{port: 10251},
PortOpenCheck{port: 10252}, PortOpenCheck{port: 10252},
HTTPProxyCheck{Proto: "https", Host: cfg.API.AdvertiseAddresses[0], Port: int(cfg.API.BindPort)}, HTTPProxyCheck{Proto: "https", Host: cfg.API.AdvertiseAddresses[0], Port: int(cfg.API.Port)},
DirAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests")}, DirAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests")},
DirAvailableCheck{Path: kubeadmapi.GlobalEnvParams.HostPKIPath}, DirAvailableCheck{Path: kubeadmapi.GlobalEnvParams.HostPKIPath},
DirAvailableCheck{Path: "/var/lib/kubelet"}, DirAvailableCheck{Path: "/var/lib/kubelet"},
@ -323,8 +322,6 @@ func RunJoinNodeChecks(cfg *kubeadmapi.NodeConfiguration) error {
ServiceCheck{Service: "kubelet", CheckIfActive: false}, ServiceCheck{Service: "kubelet", CheckIfActive: false},
ServiceCheck{Service: "docker", CheckIfActive: true}, ServiceCheck{Service: "docker", CheckIfActive: true},
PortOpenCheck{port: 10250}, PortOpenCheck{port: 10250},
HTTPProxyCheck{Proto: "https", Host: cfg.MasterAddresses[0], Port: int(cfg.APIPort)},
HTTPProxyCheck{Proto: "http", Host: cfg.MasterAddresses[0], Port: int(cfg.DiscoveryPort)},
DirAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests")}, DirAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests")},
DirAvailableCheck{Path: "/var/lib/kubelet"}, DirAvailableCheck{Path: "/var/lib/kubelet"},
FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "kubelet.conf")}, FileAvailableCheck{Path: path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "kubelet.conf")},

View File

@ -19,6 +19,7 @@ go_library(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library",
"//pkg/client/unversioned/clientcmd:go_default_library", "//pkg/client/unversioned/clientcmd:go_default_library",
"//pkg/client/unversioned/clientcmd/api:go_default_library", "//pkg/client/unversioned/clientcmd/api:go_default_library",

View File

@ -20,65 +20,82 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"regexp"
"strconv"
"strings" "strings"
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"
) )
const ( const (
TokenIDLen = 6 TokenIDBytes = 3
TokenBytes = 8 TokenBytes = 8
) )
func RandBytes(length int) ([]byte, string, error) { func RandBytes(length int) (string, error) {
b := make([]byte, length) b := make([]byte, length)
_, err := rand.Read(b) _, err := rand.Read(b)
if err != nil { if err != nil {
return nil, "", err return "", err
} }
// It's only the tokenID that doesn't care about raw byte slice, return hex.EncodeToString(b), nil
// so we just encoded it in place and ignore bytes slice where we
// do not want it
return b, hex.EncodeToString(b), nil
} }
func GenerateToken(s *kubeadmapi.Secrets) error { func GenerateToken(d *kubeadmapi.TokenDiscovery) error {
_, tokenID, err := RandBytes(TokenIDLen / 2) tokenID, err := RandBytes(TokenIDBytes)
if err != nil { if err != nil {
return err return err
} }
tokenBytes, token, err := RandBytes(TokenBytes) token, err := RandBytes(TokenBytes)
if err != nil { if err != nil {
return err return err
} }
s.TokenID = tokenID d.ID = tokenID
s.BearerToken = token d.Secret = token
s.Token = tokenBytes
s.GivenToken = fmt.Sprintf("%s.%s", tokenID, token)
return nil return nil
} }
func UseGivenTokenIfValid(s *kubeadmapi.Secrets) (bool, error) { var (
if s.GivenToken == "" { tokenRegexpString = "^([a-zA-Z0-9]{6})\\.([a-zA-Z0-9]{16})$"
return false, nil // not given tokenRegexp = regexp.MustCompile(tokenRegexpString)
)
func ParseToken(s string) (string, string, error) {
split := tokenRegexp.FindStringSubmatch(s)
if len(split) != 3 {
return "", "", fmt.Errorf("token %q was not of form %q", s, tokenRegexpString)
} }
fmt.Println("[tokens] Validating provided token") return split[1], split[2], nil
givenToken := strings.Split(strings.ToLower(s.GivenToken), ".")
// TODO(phase1+) could also print more specific messages in each case }
invalidErr := "[tokens] Provided token does not match expected <6 characters>.<16 characters> format - %s"
if len(givenToken) != 2 { func BearerToken(d *kubeadmapi.TokenDiscovery) string {
return false, fmt.Errorf(invalidErr, "not in 2-part dot-separated format") return fmt.Sprintf("%s.%s", d.ID, d.Secret)
} }
if len(givenToken[0]) != TokenIDLen {
return false, fmt.Errorf(invalidErr, fmt.Sprintf( func IsTokenValid(d *kubeadmapi.TokenDiscovery) (bool, error) {
"length of first part is incorrect [%d (given) != %d (expected) ]", if len(d.ID)+len(d.Secret) == 0 {
len(givenToken[0]), TokenIDLen)) return false, nil
} }
tokenBytes := []byte(givenToken[1]) if _, _, err := ParseToken(d.ID + "." + d.Secret); err != nil {
s.TokenID = givenToken[0] return false, err
s.BearerToken = givenToken[1] }
s.Token = tokenBytes return true, nil
return true, nil // given and valid }
func DiscoveryPort(d *kubeadmapi.TokenDiscovery) int32 {
if len(d.Addresses) == 0 {
return kubeadmapiext.DefaultDiscoveryBindPort
}
split := strings.Split(d.Addresses[0], ":")
if len(split) == 1 {
return kubeadmapiext.DefaultDiscoveryBindPort
}
if i, err := strconv.Atoi(split[1]); err != nil {
return int32(i)
}
return kubeadmapiext.DefaultDiscoveryBindPort
} }

View File

@ -17,185 +17,54 @@ limitations under the License.
package util package util
import ( import (
"bytes"
"strings"
"testing" "testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
) )
func TestUsingEmptyTokenFails(t *testing.T) { func TestTokenParseErrors(t *testing.T) {
// Simulates what happens when you omit --token on the CLI invalidTokens := []string{
s := newSecretsWithToken("") "1234567890123456789012",
"12345.1234567890123456",
given, err := UseGivenTokenIfValid(s) ".1234567890123456",
if err != nil { "123456.1234567890.123456",
t.Errorf("UseGivenTokenIfValid returned an error when the token was omitted: %v", err)
}
if given {
t.Error("UseGivenTokenIfValid returned given = true when the token was omitted; expected false")
}
}
func TestTokenValidationFailures(t *testing.T) {
var tests = []struct {
t string
expected bool
}{
{
t: "1234567890123456789012",
expected: false,
},
{
t: "12345.1234567890123456",
expected: false,
},
{
t: ".1234567890123456",
expected: false,
},
{
t: "123456.1234567890.123456",
expected: false,
},
} }
for _, rt := range tests { for _, token := range invalidTokens {
s := newSecretsWithToken(rt.t) if _, _, err := ParseToken(token); err == nil {
_, err := UseGivenTokenIfValid(s) t.Errorf("generateTokenIfNeeded did not return an error for this invalid token: [%s]", token)
if (err == nil) != rt.expected {
t.Errorf(
"failed UseGivenTokenIfValid and did not return an error for this invalid token: [%s]",
rt.t,
)
}
}
}
func TestValidTokenPopulatesSecrets(t *testing.T) {
var tests = []struct {
token string
expectedToken []byte
expectedTokenID string
expectedBearerToken string
}{
{
token: "123456.0123456789AbCdEf",
expectedToken: []byte("0123456789abcdef"),
expectedTokenID: "123456",
expectedBearerToken: "0123456789abcdef",
},
}
for _, rt := range tests {
s := newSecretsWithToken(rt.token)
given, err := UseGivenTokenIfValid(s)
if err != nil {
t.Errorf("UseGivenTokenIfValid gave an error for a valid token: %v", err)
}
if !given {
t.Error("UseGivenTokenIfValid returned given = false when given a valid token")
}
if s.TokenID != rt.expectedTokenID {
t.Errorf("UseGivenTokenIfValid did not populate the TokenID correctly; expected [%s] but got [%s]", rt.expectedTokenID, s.TokenID)
}
if s.BearerToken != rt.expectedBearerToken {
t.Errorf("UseGivenTokenIfValid did not populate the BearerToken correctly; expected [%s] but got [%s]", rt.expectedBearerToken, s.BearerToken)
}
if !bytes.Equal(s.Token, rt.expectedToken) {
t.Errorf("UseGivenTokenIfValid did not populate the Token correctly; expected %v but got %v", rt.expectedToken, s.Token)
}
}
}
func newSecretsWithToken(token string) *kubeadmapi.Secrets {
s := new(kubeadmapi.Secrets)
s.GivenToken = token
return s
}
func TestGenerateToken(t *testing.T) {
var genTest = []struct {
s kubeadmapi.Secrets
l int
n int
}{
{kubeadmapi.Secrets{}, 2, 6},
}
for _, rt := range genTest {
GenerateToken(&rt.s)
givenToken := strings.Split(strings.ToLower(rt.s.GivenToken), ".")
if len(givenToken) != rt.l {
t.Errorf(
"failed GenerateToken num parts:\n\texpected: %d\n\t actual: %d",
rt.l,
len(givenToken),
)
}
if len(givenToken[0]) != rt.n {
t.Errorf(
"failed GenerateToken first part length:\n\texpected: %d\n\t actual: %d",
rt.l,
len(givenToken),
)
} }
} }
} }
func TestUseGivenTokenIfValid(t *testing.T) { func TestGenerateToken(t *testing.T) {
var tokenTest = []struct { var cfg kubeadmapi.TokenDiscovery
s kubeadmapi.Secrets
expected bool
}{
{kubeadmapi.Secrets{GivenToken: ""}, false}, // GivenToken == ""
{kubeadmapi.Secrets{GivenToken: "noperiod"}, false}, // not 2-part '.' format
{kubeadmapi.Secrets{GivenToken: "abcd.a"}, false}, // len(tokenID) != 6
{kubeadmapi.Secrets{GivenToken: "abcdef.a"}, true},
}
for _, rt := range tokenTest { GenerateToken(&cfg)
actual, _ := UseGivenTokenIfValid(&rt.s) if len(cfg.ID) != 6 {
if actual != rt.expected { t.Errorf("failed GenerateToken first part length:\n\texpected: 6\n\t actual: %d", len(cfg.ID))
t.Errorf(
"failed UseGivenTokenIfValid:\n\texpected: %t\n\t actual: %t\n\t token:%s",
rt.expected,
actual,
rt.s.GivenToken,
)
} }
if len(cfg.Secret) != 16 {
t.Errorf("failed GenerateToken first part length:\n\texpected: 16\n\t actual: %d", len(cfg.Secret))
} }
} }
func TestRandBytes(t *testing.T) { func TestRandBytes(t *testing.T) {
var randTest = []struct { var randTest = []int{
r int 0,
l int 1,
expected error 2,
}{ 3,
{0, 0, nil}, 100,
{1, 1, nil},
{2, 2, nil},
{3, 3, nil},
{100, 100, nil},
} }
for _, rt := range randTest { for _, rt := range randTest {
actual, _, err := RandBytes(rt.r) actual, err := RandBytes(rt)
if err != rt.expected { if err != nil {
t.Errorf( t.Errorf("failed RandBytes: %v", err)
"failed RandBytes:\n\texpected: %s\n\t actual: %s",
rt.expected,
err,
)
} }
if len(actual) != rt.l { if len(actual) != rt*2 {
t.Errorf( t.Errorf("failed RandBytes:\n\texpected: %d\n\t actual: %d\n", rt*2, len(actual))
"failed RandBytes:\n\texpected: %d\n\t actual: %d\n",
rt.l,
len(actual),
)
} }
} }
} }

View File

@ -18,6 +18,8 @@ package kubeadm
import ( import (
"flag" "flag"
"os"
"path/filepath"
"regexp" "regexp"
"testing" "testing"
) )
@ -29,7 +31,7 @@ const (
var kubeadmPath string var kubeadmPath string
func init() { func init() {
flag.StringVar(&kubeadmPath, "kubeadm-path", "cluster/kubeadm.sh", "Location of kubeadm") flag.StringVar(&kubeadmPath, "kubeadm-path", filepath.Join(os.Getenv("KUBE_ROOT"), "cluster/kubeadm.sh"), "Location of kubeadm")
} }
func TestCmdTokenGenerate(t *testing.T) { func TestCmdTokenGenerate(t *testing.T) {

View File

@ -544,16 +544,10 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz
func(obj *kubeadm.MasterConfiguration, c fuzz.Continue) { func(obj *kubeadm.MasterConfiguration, c fuzz.Continue) {
c.FuzzNoCustom(obj) c.FuzzNoCustom(obj)
obj.KubernetesVersion = "v10" obj.KubernetesVersion = "v10"
obj.API.BindPort = 20 obj.API.Port = 20
obj.Discovery.BindPort = 20
obj.Networking.ServiceSubnet = "foo" obj.Networking.ServiceSubnet = "foo"
obj.Networking.DNSDomain = "foo" obj.Networking.DNSDomain = "foo"
}, },
func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) {
c.FuzzNoCustom(obj)
obj.APIPort = 20
obj.DiscoveryPort = 20
},
func(s *policy.PodDisruptionBudgetStatus, c fuzz.Continue) { func(s *policy.PodDisruptionBudgetStatus, c fuzz.Continue) {
c.FuzzNoCustom(s) // fuzz self without calling this function again c.FuzzNoCustom(s) // fuzz self without calling this function again
s.PodDisruptionsAllowed = int32(c.Rand.Intn(2)) s.PodDisruptionsAllowed = int32(c.Rand.Intn(2))