From 61a284d720e0ce0f1e67b9a0197dfd25834dda85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Sat, 4 Mar 2017 11:17:52 +0200 Subject: [PATCH] Hook up kubeadm against the BootstrapSigner/BootstrapTokenAuthenticator --- cmd/kubeadm/app/apis/kubeadm/env.go | 3 - cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go | 2 +- cmd/kubeadm/app/apis/kubeadm/register.go | 1 - cmd/kubeadm/app/apis/kubeadm/types.go | 29 +-- cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD | 1 + .../app/apis/kubeadm/v1alpha1/defaults.go | 13 +- .../app/apis/kubeadm/v1alpha1/register.go | 1 - .../app/apis/kubeadm/v1alpha1/types.go | 28 +-- .../app/apis/kubeadm/validation/validation.go | 9 + .../kubeadm/validation/validation_test.go | 24 +-- cmd/kubeadm/app/cmd/defaults.go | 26 +-- cmd/kubeadm/app/cmd/init.go | 67 +++--- cmd/kubeadm/app/cmd/join.go | 17 +- cmd/kubeadm/app/cmd/token.go | 25 +-- cmd/kubeadm/app/constants/constants.go | 13 +- cmd/kubeadm/app/discovery/BUILD | 20 +- cmd/kubeadm/app/discovery/discovery.go | 91 +++------ cmd/kubeadm/app/discovery/file/BUILD | 12 +- cmd/kubeadm/app/discovery/file/file.go | 111 +++++++++- cmd/kubeadm/app/discovery/flags.go | 95 --------- cmd/kubeadm/app/discovery/flags_test.go | 191 ------------------ cmd/kubeadm/app/discovery/https/BUILD | 6 +- cmd/kubeadm/app/discovery/https/https.go | 29 ++- cmd/kubeadm/app/discovery/token/BUILD | 25 ++- cmd/kubeadm/app/discovery/token/token.go | 128 ++++++++++-- cmd/kubeadm/app/discovery/token/token_test.go | 61 ++++++ cmd/kubeadm/app/master/BUILD | 3 +- cmd/kubeadm/app/master/discovery.go | 126 ------------ cmd/kubeadm/app/master/manifests.go | 32 +-- cmd/kubeadm/app/master/manifests_test.go | 15 +- cmd/kubeadm/app/master/templates.go | 57 ------ cmd/kubeadm/app/node/BUILD | 16 +- cmd/kubeadm/app/node/bootstrap.go | 146 ------------- cmd/kubeadm/app/node/csr.go | 34 ++-- cmd/kubeadm/app/node/discovery.go | 87 -------- cmd/kubeadm/app/node/discovery_test.go | 99 --------- cmd/kubeadm/app/node/validate.go | 51 +++++ .../{bootstrap_test.go => validate_test.go} | 157 +------------- cmd/kubeadm/app/phases/apiconfig/BUILD | 1 + .../app/phases/apiconfig/clusterroles.go | 93 +++++---- cmd/kubeadm/app/phases/token/BUILD | 17 +- cmd/kubeadm/app/phases/token/bootstrap.go | 64 ++++-- .../app/phases/token/bootstrap_test.go | 2 +- cmd/kubeadm/app/phases/token/csv.go | 42 ---- cmd/kubeadm/app/phases/token/csv_test.go | 17 -- cmd/kubeadm/app/util/kubeconfig/kubeconfig.go | 12 ++ cmd/kubeadm/app/util/token/tokens.go | 13 +- cmd/kubeadm/app/util/token/tokens_test.go | 16 +- hack/.linted_packages | 1 + hack/verify-flags/known-flags.txt | 2 + 50 files changed, 718 insertions(+), 1413 deletions(-) delete mode 100644 cmd/kubeadm/app/discovery/flags.go delete mode 100644 cmd/kubeadm/app/discovery/flags_test.go create mode 100644 cmd/kubeadm/app/discovery/token/token_test.go delete mode 100644 cmd/kubeadm/app/master/discovery.go delete mode 100644 cmd/kubeadm/app/node/bootstrap.go delete mode 100644 cmd/kubeadm/app/node/discovery.go delete mode 100644 cmd/kubeadm/app/node/discovery_test.go create mode 100644 cmd/kubeadm/app/node/validate.go rename cmd/kubeadm/app/node/{bootstrap_test.go => validate_test.go} (50%) delete mode 100644 cmd/kubeadm/app/phases/token/csv.go delete mode 100644 cmd/kubeadm/app/phases/token/csv_test.go diff --git a/cmd/kubeadm/app/apis/kubeadm/env.go b/cmd/kubeadm/app/apis/kubeadm/env.go index 5d89f645ecc..a40f37343cd 100644 --- a/cmd/kubeadm/app/apis/kubeadm/env.go +++ b/cmd/kubeadm/app/apis/kubeadm/env.go @@ -20,7 +20,6 @@ import ( "fmt" "os" "path" - "runtime" "strings" ) @@ -35,7 +34,6 @@ func SetEnvParams() *EnvParams { "host_etcd_path": "/var/lib/etcd", "hyperkube_image": "", "repo_prefix": "gcr.io/google_containers", - "discovery_image": fmt.Sprintf("gcr.io/google_containers/kube-discovery-%s:%s", runtime.GOARCH, "1.0"), "etcd_image": "", } @@ -50,7 +48,6 @@ func SetEnvParams() *EnvParams { HostEtcdPath: path.Clean(envParams["host_etcd_path"]), HyperkubeImage: envParams["hyperkube_image"], RepositoryPrefix: envParams["repo_prefix"], - DiscoveryImage: envParams["discovery_image"], EtcdImage: envParams["etcd_image"], } } diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go index 7a02ecbdcf3..7d5bdac3919 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go @@ -33,9 +33,9 @@ func KubeadmFuzzerFuncs(t apitesting.TestingCommon) []interface{} { obj.Networking.ServiceSubnet = "foo" obj.Networking.DNSDomain = "foo" obj.AuthorizationMode = "foo" - obj.Discovery.Token = &kubeadm.TokenDiscovery{} obj.CertificatesDir = "foo" obj.APIServerCertSANs = []string{} + obj.Token = "foo" }, func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) { c.FuzzNoCustom(obj) diff --git a/cmd/kubeadm/app/apis/kubeadm/register.go b/cmd/kubeadm/app/apis/kubeadm/register.go index 5215971f667..39e2c2dad7f 100644 --- a/cmd/kubeadm/app/apis/kubeadm/register.go +++ b/cmd/kubeadm/app/apis/kubeadm/register.go @@ -46,7 +46,6 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &MasterConfiguration{}, &NodeConfiguration{}, - &ClusterInfo{}, ) return nil } diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index 333761048c3..0686f302b0e 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -17,6 +17,8 @@ limitations under the License. package kubeadm import ( + "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -25,7 +27,6 @@ type EnvParams struct { HostEtcdPath string HyperkubeImage string RepositoryPrefix string - DiscoveryImage string EtcdImage string } @@ -33,13 +34,15 @@ type MasterConfiguration struct { metav1.TypeMeta API API - Discovery Discovery Etcd Etcd Networking Networking KubernetesVersion string CloudProvider string AuthorizationMode string + Token string + TokenTTL time.Duration + // SelfHosted enables an alpha deployment type where the apiserver, scheduler, and // controller manager are managed by Kubernetes itself. This option is likely to // become the default in the future. @@ -62,20 +65,6 @@ type API struct { BindPort int32 } -type Discovery struct { - HTTPS *HTTPSDiscovery - File *FileDiscovery - Token *TokenDiscovery -} - -type HTTPSDiscovery struct { - URL string -} - -type FileDiscovery struct { - Path string -} - type TokenDiscovery struct { ID string Secret string @@ -106,11 +95,3 @@ type NodeConfiguration struct { TLSBootstrapToken string Token string } - -// ClusterInfo TODO add description -type ClusterInfo struct { - metav1.TypeMeta - // TODO(phase1+) this may become simply `api.Config` - CertificateAuthorities []string - Endpoints []string -} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD index 45a09ceb981..b16645ad3c9 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD @@ -18,6 +18,7 @@ go_library( ], tags = ["automanaged"], deps = [ + "//cmd/kubeadm/app/constants:go_default_library", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime/schema", diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go index e0266ec965b..3f17a07b8a5 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go @@ -20,14 +20,15 @@ import ( "net/url" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) const ( DefaultServiceDNSDomain = "cluster.local" DefaultServicesSubnet = "10.96.0.0/12" - DefaultKubernetesVersion = "latest" + DefaultKubernetesVersion = "latest-1.6" // This is only for clusters without internet, were the latest stable version can't be determined - DefaultKubernetesFallbackVersion = "v1.6.0-alpha.1" + DefaultKubernetesFallbackVersion = "v1.6.0-beta.1" DefaultAPIBindPort = 6443 DefaultDiscoveryBindPort = 9898 DefaultAuthorizationMode = "RBAC" @@ -60,10 +61,6 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) { obj.Networking.DNSDomain = DefaultServiceDNSDomain } - if obj.Discovery.Token == nil && obj.Discovery.File == nil && obj.Discovery.HTTPS == nil { - obj.Discovery.Token = &TokenDiscovery{} - } - if obj.AuthorizationMode == "" { obj.AuthorizationMode = DefaultAuthorizationMode } @@ -71,6 +68,10 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) { if obj.CertificatesDir == "" { obj.CertificatesDir = DefaultCertificatesDir } + + if obj.TokenTTL == 0 { + obj.TokenTTL = constants.DefaultTokenDuration + } } func SetDefaults_NodeConfiguration(obj *NodeConfiguration) { diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/register.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/register.go index 9e8c191bcd0..c958bd67e29 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/register.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/register.go @@ -47,7 +47,6 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &MasterConfiguration{}, &NodeConfiguration{}, - &ClusterInfo{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go index 0c24d624c94..3fe5d7946e0 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go @@ -17,6 +17,8 @@ limitations under the License. package v1alpha1 import ( + "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -24,13 +26,15 @@ type MasterConfiguration struct { metav1.TypeMeta `json:",inline"` API API `json:"api"` - Discovery Discovery `json:"discovery"` Etcd Etcd `json:"etcd"` Networking Networking `json:"networking"` KubernetesVersion string `json:"kubernetesVersion"` CloudProvider string `json:"cloudProvider"` AuthorizationMode string `json:"authorizationMode"` + Token string `json:"token"` + TokenTTL time.Duration `json:"tokenTTL"` + // SelfHosted enables an alpha deployment type where the apiserver, scheduler, and // controller manager are managed by Kubernetes itself. This option is likely to // become the default in the future. @@ -53,20 +57,6 @@ type API struct { BindPort int32 `json:"bindPort"` } -type Discovery struct { - 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"` @@ -96,11 +86,3 @@ type NodeConfiguration struct { TLSBootstrapToken string `json:"tlsBootstrapToken"` Token string `json:"token"` } - -// ClusterInfo TODO add description -type ClusterInfo struct { - metav1.TypeMeta `json:",inline"` - // TODO(phase1+) this may become simply `api.Config` - CertificateAuthorities []string `json:"certificateAuthorities"` - Endpoints []string `json:"endpoints"` -} diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index ad4d691cb5d..f5bcf67af1f 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -56,6 +56,7 @@ func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList allErrs = append(allErrs, ValidateNetworking(&c.Networking, field.NewPath("networking"))...) allErrs = append(allErrs, ValidateAPIServerCertSANs(c.APIServerCertSANs, field.NewPath("cert-altnames"))...) allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificates-dir"))...) + allErrs = append(allErrs, ValidateToken(c.Token, field.NewPath("token"))...) return allErrs } @@ -87,6 +88,14 @@ func ValidateDiscovery(c *kubeadm.NodeConfiguration, fldPath *field.Path) field. allErrs = append(allErrs, ValidateArgSelection(c, fldPath)...) allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, fldPath)...) allErrs = append(allErrs, ValidateJoinDiscoveryTokenAPIServer(c, fldPath)...) + + if len(c.DiscoveryToken) != 0 { + allErrs = append(allErrs, ValidateToken(c.DiscoveryToken, fldPath)...) + } + if len(c.DiscoveryFile) != 0 { + allErrs = append(allErrs, ValidateDiscoveryFile(c.DiscoveryFile, fldPath)...) + } + return allErrs } diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go index 4f4c5cb464b..287b413cf04 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go @@ -172,41 +172,21 @@ func TestValidateMasterConfiguration(t *testing.T) { }{ {&kubeadm.MasterConfiguration{}, false}, {&kubeadm.MasterConfiguration{ - Discovery: kubeadm.Discovery{ - HTTPS: &kubeadm.HTTPSDiscovery{URL: "foo"}, - }, AuthorizationMode: "RBAC", Networking: kubeadm.Networking{ ServiceSubnet: "10.96.0.1/12", DNSDomain: "cluster.local", }, CertificatesDir: "/some/cert/dir", - }, true}, + }, false}, {&kubeadm.MasterConfiguration{ - Discovery: kubeadm.Discovery{ - File: &kubeadm.FileDiscovery{Path: "foo"}, - }, AuthorizationMode: "RBAC", Networking: kubeadm.Networking{ ServiceSubnet: "10.96.0.1/12", DNSDomain: "cluster.local", }, CertificatesDir: "/some/other/cert/dir", - }, true}, - {&kubeadm.MasterConfiguration{ - Discovery: kubeadm.Discovery{ - Token: &kubeadm.TokenDiscovery{ - ID: "abcdef", - Secret: "1234567890123456", - Addresses: []string{"foobar"}, - }, - }, - AuthorizationMode: "RBAC", - Networking: kubeadm.Networking{ - ServiceSubnet: "10.96.0.1/12", - DNSDomain: "cluster.local", - }, - CertificatesDir: "/yet/another/cert/dir", + Token: "abcdef.0123456789abcdef", }, true}, } for _, rt := range tests { diff --git a/cmd/kubeadm/app/cmd/defaults.go b/cmd/kubeadm/app/cmd/defaults.go index f03a1b23f5e..49fce9299ac 100644 --- a/cmd/kubeadm/app/cmd/defaults.go +++ b/cmd/kubeadm/app/cmd/defaults.go @@ -30,9 +30,7 @@ import ( "github.com/blang/semver" ) -var ( - minK8sVersion = semver.MustParse(kubeadmconstants.MinimumControlPlaneVersion) -) +var minK8sVersion = semver.MustParse(kubeadmconstants.MinimumControlPlaneVersion) func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { @@ -73,23 +71,11 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { fmt.Println("\t(/etc/systemd/system/kubelet.service.d/10-kubeadm.conf should be edited for this purpose)") } - // Validate token if any, otherwise generate - if cfg.Discovery.Token != nil { - if cfg.Discovery.Token.ID != "" && cfg.Discovery.Token.Secret != "" { - fmt.Printf("[init] A token has been provided, validating [%s]\n", tokenutil.BearerToken(cfg.Discovery.Token)) - if valid, err := tokenutil.ValidateToken(cfg.Discovery.Token); valid == false { - return err - } - } else { - fmt.Println("[init] A token has not been provided, generating one") - if err := tokenutil.GenerateToken(cfg.Discovery.Token); err != nil { - return err - } - } - - // If there aren't any addresses specified, default to the first advertised address which can be user-provided or the default network interface's IP address - if len(cfg.Discovery.Token.Addresses) == 0 { - cfg.Discovery.Token.Addresses = []string{fmt.Sprintf("%s:%d", cfg.API.AdvertiseAddress, kubeadmapiext.DefaultDiscoveryBindPort)} + if cfg.Token == "" { + var err error + cfg.Token, err = tokenutil.GenerateToken() + if err != nil { + return fmt.Errorf("couldn't generate random token: %v", err) } } diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 65edb68ec5f..58a4b601b69 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -31,7 +31,6 @@ import ( kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - "k8s.io/kubernetes/cmd/kubeadm/app/discovery" kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master" addonsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons" apiconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig" @@ -40,7 +39,6 @@ import ( tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" ) var ( @@ -56,16 +54,14 @@ var ( You can now join any number of machines by running the following on each node: - kubeadm join --discovery %s + kubeadm join --token %s %s:%d `) ) // NewCmdInit returns "kubeadm init" command. func NewCmdInit(out io.Writer) *cobra.Command { - versioned := &kubeadmapiext.MasterConfiguration{} - api.Scheme.Default(versioned) - cfg := kubeadmapi.MasterConfiguration{} - api.Scheme.Convert(versioned, &cfg, nil) + cfg := &kubeadmapiext.MasterConfiguration{} + api.Scheme.Default(cfg) var cfgPath string var skipPreFlight bool @@ -73,7 +69,11 @@ func NewCmdInit(out io.Writer) *cobra.Command { Use: "init", Short: "Run this in order to set up the Kubernetes master", Run: func(cmd *cobra.Command, args []string) { - i, err := NewInit(cfgPath, &cfg, skipPreFlight) + api.Scheme.Default(cfg) + internalcfg := &kubeadmapi.MasterConfiguration{} + api.Scheme.Convert(cfg, internalcfg, nil) + + i, err := NewInit(cfgPath, internalcfg, skipPreFlight) kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(i.Validate()) kubeadmutil.CheckErr(i.Run(out)) @@ -113,17 +113,20 @@ func NewCmdInit(out io.Writer) *cobra.Command { `Optional extra altnames to use for the API Server serving cert. Can be both IP addresses and dns names.`, ) - cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file") + cmd.PersistentFlags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") cmd.PersistentFlags().BoolVar( &skipPreFlight, "skip-preflight-checks", skipPreFlight, "Skip preflight checks normally run before modifying the system", ) - cmd.PersistentFlags().Var( - discovery.NewDiscoveryValue(&cfg.Discovery), "discovery", - "The discovery method kubeadm will use for connecting nodes to the master", - ) + cmd.PersistentFlags().StringVar( + &cfg.Token, "token", cfg.Token, + "The token to use for establishing bidirectional trust between nodes and masters.") + + cmd.PersistentFlags().DurationVar( + &cfg.TokenTTL, "token-ttl", cfg.TokenTTL, + "The duration before the bootstrap token is automatically deleted. 0 means 'never expires'.") return cmd } @@ -199,20 +202,13 @@ func (i *Init) Run(out io.Writer) error { return err } - // TODO: It's not great to have an exception for token here, but necessary because the apiserver doesn't handle this properly in the API yet - // but relies on files on disk for now, which is daunting. - if i.cfg.Discovery.Token != nil { - if err := tokenphase.CreateTokenAuthFile(i.cfg.CertificatesDir, tokenutil.BearerToken(i.cfg.Discovery.Token)); err != nil { - return err - } - } - // PHASE 3: Bootstrap the control plane if err := kubemaster.WriteStaticPodManifests(i.cfg); err != nil { return err } - client, err := kubemaster.CreateClientAndWaitForAPI(path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName)) + adminKubeConfigPath := path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName) + client, err := kubemaster.CreateClientAndWaitForAPI(adminKubeConfigPath) if err != nil { return err } @@ -225,22 +221,22 @@ func (i *Init) Run(out io.Writer) error { if i.cfg.SelfHosted { // Temporary control plane is up, now we create our self hosted control // plane components and remove the static manifests: - fmt.Println("[init] Creating self-hosted control plane...") + fmt.Println("[self-hosted] Creating self-hosted control plane...") if err := kubemaster.CreateSelfHostedControlPlane(i.cfg, client); err != nil { return err } } // PHASE 4: Set up the bootstrap tokens - if i.cfg.Discovery.Token != nil { - fmt.Printf("[token-discovery] Using token: %s\n", tokenutil.BearerToken(i.cfg.Discovery.Token)) - if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client); err != nil { - return err - } - tokenDescription := "The default bootstrap token generated by 'kubeadm init'." - if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Discovery.Token, false, kubeadmconstants.DefaultTokenDuration, kubeadmconstants.DefaultTokenUsages, tokenDescription); err != nil { - return err - } + fmt.Printf("[token] Using token: %s\n", i.cfg.Token) + + tokenDescription := "The default bootstrap token generated by 'kubeadm init'." + if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL, kubeadmconstants.DefaultTokenUsages, tokenDescription); err != nil { + return err + } + + if err := tokenphase.CreateBootstrapConfigMap(adminKubeConfigPath); err != nil { + return err } // PHASE 5: Install and deploy all addons, and configure things as necessary @@ -260,11 +256,6 @@ func (i *Init) Run(out io.Writer) error { return err } - fmt.Fprintf(out, initDoneMsgf, path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName), generateJoinArgs(i.cfg)) + fmt.Fprintf(out, initDoneMsgf, path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName), i.cfg.Token, i.cfg.API.AdvertiseAddress, i.cfg.API.BindPort) return nil } - -// generateJoinArgs generates kubeadm join arguments -func generateJoinArgs(cfg *kubeadmapi.MasterConfiguration) string { - return discovery.NewDiscoveryValue(&cfg.Discovery).String() -} diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index 790c4be32b6..8a574a0acf6 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "io/ioutil" + "os" "path/filepath" "github.com/renstrom/dedent" @@ -90,9 +91,11 @@ func NewCmdJoin(out io.Writer) *cobra.Command { `), Run: func(cmd *cobra.Command, args []string) { cfg.DiscoveryTokenAPIServers = args + api.Scheme.Default(cfg) internalcfg := &kubeadmapi.NodeConfiguration{} api.Scheme.Convert(cfg, internalcfg, nil) + j, err := NewJoin(cfgPath, args, internalcfg, skipPreFlight) kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(j.Validate()) @@ -174,7 +177,19 @@ func (j *Join) Run(out io.Writer) error { if err != nil { return err } - if err := kubenode.PerformTLSBootstrap(cfg); err != nil { + + hostname, err := os.Hostname() + if err != nil { + return err + } + client, err := kubeconfigutil.KubeConfigToClientSet(cfg) + if err != nil { + return err + } + if err := kubenode.ValidateAPIServer(client); err != nil { + return err + } + if err := kubenode.PerformTLSBootstrap(cfg, hostname); err != nil { return err } diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index 67f6f7a16f9..cc06c7291f0 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -189,36 +189,37 @@ 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.Clientset, token string, tokenDuration time.Duration, usages []string, description string) error { - td := &kubeadmapi.TokenDiscovery{} - var err error if len(token) == 0 { - err = tokenutil.GenerateToken(td) + var err error + token, err = tokenutil.GenerateToken() + if err != nil { + return err + } } else { - td.ID, td.Secret, err = tokenutil.ParseToken(token) - } - if err != nil { - return err + _, _, err := tokenutil.ParseToken(token) + if err != nil { + return err + } } // TODO: Validate usages here so we don't allow something unsupported - err = tokenphase.CreateNewToken(client, td, tokenDuration, usages, description) + err := tokenphase.CreateNewToken(client, token, tokenDuration, usages, description) if err != nil { return err } - fmt.Fprintln(out, tokenutil.BearerToken(td)) + fmt.Fprintln(out, token) return nil } // RunGenerateToken just generates a random token for the user func RunGenerateToken(out io.Writer) error { - td := &kubeadmapi.TokenDiscovery{} - err := tokenutil.GenerateToken(td) + token, err := tokenutil.GenerateToken() if err != nil { return err } - fmt.Fprintln(out, tokenutil.BearerToken(td)) + fmt.Fprintln(out, token) return nil } diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index 56236f113c5..a199731cff9 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -57,7 +57,7 @@ const ( SchedulerKubeConfigFileName = "scheduler.conf" // Important: a "v"-prefix shouldn't exist here; semver doesn't allow that - MinimumControlPlaneVersion = "1.6.0-alpha.2" + MinimumControlPlaneVersion = "1.6.0-beta.1" // Some well-known users and groups in the core Kubernetes authorization system @@ -72,6 +72,8 @@ const ( // APICallRetryInterval defines how long kubeadm should wait before retrying a failed API operation APICallRetryInterval = 500 * time.Millisecond + // DiscoveryRetryInterval specifies how long kubeadm should wait before retrying to connect to the master when doing discovery + DiscoveryRetryInterval = 5 * time.Second // Minimum amount of nodes the Service subnet should allow. // We need at least ten, because the DNS service is always at the tenth cluster clusterIP @@ -85,15 +87,6 @@ const ( // It's copied over to kubeadm until it's merged in core: https://github.com/kubernetes/kubernetes/pull/39112 LabelNodeRoleMaster = "node-role.kubernetes.io/master" - // CSVTokenBootstrapUser is currently the user the bootstrap token in the .csv file - // TODO: This should change to something more official and supported - // TODO: Prefix with kubeadm prefix - CSVTokenBootstrapUser = "kubeadm-node-csr" - // CSVTokenBootstrapGroup specifies the group the tokens in the .csv file will belong to - CSVTokenBootstrapGroup = "kubeadm:kubelet-bootstrap" - // The file name of the tokens file that can be used for bootstrapping - CSVTokenFileName = "tokens.csv" - // MinExternalEtcdVersion indicates minimum external etcd version which kubeadm supports MinExternalEtcdVersion = "3.0.14" diff --git a/cmd/kubeadm/app/discovery/BUILD b/cmd/kubeadm/app/discovery/BUILD index 8b8c1270749..5db993534d3 100644 --- a/cmd/kubeadm/app/discovery/BUILD +++ b/cmd/kubeadm/app/discovery/BUILD @@ -10,36 +10,24 @@ load( go_library( name = "go_default_library", - srcs = [ - "discovery.go", - "flags.go", - ], + srcs = ["discovery.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", - "//cmd/kubeadm/app/node:go_default_library", - "//cmd/kubeadm/app/util/token:go_default_library", - "//vendor:github.com/spf13/pflag", - "//vendor:k8s.io/client-go/tools/clientcmd", + "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//vendor:k8s.io/client-go/tools/clientcmd/api", ], ) go_test( name = "go_default_test", - srcs = [ - "discovery_test.go", - "flags_test.go", - ], + srcs = ["discovery_test.go"], library = ":go_default_library", tags = ["automanaged"], - deps = [ - "//cmd/kubeadm/app/apis/kubeadm:go_default_library", - "//vendor:github.com/davecgh/go-spew/spew", - ], + deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], ) filegroup( diff --git a/cmd/kubeadm/app/discovery/discovery.go b/cmd/kubeadm/app/discovery/discovery.go index a1463e9fd70..3e64b38de00 100644 --- a/cmd/kubeadm/app/discovery/discovery.go +++ b/cmd/kubeadm/app/discovery/discovery.go @@ -18,27 +18,47 @@ package discovery import ( "fmt" - "io/ioutil" - "net/http" "net/url" - "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubenode "k8s.io/kubernetes/cmd/kubeadm/app/node" - tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" + "k8s.io/kubernetes/cmd/kubeadm/app/discovery/file" + "k8s.io/kubernetes/cmd/kubeadm/app/discovery/https" + "k8s.io/kubernetes/cmd/kubeadm/app/discovery/token" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" ) -// For identifies and executes the desired discovery mechanism. -func For(d *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) { +const TokenUser = "tls-bootstrap-token-user" + +// For returns a KubeConfig object that can be used for doing the TLS Bootstrap with the right credentials +// Also, before returning anything, it makes sure it can trust the API Server +func For(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) { + // TODO: Print summary info about the CA certificate, along with the the checksum signature + // we also need an ability for the user to configure the client to validate received CA cert against a checksum + clusterinfo, err := GetValidatedClusterInfoObject(cfg) + if err != nil { + return nil, fmt.Errorf("couldn't validate the identity of the API Server: %v", err) + } + + return kubeconfigutil.CreateWithToken( + clusterinfo.Server, + "kubernetes", + TokenUser, + clusterinfo.CertificateAuthorityData, + cfg.TLSBootstrapToken, + ), nil +} + +// GetValidatedClusterInfoObject returns a validated Cluster object that specifies where the cluster is and the CA cert to trust +func GetValidatedClusterInfoObject(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Cluster, error) { switch { - case len(d.DiscoveryFile) != 0: - if isHTTPSURL(d.DiscoveryFile) { - return runHTTPSDiscovery(d.DiscoveryFile) + case len(cfg.DiscoveryFile) != 0: + if isHTTPSURL(cfg.DiscoveryFile) { + return https.RetrieveValidatedClusterInfo(cfg.DiscoveryFile) } - return runFileDiscovery(d.DiscoveryFile) - case len(d.DiscoveryToken) != 0: - return runTokenDiscovery(d.DiscoveryToken, d.DiscoveryTokenAPIServers) + return file.RetrieveValidatedClusterInfo(cfg.DiscoveryFile) + case len(cfg.DiscoveryToken) != 0: + return token.RetrieveValidatedClusterInfo(cfg.DiscoveryToken, cfg.DiscoveryTokenAPIServers) default: return nil, fmt.Errorf("couldn't find a valid discovery configuration.") } @@ -49,48 +69,3 @@ func isHTTPSURL(s string) bool { u, err := url.Parse(s) return err == nil && u.Scheme == "https" } - -// runFileDiscovery executes file-based discovery. -func runFileDiscovery(fd string) (*clientcmdapi.Config, error) { - return clientcmd.LoadFromFile(fd) -} - -// runHTTPSDiscovery executes HTTPS-based discovery. -func runHTTPSDiscovery(hd string) (*clientcmdapi.Config, error) { - response, err := http.Get(hd) - if err != nil { - return nil, err - } - defer response.Body.Close() - - kubeconfig, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - return clientcmd.Load(kubeconfig) -} - -// runTokenDiscovery executes token-based discovery. -func runTokenDiscovery(td string, m []string) (*clientcmdapi.Config, error) { - id, secret, err := tokenutil.ParseToken(td) - if err != nil { - return nil, err - } - t := &kubeadmapi.TokenDiscovery{ID: id, Secret: secret, Addresses: m} - - if valid, err := tokenutil.ValidateToken(t); valid == false { - return nil, err - } - - clusterInfo, err := kubenode.RetrieveTrustedClusterInfo(t) - if err != nil { - return nil, err - } - - cfg, err := kubenode.EstablishMasterConnection(t, clusterInfo) - if err != nil { - return nil, err - } - return cfg, nil -} diff --git a/cmd/kubeadm/app/discovery/file/BUILD b/cmd/kubeadm/app/discovery/file/BUILD index 62814b35954..65ec6048fb8 100644 --- a/cmd/kubeadm/app/discovery/file/BUILD +++ b/cmd/kubeadm/app/discovery/file/BUILD @@ -11,7 +11,17 @@ go_library( name = "go_default_library", srcs = ["file.go"], tags = ["automanaged"], - deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], + deps = [ + "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/util/kubeconfig:go_default_library", + "//pkg/bootstrap/api:go_default_library", + "//vendor:k8s.io/apimachinery/pkg/api/errors", + "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", + "//vendor:k8s.io/apimachinery/pkg/util/wait", + "//vendor:k8s.io/client-go/pkg/api/v1", + "//vendor:k8s.io/client-go/tools/clientcmd", + "//vendor:k8s.io/client-go/tools/clientcmd/api", + ], ) filegroup( diff --git a/cmd/kubeadm/app/discovery/file/file.go b/cmd/kubeadm/app/discovery/file/file.go index 1f9fe771054..f68e7259a6a 100644 --- a/cmd/kubeadm/app/discovery/file/file.go +++ b/cmd/kubeadm/app/discovery/file/file.go @@ -17,13 +17,114 @@ limitations under the License. package file import ( - "net/url" + "fmt" - "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" ) -func Parse(u *url.URL, c *kubeadm.Discovery) { - c.File = &kubeadm.FileDiscovery{ - Path: u.Path, +// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk +// securely to the API Server using the provided CA cert and +// optionally refreshes the cluster-info information from the cluster-info ConfigMap +func RetrieveValidatedClusterInfo(filepath string) (*clientcmdapi.Cluster, error) { + clusterinfo, err := clientcmd.LoadFromFile(filepath) + if err != nil { + return nil, err } + return ValidateClusterInfo(clusterinfo) +} + +// ValidateClusterInfo connects to the API Server and makes sure it can talk +// securely to the API Server using the provided CA cert and +// optionally refreshes the cluster-info information from the cluster-info ConfigMap +func ValidateClusterInfo(clusterinfo *clientcmdapi.Config) (*clientcmdapi.Cluster, error) { + err := validateClusterInfoKubeConfig(clusterinfo) + if err != nil { + return nil, err + } + + // This is the cluster object we've got from the cluster-info KubeConfig file + defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(clusterinfo) + + // Create a new kubeconfig object from the given, just copy over the server and the CA cert + // We do this in order to not pick up other possible misconfigurations in the clusterinfo file + configFromClusterInfo := kubeconfigutil.CreateBasic( + defaultCluster.Server, + "kubernetes", + "", // no user provided + defaultCluster.CertificateAuthorityData, + ) + + client, err := kubeconfigutil.KubeConfigToClientSet(configFromClusterInfo) + if err != nil { + return nil, err + } + + fmt.Printf("[discovery] Created cluster-info discovery client, requesting info from %q\n", defaultCluster.Server) + + var clusterinfoCM *v1.ConfigMap + wait.PollInfinite(constants.DiscoveryRetryInterval, func() (bool, error) { + var err error + clusterinfoCM, err = client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get(bootstrapapi.ConfigMapClusterInfo, metav1.GetOptions{}) + if err != nil { + if apierrors.IsForbidden(err) { + // If the request is unauthorized, the cluster admin has not granted access to the cluster info configmap for unauthenicated users + // In that case, trust the cluster admin and do not refresh the cluster-info credentials + fmt.Printf("[discovery] Could not access the %s ConfigMap for refreshing the cluster-info information, but the TLS cert is valid so proceeding...\n", bootstrapapi.ConfigMapClusterInfo) + return true, nil + } else { + fmt.Printf("[discovery] Failed to validate the API Server's identity, will try again: [%v]\n", err) + return false, nil + } + } + return true, nil + }) + + // If we couldn't fetch the cluster-info ConfigMap, just return the cluster-info object the user provided + if clusterinfoCM == nil { + return defaultCluster, nil + } + + // We somehow got hold of the ConfigMap, try to read some data from it. If we can't, fallback on the user-provided file + refreshedBaseKubeConfig, err := tryParseClusterInfoFromConfigMap(clusterinfoCM) + if err != nil { + fmt.Printf("[discovery] The %s ConfigMap isn't set up properly (%v), but the TLS cert is valid so proceeding...\n", bootstrapapi.ConfigMapClusterInfo, err) + return defaultCluster, nil + } + + fmt.Println("[discovery] Synced cluster-info information from the API Server so we have got the latest information") + // In an HA world in the future, this will make more sense, because now we've got new information, possibly about new API Servers to talk to + return kubeconfigutil.GetClusterFromKubeConfig(refreshedBaseKubeConfig), nil +} + +// tryParseClusterInfoFromConfigMap tries to parse a kubeconfig file from a ConfigMap key +func tryParseClusterInfoFromConfigMap(cm *v1.ConfigMap) (*clientcmdapi.Config, error) { + kubeConfigString, ok := cm.Data[bootstrapapi.KubeConfigKey] + if !ok || len(kubeConfigString) == 0 { + return nil, fmt.Errorf("no %s key in ConfigMap", bootstrapapi.KubeConfigKey) + } + parsedKubeConfig, err := clientcmd.Load([]byte(kubeConfigString)) + if err != nil { + return nil, fmt.Errorf("couldn't parse the kubeconfig file in the %s ConfigMap: %v", bootstrapapi.ConfigMapClusterInfo, err) + } + return parsedKubeConfig, nil +} + +// validateClusterInfoKubeConfig makes sure the user-provided cluster-info KubeConfig file is valid +func validateClusterInfoKubeConfig(clusterinfo *clientcmdapi.Config) error { + if len(clusterinfo.Clusters) < 1 { + return fmt.Errorf("the provided cluster-info KubeConfig file must have at least one Cluster defined") + } + defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(clusterinfo) + if defaultCluster == nil { + return fmt.Errorf("the provided cluster-info KubeConfig file must have an unnamed Cluster or a CurrentContext that specifies a non-nil Cluster") + } + return nil } diff --git a/cmd/kubeadm/app/discovery/flags.go b/cmd/kubeadm/app/discovery/flags.go deleted file mode 100644 index d0733491eed..00000000000 --- a/cmd/kubeadm/app/discovery/flags.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -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": - https.Parse(u, d) - return nil - case "file": - file.Parse(u, d) - return nil - case "token": - // Make sure a valid RFC 3986 URL has been passed and parsed. - // See https://github.com/kubernetes/kubeadm/issues/95#issuecomment-270431296 for more details. - if !strings.Contains(s, "@") { - s := s + "@" - u, err = url.Parse(s) - if err != nil { - return err - } - } - token.Parse(u, d) - return nil - default: - return fmt.Errorf("unknown discovery scheme") - } -} diff --git a/cmd/kubeadm/app/discovery/flags_test.go b/cmd/kubeadm/app/discovery/flags_test.go deleted file mode 100644 index c1d89025f01..00000000000 --- a/cmd/kubeadm/app/discovery/flags_test.go +++ /dev/null @@ -1,191 +0,0 @@ -/* -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 TestNewDiscoveryValue(t *testing.T) { - tests := []struct { - d *discoveryValue - expect string - }{ - { - d: &discoveryValue{ - v: &kubeadm.Discovery{}}, - expect: "unknown", - }, - { - d: &discoveryValue{ - v: &kubeadm.Discovery{ - HTTPS: &kubeadm.HTTPSDiscovery{URL: "notnil"}, - }, - }, - expect: "notnil", - }, - { - d: &discoveryValue{ - v: &kubeadm.Discovery{ - File: &kubeadm.FileDiscovery{Path: "notnil"}, - }, - }, - expect: "file://notnil", - }, - { - d: &discoveryValue{ - v: &kubeadm.Discovery{ - Token: &kubeadm.TokenDiscovery{ - ID: "foo", - Secret: "bar", - Addresses: []string{"foobar"}, - }, - }, - }, expect: "token://foo.bar@foobar", - }, - } - for _, rt := range tests { - actual := rt.d.String() - if actual != rt.expect { - t.Errorf( - "failed discoveryValue string:\n\texpected: %s\n\t actual: %s", - rt.expect, - actual, - ) - } - } -} - -func TestType(t *testing.T) { - tests := []struct { - d *discoveryValue - expect string - }{ - {d: &discoveryValue{}, expect: "discovery"}, - } - for _, rt := range tests { - actual := rt.d.Type() - if actual != rt.expect { - t.Errorf( - "failed discoveryValue type:\n\texpected: %s\n\t actual: %s", - rt.expect, - actual, - ) - } - } -} - -func TestSet(t *testing.T) { - tests := []struct { - d *discoveryValue - s string - expect bool - }{ - {d: &discoveryValue{v: &kubeadm.Discovery{}}, s: "", expect: false}, - {d: &discoveryValue{v: &kubeadm.Discovery{}}, s: "https://example.com", expect: true}, - } - for _, rt := range tests { - actual := rt.d.Set(rt.s) - if (actual == nil) != rt.expect { - t.Errorf( - "failed discoveryValue set:\n\texpected: %t\n\t actual: %t", - rt.expect, - (actual == nil), - ) - } - } -} - -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", - expect: kubeadm.Discovery{ - Token: &kubeadm.TokenDiscovery{ - ID: "c05de9", - Secret: "ab224260fb3cd718", - }, - }, - }, - { - url: "token://c05de9:ab224260fb3cd718@", - expect: kubeadm.Discovery{ - Token: &kubeadm.TokenDiscovery{ - ID: "c05de9", - Secret: "ab224260fb3cd718", - }, - }, - }, - { - 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 equal but got:\n\tactual: %s\n\texpected: %s", spew.Sdump(d), spew.Sdump(c.expect)) - } - - } -} diff --git a/cmd/kubeadm/app/discovery/https/BUILD b/cmd/kubeadm/app/discovery/https/BUILD index ec6dcb07a97..029179ae507 100644 --- a/cmd/kubeadm/app/discovery/https/BUILD +++ b/cmd/kubeadm/app/discovery/https/BUILD @@ -11,7 +11,11 @@ go_library( name = "go_default_library", srcs = ["https.go"], tags = ["automanaged"], - deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], + deps = [ + "//cmd/kubeadm/app/discovery/file:go_default_library", + "//vendor:k8s.io/client-go/tools/clientcmd", + "//vendor:k8s.io/client-go/tools/clientcmd/api", + ], ) filegroup( diff --git a/cmd/kubeadm/app/discovery/https/https.go b/cmd/kubeadm/app/discovery/https/https.go index d4c9df20061..4ad2fbdcd5b 100644 --- a/cmd/kubeadm/app/discovery/https/https.go +++ b/cmd/kubeadm/app/discovery/https/https.go @@ -17,13 +17,32 @@ limitations under the License. package https import ( - "net/url" + "io/ioutil" + "net/http" - "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/kubernetes/cmd/kubeadm/app/discovery/file" ) -func Parse(u *url.URL, c *kubeadm.Discovery) { - c.HTTPS = &kubeadm.HTTPSDiscovery{ - URL: u.String(), +// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk +// securely to the API Server using the provided CA cert and +// optionally refreshes the cluster-info information from the cluster-info ConfigMap +func RetrieveValidatedClusterInfo(httpsURL string) (*clientcmdapi.Cluster, error) { + response, err := http.Get(httpsURL) + if err != nil { + return nil, err } + defer response.Body.Close() + + kubeconfig, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + clusterinfo, err := clientcmd.Load(kubeconfig) + if err != nil { + return nil, err + } + return file.ValidateClusterInfo(clusterinfo) } diff --git a/cmd/kubeadm/app/discovery/token/BUILD b/cmd/kubeadm/app/discovery/token/BUILD index c7947ab71a4..5068a683467 100644 --- a/cmd/kubeadm/app/discovery/token/BUILD +++ b/cmd/kubeadm/app/discovery/token/BUILD @@ -5,13 +5,25 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( name = "go_default_library", srcs = ["token.go"], tags = ["automanaged"], - deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], + deps = [ + "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/util/kubeconfig:go_default_library", + "//cmd/kubeadm/app/util/token:go_default_library", + "//pkg/bootstrap/api:go_default_library", + "//pkg/controller/bootstrap:go_default_library", + "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", + "//vendor:k8s.io/apimachinery/pkg/util/wait", + "//vendor:k8s.io/client-go/pkg/api/v1", + "//vendor:k8s.io/client-go/tools/clientcmd", + "//vendor:k8s.io/client-go/tools/clientcmd/api", + ], ) filegroup( @@ -26,3 +38,14 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["token_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//cmd/kubeadm/app/util/kubeconfig:go_default_library", + "//vendor:k8s.io/client-go/tools/clientcmd/api", + ], +) diff --git a/cmd/kubeadm/app/discovery/token/token.go b/cmd/kubeadm/app/discovery/token/token.go index 3f3e62cfcd1..c0d139a2288 100644 --- a/cmd/kubeadm/app/discovery/token/token.go +++ b/cmd/kubeadm/app/discovery/token/token.go @@ -17,29 +17,117 @@ limitations under the License. package token import ( - "net/url" - "strings" + "fmt" + "sync" - "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" + bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" + "k8s.io/kubernetes/pkg/controller/bootstrap" ) -func Parse(u *url.URL, c *kubeadm.Discovery) { - var ( - hosts []string - tokenID, token string - ) - if u.Host != "" { - hosts = strings.Split(u.Host, ",") +const BootstrapUser = "token-bootstrap-client" + +// RetrieveValidatedClusterInfo connects to the API Server and tries to fetch the cluster-info ConfigMap +// It then makes sure it can trust the API Server by looking at the JWS-signed tokens +func RetrieveValidatedClusterInfo(discoveryToken string, tokenAPIServers []string) (*clientcmdapi.Cluster, error) { + + tokenId, tokenSecret, err := tokenutil.ParseToken(discoveryToken) + if err != nil { + return nil, err } - if u.User != nil { - if p, ok := u.User.Password(); ok { - tokenID = u.User.Username() - token = p + + // The function below runs for every endpoint, and all endpoints races with each other. + // The endpoint that wins the race and completes the task first gets its kubeconfig returned below + baseKubeConfig := runForEndpointsAndReturnFirst(tokenAPIServers, func(endpoint string) (*clientcmdapi.Config, error) { + + bootstrapConfig := buildInsecureBootstrapKubeConfig(endpoint) + clusterName := bootstrapConfig.Contexts[bootstrapConfig.CurrentContext].Cluster + + client, err := kubeconfigutil.KubeConfigToClientSet(bootstrapConfig) + if err != nil { + return nil, err } - } - c.Token = &kubeadm.TokenDiscovery{ - ID: tokenID, - Secret: token, - Addresses: hosts, - } + + fmt.Printf("[discovery] Created cluster-info discovery client, requesting info from %q\n", bootstrapConfig.Clusters[clusterName].Server) + + var clusterinfo *v1.ConfigMap + wait.PollInfinite(constants.DiscoveryRetryInterval, func() (bool, error) { + var err error + clusterinfo, err = client.CoreV1().ConfigMaps(metav1.NamespacePublic).Get(bootstrapapi.ConfigMapClusterInfo, metav1.GetOptions{}) + if err != nil { + fmt.Printf("[discovery] Failed to request cluster info, will try again: [%s]\n", err) + return false, nil + } + return true, nil + }) + + kubeConfigString, ok := clusterinfo.Data[bootstrapapi.KubeConfigKey] + if !ok || len(kubeConfigString) == 0 { + return nil, fmt.Errorf("there is no %s key in the %s ConfigMap. This API Server isn't set up for token bootstrapping, can't connect", bootstrapapi.KubeConfigKey, bootstrapapi.ConfigMapClusterInfo) + } + detachedJWSToken, ok := clusterinfo.Data[bootstrapapi.JWSSignatureKeyPrefix+tokenId] + if !ok || len(detachedJWSToken) == 0 { + return nil, fmt.Errorf("there is no JWS signed token in the %s ConfigMap. This token id %q is invalid for this cluster, can't connect", bootstrapapi.ConfigMapClusterInfo, tokenId) + } + if !bootstrap.DetachedTokenIsValid(detachedJWSToken, kubeConfigString, tokenId, tokenSecret) { + return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object, can't trust this API Server") + } + + finalConfig, err := clientcmd.Load([]byte(kubeConfigString)) + if err != nil { + return nil, fmt.Errorf("couldn't parse the kubeconfig file in the %s configmap: %v", bootstrapapi.ConfigMapClusterInfo, err) + } + + fmt.Printf("[discovery] Cluster info signature and contents are valid, will use API Server %q\n", bootstrapConfig.Clusters[clusterName].Server) + return finalConfig, nil + }) + + return kubeconfigutil.GetClusterFromKubeConfig(baseKubeConfig), nil +} + +// buildInsecureBootstrapKubeConfig makes a KubeConfig object that connects insecurely to the API Server for bootstrapping purposes +func buildInsecureBootstrapKubeConfig(endpoint string) *clientcmdapi.Config { + masterEndpoint := fmt.Sprintf("https://%s", endpoint) + clusterName := "kubernetes" + bootstrapConfig := kubeconfigutil.CreateBasic(masterEndpoint, clusterName, BootstrapUser, []byte{}) + bootstrapConfig.Clusters[clusterName].InsecureSkipTLSVerify = true + return bootstrapConfig +} + +// runForEndpointsAndReturnFirst loops the endpoints slice and let's the endpoints race for connecting to the master +func runForEndpointsAndReturnFirst(endpoints []string, fetchKubeConfigFunc func(string) (*clientcmdapi.Config, error)) *clientcmdapi.Config { + stopChan := make(chan struct{}) + var resultingKubeConfig *clientcmdapi.Config + var once sync.Once + var wg sync.WaitGroup + for _, endpoint := range endpoints { + wg.Add(1) + go func(apiEndpoint string) { + defer wg.Done() + wait.Until(func() { + fmt.Printf("[discovery] Trying to connect to API Server %q\n", apiEndpoint) + cfg, err := fetchKubeConfigFunc(apiEndpoint) + if err != nil { + fmt.Printf("[discovery] Failed to connect to API Server %q: %v\n", apiEndpoint, err) + return + } + fmt.Printf("[discovery] Successfully established connection with API Server %q\n", apiEndpoint) + + // connection established, stop all wait threads + once.Do(func() { + close(stopChan) + resultingKubeConfig = cfg + }) + }, constants.DiscoveryRetryInterval, stopChan) + }(endpoint) + } + wg.Wait() + return resultingKubeConfig } diff --git a/cmd/kubeadm/app/discovery/token/token_test.go b/cmd/kubeadm/app/discovery/token/token_test.go new file mode 100644 index 00000000000..06012608bd1 --- /dev/null +++ b/cmd/kubeadm/app/discovery/token/token_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package token + +import ( + "strconv" + "testing" + "time" + + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" +) + +func TestRunForEndpointsAndReturnFirst(t *testing.T) { + tests := []struct { + endpoints []string + expectedEndpoint string + }{ + { + endpoints: []string{"1", "2", "3"}, + expectedEndpoint: "1", + }, + { + endpoints: []string{"6", "5"}, + expectedEndpoint: "5", + }, + { + endpoints: []string{"10", "4"}, + expectedEndpoint: "4", + }, + } + for _, rt := range tests { + returnKubeConfig := runForEndpointsAndReturnFirst(rt.endpoints, func(endpoint string) (*clientcmdapi.Config, error) { + timeout, _ := strconv.Atoi(endpoint) + time.Sleep(time.Second * time.Duration(timeout)) + return kubeconfigutil.CreateBasic(endpoint, "foo", "foo", []byte{}), nil + }) + endpoint := returnKubeConfig.Clusters[returnKubeConfig.Contexts[returnKubeConfig.CurrentContext].Cluster].Server + if endpoint != rt.expectedEndpoint { + t.Errorf( + "failed TestRunForEndpointsAndReturnFirst:\n\texpected: %s\n\t actual: %s", + endpoint, + rt.expectedEndpoint, + ) + } + } +} diff --git a/cmd/kubeadm/app/master/BUILD b/cmd/kubeadm/app/master/BUILD index b98fece40b8..66ed69839a3 100644 --- a/cmd/kubeadm/app/master/BUILD +++ b/cmd/kubeadm/app/master/BUILD @@ -12,7 +12,6 @@ go_library( name = "go_default_library", srcs = [ "apiclient.go", - "discovery.go", "manifests.go", "selfhosted.go", "templates.go", @@ -24,6 +23,7 @@ go_library( "//cmd/kubeadm/app/images:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", + "//pkg/bootstrap/api:go_default_library", "//pkg/kubeapiserver/authorizer/modes:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//vendor:github.com/ghodss/yaml", @@ -37,7 +37,6 @@ go_library( "//vendor:k8s.io/client-go/pkg/api", "//vendor:k8s.io/client-go/pkg/api/v1", "//vendor:k8s.io/client-go/pkg/apis/extensions/v1beta1", - "//vendor:k8s.io/client-go/util/cert", ], ) diff --git a/cmd/kubeadm/app/master/discovery.go b/cmd/kubeadm/app/master/discovery.go deleted file mode 100644 index 5e932f7c4df..00000000000 --- a/cmd/kubeadm/app/master/discovery.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -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 master - -import ( - "crypto/x509" - "encoding/json" - "fmt" - "path" - "runtime" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kuberuntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/wait" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/pkg/api" - "k8s.io/client-go/pkg/api/v1" - extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1" - certutil "k8s.io/client-go/util/cert" - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" -) - -const ( - kubeDiscoverySecretName = "clusterinfo" - kubeDiscoveryName = "kube-discovery" -) - -// TODO: Remove this file as soon as jbeda's token discovery refactoring PR has merged - -func encodeKubeDiscoverySecretData(dcfg *kubeadmapi.TokenDiscovery, apicfg kubeadmapi.API, caCert *x509.Certificate) map[string][]byte { - var ( - data = map[string][]byte{} - tokenMap = map[string]string{} - ) - - tokenMap[dcfg.ID] = dcfg.Secret - - data["endpoint-list.json"], _ = json.Marshal([]string{fmt.Sprintf("https://%s:%d", apicfg.AdvertiseAddress, apicfg.BindPort)}) - data["token-map.json"], _ = json.Marshal(tokenMap) - data["ca.pem"] = certutil.EncodeCertPEM(caCert) - - return data -} - -func CreateDiscoveryDeploymentAndSecret(cfg *kubeadmapi.MasterConfiguration, client *clientset.Clientset) error { - caCertificatePath := path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName) - caCerts, err := certutil.CertsFromFile(caCertificatePath) - if err != nil { - return fmt.Errorf("couldn't load the CA certificate file %s: %v", caCertificatePath, err) - } - - // We are only putting one certificate in the certificate pem file, so it's safe to just pick the first one - // TODO: Support multiple certs here in order to be able to rotate certs - caCert := caCerts[0] - - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: kubeDiscoverySecretName}, - Type: v1.SecretTypeOpaque, - Data: encodeKubeDiscoverySecretData(cfg.Discovery.Token, cfg.API, caCert), - } - if _, err := client.Secrets(metav1.NamespaceSystem).Create(secret); err != nil { - return fmt.Errorf("failed to create %q secret [%v]", kubeDiscoverySecretName, err) - } - - if err := createDiscoveryDeployment(client); err != nil { - return err - } - - fmt.Println("[token-discovery] Created the kube-discovery deployment, waiting for it to become ready") - - start := time.Now() - wait.PollInfinite(kubeadmconstants.APICallRetryInterval, func() (bool, error) { - d, err := client.Extensions().Deployments(metav1.NamespaceSystem).Get(kubeDiscoveryName, metav1.GetOptions{}) - if err != nil { - return false, nil - } - if d.Status.AvailableReplicas < 1 { - return false, nil - } - return true, nil - }) - fmt.Printf("[token-discovery] kube-discovery is ready after %f seconds\n", time.Since(start).Seconds()) - - return nil -} - -func createDiscoveryDeployment(client *clientset.Clientset) error { - discoveryBytes, err := kubeadmutil.ParseTemplate(KubeDiscoveryDeployment, struct{ ImageRepository, Arch, MasterTaintKey string }{ - ImageRepository: kubeadmapi.GlobalEnvParams.RepositoryPrefix, - Arch: runtime.GOARCH, - MasterTaintKey: kubeadmconstants.LabelNodeRoleMaster, - }) - if err != nil { - return fmt.Errorf("error when parsing kube-discovery template: %v", err) - } - - discoveryDeployment := &extensions.Deployment{} - if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), discoveryBytes, discoveryDeployment); err != nil { - return fmt.Errorf("unable to decode kube-discovery deployment %v", err) - } - - // TODO: Set this in the yaml spec instead - discoveryDeployment.Spec.Template.Spec.Tolerations = []v1.Toleration{kubeadmconstants.MasterToleration} - - if _, err := client.ExtensionsV1beta1().Deployments(metav1.NamespaceSystem).Create(discoveryDeployment); err != nil { - return fmt.Errorf("unable to create a new discovery deployment: %v", err) - } - return nil -} diff --git a/cmd/kubeadm/app/master/manifests.go b/cmd/kubeadm/app/master/manifests.go index 5ef8464949c..e3d68887523 100644 --- a/cmd/kubeadm/app/master/manifests.go +++ b/cmd/kubeadm/app/master/manifests.go @@ -32,6 +32,7 @@ import ( kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/images" + bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -301,20 +302,20 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [ } defaultArguments := map[string]string{ - "insecure-port": "0", - "admission-control": kubeadmconstants.DefaultAdmissionControl, - "service-cluster-ip-range": cfg.Networking.ServiceSubnet, - "service-account-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName), - "client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName), - "tls-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName), - "tls-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName), - "kubelet-client-certificate": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName), - "kubelet-client-key": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName), - "token-auth-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CSVTokenFileName), - "secure-port": fmt.Sprintf("%d", cfg.API.BindPort), - "allow-privileged": "true", - "storage-backend": "etcd3", - "kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname", + "insecure-port": "0", + "admission-control": kubeadmconstants.DefaultAdmissionControl, + "service-cluster-ip-range": cfg.Networking.ServiceSubnet, + "service-account-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName), + "client-ca-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName), + "tls-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName), + "tls-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName), + "kubelet-client-certificate": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName), + "kubelet-client-key": path.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName), + "secure-port": fmt.Sprintf("%d", cfg.API.BindPort), + "allow-privileged": "true", + "experimental-bootstrap-token-auth": "true", + "storage-backend": "etcd3", + "kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname", // add options to configure the front proxy. Without the generated client cert, this will never be useable // so add it unconditionally with recommended values "requestheader-username-headers": "X-Remote-User", @@ -379,8 +380,9 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted "service-account-private-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName), "cluster-signing-cert-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CACertName), "cluster-signing-key-file": path.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName), - "insecure-experimental-approve-all-kubelet-csrs-for-group": kubeadmconstants.CSVTokenBootstrapGroup, + "insecure-experimental-approve-all-kubelet-csrs-for-group": bootstrapapi.BootstrapGroup, "use-service-account-credentials": "true", + "controllers": "*,bootstrapsigner,tokencleaner", } command = getComponentBaseCommand(controllerManager) diff --git a/cmd/kubeadm/app/master/manifests_test.go b/cmd/kubeadm/app/master/manifests_test.go index 33fc3bdc341..fc17f9b281a 100644 --- a/cmd/kubeadm/app/master/manifests_test.go +++ b/cmd/kubeadm/app/master/manifests_test.go @@ -397,11 +397,11 @@ func TestGetAPIServerCommand(t *testing.T) { "--tls-private-key-file=" + testCertsDir + "/apiserver.key", "--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt", "--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key", - "--token-auth-file=" + testCertsDir + "/tokens.csv", fmt.Sprintf("--secure-port=%d", 123), "--allow-privileged=true", "--storage-backend=etcd3", "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname", + "--experimental-bootstrap-token-auth=true", "--requestheader-username-headers=X-Remote-User", "--requestheader-group-headers=X-Remote-Group", "--requestheader-extra-headers-prefix=X-Remote-Extra-", @@ -429,11 +429,11 @@ func TestGetAPIServerCommand(t *testing.T) { "--tls-private-key-file=" + testCertsDir + "/apiserver.key", "--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt", "--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key", - "--token-auth-file=" + testCertsDir + "/tokens.csv", fmt.Sprintf("--secure-port=%d", 123), "--allow-privileged=true", "--storage-backend=etcd3", "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname", + "--experimental-bootstrap-token-auth=true", "--requestheader-username-headers=X-Remote-User", "--requestheader-group-headers=X-Remote-Group", "--requestheader-extra-headers-prefix=X-Remote-Extra-", @@ -462,11 +462,11 @@ func TestGetAPIServerCommand(t *testing.T) { "--tls-private-key-file=" + testCertsDir + "/apiserver.key", "--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt", "--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key", - "--token-auth-file=" + testCertsDir + "/tokens.csv", fmt.Sprintf("--secure-port=%d", 123), "--allow-privileged=true", "--storage-backend=etcd3", "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname", + "--experimental-bootstrap-token-auth=true", "--requestheader-username-headers=X-Remote-User", "--requestheader-group-headers=X-Remote-Group", "--requestheader-extra-headers-prefix=X-Remote-Extra-", @@ -509,8 +509,9 @@ func TestGetControllerManagerCommand(t *testing.T) { "--service-account-private-key-file=" + testCertsDir + "/sa.key", "--cluster-signing-cert-file=" + testCertsDir + "/ca.crt", "--cluster-signing-key-file=" + testCertsDir + "/ca.key", - "--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap", + "--insecure-experimental-approve-all-kubelet-csrs-for-group=system:bootstrappers", "--use-service-account-credentials=true", + "--controllers=*,bootstrapsigner,tokencleaner", }, }, { @@ -527,8 +528,9 @@ func TestGetControllerManagerCommand(t *testing.T) { "--service-account-private-key-file=" + testCertsDir + "/sa.key", "--cluster-signing-cert-file=" + testCertsDir + "/ca.crt", "--cluster-signing-key-file=" + testCertsDir + "/ca.key", - "--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap", + "--insecure-experimental-approve-all-kubelet-csrs-for-group=system:bootstrappers", "--use-service-account-credentials=true", + "--controllers=*,bootstrapsigner,tokencleaner", "--cloud-provider=foo", }, }, @@ -546,8 +548,9 @@ func TestGetControllerManagerCommand(t *testing.T) { "--service-account-private-key-file=" + testCertsDir + "/sa.key", "--cluster-signing-cert-file=" + testCertsDir + "/ca.crt", "--cluster-signing-key-file=" + testCertsDir + "/ca.key", - "--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap", + "--insecure-experimental-approve-all-kubelet-csrs-for-group=system:bootstrappers", "--use-service-account-credentials=true", + "--controllers=*,bootstrapsigner,tokencleaner", "--allocate-node-cidrs=true", "--cluster-cidr=bar", }, diff --git a/cmd/kubeadm/app/master/templates.go b/cmd/kubeadm/app/master/templates.go index 1c907d39130..5b2e88301cf 100644 --- a/cmd/kubeadm/app/master/templates.go +++ b/cmd/kubeadm/app/master/templates.go @@ -39,62 +39,5 @@ spec: - image: {{ .ImageRepository }}/pause-{{ .Arch }}:3.0 name: dummy hostNetwork: true -` - KubeDiscoveryDeployment = ` -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - labels: - k8s-app: kube-discovery - name: kube-discovery - namespace: kube-system -spec: - replicas: 1 - selector: - matchLabels: - k8s-app: kube-discovery - template: - metadata: - labels: - k8s-app: kube-discovery - # TODO: I guess we can remove all these cluster-service labels... - kubernetes.io/cluster-service: "true" - spec: - containers: - - name: kube-discovery - image: {{ .ImageRepository }}/kube-discovery-{{ .Arch }}:1.0 - imagePullPolicy: IfNotPresent - command: - - /usr/local/bin/kube-discovery - ports: - - containerPort: 9898 - hostPort: 9898 - name: http - volumeMounts: - - mountPath: /tmp/secret - name: clusterinfo - readOnly: true - hostNetwork: true - # TODO: Why doesn't the Decoder recognize this new field and decode it properly? Right now it's ignored - # tolerations: - # - key: {{ .MasterTaintKey }} - # effect: NoSchedule - securityContext: - seLinuxOptions: - type: spc_t - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: beta.kubernetes.io/arch - operator: In - values: - - {{ .Arch }} - volumes: - - name: clusterinfo - secret: - defaultMode: 420 - secretName: clusterinfo ` ) diff --git a/cmd/kubeadm/app/node/BUILD b/cmd/kubeadm/app/node/BUILD index 6268a30c19a..23c29d58c20 100644 --- a/cmd/kubeadm/app/node/BUILD +++ b/cmd/kubeadm/app/node/BUILD @@ -11,22 +11,16 @@ load( go_library( name = "go_default_library", srcs = [ - "bootstrap.go", "csr.go", - "discovery.go", + "validate.go", ], tags = ["automanaged"], deps = [ - "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", - "//cmd/kubeadm/app/util/token:go_default_library", "//pkg/kubelet/util/csr:go_default_library", - "//vendor:github.com/square/go-jose", "//vendor:k8s.io/apimachinery/pkg/types", - "//vendor:k8s.io/apimachinery/pkg/util/wait", "//vendor:k8s.io/client-go/kubernetes", - "//vendor:k8s.io/client-go/pkg/apis/certificates", - "//vendor:k8s.io/client-go/tools/clientcmd", + "//vendor:k8s.io/client-go/pkg/apis/certificates/v1beta1", "//vendor:k8s.io/client-go/tools/clientcmd/api", "//vendor:k8s.io/client-go/util/cert", ], @@ -34,14 +28,10 @@ go_library( go_test( name = "go_default_test", - srcs = [ - "bootstrap_test.go", - "discovery_test.go", - ], + srcs = ["validate_test.go"], library = ":go_default_library", tags = ["automanaged"], deps = [ - "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/version", "//vendor:k8s.io/client-go/discovery", diff --git a/cmd/kubeadm/app/node/bootstrap.go b/cmd/kubeadm/app/node/bootstrap.go deleted file mode 100644 index 11551e4cca4..00000000000 --- a/cmd/kubeadm/app/node/bootstrap.go +++ /dev/null @@ -1,146 +0,0 @@ -/* -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 node - -import ( - "fmt" - "os" - "sync" - "time" - - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/pkg/apis/certificates" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" -) - -// retryTimeout between the subsequent attempts to connect -// to an API endpoint -const retryTimeout = 5 - -type apiClient struct { - clientSet *clientset.Clientset - clientConfig *clientcmdapi.Config -} - -// EstablishMasterConnection establishes a connection with exactly one of the provided API endpoints. -// 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 -// connection attempts for other endpoints and returns the valid client configuration, if any. -func EstablishMasterConnection(c *kubeadmapi.TokenDiscovery, clusterInfo *kubeadmapi.ClusterInfo) (*clientcmdapi.Config, error) { - hostName, err := os.Hostname() - if err != nil { - return nil, fmt.Errorf("failed to get node hostname [%v]", err) - } - // TODO(phase1+) https://github.com/kubernetes/kubernetes/issues/33641 - nodeName := types.NodeName(hostName) - - endpoints := clusterInfo.Endpoints - caCert := []byte(clusterInfo.CertificateAuthorities[0]) - - stopChan := make(chan struct{}) - var clientConfig *clientcmdapi.Config - var once sync.Once - var wg sync.WaitGroup - for _, endpoint := range endpoints { - ac, err := createClients(caCert, endpoint, tokenutil.BearerToken(c), nodeName) - if err != nil { - fmt.Printf("[bootstrap] Warning: %s. Skipping endpoint %s\n", err, endpoint) - continue - } - wg.Add(1) - go func(apiEndpoint string) { - defer wg.Done() - wait.Until(func() { - fmt.Printf("[bootstrap] Trying to connect to endpoint %s\n", apiEndpoint) - err := checkAPIEndpoint(ac.clientSet, apiEndpoint) - if err != nil { - fmt.Printf("[bootstrap] Endpoint check failed [%v]\n", err) - return - } - fmt.Printf("[bootstrap] Successfully established connection with endpoint %q\n", apiEndpoint) - - // connection established, stop all wait threads - once.Do(func() { - close(stopChan) - clientConfig = ac.clientConfig - }) - }, retryTimeout*time.Second, stopChan) - }(endpoint) - } - wg.Wait() - - if clientConfig == nil { - return nil, fmt.Errorf("failed to create bootstrap clients for any of the provided API endpoints") - } - - return clientConfig, nil -} - -// creates a set of clients for this endpoint -func createClients(caCert []byte, endpoint, token string, nodeName types.NodeName) (*apiClient, error) { - clientConfig := kubeconfigutil.CreateWithToken( - endpoint, - "kubernetes", - fmt.Sprintf("kubelet-%s", nodeName), - caCert, - token, - ) - - bootstrapClientConfig, err := clientcmd.NewDefaultClientConfig(*clientConfig, &clientcmd.ConfigOverrides{}).ClientConfig() - if err != nil { - return nil, fmt.Errorf("failed to create API client configuration [%v]", err) - } - clientSet, err := clientset.NewForConfig(bootstrapClientConfig) - if err != nil { - return nil, fmt.Errorf("failed to create clients for the API endpoint %q: [%v]", endpoint, err) - } - - ac := &apiClient{ - clientSet: clientSet, - clientConfig: clientConfig, - } - return ac, nil -} - -// checks the connection requirements for a specific API endpoint -func checkAPIEndpoint(clientSet *clientset.Clientset, endpoint string) error { - // check general connectivity - version, err := clientSet.DiscoveryClient.ServerVersion() - if err != nil { - return fmt.Errorf("failed to connect to %q [%v]", endpoint, err) - } - fmt.Printf("[bootstrap] Detected server version: %s\n", version.String()) - - // check certificates API - serverGroups, err := clientSet.DiscoveryClient.ServerGroups() - if err != nil { - return fmt.Errorf("certificate API check failed: failed to retrieve a list of supported API objects [%v]", err) - } - for _, group := range serverGroups.Groups { - if group.Name == certificates.GroupName { - return nil - } - } - return fmt.Errorf("certificate API check failed: API version %s does not support certificates API, use v1.4.0 or newer", - version.String()) -} diff --git a/cmd/kubeadm/app/node/csr.go b/cmd/kubeadm/app/node/csr.go index 55a6601c9c3..2f4f2b44d61 100644 --- a/cmd/kubeadm/app/node/csr.go +++ b/cmd/kubeadm/app/node/csr.go @@ -18,32 +18,23 @@ package node import ( "fmt" - "os" "k8s.io/apimachinery/pkg/types" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" certutil "k8s.io/client-go/util/cert" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/pkg/kubelet/util/csr" ) -// PerformTLSBootstrap executes a node certificate signing request. -func PerformTLSBootstrap(cfg *clientcmdapi.Config) error { - hostName, err := os.Hostname() - if err != nil { - return err - } - name := types.NodeName(hostName) +const CSRContextAndUser = "kubelet-csr" - rc, err := clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{}).ClientConfig() - if err != nil { - return err - } - c, err := clientset.NewForConfig(rc) +// PerformTLSBootstrap executes a node certificate signing request. +func PerformTLSBootstrap(cfg *clientcmdapi.Config, hostName string) error { + client, err := kubeconfigutil.KubeConfigToClientSet(cfg) if err != nil { return err } + fmt.Println("[csr] Created API client to obtain unique certificate for this node, generating keys and certificate signing request") key, err := certutil.MakeEllipticPrivateKeyPEM() @@ -51,21 +42,20 @@ func PerformTLSBootstrap(cfg *clientcmdapi.Config) error { return fmt.Errorf("failed to generate private key [%v]", err) } - cert, err := csr.RequestNodeCertificate(c.Certificates().CertificateSigningRequests(), key, name) + cert, err := csr.RequestNodeCertificate(client.CertificatesV1beta1().CertificateSigningRequests(), key, types.NodeName(hostName)) if err != nil { return fmt.Errorf("failed to request signed certificate from the API server [%v]", err) } - fmt.Printf("[csr] Received signed certificate from the API server") - fmt.Println("[csr] Generating kubelet configuration") + fmt.Println("[csr] Received signed certificate from the API server, generating KubeConfig...") - cfg.AuthInfos["kubelet"] = &clientcmdapi.AuthInfo{ + cfg.AuthInfos[CSRContextAndUser] = &clientcmdapi.AuthInfo{ ClientKeyData: key, ClientCertificateData: cert, } - cfg.Contexts["kubelet"] = &clientcmdapi.Context{ - AuthInfo: "kubelet", + cfg.Contexts[CSRContextAndUser] = &clientcmdapi.Context{ + AuthInfo: CSRContextAndUser, Cluster: cfg.Contexts[cfg.CurrentContext].Cluster, } - cfg.CurrentContext = "kubelet" + cfg.CurrentContext = CSRContextAndUser return nil } diff --git a/cmd/kubeadm/app/node/discovery.go b/cmd/kubeadm/app/node/discovery.go deleted file mode 100644 index 439d55a26c7..00000000000 --- a/cmd/kubeadm/app/node/discovery.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -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 node - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - jose "github.com/square/go-jose" - "k8s.io/apimachinery/pkg/util/wait" - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" -) - -// the amount of time to wait between each request to the discovery API -const discoveryRetryTimeout = 5 * time.Second - -func RetrieveTrustedClusterInfo(d *kubeadmapi.TokenDiscovery) (*kubeadmapi.ClusterInfo, error) { - if len(d.Addresses) == 0 { - return nil, fmt.Errorf("the address is required to generate the requestURL") - } - requestURL := fmt.Sprintf("http://%s/cluster-info/v1/?token-id=%s", d.Addresses[0], d.ID) - req, err := http.NewRequest("GET", requestURL, nil) - if err != nil { - return nil, fmt.Errorf("failed to construct an HTTP request [%v]", err) - } - - fmt.Printf("[discovery] Created cluster info discovery client, requesting info from %q\n", requestURL) - - var res *http.Response - wait.PollInfinite(discoveryRetryTimeout, func() (bool, error) { - res, err = http.DefaultClient.Do(req) - if err != nil { - fmt.Printf("[discovery] Failed to request cluster info, will try again: [%s]\n", err) - return false, nil - } - return true, nil - }) - - buf := new(bytes.Buffer) - io.Copy(buf, res.Body) - res.Body.Close() - - object, err := jose.ParseSigned(buf.String()) - if err != nil { - return nil, fmt.Errorf("failed to parse response as JWS object [%v]", err) - } - - fmt.Println("[discovery] Cluster info object received, verifying signature using given token") - - output, err := object.Verify([]byte(d.Secret)) - if err != nil { - return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object [%v]", err) - } - - clusterInfo := kubeadmapi.ClusterInfo{} - - if err := json.Unmarshal(output, &clusterInfo); err != nil { - return nil, fmt.Errorf("failed to decode received cluster info object [%v]", err) - } - - if len(clusterInfo.CertificateAuthorities) == 0 || len(clusterInfo.Endpoints) == 0 { - return nil, fmt.Errorf("cluster info object is invalid - no endpoint(s) and/or root CA certificate(s) found") - } - - // TODO(phase1+) print summary info about the CA certificate, along with the the checksum signature - // we also need an ability for the user to configure the client to validate received CA cert against a checksum - fmt.Printf("[discovery] Cluster info signature and contents are valid, will use API endpoints %v\n", clusterInfo.Endpoints) - return &clusterInfo, nil -} diff --git a/cmd/kubeadm/app/node/discovery_test.go b/cmd/kubeadm/app/node/discovery_test.go deleted file mode 100644 index c5efa5d8204..00000000000 --- a/cmd/kubeadm/app/node/discovery_test.go +++ /dev/null @@ -1,99 +0,0 @@ -/* -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 node - -import ( - "encoding/json" - "net" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" -) - -type rawJsonWebSignatureFake struct { - Payload string `json:"payload,omitempty"` - Signatures string `json:"signatures,omitempty"` - Protected string `json:"protected,omitempty"` - Header string `json:"header,omitempty"` - Signature string `json:"signature,omitempty"` -} - -func TestRetrieveTrustedClusterInfo(t *testing.T) { - j := rawJsonWebSignatureFake{} - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - switch req.URL.Path { - default: - output, err := json.Marshal(j) - if err != nil { - t.Errorf("unexpected encoding error: %v", err) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(output) - } - })) - defer srv.Close() - - pURL, err := url.Parse(srv.URL) - if err != nil { - t.Fatalf("encountered an error while trying to parse httptest server url: %v", err) - } - host, port, err := net.SplitHostPort(pURL.Host) - if err != nil { - t.Fatalf("encountered an error while trying to split host and port info from httptest server: %v", err) - } - iPort, err := strconv.Atoi(port) - if err != nil { - t.Fatalf("encountered an error while trying to convert string to int (httptest server port): %v", err) - } - tests := []struct { - h string - p int - payload string - expect bool - }{ - { - h: host, - p: iPort, - payload: "", - expect: false, - }, - { - h: host, - p: iPort, - payload: "foo", - expect: false, - }, - } - for _, rt := range tests { - j.Payload = rt.payload - nc := &kubeadmapi.TokenDiscovery{Addresses: []string{rt.h + ":" + strconv.Itoa(rt.p)}} - _, actual := RetrieveTrustedClusterInfo(nc) - if (actual == nil) != rt.expect { - t.Errorf( - "failed createClients:\n\texpected: %t\n\t actual: %t", - rt.expect, - (actual == nil), - ) - } - } -} diff --git a/cmd/kubeadm/app/node/validate.go b/cmd/kubeadm/app/node/validate.go new file mode 100644 index 00000000000..320c29c33a9 --- /dev/null +++ b/cmd/kubeadm/app/node/validate.go @@ -0,0 +1,51 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package node + +import ( + "fmt" + + clientset "k8s.io/client-go/kubernetes" + certsapi "k8s.io/client-go/pkg/apis/certificates/v1beta1" +) + +// ValidateAPIServer makes sure the server we're connecting to supports the Beta Certificates API +func ValidateAPIServer(client *clientset.Clientset) error { + version, err := client.DiscoveryClient.ServerVersion() + if err != nil { + return fmt.Errorf("failed to check server version: %v", err) + } + fmt.Printf("[bootstrap] Detected server version: %s\n", version.String()) + + // Check certificates API. If the server supports the version of the Certificates API we're using, we're good to go + serverGroups, err := client.DiscoveryClient.ServerGroups() + if err != nil { + return fmt.Errorf("certificate API check failed: failed to retrieve a list of supported API objects [%v]", err) + } + for _, group := range serverGroups.Groups { + if group.Name == certsapi.SchemeGroupVersion.Group { + for _, version := range group.Versions { + if version.Version == certsapi.SchemeGroupVersion.Version { + fmt.Printf("[bootstrap] The server supports the Certificates API (%s/%s)\n", certsapi.SchemeGroupVersion.Group, certsapi.SchemeGroupVersion.Version) + return nil + } + } + } + } + return fmt.Errorf("certificate API check failed: API server with version %s doesn't support Certificates API (%s/%s), use v1.6.0 or newer", + version.String(), certsapi.SchemeGroupVersion.Group, certsapi.SchemeGroupVersion.Version) +} diff --git a/cmd/kubeadm/app/node/bootstrap_test.go b/cmd/kubeadm/app/node/validate_test.go similarity index 50% rename from cmd/kubeadm/app/node/bootstrap_test.go rename to cmd/kubeadm/app/node/validate_test.go index 3d545406742..682a1ca5005 100644 --- a/cmd/kubeadm/app/node/bootstrap_test.go +++ b/cmd/kubeadm/app/node/validate_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Kubernetes Authors. +Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,150 +27,9 @@ import ( "k8s.io/client-go/discovery" clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" ) -func TestEstablishMasterConnection(t *testing.T) { - srv := stubServer(t) - defer srv.Close() - - tests := []struct { - c string - e string - expect bool - }{ - { - c: "", - e: "", - expect: false, - }, - { - c: "", - e: srv.URL, - expect: true, - }, - { - c: "foo", - e: srv.URL, - expect: true, - }, - } - for _, rt := range tests { - s := &kubeadmapi.TokenDiscovery{} - c := &kubeadmapi.ClusterInfo{Endpoints: []string{rt.e}, CertificateAuthorities: []string{rt.c}} - _, actual := EstablishMasterConnection(s, c) - if (actual == nil) != rt.expect { - t.Errorf( - "failed EstablishMasterConnection:\n\texpected: %t\n\t actual: %t", - rt.expect, - (actual == nil), - ) - } - } -} - -func TestEstablishMasterConnectionWithMultipleEndpoints(t *testing.T) { - // ref. https://github.com/kubernetes/kubernetes/issues/36988 - - srv := stubServer(t) - defer srv.Close() - - s := &kubeadmapi.TokenDiscovery{} - c := &kubeadmapi.ClusterInfo{Endpoints: []string{srv.URL, srv.URL}, CertificateAuthorities: []string{"foo"}} - - defer func() { - if r := recover(); r != nil { - t.Errorf("failed EstablishMasterConnectionWithMultipleEndpoints; got a panic.") - } - }() - - EstablishMasterConnection(s, c) -} - -func stubServer(t *testing.T) *httptest.Server { - expect := version.Info{ - Major: "foo", - Minor: "bar", - GitCommit: "baz", - } - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - var obj interface{} - switch req.URL.Path { - case "/api": - obj = &metav1.APIVersions{ - Versions: []string{ - "v1.4", - }, - } - output, err := json.Marshal(obj) - if err != nil { - t.Fatalf("unexpected encoding error: %v", err) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(output) - case "/apis": - obj = &metav1.APIGroupList{ - Groups: []metav1.APIGroup{ - { - Name: "certificates.k8s.io", - Versions: []metav1.GroupVersionForDiscovery{ - {GroupVersion: "extensions/v1beta1"}, - }, - }, - }, - } - output, err := json.Marshal(obj) - if err != nil { - t.Fatalf("unexpected encoding error: %v", err) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(output) - default: - output, err := json.Marshal(expect) - if err != nil { - t.Errorf("unexpected encoding error: %v", err) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(output) - } - })) - - return srv -} - -func TestCreateClients(t *testing.T) { - tests := []struct { - e string - expect bool - }{ - { - e: "", - expect: false, - }, - { - e: "foo", - expect: true, - }, - } - for _, rt := range tests { - _, actual := createClients(nil, rt.e, "", "") - if (actual == nil) != rt.expect { - t.Errorf( - "failed createClients:\n\texpected: %t\n\t actual: %t", - rt.expect, - (actual == nil), - ) - } - } -} - -func TestCheckAPIEndpoint(t *testing.T) { +func TestValidateAPIServer(t *testing.T) { expect := version.Info{ Major: "foo", Minor: "bar", @@ -191,7 +50,7 @@ func TestCheckAPIEndpoint(t *testing.T) { case "/api": obj = &metav1.APIVersions{ Versions: []string{ - "v1.4", + "v1.6.0", }, } output, err := json.Marshal(obj) @@ -222,7 +81,7 @@ func TestCheckAPIEndpoint(t *testing.T) { case "/api": obj = &metav1.APIVersions{ Versions: []string{ - "v1.4", + "v1.6.0", }, } output, err := json.Marshal(obj) @@ -239,7 +98,7 @@ func TestCheckAPIEndpoint(t *testing.T) { { Name: "certificates.k8s.io", Versions: []metav1.GroupVersionForDiscovery{ - {GroupVersion: "extensions/v1beta1"}, + {GroupVersion: "certificates.k8s.io/v1beta1", Version: "v1beta1"}, }, }, }, @@ -271,13 +130,13 @@ func TestCheckAPIEndpoint(t *testing.T) { rc := &restclient.Config{Host: rt.s.URL} c, err := discovery.NewDiscoveryClientForConfig(rc) if err != nil { - t.Fatalf("encountered an error while trying to get New Discovery Client: %v", err) + t.Fatalf("encountered an error while trying to get the new discovery client: %v", err) } cs := &clientset.Clientset{DiscoveryClient: c} - actual := checkAPIEndpoint(cs, "") + actual := ValidateAPIServer(cs) if (actual == nil) != rt.expect { t.Errorf( - "failed runChecks:\n\texpected: %t\n\t actual: %t", + "failed TestValidateAPIServer:\n\texpected: %t\n\t actual: %t", rt.expect, (actual == nil), ) diff --git a/cmd/kubeadm/app/phases/apiconfig/BUILD b/cmd/kubeadm/app/phases/apiconfig/BUILD index b1a4fe326b5..0d681f63a03 100644 --- a/cmd/kubeadm/app/phases/apiconfig/BUILD +++ b/cmd/kubeadm/app/phases/apiconfig/BUILD @@ -16,6 +16,7 @@ go_library( tags = ["automanaged"], deps = [ "//cmd/kubeadm/app/constants:go_default_library", + "//pkg/bootstrap/api:go_default_library", "//vendor:k8s.io/apimachinery/pkg/api/errors", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/types", diff --git a/cmd/kubeadm/app/phases/apiconfig/clusterroles.go b/cmd/kubeadm/app/phases/apiconfig/clusterroles.go index 20e35d9ed3a..795c4d272e8 100644 --- a/cmd/kubeadm/app/phases/apiconfig/clusterroles.go +++ b/cmd/kubeadm/app/phases/apiconfig/clusterroles.go @@ -24,33 +24,35 @@ import ( "k8s.io/client-go/pkg/api/v1" rbac "k8s.io/client-go/pkg/apis/rbac/v1beta1" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" ) const ( - // TODO: This role should eventually be a system:-prefixed, automatically bootstrapped ClusterRole - - // KubeDNSClusterRoleName sets the name for the kube-dns ClusterRole - KubeDNSClusterRoleName = "kubeadm:kube-dns" // KubeProxyClusterRoleName sets the name for the kube-proxy ClusterRole KubeProxyClusterRoleName = "system:node-proxier" // NodeBootstrapperClusterRoleName sets the name for the TLS Node Bootstrapper ClusterRole NodeBootstrapperClusterRoleName = "system:node-bootstrapper" + // BootstrapSignerClusterRoleName sets the name for the ClusterRole that allows access to ConfigMaps in the kube-public ns + BootstrapSignerClusterRoleName = "system:bootstrap-signer-clusterinfo" // Constants clusterRoleKind = "ClusterRole" + roleKind = "Role" serviceAccountKind = "ServiceAccount" rbacAPIGroup = "rbac.authorization.k8s.io" + anonymousUser = "system:anonymous" ) // TODO: Are there any unit tests that could be made for this file other than duplicating all values and logic in a separate file? // CreateRBACRules creates the essential RBAC rules for a minimally set-up cluster func CreateRBACRules(clientset *clientset.Clientset) error { - // Create the ClusterRoles we need for our RBAC rules - if err := CreateClusterRoles(clientset); err != nil { + if err := CreateRoles(clientset); err != nil { + return err + } + if err := CreateRoleBindings(clientset); err != nil { return err } - // Create the CreateClusterRoleBindings we need for our RBAC rules if err := CreateClusterRoleBindings(clientset); err != nil { return err } @@ -84,19 +86,53 @@ func CreateServiceAccounts(clientset *clientset.Clientset) error { return nil } -// CreateClusterRoles creates the ClusterRoles that aren't bootstrapped by the apiserver -func CreateClusterRoles(clientset *clientset.Clientset) error { - // TODO: Remove this ClusterRole when it's automatically bootstrapped in the apiserver - clusterRole := rbac.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{Name: KubeDNSClusterRoleName}, - Rules: []rbac.PolicyRule{ - rbac.NewRule("list", "watch").Groups("").Resources("endpoints", "services").RuleOrDie(), - // TODO: remove watch rule when https://github.com/kubernetes/kubernetes/pull/38816 gets merged - rbac.NewRule("get", "list", "watch").Groups("").Resources("configmaps").RuleOrDie(), +// CreateRoles creates namespaces RBAC Roles +func CreateRoles(clientset *clientset.Clientset) error { + roles := []rbac.Role{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: BootstrapSignerClusterRoleName, + Namespace: metav1.NamespacePublic, + }, + Rules: []rbac.PolicyRule{ + rbac.NewRule("get").Groups("").Resources("configmaps").RuleOrDie(), + }, }, } - if _, err := clientset.Rbac().ClusterRoles().Create(&clusterRole); err != nil { - return err + for _, role := range roles { + if _, err := clientset.RbacV1beta1().Roles(metav1.NamespacePublic).Create(&role); err != nil { + return err + } + } + return nil +} + +// CreateRoleBindings creates all namespaced and necessary bindings between bootstrapped & kubeadm-created ClusterRoles and subjects kubeadm is using +func CreateRoleBindings(clientset *clientset.Clientset) error { + roleBindings := []rbac.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "kubeadm:bootstrap-signer-clusterinfo", + Namespace: metav1.NamespacePublic, + }, + RoleRef: rbac.RoleRef{ + APIGroup: rbacAPIGroup, + Kind: roleKind, + Name: BootstrapSignerClusterRoleName, + }, + Subjects: []rbac.Subject{ + { + Kind: "User", + Name: anonymousUser, + }, + }, + }, + } + + for _, roleBinding := range roleBindings { + if _, err := clientset.RbacV1beta1().RoleBindings(metav1.NamespacePublic).Create(&roleBinding); err != nil { + return err + } } return nil } @@ -116,24 +152,7 @@ func CreateClusterRoleBindings(clientset *clientset.Clientset) error { Subjects: []rbac.Subject{ { Kind: "Group", - Name: kubeadmconstants.CSVTokenBootstrapGroup, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "kubeadm:kube-dns", - }, - RoleRef: rbac.RoleRef{ - APIGroup: rbacAPIGroup, - Kind: clusterRoleKind, - Name: KubeDNSClusterRoleName, - }, - Subjects: []rbac.Subject{ - { - Kind: serviceAccountKind, - Name: kubeadmconstants.KubeDNSServiceAccountName, - Namespace: metav1.NamespaceSystem, + Name: bootstrapapi.BootstrapGroup, }, }, }, @@ -157,7 +176,7 @@ func CreateClusterRoleBindings(clientset *clientset.Clientset) error { } for _, clusterRoleBinding := range clusterRoleBindings { - if _, err := clientset.Rbac().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil { + if _, err := clientset.RbacV1beta1().ClusterRoleBindings().Create(&clusterRoleBinding); err != nil { return err } } diff --git a/cmd/kubeadm/app/phases/token/BUILD b/cmd/kubeadm/app/phases/token/BUILD index c1e08e6ab6c..508df6db4b0 100644 --- a/cmd/kubeadm/app/phases/token/BUILD +++ b/cmd/kubeadm/app/phases/token/BUILD @@ -10,10 +10,7 @@ load( go_test( name = "go_default_test", - srcs = [ - "bootstrap_test.go", - "csv_test.go", - ], + srcs = ["bootstrap_test.go"], library = ":go_default_library", tags = ["automanaged"], deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], @@ -21,22 +18,18 @@ go_test( go_library( name = "go_default_library", - srcs = [ - "bootstrap.go", - "csv.go", - ], + srcs = ["bootstrap.go"], tags = ["automanaged"], deps = [ - "//cmd/kubeadm/app/apis/kubeadm:go_default_library", - "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//cmd/kubeadm/app/util/token:go_default_library", "//pkg/bootstrap/api:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", "//vendor:k8s.io/apimachinery/pkg/api/errors", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", - "//vendor:k8s.io/apimachinery/pkg/util/uuid", "//vendor:k8s.io/client-go/kubernetes", "//vendor:k8s.io/client-go/pkg/api/v1", + "//vendor:k8s.io/client-go/tools/clientcmd", + "//vendor:k8s.io/client-go/tools/clientcmd/api", ], ) diff --git a/cmd/kubeadm/app/phases/token/bootstrap.go b/cmd/kubeadm/app/phases/token/bootstrap.go index 6369a7616c1..602159e61e0 100644 --- a/cmd/kubeadm/app/phases/token/bootstrap.go +++ b/cmd/kubeadm/app/phases/token/bootstrap.go @@ -24,7 +24,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/pkg/api/v1" - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" ) @@ -32,26 +34,26 @@ import ( const tokenCreateRetries = 5 // CreateNewToken tries to create a token and fails if one with the same ID already exists -func CreateNewToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, tokenDuration time.Duration, usages []string, description string) error { - return UpdateOrCreateToken(client, d, true, tokenDuration, usages, description) +func CreateNewToken(client *clientset.Clientset, token string, tokenDuration time.Duration, usages []string, description string) error { + return UpdateOrCreateToken(client, token, true, tokenDuration, usages, description) } // UpdateOrCreateToken attempts to update a token with the given ID, or create if it does not already exist. -func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, failIfExists bool, tokenDuration time.Duration, usages []string, description string) error { - // Let's make sure the token is valid - if valid, err := tokenutil.ValidateToken(d); !valid { +func UpdateOrCreateToken(client *clientset.Clientset, token string, failIfExists bool, tokenDuration time.Duration, usages []string, description string) error { + tokenID, tokenSecret, err := tokenutil.ParseToken(token) + if err != nil { return err } - secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, d.ID) + secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID) var lastErr error for i := 0; i < tokenCreateRetries; i++ { secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{}) if err == nil { if failIfExists { - return fmt.Errorf("a token with id %q already exists", d.ID) + return fmt.Errorf("a token with id %q already exists", tokenID) } // Secret with this ID already exists, update it: - secret.Data = encodeTokenSecretData(d, tokenDuration, usages, description) + secret.Data = encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, description) if _, err := client.Secrets(metav1.NamespaceSystem).Update(secret); err == nil { return nil } else { @@ -67,7 +69,7 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove Name: secretName, }, Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken), - Data: encodeTokenSecretData(d, tokenDuration, usages, description), + Data: encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, description), } if _, err := client.Secrets(metav1.NamespaceSystem).Create(secret); err == nil { return nil @@ -85,11 +87,47 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove ) } +// CreateBootstrapConfigMap creates the public cluster-info ConfigMap +func CreateBootstrapConfigMap(file string) error { + adminConfig, err := clientcmd.LoadFromFile(file) + if err != nil { + return fmt.Errorf("failed to load admin kubeconfig [%v]", err) + } + client, err := kubeconfigutil.KubeConfigToClientSet(adminConfig) + if err != nil { + return err + } + + adminCluster := adminConfig.Contexts[adminConfig.CurrentContext].Cluster + // Copy the cluster from admin.conf to the bootstrap kubeconfig, contains the CA cert and the server URL + bootstrapConfig := &clientcmdapi.Config{ + Clusters: map[string]*clientcmdapi.Cluster{ + "": adminConfig.Clusters[adminCluster], + }, + } + bootstrapBytes, err := clientcmd.Write(*bootstrapConfig) + if err != nil { + return err + } + + bootstrapConfigMap := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: bootstrapapi.ConfigMapClusterInfo}, + Data: map[string]string{ + bootstrapapi.KubeConfigKey: string(bootstrapBytes), + }, + } + + if _, err := client.CoreV1().ConfigMaps(metav1.NamespacePublic).Create(&bootstrapConfigMap); err != nil { + return err + } + return nil +} + // encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret -func encodeTokenSecretData(d *kubeadmapi.TokenDiscovery, duration time.Duration, usages []string, description string) map[string][]byte { +func encodeTokenSecretData(tokenId, tokenSecret string, duration time.Duration, usages []string, description string) map[string][]byte { data := map[string][]byte{ - bootstrapapi.BootstrapTokenIDKey: []byte(d.ID), - bootstrapapi.BootstrapTokenSecretKey: []byte(d.Secret), + bootstrapapi.BootstrapTokenIDKey: []byte(tokenId), + bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret), } if duration > 0 { diff --git a/cmd/kubeadm/app/phases/token/bootstrap_test.go b/cmd/kubeadm/app/phases/token/bootstrap_test.go index 052c171c4ee..40f970fe86b 100644 --- a/cmd/kubeadm/app/phases/token/bootstrap_test.go +++ b/cmd/kubeadm/app/phases/token/bootstrap_test.go @@ -33,7 +33,7 @@ func TestEncodeTokenSecretData(t *testing.T) { {token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default } for _, rt := range tests { - actual := encodeTokenSecretData(rt.token, rt.t, []string{}, "") + actual := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, "") if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) { t.Errorf( "failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s", diff --git a/cmd/kubeadm/app/phases/token/csv.go b/cmd/kubeadm/app/phases/token/csv.go deleted file mode 100644 index 765f5ff4263..00000000000 --- a/cmd/kubeadm/app/phases/token/csv.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package token - -import ( - "bytes" - "fmt" - "os" - "path" - - "k8s.io/apimachinery/pkg/util/uuid" - kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" -) - -// CreateTokenAuthFile creates the CSV file that can be used for allowing users with tokens access to the API Server -func CreateTokenAuthFile(certsDir, bt string) error { - tokenAuthFilePath := path.Join(certsDir, kubeadmconstants.CSVTokenFileName) - if err := os.MkdirAll(certsDir, 0700); err != nil { - return fmt.Errorf("failed to create directory %q [%v]", certsDir, err) - } - serialized := []byte(fmt.Sprintf("%s,%s,%s,%s\n", bt, kubeadmconstants.CSVTokenBootstrapUser, uuid.NewUUID(), kubeadmconstants.CSVTokenBootstrapGroup)) - // DumpReaderToFile create a file with mode 0600 - if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), tokenAuthFilePath); err != nil { - return fmt.Errorf("failed to save token auth file (%q) [%v]", tokenAuthFilePath, err) - } - return nil -} diff --git a/cmd/kubeadm/app/phases/token/csv_test.go b/cmd/kubeadm/app/phases/token/csv_test.go deleted file mode 100644 index abadcab9d08..00000000000 --- a/cmd/kubeadm/app/phases/token/csv_test.go +++ /dev/null @@ -1,17 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package token diff --git a/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go index 6724f60e510..8f380ede1a3 100644 --- a/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go @@ -99,3 +99,15 @@ func WriteToDisk(filename string, kubeconfig *clientcmdapi.Config) error { fmt.Printf("[kubeconfig] Wrote KubeConfig file to disk: %q\n", filename) return nil } + +// GetClusterFromKubeConfig returns the default Cluster of the specified KubeConfig +func GetClusterFromKubeConfig(config *clientcmdapi.Config) *clientcmdapi.Cluster { + // If there is an unnamed cluster object, use it + if config.Clusters[""] != nil { + return config.Clusters[""] + } + if config.Contexts[config.CurrentContext] != nil { + return config.Clusters[config.Contexts[config.CurrentContext].Cluster] + } + return nil +} diff --git a/cmd/kubeadm/app/util/token/tokens.go b/cmd/kubeadm/app/util/token/tokens.go index 9240680ee84..52697173561 100644 --- a/cmd/kubeadm/app/util/token/tokens.go +++ b/cmd/kubeadm/app/util/token/tokens.go @@ -21,7 +21,6 @@ import ( "encoding/hex" "fmt" "regexp" - "strings" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" ) @@ -50,20 +49,18 @@ func randBytes(length int) (string, error) { // GenerateToken generates a new token with a token ID that is valid as a // Kubernetes DNS label. // For more info, see kubernetes/pkg/util/validation/validation.go. -func GenerateToken(d *kubeadmapi.TokenDiscovery) error { +func GenerateToken() (string, error) { tokenID, err := randBytes(TokenIDBytes) if err != nil { - return err + return "", err } - token, err := randBytes(TokenSecretBytes) + tokenSecret, err := randBytes(TokenSecretBytes) if err != nil { - return err + return "", err } - d.ID = strings.ToLower(tokenID) - d.Secret = strings.ToLower(token) - return nil + return fmt.Sprintf("%s.%s", tokenID, tokenSecret), nil } // ParseTokenID tries and parse a valid token ID from a string. diff --git a/cmd/kubeadm/app/util/token/tokens_test.go b/cmd/kubeadm/app/util/token/tokens_test.go index 980e371897d..b9b617c20a7 100644 --- a/cmd/kubeadm/app/util/token/tokens_test.go +++ b/cmd/kubeadm/app/util/token/tokens_test.go @@ -117,15 +117,19 @@ func TestValidateToken(t *testing.T) { } func TestGenerateToken(t *testing.T) { - td := &kubeadmapi.TokenDiscovery{} - if err := GenerateToken(td); err != nil { + token, err := GenerateToken() + if err != nil { t.Fatalf("GenerateToken returned an unexpected error: %+v", err) } - if len(td.ID) != 6 { - t.Errorf("failed GenerateToken first part length:\n\texpected: 6\n\t actual: %d", len(td.ID)) + tokenID, tokenSecret, err := ParseToken(token) + if err != nil { + t.Fatalf("GenerateToken returned an unexpected error: %+v", err) } - if len(td.Secret) != 16 { - t.Errorf("failed GenerateToken second part length:\n\texpected: 16\n\t actual: %d", len(td.Secret)) + if len(tokenID) != 6 { + t.Errorf("failed GenerateToken first part length:\n\texpected: 6\n\t actual: %d", len(tokenID)) + } + if len(tokenSecret) != 16 { + t.Errorf("failed GenerateToken second part length:\n\texpected: 16\n\t actual: %d", len(tokenSecret)) } } diff --git a/hack/.linted_packages b/hack/.linted_packages index 4289acdb81b..6f45b7ee24c 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -18,6 +18,7 @@ cmd/kube-discovery cmd/kube-proxy cmd/kubeadm cmd/kubeadm/app/apis/kubeadm/install +cmd/kubeadm/app/discovery/https cmd/kubeadm/app/phases/apiconfig cmd/kubeadm/app/phases/certs cmd/kubeadm/app/phases/kubeconfig diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index fdab1d2d6ed..3ef214f693b 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -64,6 +64,7 @@ bench-pods bench-quiet bench-tasks bench-workers +bind-addrsse bind-address bind-pods-burst bind-pods-qps @@ -662,6 +663,7 @@ tls-cert-file tls-private-key-file tls-sni-cert-key token-auth-file +token-ttl to-version to-version ttl-keys-prefix