From 576b8d3f4fdb0961981bc04dc647b19a8c90ac4e Mon Sep 17 00:00:00 2001 From: "Rostislav M. Georgiev" Date: Wed, 22 Aug 2018 11:25:16 +0300 Subject: [PATCH] kubeadm: Split discovery from JoinConfiguration This change splits out discovery fields from JoinConfiguration by performing the following changes: - Introduce a BootstrapTokenDiscovery structure, that houses configuration options needed for bootstrap token based discovery. - Introduce a FileDiscovery structure, that houses configuration options (currently only a single option) needed for KubeConfig based discovery. - Introduce a Discovery structure, that houses common options (such as discovery timeout and TLS bootstrap token) as well as pointer to an instance of either BootstrapTokenDiscovery or FileDiscovery structures. - Replace the old discovery related JoinConfiguration members with a single Discovery member. This change is required in order to cleanup the code of unnecessary logic and make the serialized JoinConfiguration more structured (and therefore, more intuitive). Signed-off-by: Rostislav M. Georgiev --- cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go | 8 +- cmd/kubeadm/app/apis/kubeadm/types.go | 88 +++++--- cmd/kubeadm/app/apis/kubeadm/v1alpha3/BUILD | 1 + .../app/apis/kubeadm/v1alpha3/conversion.go | 76 +++++++ .../v1alpha3/zz_generated.conversion.go | 45 ++-- .../app/apis/kubeadm/v1beta1/defaults.go | 48 +++-- cmd/kubeadm/app/apis/kubeadm/v1beta1/types.go | 87 +++++--- .../v1beta1/zz_generated.conversion.go | 124 +++++++++-- .../kubeadm/v1beta1/zz_generated.deepcopy.go | 89 ++++++-- .../kubeadm/v1beta1/zz_generated.defaults.go | 4 + .../app/apis/kubeadm/validation/validation.go | 80 +++---- .../kubeadm/validation/validation_test.go | 195 +++++++++--------- .../app/apis/kubeadm/zz_generated.deepcopy.go | 89 ++++++-- cmd/kubeadm/app/cmd/config.go | 10 +- cmd/kubeadm/app/cmd/join.go | 74 ++++--- cmd/kubeadm/app/cmd/join_test.go | 3 +- cmd/kubeadm/app/discovery/discovery.go | 15 +- cmd/kubeadm/app/discovery/discovery_test.go | 24 ++- cmd/kubeadm/app/discovery/token/token.go | 6 +- cmd/kubeadm/app/preflight/checks.go | 22 +- cmd/kubeadm/app/preflight/checks_test.go | 12 +- .../testdata/conversion/node/internal.yaml | 19 +- .../testdata/conversion/node/v1beta1.yaml | 16 +- .../testdata/defaulting/node/defaulted.yaml | 16 +- .../testdata/validation/invalid_nodecfg.yaml | 16 +- 25 files changed, 789 insertions(+), 378 deletions(-) create mode 100644 cmd/kubeadm/app/apis/kubeadm/v1alpha3/conversion.go diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go index 08b6a910e59..a1768876783 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go @@ -133,7 +133,9 @@ func fuzzJoinConfiguration(obj *kubeadm.JoinConfiguration, c fuzz.Continue) { // Pinning values for fields that get defaults if fuzz value is empty string or nil (thus making the round trip test fail) obj.CACertPath = "foo" obj.ClusterName = "bar" - obj.DiscoveryTimeout = &metav1.Duration{Duration: 1234} - obj.DiscoveryToken = "baz" - obj.TLSBootstrapToken = "qux" + obj.Discovery = kubeadm.Discovery{ + BootstrapToken: &kubeadm.BootstrapTokenDiscovery{Token: "baz"}, + TLSBootstrapToken: "qux", + Timeout: &metav1.Duration{Duration: 1234}, + } } diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index d2471c67a19..9cd7f273740 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -280,41 +280,13 @@ type JoinConfiguration struct { // secure comunications between node and master. // Defaults to "/etc/kubernetes/pki/ca.crt". CACertPath string - // DiscoveryFile is a file or url to a kubeconfig file from which to - // load cluster information. - DiscoveryFile string - // DiscoveryToken is a token used to validate cluster information - // fetched from the master. - DiscoveryToken string - // DiscoveryTokenAPIServers is a set of IPs to API servers from which info - // will be fetched. Currently we only pay attention to one API server but - // hope to support >1 in the future. - DiscoveryTokenAPIServers []string - // DiscoveryTimeout modifies the discovery timeout - DiscoveryTimeout *metav1.Duration - // TLSBootstrapToken is a token used for TLS bootstrapping. - // Defaults to Token. - TLSBootstrapToken string - // Token is used for both discovery and TLS bootstrapping. - Token string + + // Discovery specifies the options for the kubelet to use during the TLS Bootstrap process + Discovery Discovery + // The cluster name ClusterName string - // DiscoveryTokenCACertHashes specifies a set of public key pins to verify - // when token-based discovery is used. The root CA found during discovery - // must match one of these values. Specifying an empty set disables root CA - // pinning, which can be unsafe. Each hash is specified as ":", - // where the only currently supported type is "sha256". This is a hex-encoded - // SHA-256 hash of the Subject Public Key Info (SPKI) object in DER-encoded - // ASN.1. These hashes can be calculated using, for example, OpenSSL: - // openssl x509 -pubkey -in ca.crt openssl rsa -pubin -outform der 2>&/dev/null | openssl dgst -sha256 -hex - DiscoveryTokenCACertHashes []string - - // DiscoveryTokenUnsafeSkipCAVerification allows token-based discovery - // without CA verification via DiscoveryTokenCACertHashes. This can weaken - // the security of kubeadm since other nodes can impersonate the master. - DiscoveryTokenUnsafeSkipCAVerification bool - // ControlPlane flag specifies that the joining node should host an additional // control plane instance. ControlPlane bool @@ -326,6 +298,58 @@ type JoinConfiguration struct { FeatureGates map[string]bool } +// Discovery specifies the options for the kubelet to use during the TLS Bootstrap process +type Discovery struct { + // BootstrapToken is used to set the options for bootstrap token based discovery + // BootstrapToken and File are mutually exclusive + BootstrapToken *BootstrapTokenDiscovery + + // File is used to specify a file or URL to a kubeconfig file from which to load cluster information + // BootstrapToken and File are mutually exclusive + File *FileDiscovery + + // TLSBootstrapToken is a token used for TLS bootstrapping. + // If .BootstrapToken is set, this field is defaulted to .BootstrapToken.Token, but can be overridden. + // If .File is set, this field **must be set** in case the KubeConfigFile does not contain any other authentication information + TLSBootstrapToken string + + // Timeout modifies the discovery timeout + Timeout *metav1.Duration +} + +// BootstrapTokenDiscovery is used to set the options for bootstrap token based discovery +type BootstrapTokenDiscovery struct { + // Token is a token used to validate cluster information + // fetched from the master. + Token string + + // APIServerEndpoints is a set of IPs or domain names to API servers from which info + // will be fetched. Currently we only pay attention to one API server but + // hope to support >1 in the future. + APIServerEndpoints []string + + // CACertHashes specifies a set of public key pins to verify + // when token-based discovery is used. The root CA found during discovery + // must match one of these values. Specifying an empty set disables root CA + // pinning, which can be unsafe. Each hash is specified as ":", + // where the only currently supported type is "sha256". This is a hex-encoded + // SHA-256 hash of the Subject Public Key Info (SPKI) object in DER-encoded + // ASN.1. These hashes can be calculated using, for example, OpenSSL: + // openssl x509 -pubkey -in ca.crt openssl rsa -pubin -outform der 2>&/dev/null | openssl dgst -sha256 -hex + CACertHashes []string + + // UnsafeSkipCAVerification allows token-based discovery + // without CA verification via CACertHashes. This can weaken + // the security of kubeadm since other nodes can impersonate the master. + UnsafeSkipCAVerification bool +} + +// FileDiscovery is used to specify a file or URL to a kubeconfig file from which to load cluster information +type FileDiscovery struct { + // KubeConfigPath is used to specify the actual file path or URL to the kubeconfig file from which to load cluster information + KubeConfigPath string +} + // GetControlPlaneImageRepository returns name of image repository // for control plane images (API,Controller Manager,Scheduler and Proxy) // It will override location with CI registry name in case user requests special diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha3/BUILD b/cmd/kubeadm/app/apis/kubeadm/v1alpha3/BUILD index 15fa16393bb..9d94d61c98b 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha3/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha3/BUILD @@ -4,6 +4,7 @@ go_library( name = "go_default_library", srcs = [ "bootstraptokenstring.go", + "conversion.go", "defaults.go", "defaults_unix.go", "defaults_windows.go", diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha3/conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha3/conversion.go new file mode 100644 index 00000000000..813d1f65b63 --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha3/conversion.go @@ -0,0 +1,76 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha3 + +import ( + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" +) + +func Convert_v1alpha3_JoinConfiguration_To_kubeadm_JoinConfiguration(in *JoinConfiguration, out *kubeadm.JoinConfiguration, s conversion.Scope) error { + if err := autoConvert_v1alpha3_JoinConfiguration_To_kubeadm_JoinConfiguration(in, out, s); err != nil { + return err + } + + out.Discovery.Timeout = in.DiscoveryTimeout + + if len(in.TLSBootstrapToken) != 0 { + out.Discovery.TLSBootstrapToken = in.TLSBootstrapToken + } else { + out.Discovery.TLSBootstrapToken = in.Token + } + + if len(in.DiscoveryFile) != 0 { + out.Discovery.File = &kubeadm.FileDiscovery{ + KubeConfigPath: in.DiscoveryFile, + } + } else { + out.Discovery.BootstrapToken = &kubeadm.BootstrapTokenDiscovery{ + APIServerEndpoints: in.DiscoveryTokenAPIServers, + CACertHashes: in.DiscoveryTokenCACertHashes, + UnsafeSkipCAVerification: in.DiscoveryTokenUnsafeSkipCAVerification, + } + if len(in.DiscoveryToken) != 0 { + out.Discovery.BootstrapToken.Token = in.DiscoveryToken + } else { + out.Discovery.BootstrapToken.Token = in.Token + } + } + + return nil +} + +func Convert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration(in *kubeadm.JoinConfiguration, out *JoinConfiguration, s conversion.Scope) error { + if err := autoConvert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration(in, out, s); err != nil { + return err + } + + out.DiscoveryTimeout = in.Discovery.Timeout + out.TLSBootstrapToken = in.Discovery.TLSBootstrapToken + + if in.Discovery.BootstrapToken != nil { + out.DiscoveryToken = in.Discovery.BootstrapToken.Token + out.DiscoveryTokenAPIServers = in.Discovery.BootstrapToken.APIServerEndpoints + out.DiscoveryTokenCACertHashes = in.Discovery.BootstrapToken.CACertHashes + out.DiscoveryTokenUnsafeSkipCAVerification = in.Discovery.BootstrapToken.UnsafeSkipCAVerification + + } else if in.Discovery.File != nil { + out.DiscoveryFile = in.Discovery.File.KubeConfigPath + } + + return nil +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha3/zz_generated.conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha3/zz_generated.conversion.go index 0aef47e803a..b603541c317 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha3/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha3/zz_generated.conversion.go @@ -177,6 +177,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*kubeadm.JoinConfiguration)(nil), (*JoinConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration(a.(*kubeadm.JoinConfiguration), b.(*JoinConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*JoinConfiguration)(nil), (*kubeadm.JoinConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_JoinConfiguration_To_kubeadm_JoinConfiguration(a.(*JoinConfiguration), b.(*kubeadm.JoinConfiguration), scope) + }); err != nil { + return err + } return nil } @@ -483,15 +493,15 @@ func autoConvert_v1alpha3_JoinConfiguration_To_kubeadm_JoinConfiguration(in *Joi return err } out.CACertPath = in.CACertPath - out.DiscoveryFile = in.DiscoveryFile - out.DiscoveryToken = in.DiscoveryToken - out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers)) - out.DiscoveryTimeout = (*v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout)) - out.TLSBootstrapToken = in.TLSBootstrapToken - out.Token = in.Token + // WARNING: in.DiscoveryFile requires manual conversion: does not exist in peer-type + // WARNING: in.DiscoveryToken requires manual conversion: does not exist in peer-type + // WARNING: in.DiscoveryTokenAPIServers requires manual conversion: does not exist in peer-type + // WARNING: in.DiscoveryTimeout requires manual conversion: does not exist in peer-type + // WARNING: in.TLSBootstrapToken requires manual conversion: does not exist in peer-type + // WARNING: in.Token requires manual conversion: does not exist in peer-type out.ClusterName = in.ClusterName - out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes)) - out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification + // WARNING: in.DiscoveryTokenCACertHashes requires manual conversion: does not exist in peer-type + // WARNING: in.DiscoveryTokenUnsafeSkipCAVerification requires manual conversion: does not exist in peer-type out.ControlPlane = in.ControlPlane if err := Convert_v1alpha3_APIEndpoint_To_kubeadm_APIEndpoint(&in.APIEndpoint, &out.APIEndpoint, s); err != nil { return err @@ -500,25 +510,13 @@ func autoConvert_v1alpha3_JoinConfiguration_To_kubeadm_JoinConfiguration(in *Joi return nil } -// Convert_v1alpha3_JoinConfiguration_To_kubeadm_JoinConfiguration is an autogenerated conversion function. -func Convert_v1alpha3_JoinConfiguration_To_kubeadm_JoinConfiguration(in *JoinConfiguration, out *kubeadm.JoinConfiguration, s conversion.Scope) error { - return autoConvert_v1alpha3_JoinConfiguration_To_kubeadm_JoinConfiguration(in, out, s) -} - func autoConvert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration(in *kubeadm.JoinConfiguration, out *JoinConfiguration, s conversion.Scope) error { if err := Convert_kubeadm_NodeRegistrationOptions_To_v1alpha3_NodeRegistrationOptions(&in.NodeRegistration, &out.NodeRegistration, s); err != nil { return err } out.CACertPath = in.CACertPath - out.DiscoveryFile = in.DiscoveryFile - out.DiscoveryToken = in.DiscoveryToken - out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers)) - out.DiscoveryTimeout = (*v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout)) - out.TLSBootstrapToken = in.TLSBootstrapToken - out.Token = in.Token + // WARNING: in.Discovery requires manual conversion: does not exist in peer-type out.ClusterName = in.ClusterName - out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes)) - out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification out.ControlPlane = in.ControlPlane if err := Convert_kubeadm_APIEndpoint_To_v1alpha3_APIEndpoint(&in.APIEndpoint, &out.APIEndpoint, s); err != nil { return err @@ -527,11 +525,6 @@ func autoConvert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration(in *kub return nil } -// Convert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration is an autogenerated conversion function. -func Convert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration(in *kubeadm.JoinConfiguration, out *JoinConfiguration, s conversion.Scope) error { - return autoConvert_kubeadm_JoinConfiguration_To_v1alpha3_JoinConfiguration(in, out, s) -} - func autoConvert_v1alpha3_LocalEtcd_To_kubeadm_LocalEtcd(in *LocalEtcd, out *kubeadm.LocalEtcd, s conversion.Scope) error { out.Image = in.Image out.DataDir = in.DataDir diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta1/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1beta1/defaults.go index 0b66549e6df..68564fa7720 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta1/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta1/defaults.go @@ -120,30 +120,14 @@ func SetDefaults_JoinConfiguration(obj *JoinConfiguration) { if obj.CACertPath == "" { obj.CACertPath = DefaultCACertPath } - if len(obj.TLSBootstrapToken) == 0 { - obj.TLSBootstrapToken = obj.Token - } - if len(obj.DiscoveryToken) == 0 && len(obj.DiscoveryFile) == 0 { - obj.DiscoveryToken = obj.Token - } - // Make sure file URLs become paths - if len(obj.DiscoveryFile) != 0 { - u, err := url.Parse(obj.DiscoveryFile) - if err == nil && u.Scheme == "file" { - obj.DiscoveryFile = u.Path - } - } - if obj.DiscoveryTimeout == nil { - obj.DiscoveryTimeout = &metav1.Duration{ - Duration: DefaultDiscoveryTimeout, - } - } + if obj.ClusterName == "" { obj.ClusterName = DefaultClusterName } SetDefaults_NodeRegistrationOptions(&obj.NodeRegistration) SetDefaults_APIEndpoint(&obj.APIEndpoint) + SetDefaults_Discovery(&obj.Discovery) } func SetDefaults_NodeRegistrationOptions(obj *NodeRegistrationOptions) { @@ -152,6 +136,34 @@ func SetDefaults_NodeRegistrationOptions(obj *NodeRegistrationOptions) { } } +// SetDefaults_Discovery assigns default values for the discovery process +func SetDefaults_Discovery(obj *Discovery) { + if len(obj.TLSBootstrapToken) == 0 && obj.BootstrapToken != nil { + obj.TLSBootstrapToken = obj.BootstrapToken.Token + } + + if obj.Timeout == nil { + obj.Timeout = &metav1.Duration{ + Duration: DefaultDiscoveryTimeout, + } + } + + if obj.File != nil { + SetDefaults_FileDiscovery(obj.File) + } +} + +// SetDefaults_FileDiscovery assigns default values for file based discovery +func SetDefaults_FileDiscovery(obj *FileDiscovery) { + // Make sure file URL becomes path + if len(obj.KubeConfigPath) != 0 { + u, err := url.Parse(obj.KubeConfigPath) + if err == nil && u.Scheme == "file" { + obj.KubeConfigPath = u.Path + } + } +} + // SetDefaults_AuditPolicyConfiguration sets default values for the AuditPolicyConfiguration func SetDefaults_AuditPolicyConfiguration(obj *ClusterConfiguration) { if obj.AuditPolicyConfiguration.LogDir == "" { diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta1/types.go b/cmd/kubeadm/app/apis/kubeadm/v1beta1/types.go index 11336648121..a4ede7148bd 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta1/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta1/types.go @@ -258,42 +258,13 @@ type JoinConfiguration struct { // secure comunications between node and master. // Defaults to "/etc/kubernetes/pki/ca.crt". CACertPath string `json:"caCertPath"` - // DiscoveryFile is a file or url to a kubeconfig file from which to - // load cluster information. - DiscoveryFile string `json:"discoveryFile"` - // DiscoveryToken is a token used to validate cluster information - // fetched from the master. - DiscoveryToken string `json:"discoveryToken"` - // DiscoveryTokenAPIServers is a set of IPs to API servers from which info - // will be fetched. Currently we only pay attention to one API server but - // hope to support >1 in the future. - DiscoveryTokenAPIServers []string `json:"discoveryTokenAPIServers,omitempty"` - // DiscoveryTimeout modifies the discovery timeout - DiscoveryTimeout *metav1.Duration `json:"discoveryTimeout,omitempty"` - // TLSBootstrapToken is a token used for TLS bootstrapping. - // Defaults to Token. - TLSBootstrapToken string `json:"tlsBootstrapToken"` - // Token is used for both discovery and TLS bootstrapping. - Token string `json:"token"` + + // Discovery specifies the options for the kubelet to use during the TLS Bootstrap process + Discovery Discovery `json:"discovery"` // ClusterName is the name for the cluster in kubeconfig. ClusterName string `json:"clusterName,omitempty"` - // DiscoveryTokenCACertHashes specifies a set of public key pins to verify - // when token-based discovery is used. The root CA found during discovery - // must match one of these values. Specifying an empty set disables root CA - // pinning, which can be unsafe. Each hash is specified as ":", - // where the only currently supported type is "sha256". This is a hex-encoded - // SHA-256 hash of the Subject Public Key Info (SPKI) object in DER-encoded - // ASN.1. These hashes can be calculated using, for example, OpenSSL: - // openssl x509 -pubkey -in ca.crt openssl rsa -pubin -outform der 2>&/dev/null | openssl dgst -sha256 -hex - DiscoveryTokenCACertHashes []string `json:"discoveryTokenCACertHashes,omitempty"` - - // DiscoveryTokenUnsafeSkipCAVerification allows token-based discovery - // without CA verification via DiscoveryTokenCACertHashes. This can weaken - // the security of kubeadm since other nodes can impersonate the master. - DiscoveryTokenUnsafeSkipCAVerification bool `json:"discoveryTokenUnsafeSkipCAVerification"` - // ControlPlane flag specifies that the joining node should host an additional // control plane instance. ControlPlane bool `json:"controlPlane,omitempty"` @@ -305,6 +276,58 @@ type JoinConfiguration struct { FeatureGates map[string]bool `json:"featureGates,omitempty"` } +// Discovery specifies the options for the kubelet to use during the TLS Bootstrap process +type Discovery struct { + // BootstrapToken is used to set the options for bootstrap token based discovery + // BootstrapToken and File are mutually exclusive + BootstrapToken *BootstrapTokenDiscovery `json:"bootstrapToken,omitempty"` + + // File is used to specify a file or URL to a kubeconfig file from which to load cluster information + // BootstrapToken and File are mutually exclusive + File *FileDiscovery `json:"file,omitempty"` + + // TLSBootstrapToken is a token used for TLS bootstrapping. + // If .BootstrapToken is set, this field is defaulted to .BootstrapToken.Token, but can be overridden. + // If .File is set, this field **must be set** in case the KubeConfigFile does not contain any other authentication information + TLSBootstrapToken string `json:"tlsBootstrapToken"` + + // Timeout modifies the discovery timeout + Timeout *metav1.Duration `json:"timeout,omitempty"` +} + +// BootstrapTokenDiscovery is used to set the options for bootstrap token based discovery +type BootstrapTokenDiscovery struct { + // Token is a token used to validate cluster information + // fetched from the master. + Token string `json:"token"` + + // APIServerEndpoints is a set of IPs or domain names to API servers from which info + // will be fetched. Currently we only pay attention to one API server but + // hope to support >1 in the future. + APIServerEndpoints []string `json:"apiServerEndpoints,omitempty"` + + // CACertHashes specifies a set of public key pins to verify + // when token-based discovery is used. The root CA found during discovery + // must match one of these values. Specifying an empty set disables root CA + // pinning, which can be unsafe. Each hash is specified as ":", + // where the only currently supported type is "sha256". This is a hex-encoded + // SHA-256 hash of the Subject Public Key Info (SPKI) object in DER-encoded + // ASN.1. These hashes can be calculated using, for example, OpenSSL: + // openssl x509 -pubkey -in ca.crt openssl rsa -pubin -outform der 2>&/dev/null | openssl dgst -sha256 -hex + CACertHashes []string `json:"caCertHashes,omitempty"` + + // UnsafeSkipCAVerification allows token-based discovery + // without CA verification via CACertHashes. This can weaken + // the security of kubeadm since other nodes can impersonate the master. + UnsafeSkipCAVerification bool `json:"unsafeSkipCAVerification"` +} + +// FileDiscovery is used to specify a file or URL to a kubeconfig file from which to load cluster information +type FileDiscovery struct { + // KubeConfigPath is used to specify the actual file path or URL to the kubeconfig file from which to load cluster information + KubeConfigPath string `json:"kubeConfigPath"` +} + // HostPathMount contains elements describing volumes that are mounted from the // host. type HostPathMount struct { diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.conversion.go index 41b3fc91bef..71ebf82fb53 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.conversion.go @@ -67,6 +67,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*BootstrapTokenDiscovery)(nil), (*kubeadm.BootstrapTokenDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(a.(*BootstrapTokenDiscovery), b.(*kubeadm.BootstrapTokenDiscovery), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeadm.BootstrapTokenDiscovery)(nil), (*BootstrapTokenDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeadm_BootstrapTokenDiscovery_To_v1beta1_BootstrapTokenDiscovery(a.(*kubeadm.BootstrapTokenDiscovery), b.(*BootstrapTokenDiscovery), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*BootstrapTokenString)(nil), (*kubeadm.BootstrapTokenString)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_BootstrapTokenString_To_kubeadm_BootstrapTokenString(a.(*BootstrapTokenString), b.(*kubeadm.BootstrapTokenString), scope) }); err != nil { @@ -97,6 +107,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*Discovery)(nil), (*kubeadm.Discovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Discovery_To_kubeadm_Discovery(a.(*Discovery), b.(*kubeadm.Discovery), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeadm.Discovery)(nil), (*Discovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeadm_Discovery_To_v1beta1_Discovery(a.(*kubeadm.Discovery), b.(*Discovery), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*Etcd)(nil), (*kubeadm.Etcd)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_Etcd_To_kubeadm_Etcd(a.(*Etcd), b.(*kubeadm.Etcd), scope) }); err != nil { @@ -117,6 +137,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*FileDiscovery)(nil), (*kubeadm.FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_FileDiscovery_To_kubeadm_FileDiscovery(a.(*FileDiscovery), b.(*kubeadm.FileDiscovery), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kubeadm.FileDiscovery)(nil), (*FileDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kubeadm_FileDiscovery_To_v1beta1_FileDiscovery(a.(*kubeadm.FileDiscovery), b.(*FileDiscovery), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*HostPathMount)(nil), (*kubeadm.HostPathMount)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_HostPathMount_To_kubeadm_HostPathMount(a.(*HostPathMount), b.(*kubeadm.HostPathMount), scope) }); err != nil { @@ -256,6 +286,32 @@ func Convert_kubeadm_BootstrapToken_To_v1beta1_BootstrapToken(in *kubeadm.Bootst return autoConvert_kubeadm_BootstrapToken_To_v1beta1_BootstrapToken(in, out, s) } +func autoConvert_v1beta1_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(in *BootstrapTokenDiscovery, out *kubeadm.BootstrapTokenDiscovery, s conversion.Scope) error { + out.Token = in.Token + out.APIServerEndpoints = *(*[]string)(unsafe.Pointer(&in.APIServerEndpoints)) + out.CACertHashes = *(*[]string)(unsafe.Pointer(&in.CACertHashes)) + out.UnsafeSkipCAVerification = in.UnsafeSkipCAVerification + return nil +} + +// Convert_v1beta1_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery is an autogenerated conversion function. +func Convert_v1beta1_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(in *BootstrapTokenDiscovery, out *kubeadm.BootstrapTokenDiscovery, s conversion.Scope) error { + return autoConvert_v1beta1_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(in, out, s) +} + +func autoConvert_kubeadm_BootstrapTokenDiscovery_To_v1beta1_BootstrapTokenDiscovery(in *kubeadm.BootstrapTokenDiscovery, out *BootstrapTokenDiscovery, s conversion.Scope) error { + out.Token = in.Token + out.APIServerEndpoints = *(*[]string)(unsafe.Pointer(&in.APIServerEndpoints)) + out.CACertHashes = *(*[]string)(unsafe.Pointer(&in.CACertHashes)) + out.UnsafeSkipCAVerification = in.UnsafeSkipCAVerification + return nil +} + +// Convert_kubeadm_BootstrapTokenDiscovery_To_v1beta1_BootstrapTokenDiscovery is an autogenerated conversion function. +func Convert_kubeadm_BootstrapTokenDiscovery_To_v1beta1_BootstrapTokenDiscovery(in *kubeadm.BootstrapTokenDiscovery, out *BootstrapTokenDiscovery, s conversion.Scope) error { + return autoConvert_kubeadm_BootstrapTokenDiscovery_To_v1beta1_BootstrapTokenDiscovery(in, out, s) +} + func autoConvert_v1beta1_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in *BootstrapTokenString, out *kubeadm.BootstrapTokenString, s conversion.Scope) error { out.ID = in.ID out.Secret = in.Secret @@ -364,6 +420,32 @@ func Convert_kubeadm_ClusterStatus_To_v1beta1_ClusterStatus(in *kubeadm.ClusterS return autoConvert_kubeadm_ClusterStatus_To_v1beta1_ClusterStatus(in, out, s) } +func autoConvert_v1beta1_Discovery_To_kubeadm_Discovery(in *Discovery, out *kubeadm.Discovery, s conversion.Scope) error { + out.BootstrapToken = (*kubeadm.BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) + out.File = (*kubeadm.FileDiscovery)(unsafe.Pointer(in.File)) + out.TLSBootstrapToken = in.TLSBootstrapToken + out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) + return nil +} + +// Convert_v1beta1_Discovery_To_kubeadm_Discovery is an autogenerated conversion function. +func Convert_v1beta1_Discovery_To_kubeadm_Discovery(in *Discovery, out *kubeadm.Discovery, s conversion.Scope) error { + return autoConvert_v1beta1_Discovery_To_kubeadm_Discovery(in, out, s) +} + +func autoConvert_kubeadm_Discovery_To_v1beta1_Discovery(in *kubeadm.Discovery, out *Discovery, s conversion.Scope) error { + out.BootstrapToken = (*BootstrapTokenDiscovery)(unsafe.Pointer(in.BootstrapToken)) + out.File = (*FileDiscovery)(unsafe.Pointer(in.File)) + out.TLSBootstrapToken = in.TLSBootstrapToken + out.Timeout = (*v1.Duration)(unsafe.Pointer(in.Timeout)) + return nil +} + +// Convert_kubeadm_Discovery_To_v1beta1_Discovery is an autogenerated conversion function. +func Convert_kubeadm_Discovery_To_v1beta1_Discovery(in *kubeadm.Discovery, out *Discovery, s conversion.Scope) error { + return autoConvert_kubeadm_Discovery_To_v1beta1_Discovery(in, out, s) +} + func autoConvert_v1beta1_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conversion.Scope) error { out.Local = (*kubeadm.LocalEtcd)(unsafe.Pointer(in.Local)) out.External = (*kubeadm.ExternalEtcd)(unsafe.Pointer(in.External)) @@ -412,6 +494,26 @@ func Convert_kubeadm_ExternalEtcd_To_v1beta1_ExternalEtcd(in *kubeadm.ExternalEt return autoConvert_kubeadm_ExternalEtcd_To_v1beta1_ExternalEtcd(in, out, s) } +func autoConvert_v1beta1_FileDiscovery_To_kubeadm_FileDiscovery(in *FileDiscovery, out *kubeadm.FileDiscovery, s conversion.Scope) error { + out.KubeConfigPath = in.KubeConfigPath + return nil +} + +// Convert_v1beta1_FileDiscovery_To_kubeadm_FileDiscovery is an autogenerated conversion function. +func Convert_v1beta1_FileDiscovery_To_kubeadm_FileDiscovery(in *FileDiscovery, out *kubeadm.FileDiscovery, s conversion.Scope) error { + return autoConvert_v1beta1_FileDiscovery_To_kubeadm_FileDiscovery(in, out, s) +} + +func autoConvert_kubeadm_FileDiscovery_To_v1beta1_FileDiscovery(in *kubeadm.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { + out.KubeConfigPath = in.KubeConfigPath + return nil +} + +// Convert_kubeadm_FileDiscovery_To_v1beta1_FileDiscovery is an autogenerated conversion function. +func Convert_kubeadm_FileDiscovery_To_v1beta1_FileDiscovery(in *kubeadm.FileDiscovery, out *FileDiscovery, s conversion.Scope) error { + return autoConvert_kubeadm_FileDiscovery_To_v1beta1_FileDiscovery(in, out, s) +} + func autoConvert_v1beta1_HostPathMount_To_kubeadm_HostPathMount(in *HostPathMount, out *kubeadm.HostPathMount, s conversion.Scope) error { out.Name = in.Name out.HostPath = in.HostPath @@ -483,15 +585,10 @@ func autoConvert_v1beta1_JoinConfiguration_To_kubeadm_JoinConfiguration(in *Join return err } out.CACertPath = in.CACertPath - out.DiscoveryFile = in.DiscoveryFile - out.DiscoveryToken = in.DiscoveryToken - out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers)) - out.DiscoveryTimeout = (*v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout)) - out.TLSBootstrapToken = in.TLSBootstrapToken - out.Token = in.Token + if err := Convert_v1beta1_Discovery_To_kubeadm_Discovery(&in.Discovery, &out.Discovery, s); err != nil { + return err + } out.ClusterName = in.ClusterName - out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes)) - out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification out.ControlPlane = in.ControlPlane if err := Convert_v1beta1_APIEndpoint_To_kubeadm_APIEndpoint(&in.APIEndpoint, &out.APIEndpoint, s); err != nil { return err @@ -510,15 +607,10 @@ func autoConvert_kubeadm_JoinConfiguration_To_v1beta1_JoinConfiguration(in *kube return err } out.CACertPath = in.CACertPath - out.DiscoveryFile = in.DiscoveryFile - out.DiscoveryToken = in.DiscoveryToken - out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers)) - out.DiscoveryTimeout = (*v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout)) - out.TLSBootstrapToken = in.TLSBootstrapToken - out.Token = in.Token + if err := Convert_kubeadm_Discovery_To_v1beta1_Discovery(&in.Discovery, &out.Discovery, s); err != nil { + return err + } out.ClusterName = in.ClusterName - out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes)) - out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification out.ControlPlane = in.ControlPlane if err := Convert_kubeadm_APIEndpoint_To_v1beta1_APIEndpoint(&in.APIEndpoint, &out.APIEndpoint, s); err != nil { return err diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.deepcopy.go index 93c33284d25..d1075107875 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.deepcopy.go @@ -103,6 +103,32 @@ func (in *BootstrapToken) DeepCopy() *BootstrapToken { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BootstrapTokenDiscovery) DeepCopyInto(out *BootstrapTokenDiscovery) { + *out = *in + if in.APIServerEndpoints != nil { + in, out := &in.APIServerEndpoints, &out.APIServerEndpoints + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.CACertHashes != nil { + in, out := &in.CACertHashes, &out.CACertHashes + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapTokenDiscovery. +func (in *BootstrapTokenDiscovery) DeepCopy() *BootstrapTokenDiscovery { + if in == nil { + return nil + } + out := new(BootstrapTokenDiscovery) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BootstrapTokenString) DeepCopyInto(out *BootstrapTokenString) { *out = *in @@ -227,6 +253,37 @@ func (in *ClusterStatus) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Discovery) DeepCopyInto(out *Discovery) { + *out = *in + if in.BootstrapToken != nil { + in, out := &in.BootstrapToken, &out.BootstrapToken + *out = new(BootstrapTokenDiscovery) + (*in).DeepCopyInto(*out) + } + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + **out = **in + } + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Discovery. +func (in *Discovery) DeepCopy() *Discovery { + if in == nil { + return nil + } + out := new(Discovery) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Etcd) DeepCopyInto(out *Etcd) { *out = *in @@ -274,6 +331,22 @@ func (in *ExternalEtcd) DeepCopy() *ExternalEtcd { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FileDiscovery) DeepCopyInto(out *FileDiscovery) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FileDiscovery. +func (in *FileDiscovery) DeepCopy() *FileDiscovery { + if in == nil { + return nil + } + out := new(FileDiscovery) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HostPathMount) DeepCopyInto(out *HostPathMount) { *out = *in @@ -330,21 +403,7 @@ func (in *JoinConfiguration) DeepCopyInto(out *JoinConfiguration) { *out = *in out.TypeMeta = in.TypeMeta in.NodeRegistration.DeepCopyInto(&out.NodeRegistration) - if in.DiscoveryTokenAPIServers != nil { - in, out := &in.DiscoveryTokenAPIServers, &out.DiscoveryTokenAPIServers - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.DiscoveryTimeout != nil { - in, out := &in.DiscoveryTimeout, &out.DiscoveryTimeout - *out = new(v1.Duration) - **out = **in - } - if in.DiscoveryTokenCACertHashes != nil { - in, out := &in.DiscoveryTokenCACertHashes, &out.DiscoveryTokenCACertHashes - *out = make([]string, len(*in)) - copy(*out, *in) - } + in.Discovery.DeepCopyInto(&out.Discovery) out.APIEndpoint = in.APIEndpoint if in.FeatureGates != nil { in, out := &in.FeatureGates, &out.FeatureGates diff --git a/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.defaults.go index ce25be8e973..a27612cce03 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1beta1/zz_generated.defaults.go @@ -56,5 +56,9 @@ func SetObjectDefaults_InitConfiguration(in *InitConfiguration) { func SetObjectDefaults_JoinConfiguration(in *JoinConfiguration) { SetDefaults_JoinConfiguration(in) SetDefaults_NodeRegistrationOptions(&in.NodeRegistration) + SetDefaults_Discovery(&in.Discovery) + if in.Discovery.File != nil { + SetDefaults_FileDiscovery(in.Discovery.File) + } SetDefaults_APIEndpoint(&in.APIEndpoint) } diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index 5ecff3728b9..2dbd34826bf 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -68,7 +68,7 @@ func ValidateClusterConfiguration(c *kubeadm.ClusterConfiguration) field.ErrorLi // ValidateJoinConfiguration validates node configuration and collects all encountered errors func ValidateJoinConfiguration(c *kubeadm.JoinConfiguration) field.ErrorList { allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateDiscovery(c)...) + allErrs = append(allErrs, ValidateDiscovery(&c.Discovery, field.NewPath("discovery"))...) allErrs = append(allErrs, ValidateNodeRegistrationOptions(&c.NodeRegistration, field.NewPath("nodeRegistration"))...) allErrs = append(allErrs, ValidateAPIEndpoint(&c.APIEndpoint, field.NewPath("apiEndpoint"))...) @@ -92,56 +92,66 @@ func ValidateNodeRegistrationOptions(nro *kubeadm.NodeRegistrationOptions, fldPa } // ValidateDiscovery validates discovery related configuration and collects all encountered errors -func ValidateDiscovery(c *kubeadm.JoinConfiguration) field.ErrorList { +func ValidateDiscovery(d *kubeadm.Discovery, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - if len(c.DiscoveryToken) != 0 { - allErrs = append(allErrs, ValidateToken(c.DiscoveryToken, field.NewPath("discoveryToken"))...) + + if d.BootstrapToken == nil && d.File == nil { + allErrs = append(allErrs, field.Invalid(fldPath, "", "bootstrapToken or file must be set")) } - if len(c.DiscoveryFile) != 0 { - allErrs = append(allErrs, ValidateDiscoveryFile(c.DiscoveryFile, field.NewPath("discoveryFile"))...) - if len(c.TLSBootstrapToken) != 0 { - allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, field.NewPath("tlsBootstrapToken"))...) + + if d.BootstrapToken != nil && d.File != nil { + allErrs = append(allErrs, field.Invalid(fldPath, "", "bootstrapToken and file cannot both be set")) + } + + if d.BootstrapToken != nil { + allErrs = append(allErrs, ValidateDiscoveryBootstrapToken(d.BootstrapToken, fldPath.Child("bootstrapToken"))...) + allErrs = append(allErrs, ValidateToken(d.TLSBootstrapToken, fldPath.Child("tlsBootstrapToken"))...) + } + + if d.File != nil { + allErrs = append(allErrs, ValidateDiscoveryFile(d.File, fldPath.Child("file"))...) + if len(d.TLSBootstrapToken) != 0 { + allErrs = append(allErrs, ValidateToken(d.TLSBootstrapToken, fldPath.Child("tlsBootstrapToken"))...) } - } else { - allErrs = append(allErrs, ValidateToken(c.TLSBootstrapToken, field.NewPath("tlsBootstrapToken"))...) } - allErrs = append(allErrs, ValidateArgSelection(c, field.NewPath("discovery"))...) - allErrs = append(allErrs, ValidateJoinDiscoveryTokenAPIServer(c.DiscoveryTokenAPIServers, field.NewPath("discoveryTokenAPIServers"))...) return allErrs } -// ValidateArgSelection validates discovery related configuration and collects all encountered errors -func ValidateArgSelection(cfg *kubeadm.JoinConfiguration, fldPath *field.Path) field.ErrorList { +// ValidateDiscoveryBootstrapToken validates bootstrap token discovery configuration +func ValidateDiscoveryBootstrapToken(b *kubeadm.BootstrapTokenDiscovery, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - if len(cfg.DiscoveryToken) != 0 && len(cfg.DiscoveryFile) != 0 { - allErrs = append(allErrs, field.Invalid(fldPath, "", "discoveryToken and discoveryFile cannot both be set")) - } - if len(cfg.DiscoveryToken) == 0 && len(cfg.DiscoveryFile) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath, "", "discoveryToken or discoveryFile must be set")) - } - if len(cfg.DiscoveryTokenAPIServers) < 1 && len(cfg.DiscoveryToken) != 0 { - allErrs = append(allErrs, field.Required(fldPath, "discoveryTokenAPIServers not set")) - } - if len(cfg.DiscoveryFile) != 0 && len(cfg.DiscoveryTokenCACertHashes) != 0 { - allErrs = append(allErrs, field.Invalid(fldPath, "", "discoveryTokenCACertHashes cannot be used with discoveryFile")) - } - - if len(cfg.DiscoveryFile) == 0 && len(cfg.DiscoveryToken) != 0 && - len(cfg.DiscoveryTokenCACertHashes) == 0 && !cfg.DiscoveryTokenUnsafeSkipCAVerification { - allErrs = append(allErrs, field.Invalid(fldPath, "", "using token-based discovery without discoveryTokenCACertHashes can be unsafe. set --discovery-token-unsafe-skip-ca-verification to continue")) + if len(b.APIServerEndpoints) < 1 { + allErrs = append(allErrs, field.Required(fldPath, "APIServerEndpoints not set")) } // TODO remove once we support multiple api servers - if len(cfg.DiscoveryTokenAPIServers) > 1 { + if len(b.APIServerEndpoints) > 1 { fmt.Println("[validation] WARNING: kubeadm doesn't fully support multiple API Servers yet") } + + if len(b.CACertHashes) == 0 && !b.UnsafeSkipCAVerification { + allErrs = append(allErrs, field.Invalid(fldPath, "", "using token-based discovery without caCertHashes can be unsafe. Set unsafeSkipCAVerification to continue")) + } + + allErrs = append(allErrs, ValidateToken(b.Token, fldPath.Child("token"))...) + allErrs = append(allErrs, ValidateDiscoveryTokenAPIServer(b.APIServerEndpoints, fldPath.Child("apiServerEndpoints"))...) + return allErrs } -// ValidateJoinDiscoveryTokenAPIServer validates discovery token for API server -func ValidateJoinDiscoveryTokenAPIServer(apiServers []string, fldPath *field.Path) field.ErrorList { +// ValidateDiscoveryFile validates file discovery configuration +func ValidateDiscoveryFile(f *kubeadm.FileDiscovery, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + allErrs = append(allErrs, ValidateDiscoveryKubeConfigPath(f.KubeConfigPath, fldPath.Child("kubeConfigPath"))...) + + return allErrs +} + +// ValidateDiscoveryTokenAPIServer validates discovery token for API server +func ValidateDiscoveryTokenAPIServer(apiServers []string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for _, m := range apiServers { _, _, err := net.SplitHostPort(m) @@ -152,8 +162,8 @@ func ValidateJoinDiscoveryTokenAPIServer(apiServers []string, fldPath *field.Pat return allErrs } -// ValidateDiscoveryFile validates location of a discovery file -func ValidateDiscoveryFile(discoveryFile string, fldPath *field.Path) field.ErrorList { +// ValidateDiscoveryKubeConfigPath validates location of a discovery file +func ValidateDiscoveryKubeConfigPath(discoveryFile string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} u, err := url.Parse(discoveryFile) if err != nil { diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go index 604c3e129bc..f955f0514a9 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go @@ -34,20 +34,19 @@ import ( func TestValidateToken(t *testing.T) { var tests = []struct { - c *kubeadm.JoinConfiguration - f *field.Path + token string expected bool }{ - {&kubeadm.JoinConfiguration{Token: "772ef5.6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}}, nil, true}, - {&kubeadm.JoinConfiguration{Token: ".6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}}, nil, false}, - {&kubeadm.JoinConfiguration{Token: "772ef5.", DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}}, nil, false}, - {&kubeadm.JoinConfiguration{Token: "772ef5.6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"2001:db8::100:6443"}}, nil, true}, - {&kubeadm.JoinConfiguration{Token: ".6b6baab1d4a0a171", DiscoveryTokenAPIServers: []string{"2001:db8::100:6443"}}, nil, false}, - {&kubeadm.JoinConfiguration{Token: "772ef5.", DiscoveryTokenAPIServers: []string{"2001:db8::100:6443"}}, nil, false}, - {&kubeadm.JoinConfiguration{Token: "abcdef.1234567890123456@foobar", DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}}, nil, false}, + {"772ef5.6b6baab1d4a0a171", true}, + {".6b6baab1d4a0a171", false}, + {"772ef5.", false}, + {"772ef5.6b6baab1d4a0a171", true}, + {".6b6baab1d4a0a171", false}, + {"772ef5.", false}, + {"abcdef.1234567890123456@foobar", false}, } for _, rt := range tests { - err := ValidateToken(rt.c.Token, rt.f).ToAggregate() + err := ValidateToken(rt.token, nil).ToAggregate() if (err == nil) != rt.expected { t.Errorf( "failed ValidateToken:\n\texpected: %t\n\t actual: %t", @@ -532,9 +531,15 @@ func TestValidateJoinConfiguration(t *testing.T) { }{ {&kubeadm.JoinConfiguration{}, false}, {&kubeadm.JoinConfiguration{ - DiscoveryFile: "foo", - DiscoveryToken: "abcdef.1234567890123456@foobar", - CACertPath: "/some/cert.crt", + CACertPath: "/some/cert.crt", + Discovery: kubeadm.Discovery{ + BootstrapToken: &kubeadm.BootstrapTokenDiscovery{ + Token: "abcdef.1234567890123456@foobar", + }, + File: &kubeadm.FileDiscovery{ + KubeConfigPath: "foo", + }, + }, }, false}, } for _, rt := range tests { @@ -642,133 +647,129 @@ func TestValidateIgnorePreflightErrors(t *testing.T) { } } -func TestValidateArgSelection(t *testing.T) { +func TestValidateDiscovery(t *testing.T) { var tests = []struct { name string - c *kubeadm.JoinConfiguration + d *kubeadm.Discovery expected bool }{ { - "invalid: DiscoveryToken and DiscoveryFile cannot both be set", - &kubeadm.JoinConfiguration{ - DiscoveryFile: "https://url/file.conf", - DiscoveryToken: "abcdef.1234567890123456", + "invalid: .BootstrapToken and .File cannot both be set", + &kubeadm.Discovery{ + BootstrapToken: &kubeadm.BootstrapTokenDiscovery{ + Token: "abcdef.1234567890123456", + }, + File: &kubeadm.FileDiscovery{ + KubeConfigPath: "https://url/file.conf", + }, }, false, }, { - "invalid: DiscoveryToken or DiscoveryFile must be set", - &kubeadm.JoinConfiguration{ - DiscoveryFile: "", - DiscoveryToken: "", + "invalid: .BootstrapToken or .File must be set", + &kubeadm.Discovery{}, + false, + }, + } + for _, rt := range tests { + t.Run(rt.name, func(t *testing.T) { + err := ValidateDiscovery(rt.d, nil).ToAggregate() + if (err == nil) != rt.expected { + t.Errorf( + "test case failed: ValidateDiscovery:\n\texpected: %t\n\t actual: %t", + rt.expected, + (err == nil), + ) + } + }) + } +} + +func TestValidateDiscoveryBootstrapToken(t *testing.T) { + var tests = []struct { + name string + btd *kubeadm.BootstrapTokenDiscovery + expected bool + }{ + { + "invalid: .APIServerEndpoints not set", + &kubeadm.BootstrapTokenDiscovery{ + Token: "abcdef.1234567890123456", }, false, }, { - "invalid: DiscoveryTokenAPIServers not set", - &kubeadm.JoinConfiguration{ - DiscoveryToken: "abcdef.1234567890123456", - }, - false, - }, - { - "invalid: DiscoveryTokenCACertHashes cannot be used with DiscoveryFile", - &kubeadm.JoinConfiguration{ - DiscoveryFile: "https://url/file.conf", - DiscoveryTokenCACertHashes: []string{"sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, - }, - false, - }, - { - "invalid: using token-based discovery without DiscoveryTokenCACertHashes and DiscoveryTokenUnsafeSkipCAVerification", - &kubeadm.JoinConfiguration{ - DiscoveryToken: "abcdef.1234567890123456", - DiscoveryTokenUnsafeSkipCAVerification: false, - DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, + "invalid: using token-based discovery without .BootstrapToken.CACertHashes and .BootstrapToken.UnsafeSkipCAVerification", + &kubeadm.BootstrapTokenDiscovery{ + Token: "abcdef.1234567890123456", + APIServerEndpoints: []string{"192.168.122.100:6443"}, + UnsafeSkipCAVerification: false, }, false, }, { "WARNING: kubeadm doesn't fully support multiple API Servers yet", - &kubeadm.JoinConfiguration{ - DiscoveryToken: "abcdef.1234567890123456", - DiscoveryTokenUnsafeSkipCAVerification: true, - DiscoveryTokenAPIServers: []string{"192.168.122.100:6443", "192.168.122.88:6443"}, + &kubeadm.BootstrapTokenDiscovery{ + Token: "abcdef.1234567890123456", + APIServerEndpoints: []string{"192.168.122.100:6443", "192.168.122.88:6443"}, + UnsafeSkipCAVerification: true, }, true, }, { - "valid: DiscoveryFile with DiscoveryTokenAPIServers", - &kubeadm.JoinConfiguration{ - DiscoveryFile: "https://url/file.conf", - DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, + "valid: using token-based discovery with .BootstrapToken.CACertHashes", + &kubeadm.BootstrapTokenDiscovery{ + Token: "abcdef.1234567890123456", + APIServerEndpoints: []string{"192.168.122.100:6443"}, + CACertHashes: []string{"sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, + UnsafeSkipCAVerification: false, }, true, }, { - "valid: DiscoveryFile without DiscoveryTokenAPIServers", - &kubeadm.JoinConfiguration{ - DiscoveryFile: "https://url/file.conf", - }, - true, - }, - { - "valid: using token-based discovery with DiscoveryTokenCACertHashes", - &kubeadm.JoinConfiguration{ - DiscoveryToken: "abcdef.1234567890123456", - DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, - DiscoveryTokenCACertHashes: []string{"sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, - DiscoveryTokenUnsafeSkipCAVerification: false, - }, - true, - }, - { - "valid: using token-based discovery with DiscoveryTokenCACertHashe but skip ca verification", - &kubeadm.JoinConfiguration{ - DiscoveryToken: "abcdef.1234567890123456", - DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, - DiscoveryTokenCACertHashes: []string{"sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, - DiscoveryTokenUnsafeSkipCAVerification: true, + "valid: using token-based discovery with .BootstrapToken.CACertHashe but skip ca verification", + &kubeadm.BootstrapTokenDiscovery{ + Token: "abcdef.1234567890123456", + APIServerEndpoints: []string{"192.168.122.100:6443"}, + CACertHashes: []string{"sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, + UnsafeSkipCAVerification: true, }, true, }, } for _, rt := range tests { - err := ValidateArgSelection(rt.c, nil).ToAggregate() - if (err == nil) != rt.expected { - t.Errorf( - "%s test case failed: ValidateArgSelection:\n\texpected: %t\n\t actual: %t", - rt.name, - rt.expected, - (err == nil), - ) - } + t.Run(rt.name, func(t *testing.T) { + err := ValidateDiscoveryBootstrapToken(rt.btd, nil).ToAggregate() + if (err == nil) != rt.expected { + t.Errorf( + "test case failed: ValidateDiscoveryBootstrapToken:\n\texpected: %t\n\t actual: %t", + rt.expected, + (err == nil), + ) + } + }) } } -func TestValidateJoinDiscoveryTokenAPIServer(t *testing.T) { +func TestValidateDiscoveryTokenAPIServer(t *testing.T) { var tests = []struct { - s *kubeadm.JoinConfiguration - expected bool + apiServerEndpoints []string + expected bool }{ { - &kubeadm.JoinConfiguration{ - DiscoveryTokenAPIServers: []string{"192.168.122.100"}, - }, + []string{"192.168.122.100"}, false, }, { - &kubeadm.JoinConfiguration{ - DiscoveryTokenAPIServers: []string{"192.168.122.100:6443"}, - }, + []string{"192.168.122.100:6443"}, true, }, } for _, rt := range tests { - actual := ValidateJoinDiscoveryTokenAPIServer(rt.s.DiscoveryTokenAPIServers, nil) + actual := ValidateDiscoveryTokenAPIServer(rt.apiServerEndpoints, nil) if (len(actual) == 0) != rt.expected { t.Errorf( - "failed ValidateJoinDiscoveryTokenAPIServer:\n\texpected: %t\n\t actual: %t", + "failed ValidateDiscoveryTokenAPIServer:\n\texpected: %t\n\t actual: %t", rt.expected, (len(actual) == 0), ) @@ -776,7 +777,7 @@ func TestValidateJoinDiscoveryTokenAPIServer(t *testing.T) { } } -func TestValidateDiscoveryFile(t *testing.T) { +func TestValidateDiscoveryKubeConfigPath(t *testing.T) { tmpfile, err := ioutil.TempFile("/tmp", "test_discovery_file") if err != nil { t.Errorf("Error creating temporary file: %v", err) @@ -796,10 +797,10 @@ func TestValidateDiscoveryFile(t *testing.T) { {"https://url/file.conf", true}, } for i, rt := range tests { - actual := ValidateDiscoveryFile(rt.s, nil) + actual := ValidateDiscoveryKubeConfigPath(rt.s, nil) if (len(actual) == 0) != rt.expected { t.Errorf( - "%d: failed ValidateDiscoveryFile:\n\texpected: %t\n\t actual: %t", + "%d: failed ValidateDiscoveryKubeConfigPath:\n\texpected: %t\n\t actual: %t", i, rt.expected, (len(actual) == 0), diff --git a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go index 310e205f241..1de5d51da72 100644 --- a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go @@ -105,6 +105,32 @@ func (in *BootstrapToken) DeepCopy() *BootstrapToken { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BootstrapTokenDiscovery) DeepCopyInto(out *BootstrapTokenDiscovery) { + *out = *in + if in.APIServerEndpoints != nil { + in, out := &in.APIServerEndpoints, &out.APIServerEndpoints + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.CACertHashes != nil { + in, out := &in.CACertHashes, &out.CACertHashes + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapTokenDiscovery. +func (in *BootstrapTokenDiscovery) DeepCopy() *BootstrapTokenDiscovery { + if in == nil { + return nil + } + out := new(BootstrapTokenDiscovery) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BootstrapTokenString) DeepCopyInto(out *BootstrapTokenString) { *out = *in @@ -256,6 +282,37 @@ func (in *ComponentConfigs) DeepCopy() *ComponentConfigs { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Discovery) DeepCopyInto(out *Discovery) { + *out = *in + if in.BootstrapToken != nil { + in, out := &in.BootstrapToken, &out.BootstrapToken + *out = new(BootstrapTokenDiscovery) + (*in).DeepCopyInto(*out) + } + if in.File != nil { + in, out := &in.File, &out.File + *out = new(FileDiscovery) + **out = **in + } + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Discovery. +func (in *Discovery) DeepCopy() *Discovery { + if in == nil { + return nil + } + out := new(Discovery) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Etcd) DeepCopyInto(out *Etcd) { *out = *in @@ -303,6 +360,22 @@ func (in *ExternalEtcd) DeepCopy() *ExternalEtcd { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FileDiscovery) DeepCopyInto(out *FileDiscovery) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FileDiscovery. +func (in *FileDiscovery) DeepCopy() *FileDiscovery { + if in == nil { + return nil + } + out := new(FileDiscovery) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HostPathMount) DeepCopyInto(out *HostPathMount) { *out = *in @@ -359,21 +432,7 @@ func (in *JoinConfiguration) DeepCopyInto(out *JoinConfiguration) { *out = *in out.TypeMeta = in.TypeMeta in.NodeRegistration.DeepCopyInto(&out.NodeRegistration) - if in.DiscoveryTokenAPIServers != nil { - in, out := &in.DiscoveryTokenAPIServers, &out.DiscoveryTokenAPIServers - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.DiscoveryTimeout != nil { - in, out := &in.DiscoveryTimeout, &out.DiscoveryTimeout - *out = new(v1.Duration) - **out = **in - } - if in.DiscoveryTokenCACertHashes != nil { - in, out := &in.DiscoveryTokenCACertHashes, &out.DiscoveryTokenCACertHashes - *out = make([]string, len(*in)) - copy(*out, *in) - } + in.Discovery.DeepCopyInto(&out.Discovery) out.APIEndpoint = in.APIEndpoint if in.FeatureGates != nil { in, out := &in.FeatureGates, &out.FeatureGates diff --git a/cmd/kubeadm/app/cmd/config.go b/cmd/kubeadm/app/cmd/config.go index 6ce9d29e14b..f5ce2002e65 100644 --- a/cmd/kubeadm/app/cmd/config.go +++ b/cmd/kubeadm/app/cmd/config.go @@ -195,9 +195,13 @@ func getDefaultInitConfigBytes(kind string) ([]byte, error) { func getDefaultNodeConfigBytes() ([]byte, error) { internalcfg, err := configutil.JoinConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1beta1.JoinConfiguration{ - Token: sillyToken.Token.String(), - DiscoveryTokenAPIServers: []string{"kube-apiserver:6443"}, - DiscoveryTokenUnsafeSkipCAVerification: true, // TODO: DiscoveryTokenUnsafeSkipCAVerification: true needs to be set for validation to pass, but shouldn't be recommended as the default + Discovery: kubeadmapiv1beta1.Discovery{ + BootstrapToken: &kubeadmapiv1beta1.BootstrapTokenDiscovery{ + Token: sillyToken.Token.String(), + APIServerEndpoints: []string{"kube-apiserver:6443"}, + UnsafeSkipCAVerification: true, // TODO: UnsafeSkipCAVerification: true needs to be set for validation to pass, but shouldn't be recommended as the default + }, + }, }) if err != nil { return []byte{}, err diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index 03233588e50..e07c003f5cb 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -159,6 +159,10 @@ func NewCmdJoin(out io.Writer) *cobra.Command { cfg := &kubeadmapiv1beta1.JoinConfiguration{} kubeadmscheme.Scheme.Default(cfg) + fd := &kubeadmapiv1beta1.FileDiscovery{} + btd := &kubeadmapiv1beta1.BootstrapTokenDiscovery{} + + var token string var cfgPath string var featureGatesString string var ignorePreflightErrors []string @@ -168,22 +172,37 @@ func NewCmdJoin(out io.Writer) *cobra.Command { Short: "Run this on any machine you wish to join an existing cluster", Long: joinLongDescription, Run: func(cmd *cobra.Command, args []string) { - j, err := NewValidJoin(cmd.PersistentFlags(), cfg, args, cfgPath, featureGatesString, ignorePreflightErrors) + + if len(fd.KubeConfigPath) != 0 { + cfg.Discovery.File = fd + } else { + cfg.Discovery.BootstrapToken = btd + cfg.Discovery.BootstrapToken.APIServerEndpoints = args + if len(cfg.Discovery.BootstrapToken.Token) == 0 { + cfg.Discovery.BootstrapToken.Token = token + } + } + + if len(cfg.Discovery.TLSBootstrapToken) == 0 { + cfg.Discovery.TLSBootstrapToken = token + } + + j, err := NewValidJoin(cmd.PersistentFlags(), cfg, cfgPath, featureGatesString, ignorePreflightErrors) kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(j.Run(out)) }, } - AddJoinConfigFlags(cmd.PersistentFlags(), cfg, &featureGatesString) + AddJoinConfigFlags(cmd.PersistentFlags(), cfg, &featureGatesString, &token) + AddJoinBootstrapTokenDiscoveryFlags(cmd.PersistentFlags(), btd) + AddJoinFileDiscoveryFlags(cmd.PersistentFlags(), fd) AddJoinOtherFlags(cmd.PersistentFlags(), &cfgPath, &ignorePreflightErrors) return cmd } // NewValidJoin validates the command line that are passed to the cobra command -func NewValidJoin(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfiguration, args []string, cfgPath, featureGatesString string, ignorePreflightErrors []string) (*Join, error) { - cfg.DiscoveryTokenAPIServers = args - +func NewValidJoin(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfiguration, cfgPath, featureGatesString string, ignorePreflightErrors []string) (*Join, error) { var err error if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil { return nil, err @@ -198,32 +217,17 @@ func NewValidJoin(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfiguratio return nil, err } - return NewJoin(cfgPath, args, cfg, ignorePreflightErrorsSet) + return NewJoin(cfgPath, cfg, ignorePreflightErrorsSet) } // AddJoinConfigFlags adds join flags bound to the config to the specified flagset -func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfiguration, featureGatesString *string) { - flagSet.StringVar( - &cfg.DiscoveryFile, "discovery-file", "", - "A file or url from which to load cluster information.") - flagSet.StringVar( - &cfg.DiscoveryToken, "discovery-token", "", - "A token used to validate cluster information fetched from the api server.") +func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfiguration, featureGatesString *string, token *string) { flagSet.StringVar( &cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name, "Specify the node name.") flagSet.StringVar( - &cfg.TLSBootstrapToken, "tls-bootstrap-token", "", - "A token used for TLS bootstrapping.") - flagSet.StringSliceVar( - &cfg.DiscoveryTokenCACertHashes, "discovery-token-ca-cert-hash", []string{}, - "For token-based discovery, validate that the root CA public key matches this hash (format: \":\").") - flagSet.BoolVar( - &cfg.DiscoveryTokenUnsafeSkipCAVerification, "discovery-token-unsafe-skip-ca-verification", false, - "For token-based discovery, allow joining without --discovery-token-ca-cert-hash pinning.") - flagSet.StringVar( - &cfg.Token, "token", "", - "Use this token for both discovery-token and tls-bootstrap-token.") + token, "token", "", + "Use this token for both discovery-token and tls-bootstrap-token when those values are not provided.") flagSet.StringVar( featureGatesString, "feature-gates", *featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+ @@ -245,6 +249,26 @@ func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfig ) } +// AddJoinBootstrapTokenDiscoveryFlags adds bootstrap token specific discovery flags to the specified flagset +func AddJoinBootstrapTokenDiscoveryFlags(flagSet *flag.FlagSet, btd *kubeadmapiv1beta1.BootstrapTokenDiscovery) { + flagSet.StringVar( + &btd.Token, "discovery-token", "", + "A token used to validate cluster information fetched from the API server.") + flagSet.StringSliceVar( + &btd.CACertHashes, "discovery-token-ca-cert-hash", []string{}, + "For token-based discovery, validate that the root CA public key matches this hash (format: \":\").") + flagSet.BoolVar( + &btd.UnsafeSkipCAVerification, "discovery-token-unsafe-skip-ca-verification", false, + "For token-based discovery, allow joining without --discovery-token-ca-cert-hash pinning.") +} + +// AddJoinFileDiscoveryFlags adds file discovery flags to the specified flagset +func AddJoinFileDiscoveryFlags(flagSet *flag.FlagSet, fd *kubeadmapiv1beta1.FileDiscovery) { + flagSet.StringVar( + &fd.KubeConfigPath, "discovery-file", "", + "A file or URL from which to load cluster information.") +} + // AddJoinOtherFlags adds join flags that are not bound to a configuration file to the given flagset func AddJoinOtherFlags(flagSet *flag.FlagSet, cfgPath *string, ignorePreflightErrors *[]string) { flagSet.StringVar( @@ -264,7 +288,7 @@ type Join struct { } // NewJoin instantiates Join struct with given arguments -func NewJoin(cfgPath string, args []string, defaultcfg *kubeadmapiv1beta1.JoinConfiguration, ignorePreflightErrors sets.String) (*Join, error) { +func NewJoin(cfgPath string, defaultcfg *kubeadmapiv1beta1.JoinConfiguration, ignorePreflightErrors sets.String) (*Join, error) { if defaultcfg.NodeRegistration.Name == "" { glog.V(1).Infoln("[join] found NodeName empty; using OS hostname as NodeName") diff --git a/cmd/kubeadm/app/cmd/join_test.go b/cmd/kubeadm/app/cmd/join_test.go index b891e0bdf23..4a83ec5149f 100644 --- a/cmd/kubeadm/app/cmd/join_test.go +++ b/cmd/kubeadm/app/cmd/join_test.go @@ -70,7 +70,6 @@ func TestNewValidJoin(t *testing.T) { testCases := []struct { name string - args []string skipPreFlight bool cfgPath string configToWrite string @@ -163,7 +162,7 @@ func TestNewValidJoin(t *testing.T) { } } - join, err := NewValidJoin(cmd.PersistentFlags(), cfg, tc.args, tc.cfgPath, tc.featureGatesString, tc.ignorePreflightErrors) + join, err := NewValidJoin(cmd.PersistentFlags(), cfg, tc.cfgPath, tc.featureGatesString, tc.ignorePreflightErrors) if tc.nodeConfig != nil { join.cfg = tc.nodeConfig diff --git a/cmd/kubeadm/app/discovery/discovery.go b/cmd/kubeadm/app/discovery/discovery.go index 8b2bb599d9b..3cbbafa5568 100644 --- a/cmd/kubeadm/app/discovery/discovery.go +++ b/cmd/kubeadm/app/discovery/discovery.go @@ -41,7 +41,7 @@ func For(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { return nil, fmt.Errorf("couldn't validate the identity of the API Server: %v", err) } - if len(cfg.TLSBootstrapToken) == 0 { + if len(cfg.Discovery.TLSBootstrapToken) == 0 { return config, nil } clusterinfo := kubeconfigutil.GetClusterFromKubeConfig(config) @@ -50,19 +50,20 @@ func For(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { cfg.ClusterName, TokenUser, clusterinfo.CertificateAuthorityData, - cfg.TLSBootstrapToken, + cfg.Discovery.TLSBootstrapToken, ), nil } // DiscoverValidatedKubeConfig returns a validated Config object that specifies where the cluster is and the CA cert to trust func DiscoverValidatedKubeConfig(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { switch { - case len(cfg.DiscoveryFile) != 0: - if isHTTPSURL(cfg.DiscoveryFile) { - return https.RetrieveValidatedConfigInfo(cfg.DiscoveryFile, cfg.ClusterName) + case cfg.Discovery.File != nil: + kubeConfigPath := cfg.Discovery.File.KubeConfigPath + if isHTTPSURL(kubeConfigPath) { + return https.RetrieveValidatedConfigInfo(kubeConfigPath, cfg.ClusterName) } - return file.RetrieveValidatedConfigInfo(cfg.DiscoveryFile, cfg.ClusterName) - case len(cfg.DiscoveryToken) != 0: + return file.RetrieveValidatedConfigInfo(kubeConfigPath, cfg.ClusterName) + case cfg.Discovery.BootstrapToken != nil: return token.RetrieveValidatedConfigInfo(cfg) default: return nil, fmt.Errorf("couldn't find a valid discovery configuration") diff --git a/cmd/kubeadm/app/discovery/discovery_test.go b/cmd/kubeadm/app/discovery/discovery_test.go index 42ecc03c103..96fb4f14e63 100644 --- a/cmd/kubeadm/app/discovery/discovery_test.go +++ b/cmd/kubeadm/app/discovery/discovery_test.go @@ -30,25 +30,31 @@ func TestFor(t *testing.T) { {d: kubeadm.JoinConfiguration{}, expect: false}, { d: kubeadm.JoinConfiguration{ - DiscoveryFile: "notnil", + Discovery: kubeadm.Discovery{ + File: &kubeadm.FileDiscovery{ + KubeConfigPath: "notnil", + }, + }, }, expect: false, }, { d: kubeadm.JoinConfiguration{ - DiscoveryFile: "https://localhost", + Discovery: kubeadm.Discovery{ + File: &kubeadm.FileDiscovery{ + KubeConfigPath: "https://localhost", + }, + }, }, expect: false, }, { d: kubeadm.JoinConfiguration{ - DiscoveryFile: "notnil", - }, - expect: false, - }, - { - d: kubeadm.JoinConfiguration{ - DiscoveryToken: "foo.bar@foobar", + Discovery: kubeadm.Discovery{ + BootstrapToken: &kubeadm.BootstrapTokenDiscovery{ + Token: "foo.bar@foobar", + }, + }, }, expect: false, }, diff --git a/cmd/kubeadm/app/discovery/token/token.go b/cmd/kubeadm/app/discovery/token/token.go index 1ce6dea54c1..f919d753cd8 100644 --- a/cmd/kubeadm/app/discovery/token/token.go +++ b/cmd/kubeadm/app/discovery/token/token.go @@ -44,21 +44,21 @@ const BootstrapUser = "token-bootstrap-client" // It then makes sure it can trust the API Server by looking at the JWS-signed tokens and (if cfg.DiscoveryTokenCACertHashes is not empty) // validating the cluster CA against a set of pinned public keys func RetrieveValidatedConfigInfo(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { - token, err := kubeadmapi.NewBootstrapTokenString(cfg.DiscoveryToken) + token, err := kubeadmapi.NewBootstrapTokenString(cfg.Discovery.BootstrapToken.Token) if err != nil { return nil, err } // Load the cfg.DiscoveryTokenCACertHashes into a pubkeypin.Set pubKeyPins := pubkeypin.NewSet() - err = pubKeyPins.Allow(cfg.DiscoveryTokenCACertHashes...) + err = pubKeyPins.Allow(cfg.Discovery.BootstrapToken.CACertHashes...) if err != nil { return nil, err } // 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, err := runForEndpointsAndReturnFirst(cfg.DiscoveryTokenAPIServers, cfg.DiscoveryTimeout.Duration, func(endpoint string) (*clientcmdapi.Config, error) { + baseKubeConfig, err := runForEndpointsAndReturnFirst(cfg.Discovery.BootstrapToken.APIServerEndpoints, cfg.Discovery.Timeout.Duration, func(endpoint string) (*clientcmdapi.Config, error) { insecureBootstrapConfig := buildInsecureBootstrapKubeConfig(endpoint, cfg.ClusterName) clusterName := insecureBootstrapConfig.Contexts[insecureBootstrapConfig.CurrentContext].Cluster diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index e3ff7ee0ceb..56af6a49a00 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -928,16 +928,18 @@ func RunJoinNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.JoinConfigura } addIPv6Checks := false - for _, server := range cfg.DiscoveryTokenAPIServers { - ipstr, _, err := net.SplitHostPort(server) - if err == nil { - checks = append(checks, - HTTPProxyCheck{Proto: "https", Host: ipstr}, - ) - if !addIPv6Checks { - if ip := net.ParseIP(ipstr); ip != nil { - if ip.To4() == nil && ip.To16() != nil { - addIPv6Checks = true + if cfg.Discovery.BootstrapToken != nil { + for _, server := range cfg.Discovery.BootstrapToken.APIServerEndpoints { + ipstr, _, err := net.SplitHostPort(server) + if err == nil { + checks = append(checks, + HTTPProxyCheck{Proto: "https", Host: ipstr}, + ) + if !addIPv6Checks { + if ip := net.ParseIP(ipstr); ip != nil { + if ip.To4() == nil && ip.To16() != nil { + addIPv6Checks = true + } } } } diff --git a/cmd/kubeadm/app/preflight/checks_test.go b/cmd/kubeadm/app/preflight/checks_test.go index a71061bbc76..efdf901a14c 100644 --- a/cmd/kubeadm/app/preflight/checks_test.go +++ b/cmd/kubeadm/app/preflight/checks_test.go @@ -254,13 +254,21 @@ func TestRunJoinNodeChecks(t *testing.T) { }, { cfg: &kubeadmapi.JoinConfiguration{ - DiscoveryTokenAPIServers: []string{"192.168.1.15"}, + Discovery: kubeadmapi.Discovery{ + BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{ + APIServerEndpoints: []string{"192.168.1.15"}, + }, + }, }, expected: false, }, { cfg: &kubeadmapi.JoinConfiguration{ - DiscoveryTokenAPIServers: []string{"2001:1234::1:15"}, + Discovery: kubeadmapi.Discovery{ + BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{ + APIServerEndpoints: []string{"2001:1234::1:15"}, + }, + }, }, expected: false, }, diff --git a/cmd/kubeadm/app/util/config/testdata/conversion/node/internal.yaml b/cmd/kubeadm/app/util/config/testdata/conversion/node/internal.yaml index f800439c05b..9eaa51dd9a9 100644 --- a/cmd/kubeadm/app/util/config/testdata/conversion/node/internal.yaml +++ b/cmd/kubeadm/app/util/config/testdata/conversion/node/internal.yaml @@ -4,18 +4,19 @@ APIEndpoint: CACertPath: /etc/kubernetes/pki/ca.crt ClusterName: kubernetes ControlPlane: false -DiscoveryFile: "" -DiscoveryTimeout: 5m0s -DiscoveryToken: abcdef.0123456789abcdef -DiscoveryTokenAPIServers: -- kube-apiserver:6443 -DiscoveryTokenCACertHashes: null -DiscoveryTokenUnsafeSkipCAVerification: true +Discovery: + BootstrapToken: + APIServerEndpoints: + - kube-apiserver:6443 + CACertHashes: null + Token: abcdef.0123456789abcdef + UnsafeSkipCAVerification: true + File: null + TLSBootstrapToken: abcdef.0123456789abcdef + Timeout: 5m0s FeatureGates: null NodeRegistration: CRISocket: /var/run/dockershim.sock KubeletExtraArgs: null Name: master-1 Taints: null -TLSBootstrapToken: abcdef.0123456789abcdef -Token: abcdef.0123456789abcdef diff --git a/cmd/kubeadm/app/util/config/testdata/conversion/node/v1beta1.yaml b/cmd/kubeadm/app/util/config/testdata/conversion/node/v1beta1.yaml index a55a6640dd8..064e9a9f9d2 100644 --- a/cmd/kubeadm/app/util/config/testdata/conversion/node/v1beta1.yaml +++ b/cmd/kubeadm/app/util/config/testdata/conversion/node/v1beta1.yaml @@ -4,15 +4,15 @@ apiEndpoint: apiVersion: kubeadm.k8s.io/v1beta1 caCertPath: /etc/kubernetes/pki/ca.crt clusterName: kubernetes -discoveryFile: "" -discoveryTimeout: 5m0s -discoveryToken: abcdef.0123456789abcdef -discoveryTokenAPIServers: -- kube-apiserver:6443 -discoveryTokenUnsafeSkipCAVerification: true +discovery: + bootstrapToken: + apiServerEndpoints: + - kube-apiserver:6443 + token: abcdef.0123456789abcdef + unsafeSkipCAVerification: true + timeout: 5m0s + tlsBootstrapToken: abcdef.0123456789abcdef kind: JoinConfiguration nodeRegistration: criSocket: /var/run/dockershim.sock name: master-1 -tlsBootstrapToken: abcdef.0123456789abcdef -token: abcdef.0123456789abcdef diff --git a/cmd/kubeadm/app/util/config/testdata/defaulting/node/defaulted.yaml b/cmd/kubeadm/app/util/config/testdata/defaulting/node/defaulted.yaml index 040180381e3..0a18368a35f 100644 --- a/cmd/kubeadm/app/util/config/testdata/defaulting/node/defaulted.yaml +++ b/cmd/kubeadm/app/util/config/testdata/defaulting/node/defaulted.yaml @@ -4,15 +4,15 @@ apiEndpoint: apiVersion: kubeadm.k8s.io/v1beta1 caCertPath: /etc/kubernetes/pki/ca.crt clusterName: kubernetes -discoveryFile: "" -discoveryTimeout: 5m0s -discoveryToken: abcdef.0123456789abcdef -discoveryTokenAPIServers: -- kube-apiserver:6443 -discoveryTokenUnsafeSkipCAVerification: true +discovery: + bootstrapToken: + apiServerEndpoints: + - kube-apiserver:6443 + token: abcdef.0123456789abcdef + unsafeSkipCAVerification: true + timeout: 5m0s + tlsBootstrapToken: abcdef.0123456789abcdef kind: JoinConfiguration nodeRegistration: criSocket: /var/run/dockershim.sock name: thegopher -tlsBootstrapToken: abcdef.0123456789abcdef -token: abcdef.0123456789abcdef diff --git a/cmd/kubeadm/app/util/config/testdata/validation/invalid_nodecfg.yaml b/cmd/kubeadm/app/util/config/testdata/validation/invalid_nodecfg.yaml index c839a276e14..3c08cb7920a 100644 --- a/cmd/kubeadm/app/util/config/testdata/validation/invalid_nodecfg.yaml +++ b/cmd/kubeadm/app/util/config/testdata/validation/invalid_nodecfg.yaml @@ -1,5 +1,15 @@ apiVersion: kubeadm.k8s.io/v1beta1 kind: NodeConfiguration -apiEndpoint: - advertiseAddress: INVALID-ADDRESS-!!!! - bindPort: 6443 +caCertPath: relativepath +discovery: + timeout: not-a-time + bootstrapToken: + token: invalidtoken + apiServerEndpoints: + - INVALID_URL + unsafeSkipCAVerification: false + file: + kubeConfigPath: relativepath +nodeRegistration: + criSocket: relativepath + name: NODE-1