From 33f59e438e93b824492caff5d39fd143b43eac9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 31 May 2018 22:18:27 +0300 Subject: [PATCH 1/5] Move helper funcs and constants to the client-go Bootstrap Token package from kubeadm --- .../tools/bootstrap/token/{api => }/OWNERS | 0 .../tools/bootstrap/token/api/doc.go | 4 +- .../tools/bootstrap/token/api/types.go | 22 ++- .../tools/bootstrap/token/util/helpers.go | 93 +++++++++++- .../bootstrap/token/util/helpers_test.go | 137 ++++++++++++++++++ 5 files changed, 243 insertions(+), 13 deletions(-) rename staging/src/k8s.io/client-go/tools/bootstrap/token/{api => }/OWNERS (100%) diff --git a/staging/src/k8s.io/client-go/tools/bootstrap/token/api/OWNERS b/staging/src/k8s.io/client-go/tools/bootstrap/token/OWNERS similarity index 100% rename from staging/src/k8s.io/client-go/tools/bootstrap/token/api/OWNERS rename to staging/src/k8s.io/client-go/tools/bootstrap/token/OWNERS diff --git a/staging/src/k8s.io/client-go/tools/bootstrap/token/api/doc.go b/staging/src/k8s.io/client-go/tools/bootstrap/token/api/doc.go index b9910c35aab..249e0a059a6 100644 --- a/staging/src/k8s.io/client-go/tools/bootstrap/token/api/doc.go +++ b/staging/src/k8s.io/client-go/tools/bootstrap/token/api/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package api (pkg/bootstrap/token/api) contains constants and types needed for +// Package api (k8s.io/client-go/tools/bootstrap/token/api) contains constants and types needed for // bootstrap tokens as maintained by the BootstrapSigner and TokenCleaner -// controllers (in pkg/controller/bootstrap) +// controllers (in k8s.io/kubernetes/pkg/controller/bootstrap) package api // import "k8s.io/client-go/tools/bootstrap/token/api" diff --git a/staging/src/k8s.io/client-go/tools/bootstrap/token/api/types.go b/staging/src/k8s.io/client-go/tools/bootstrap/token/api/types.go index c30814c0e26..3bea78b1766 100644 --- a/staging/src/k8s.io/client-go/tools/bootstrap/token/api/types.go +++ b/staging/src/k8s.io/client-go/tools/bootstrap/token/api/types.go @@ -86,14 +86,26 @@ const ( // authenticate as. The full username given is "system:bootstrap:". BootstrapUserPrefix = "system:bootstrap:" - // BootstrapGroupPattern is the valid regex pattern that all groups - // assigned to a bootstrap token by BootstrapTokenExtraGroupsKey must match. - // See also ValidateBootstrapGroupName(). - BootstrapGroupPattern = "system:bootstrappers:[a-z0-9:-]{0,255}[a-z0-9]" - // BootstrapDefaultGroup is the default group for bootstrapping bearer // tokens (in addition to any groups from BootstrapTokenExtraGroupsKey). BootstrapDefaultGroup = "system:bootstrappers" + + // BootstrapGroupPattern is the valid regex pattern that all groups + // assigned to a bootstrap token by BootstrapTokenExtraGroupsKey must match. + // See also util.ValidateBootstrapGroupName() + BootstrapGroupPattern = `\Asystem:bootstrappers:[a-z0-9:-]{0,255}[a-z0-9]\z` + + // BootstrapTokenPattern defines the {id}.{secret} regular expression pattern + BootstrapTokenPattern = `\A([a-z0-9]{6})\.([a-z0-9]{16})\z` + + // BootstrapTokenIDPattern defines token's id regular expression pattern + BootstrapTokenIDPattern = `\A([a-z0-9]{6})\z` + + // BootstrapTokenIDBytes defines the number of bytes used for the Bootstrap Token's ID field + BootstrapTokenIDBytes = 6 + + // BootstrapTokenSecretBytes defines the number of bytes used the Bootstrap Token's Secret field + BootstrapTokenSecretBytes = 16 ) // KnownTokenUsages specifies the known functions a token will get. diff --git a/staging/src/k8s.io/client-go/tools/bootstrap/token/util/helpers.go b/staging/src/k8s.io/client-go/tools/bootstrap/token/util/helpers.go index d28fd28f2ec..bb1fbeb6580 100644 --- a/staging/src/k8s.io/client-go/tools/bootstrap/token/util/helpers.go +++ b/staging/src/k8s.io/client-go/tools/bootstrap/token/util/helpers.go @@ -17,20 +17,101 @@ limitations under the License. package util import ( + "bufio" + "crypto/rand" "fmt" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/tools/bootstrap/token/api" "regexp" "strings" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/tools/bootstrap/token/api" ) -var bootstrapGroupRegexp = regexp.MustCompile(`\A` + api.BootstrapGroupPattern + `\z`) +// validBootstrapTokenChars defines the characters a bootstrap token can consist of +const validBootstrapTokenChars = "0123456789abcdefghijklmnopqrstuvwxyz" + +var ( + // BootstrapTokenRegexp is a compiled regular expression of TokenRegexpString + BootstrapTokenRegexp = regexp.MustCompile(api.BootstrapTokenPattern) + // BootstrapTokenIDRegexp is a compiled regular expression of TokenIDRegexpString + BootstrapTokenIDRegexp = regexp.MustCompile(api.BootstrapTokenIDPattern) + // BootstrapGroupRegexp is a compiled regular expression of BootstrapGroupPattern + BootstrapGroupRegexp = regexp.MustCompile(api.BootstrapGroupPattern) +) + +// GenerateBootstrapToken generates a new, random Bootstrap Token. +func GenerateBootstrapToken() (string, error) { + tokenID, err := randBytes(api.BootstrapTokenIDBytes) + if err != nil { + return "", err + } + + tokenSecret, err := randBytes(api.BootstrapTokenSecretBytes) + if err != nil { + return "", err + } + + return TokenFromIDAndSecret(tokenID, tokenSecret), nil +} + +// randBytes returns a random string consisting of the characters in +// validBootstrapTokenChars, with the length customized by the parameter +func randBytes(length int) (string, error) { + // len("0123456789abcdefghijklmnopqrstuvwxyz") = 36 which doesn't evenly divide + // the possible values of a byte: 256 mod 36 = 4. Discard any random bytes we + // read that are >= 252 so the bytes we evenly divide the character set. + const maxByteValue = 252 + + var ( + b byte + err error + token = make([]byte, length) + ) + + reader := bufio.NewReaderSize(rand.Reader, length*2) + for i := range token { + for { + if b, err = reader.ReadByte(); err != nil { + return "", err + } + if b < maxByteValue { + break + } + } + + token[i] = validBootstrapTokenChars[int(b)%len(validBootstrapTokenChars)] + } + + return string(token), nil +} + +// TokenFromIDAndSecret returns the full token which is of the form "{id}.{secret}" +func TokenFromIDAndSecret(id, secret string) string { + return fmt.Sprintf("%s.%s", id, secret) +} + +// IsValidBootstrapToken returns whether the given string is valid as a Bootstrap Token and +// in other words satisfies the BootstrapTokenRegexp +func IsValidBootstrapToken(token string) bool { + return BootstrapTokenRegexp.MatchString(token) +} + +// IsValidBootstrapTokenID returns whether the given string is valid as a Bootstrap Token ID and +// in other words satisfies the BootstrapTokenIDRegexp +func IsValidBootstrapTokenID(tokenID string) bool { + return BootstrapTokenIDRegexp.MatchString(tokenID) +} + +// BootstrapTokenSecretName returns the expected name for the Secret storing the +// Bootstrap Token in the Kubernetes API. +func BootstrapTokenSecretName(tokenID string) string { + return fmt.Sprintf("%s%s", api.BootstrapTokenSecretPrefix, tokenID) +} // ValidateBootstrapGroupName checks if the provided group name is a valid // bootstrap group name. Returns nil if valid or a validation error if invalid. -// TODO(mattmoyer): this validation should migrate out to client-go (see https://github.com/kubernetes/client-go/issues/114) func ValidateBootstrapGroupName(name string) error { - if bootstrapGroupRegexp.Match([]byte(name)) { + if BootstrapGroupRegexp.Match([]byte(name)) { return nil } return fmt.Errorf("bootstrap group %q is invalid (must match %s)", name, api.BootstrapGroupPattern) @@ -46,7 +127,7 @@ func ValidateUsages(usages []string) error { } } if len(invalidUsages) > 0 { - return fmt.Errorf("invalide bootstrap token usage string: %s, valid usage options: %s", strings.Join(invalidUsages.List(), ","), strings.Join(api.KnownTokenUsages, ",")) + return fmt.Errorf("invalid bootstrap token usage string: %s, valid usage options: %s", strings.Join(invalidUsages.List(), ","), strings.Join(api.KnownTokenUsages, ",")) } return nil } diff --git a/staging/src/k8s.io/client-go/tools/bootstrap/token/util/helpers_test.go b/staging/src/k8s.io/client-go/tools/bootstrap/token/util/helpers_test.go index 915bf754026..a1fe6092ff6 100644 --- a/staging/src/k8s.io/client-go/tools/bootstrap/token/util/helpers_test.go +++ b/staging/src/k8s.io/client-go/tools/bootstrap/token/util/helpers_test.go @@ -21,6 +21,143 @@ import ( "testing" ) +func TestGenerateBootstrapToken(t *testing.T) { + token, err := GenerateBootstrapToken() + if err != nil { + t.Fatalf("GenerateBootstrapToken returned an unexpected error: %+v", err) + } + if !IsValidBootstrapToken(token) { + t.Errorf("GenerateBootstrapToken didn't generate a valid token: %q", token) + } +} + +func TestRandBytes(t *testing.T) { + var randTest = []int{ + 0, + 1, + 2, + 3, + 100, + } + + for _, rt := range randTest { + actual, err := randBytes(rt) + if err != nil { + t.Errorf("failed randBytes: %v", err) + } + if len(actual) != rt { + t.Errorf("failed randBytes:\n\texpected: %d\n\t actual: %d\n", rt, len(actual)) + } + } +} + +func TestTokenFromIDAndSecret(t *testing.T) { + var tests = []struct { + id string + secret string + expected string + }{ + {"foo", "bar", "foo.bar"}, // should use default + {"abcdef", "abcdef0123456789", "abcdef.abcdef0123456789"}, + {"h", "b", "h.b"}, + } + for _, rt := range tests { + actual := TokenFromIDAndSecret(rt.id, rt.secret) + if actual != rt.expected { + t.Errorf( + "failed TokenFromIDAndSecret:\n\texpected: %s\n\t actual: %s", + rt.expected, + actual, + ) + } + } +} + +func TestIsValidBootstrapToken(t *testing.T) { + var tests = []struct { + token string + expected bool + }{ + {token: "", expected: false}, + {token: ".", expected: false}, + {token: "1234567890123456789012", expected: false}, // invalid parcel size + {token: "12345.1234567890123456", expected: false}, // invalid parcel size + {token: ".1234567890123456", expected: false}, // invalid parcel size + {token: "123456.", expected: false}, // invalid parcel size + {token: "123456:1234567890.123456", expected: false}, // invalid separation + {token: "abcdef:1234567890123456", expected: false}, // invalid separation + {token: "Abcdef.1234567890123456", expected: false}, // invalid token id + {token: "123456.AABBCCDDEEFFGGHH", expected: false}, // invalid token secret + {token: "123456.AABBCCD-EEFFGGHH", expected: false}, // invalid character + {token: "abc*ef.1234567890123456", expected: false}, // invalid character + {token: "abcdef.1234567890123456", expected: true}, + {token: "123456.aabbccddeeffgghh", expected: true}, + {token: "ABCDEF.abcdef0123456789", expected: false}, + {token: "abcdef.abcdef0123456789", expected: true}, + {token: "123456.1234560123456789", expected: true}, + } + for _, rt := range tests { + actual := IsValidBootstrapToken(rt.token) + if actual != rt.expected { + t.Errorf( + "failed IsValidBootstrapToken for the token %q\n\texpected: %t\n\t actual: %t", + rt.token, + rt.expected, + actual, + ) + } + } +} + +func TestIsValidBootstrapTokenID(t *testing.T) { + var tests = []struct { + tokenID string + expected bool + }{ + {tokenID: "", expected: false}, + {tokenID: "1234567890123456789012", expected: false}, + {tokenID: "12345", expected: false}, + {tokenID: "Abcdef", expected: false}, + {tokenID: "ABCDEF", expected: false}, + {tokenID: "abcdef.", expected: false}, + {tokenID: "abcdef", expected: true}, + {tokenID: "123456", expected: true}, + } + for _, rt := range tests { + actual := IsValidBootstrapTokenID(rt.tokenID) + if actual != rt.expected { + t.Errorf( + "failed IsValidBootstrapTokenID for the token %q\n\texpected: %t\n\t actual: %t", + rt.tokenID, + rt.expected, + actual, + ) + } + } +} + +func TestBootstrapTokenSecretName(t *testing.T) { + var tests = []struct { + tokenID string + expected string + }{ + {"foo", "bootstrap-token-foo"}, + {"bar", "bootstrap-token-bar"}, + {"", "bootstrap-token-"}, + {"abcdef", "bootstrap-token-abcdef"}, + } + for _, rt := range tests { + actual := BootstrapTokenSecretName(rt.tokenID) + if actual != rt.expected { + t.Errorf( + "failed BootstrapTokenSecretName:\n\texpected: %s\n\t actual: %s", + rt.expected, + actual, + ) + } + } +} + func TestValidateBootstrapGroupName(t *testing.T) { tests := []struct { name string From c47303958056a2fee261f95870463789d39f6f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 31 May 2018 22:19:26 +0300 Subject: [PATCH 2/5] kubeadm: Initial refactor of the Bootstrap Tokens. Add the new API objects, add/move helpers and start using the new flow in the code --- .../app/apis/kubeadm/bootstraptokenhelpers.go | 168 ++++++++++++++++++ .../app/apis/kubeadm/bootstraptokenstring.go | 89 ++++++++++ cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go | 15 +- cmd/kubeadm/app/apis/kubeadm/types.go | 48 ++--- .../app/apis/kubeadm/v1alpha1/conversion.go | 74 ++++++-- .../kubeadm/v1alpha2/bootstraptokenstring.go | 89 ++++++++++ .../app/apis/kubeadm/v1alpha2/defaults.go | 47 +++-- .../app/apis/kubeadm/v1alpha2/types.go | 46 ++--- .../app/apis/kubeadm/validation/validation.go | 32 ++-- cmd/kubeadm/app/cmd/config.go | 16 +- cmd/kubeadm/app/cmd/init.go | 46 +++-- cmd/kubeadm/app/cmd/options/token.go | 91 ++++++++++ cmd/kubeadm/app/cmd/phases/bootstraptoken.go | 74 ++++---- cmd/kubeadm/app/cmd/phases/markmaster.go | 2 +- cmd/kubeadm/app/cmd/token.go | 163 +++++++---------- cmd/kubeadm/app/discovery/token/token.go | 9 +- .../app/phases/bootstraptoken/node/token.go | 114 +++--------- .../app/phases/uploadconfig/uploadconfig.go | 2 +- cmd/kubeadm/app/util/config/masterconfig.go | 24 ++- cmd/kubeadm/app/util/token/tokens.go | 125 ------------- 20 files changed, 800 insertions(+), 474 deletions(-) create mode 100644 cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go create mode 100644 cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go create mode 100644 cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring.go create mode 100644 cmd/kubeadm/app/cmd/options/token.go delete mode 100644 cmd/kubeadm/app/util/token/tokens.go diff --git a/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go new file mode 100644 index 00000000000..3bc0ed5a190 --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go @@ -0,0 +1,168 @@ +/* +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 kubeadm + +import ( + "fmt" + "sort" + "strings" + "time" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" + bootstraputil "k8s.io/client-go/tools/bootstrap/token/util" +) + +// ToSecret converts the given BootstrapToken object to its Secret representation that +// may be submitted to the API Server in order to be stored. +func (bt *BootstrapToken) ToSecret() *v1.Secret { + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: bootstraputil.BootstrapTokenSecretName(bt.Token.ID), + Namespace: metav1.NamespaceSystem, + }, + Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken), + Data: encodeTokenSecretData(bt, time.Now()), + } +} + +// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret +// now is passed in order to be able to used in unit testing +func encodeTokenSecretData(token *BootstrapToken, now time.Time) map[string][]byte { + data := map[string][]byte{ + bootstrapapi.BootstrapTokenIDKey: []byte(token.Token.ID), + bootstrapapi.BootstrapTokenSecretKey: []byte(token.Token.Secret), + } + + if len(token.Description) > 0 { + data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(token.Description) + } + + // If for some strange reason both token.TTL and token.Expires would be set + // (they are mutually exlusive in validation so this shouldn't be the case), + // token.Expires has higher priority, as can be seen in the logic here. + if token.Expires != nil { + // Format the expiration date accordingly + // TODO: This maybe should be a helper function in bootstraputil? + expirationString := token.Expires.Time.Format(time.RFC3339) + data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(expirationString) + + } else if token.TTL != nil && token.TTL.Duration > 0 { + // Only if .Expires is unset, TTL might have an effect + // Get the current time, add the specified duration, and format it accordingly + expirationString := now.Add(token.TTL.Duration).Format(time.RFC3339) + data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(expirationString) + } + + for _, usage := range token.Usages { + data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true") + } + + if len(token.Groups) > 0 { + data[bootstrapapi.BootstrapTokenExtraGroupsKey] = []byte(strings.Join(token.Groups, ",")) + } + return data +} + +// BootstrapTokenFromSecret returns a BootstrapToken object from the given Secret +func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) { + // Get the Token ID field from the Secret data + tokenID := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey) + if len(tokenID) == 0 { + return nil, fmt.Errorf("Bootstrap Token Secret has no token-id data: %s\n", secret.Name) + } + + // Enforce the right naming convention + if secret.Name != bootstraputil.BootstrapTokenSecretName(tokenID) { + return nil, fmt.Errorf("bootstrap token name is not of the form '%s(token-id)'. Actual: %q. Expected: %q\n", + bootstrapapi.BootstrapTokenSecretPrefix, secret.Name, bootstraputil.BootstrapTokenSecretName(tokenID)) + } + + tokenSecret := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey) + if len(tokenSecret) == 0 { + return nil, fmt.Errorf("Bootstrap Token Secret has no token-secret data: %s\n", secret.Name) + } + + // Create the BootstrapTokenString object based on the ID and Secret + bts, err := NewBootstrapTokenStringFromIDAndSecret(tokenID, tokenSecret) + if err != nil { + return nil, fmt.Errorf("Bootstrap Token Secret is invalid and couldn't be parsed: %v\n", err) + } + + // Get the description (if any) from the Secret + description := getSecretString(secret, bootstrapapi.BootstrapTokenDescriptionKey) + + // Expiration time is optional, if not specified this implies the token + // never expires. + secretExpiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey) + var expires *metav1.Time + if len(secretExpiration) > 0 { + expTime, err := time.Parse(time.RFC3339, secretExpiration) + if err != nil { + return nil, fmt.Errorf("can't parse expiration time of bootstrap token %q: %v", secret.Name, err) + } + expires = &metav1.Time{expTime} + } + + // Build an usages string slice from the Secret data + var usages []string + for k, v := range secret.Data { + // Skip all fields that don't include this prefix + if !strings.HasPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix) { + continue + } + // Skip those that don't have this usage set to true + if string(v) != "true" { + continue + } + usages = append(usages, strings.TrimPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix)) + } + // Only sort the slice if defined + if usages != nil { + sort.Strings(usages) + } + + // Get the extra groups information from the Secret + // It's done this way to make .Groups be nil in case there is no items, rather than an + // empty slice or an empty slice with a "" string only + var groups []string + groupsString := getSecretString(secret, bootstrapapi.BootstrapTokenExtraGroupsKey) + g := strings.Split(groupsString, ",") + if len(g) > 0 && len(g[0]) > 0 { + groups = g + } + + return &BootstrapToken{ + Token: bts, + Description: description, + Expires: expires, + Usages: usages, + Groups: groups, + }, nil +} + +// getSecretString returns the string value for the given key in the specified Secret +func getSecretString(secret *v1.Secret, key string) string { + if secret.Data == nil { + return "" + } + if val, ok := secret.Data[key]; ok { + return string(val) + } + return "" +} diff --git a/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go new file mode 100644 index 00000000000..8bbd11ebada --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go @@ -0,0 +1,89 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Note: This file should be kept in sync with the similar one for the external API +// TODO: The BootstrapTokenString object should move out to either k8s.io/client-go or k8s.io/api in the future +// (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now. +package kubeadm + +import ( + "fmt" + "strings" + + bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" + bootstraputil "k8s.io/client-go/tools/bootstrap/token/util" +) + +// BootstrapTokenString is a token of the format abcdef.abcdef0123456789 that is used +// for both validation of the authenticy of the API server from a joining node's point +// of view and as an authentication method for the node in the bootstrap phase of +// "kubeadm join". This token is and should be short-lived +type BootstrapTokenString struct { + ID string + Secret string +} + +// MarshalJSON implements the json.Marshaler interface. +func (bts BootstrapTokenString) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, bts.String())), nil +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (bts *BootstrapTokenString) UnmarshalJSON(b []byte) error { + // If the token is represented as "", just return quickly without an error + if len(b) == 0 { + return nil + } + + // Remove unnecessary " characters coming from the JSON parser + token := strings.Replace(string(b), `"`, ``, -1) + // Convert the string Token to a BootstrapTokenString object + newbts, err := NewBootstrapTokenString(token) + if err != nil { + return err + } + bts.ID = newbts.ID + bts.Secret = newbts.Secret + return nil +} + +// String returns the string representation of the BootstrapTokenString +func (bts BootstrapTokenString) String() string { + if len(bts.ID) > 0 && len(bts.Secret) > 0 { + return bootstraputil.TokenFromIDAndSecret(bts.ID, bts.Secret) + } + return "" +} + +// NewBootstrapTokenString converts the given Bootstrap Token as a string +// to the BootstrapTokenString object used for serialization/deserialization +// and internal usage. It also automatically validates that the given token +// is of the right format +func NewBootstrapTokenString(token string) (*BootstrapTokenString, error) { + substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token) + // TODO: Add a constant for the 3 value here, and explain better why it's needed (other than because how the regexp parsin works) + if len(substrs) != 3 { + return nil, fmt.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern) + } + + return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil +} + +// NewBootstrapTokenStringFromIDAndSecret is a wrapper around NewBootstrapTokenString +// that allows the caller to specify the ID and Secret separately +func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) { + return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret)) +} diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go index b640d6e533e..4478c92d1e7 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go @@ -43,10 +43,17 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { obj.CertificatesDir = "foo" obj.APIServerCertSANs = []string{"foo"} - obj.Token = "foo" - obj.TokenTTL = &metav1.Duration{Duration: 1 * time.Hour} - obj.TokenUsages = []string{"foo"} - obj.TokenGroups = []string{"foo"} + obj.BootstrapTokens = []kubeadm.BootstrapToken{ + { + Token: &kubeadm.BootstrapTokenString{ + ID: "abcdef", + Secret: "abcdef0123456789", + }, + TTL: &metav1.Duration{Duration: 1 * time.Hour}, + Usages: []string{"foo"}, + Groups: []string{"foo"}, + }, + } obj.ImageRepository = "foo" obj.CIImageRepository = "" obj.UnifiedControlPlaneImage = "foo" diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index f83d63e9bd6..2cad0226185 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -48,15 +48,9 @@ type MasterConfiguration struct { // NodeRegistration holds fields that relate to registering the new master node to the cluster NodeRegistration NodeRegistrationOptions - // Token is used for establishing bidirectional trust between nodes and masters. - // Used for joining nodes in the cluster. - Token string - // TokenTTL defines the ttl for Token. Defaults to 24h. - TokenTTL *metav1.Duration - // TokenUsages describes the ways in which this token can be used. - TokenUsages []string - // Extra groups that this token will authenticate as when used for authentication - TokenGroups []string + // BootstrapTokens is respected at `kubeadm init` time and describes a set of Bootstrap Tokens to create. + // This information IS NOT uploaded to the kubeadm cluster configmap, due to its sensitive nature + BootstrapTokens []BootstrapToken // APIServerExtraArgs is a set of extra flags to pass to the API Server or override // default ones in form of =. @@ -153,18 +147,6 @@ type NodeRegistrationOptions struct { ExtraArgs map[string]string } -// TokenDiscovery contains elements needed for token discovery. -type TokenDiscovery struct { - // ID is the first part of a bootstrap token. Considered public information. - // It is used when referring to a token without leaking the secret part. - ID string - // Secret is the second part of a bootstrap token. Should only be shared - // with trusted parties. - Secret string - // TODO: Seems unused. Remove? - // Addresses []string -} - // Networking contains elements describing cluster's networking configuration. type Networking struct { // ServiceSubnet is the subnet used by k8s services. Defaults to "10.96.0.0/12". @@ -175,6 +157,30 @@ type Networking struct { DNSDomain string } +// BootstrapToken describes one bootstrap token, stored as a Secret in the cluster +// TODO: The BootstrapToken object should move out to either k8s.io/client-go or k8s.io/api in the future +// (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now. +type BootstrapToken struct { + // Token is used for establishing bidirectional trust between nodes and masters. + // Used for joining nodes in the cluster. + Token *BootstrapTokenString + // Description sets a human-friendly message why this token exists and what it's used + // for, so other administrators can know its purpose. + Description string + // TTL defines the time to live for this token. Defaults to 24h. + // Expires and TTL are mutually exclusive. + TTL *metav1.Duration + // Expires specifies the timestamp when this token expires. Defaults to being set + // dynamically at runtime based on the TTL. Expires and TTL are mutually exclusive. + Expires *metav1.Time + // Usages describes the ways in which this token can be used. Can by default be used + // for establishing bidirectional trust, but that can be changed here. + Usages []string + // Groups specifies the extra groups that this token will authenticate as when/if + // used for authentication + Groups []string +} + // Etcd contains elements describing Etcd configuration. type Etcd struct { diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/conversion.go index 54a598af1c9..f8c3ed7faf6 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/conversion.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 import ( + "fmt" "reflect" "strings" @@ -54,6 +55,9 @@ func Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in *Mas UpgradeCloudProvider(in, out) UpgradeAuthorizationModes(in, out) UpgradeNodeRegistrationOptionsForMaster(in, out) + if err := UpgradeBootstrapTokens(in, out); err != nil { + return err + } // We don't support migrating information from the .PrivilegedPods field which was removed in v1alpha2 // We don't support migrating information from the .ImagePullPolicy field which was removed in v1alpha2 @@ -100,25 +104,6 @@ func Convert_v1alpha1_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conver return nil } -// no-op, as we don't support converting from newer API to old alpha API -func Convert_kubeadm_Etcd_To_v1alpha1_Etcd(in *kubeadm.Etcd, out *Etcd, s conversion.Scope) error { - - if in.External != nil { - out.Endpoints = in.External.Endpoints - out.CAFile = in.External.CAFile - out.CertFile = in.External.CertFile - out.KeyFile = in.External.KeyFile - } else { - out.Image = in.Local.Image - out.DataDir = in.Local.DataDir - out.ExtraArgs = in.Local.ExtraArgs - out.ServerCertSANs = in.Local.ServerCertSANs - out.PeerCertSANs = in.Local.PeerCertSANs - } - - return nil -} - // UpgradeCloudProvider handles the removal of .CloudProvider as smoothly as possible func UpgradeCloudProvider(in *MasterConfiguration, out *kubeadm.MasterConfiguration) { if len(in.CloudProvider) != 0 { @@ -160,8 +145,30 @@ func UpgradeNodeRegistrationOptionsForMaster(in *MasterConfiguration, out *kubea } } +func UpgradeBootstrapTokens(in *MasterConfiguration, out *kubeadm.MasterConfiguration) error { + if len(in.Token) == 0 { + return nil + } + + bts, err := kubeadm.NewBootstrapTokenString(in.Token) + if err != nil { + return fmt.Errorf("can't parse .Token, and hence can't convert v1alpha1 API to a newer version: %v", err) + } + + out.BootstrapTokens = []kubeadm.BootstrapToken{ + { + Token: bts, + TTL: in.TokenTTL, + Usages: in.TokenUsages, + Groups: in.TokenGroups, + }, + } + return nil +} + // Downgrades below +// This downgrade path IS NOT SUPPORTED. This is just here for roundtripping purposes at the moment. func Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in *kubeadm.MasterConfiguration, out *MasterConfiguration, s conversion.Scope) error { if err := autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in, out, s); err != nil { return err @@ -172,9 +179,17 @@ func Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in *kub out.CRISocket = in.NodeRegistration.CRISocket out.NoTaintMaster = in.NodeRegistration.Taints != nil && len(in.NodeRegistration.Taints) == 0 + if len(in.BootstrapTokens) > 0 { + out.Token = in.BootstrapTokens[0].Token.String() + out.TokenTTL = in.BootstrapTokens[0].TTL + out.TokenUsages = in.BootstrapTokens[0].Usages + out.TokenGroups = in.BootstrapTokens[0].Groups + } + return nil } +// This downgrade path IS NOT SUPPORTED. This is just here for roundtripping purposes at the moment. func Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kubeadm.NodeConfiguration, out *NodeConfiguration, s conversion.Scope) error { if err := autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in, out, s); err != nil { return err @@ -183,6 +198,27 @@ func Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kubeadm // Converting from newer API version to an older API version isn't supported. This is here only for the roundtrip tests meanwhile. out.NodeName = in.NodeRegistration.Name out.CRISocket = in.NodeRegistration.CRISocket + return nil +} + +// This downgrade path IS NOT SUPPORTED. This is just here for roundtripping purposes at the moment. +func Convert_kubeadm_Etcd_To_v1alpha1_Etcd(in *kubeadm.Etcd, out *Etcd, s conversion.Scope) error { + if err := autoConvert_kubeadm_Etcd_To_v1alpha1_Etcd(in, out, s); err != nil { + return err + } + + if in.External != nil { + out.Endpoints = in.External.Endpoints + out.CAFile = in.External.CAFile + out.CertFile = in.External.CertFile + out.KeyFile = in.External.KeyFile + } else { + out.Image = in.Local.Image + out.DataDir = in.Local.DataDir + out.ExtraArgs = in.Local.ExtraArgs + out.ServerCertSANs = in.Local.ServerCertSANs + out.PeerCertSANs = in.Local.PeerCertSANs + } return nil } diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring.go new file mode 100644 index 00000000000..546b8f898b4 --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring.go @@ -0,0 +1,89 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Note: This file should be kept in sync with the similar one for the internal API +// TODO: The BootstrapTokenString object should move out to either k8s.io/client-go or k8s.io/api in the future +// (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now. +package v1alpha2 + +import ( + "fmt" + "strings" + + bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" + bootstraputil "k8s.io/client-go/tools/bootstrap/token/util" +) + +// BootstrapTokenString is a token of the format abcdef.abcdef0123456789 that is used +// for both validation of the authenticy of the API server from a joining node's point +// of view and as an authentication method for the node in the bootstrap phase of +// "kubeadm join". This token is and should be short-lived +type BootstrapTokenString struct { + ID string + Secret string +} + +// MarshalJSON implements the json.Marshaler interface. +func (bts BootstrapTokenString) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, bts.String())), nil +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (bts *BootstrapTokenString) UnmarshalJSON(b []byte) error { + // If the token is represented as "", just return quickly without an error + if len(b) == 0 { + return nil + } + + // Remove unnecessary " characters coming from the JSON parser + token := strings.Replace(string(b), `"`, ``, -1) + // Convert the string Token to a BootstrapTokenString object + newbts, err := NewBootstrapTokenString(token) + if err != nil { + return err + } + bts.ID = newbts.ID + bts.Secret = newbts.Secret + return nil +} + +// String returns the string representation of the BootstrapTokenString +func (bts BootstrapTokenString) String() string { + if len(bts.ID) > 0 && len(bts.Secret) > 0 { + return bootstraputil.TokenFromIDAndSecret(bts.ID, bts.Secret) + } + return "" +} + +// NewBootstrapTokenString converts the given Bootstrap Token as a string +// to the BootstrapTokenString object used for serialization/deserialization +// and internal usage. It also automatically validates that the given token +// is of the right format +func NewBootstrapTokenString(token string) (*BootstrapTokenString, error) { + substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token) + // TODO: Add a constant for the 3 value here, and explain better why it's needed (other than because how the regexp parsin works) + if len(substrs) != 3 { + return nil, fmt.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern) + } + + return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil +} + +// NewBootstrapTokenStringFromIDAndSecret is a wrapper around NewBootstrapTokenString +// that allows the caller to specify the ID and Secret separately +func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) { + return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret)) +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/defaults.go index d9467e27f87..f085904efbd 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/defaults.go @@ -97,20 +97,6 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) { obj.CertificatesDir = DefaultCertificatesDir } - if obj.TokenTTL == nil { - obj.TokenTTL = &metav1.Duration{ - Duration: constants.DefaultTokenDuration, - } - } - - if len(obj.TokenUsages) == 0 { - obj.TokenUsages = constants.DefaultTokenUsages - } - - if len(obj.TokenGroups) == 0 { - obj.TokenGroups = constants.DefaultTokenGroups - } - if obj.ImageRepository == "" { obj.ImageRepository = DefaultImageRepository } @@ -120,6 +106,7 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) { } SetDefaults_NodeRegistrationOptions(&obj.NodeRegistration) + SetDefaults_BootstrapTokens(obj) SetDefaults_KubeletConfiguration(obj) SetDefaults_Etcd(obj) SetDefaults_ProxyConfiguration(obj) @@ -248,3 +235,35 @@ func SetDefaults_AuditPolicyConfiguration(obj *MasterConfiguration) { obj.AuditPolicyConfiguration.LogMaxAge = &DefaultAuditPolicyLogMaxAge } } + +// SetDefaults_BootstrapTokens sets the defaults for the .BootstrapTokens field +// If the slice is empty, it's defaulted with one token. Otherwise it just loops +// through the slice and sets the defaults for the omitempty fields that are TTL, +// Usages and Groups. Token is NOT defaulted with a random one in the API defaulting +// layer, but set to a random value later at runtime if not set before. +func SetDefaults_BootstrapTokens(obj *MasterConfiguration) { + + if obj.BootstrapTokens == nil || len(obj.BootstrapTokens) == 0 { + obj.BootstrapTokens = []BootstrapToken{{}} + } + + for _, bt := range obj.BootstrapTokens { + SetDefaults_BootstrapToken(&bt) + } +} + +// SetDefaults_BootstrapToken sets the defaults for an individual Bootstrap Token +func SetDefaults_BootstrapToken(bt *BootstrapToken) { + if bt.TTL == nil { + bt.TTL = &metav1.Duration{ + Duration: constants.DefaultTokenDuration, + } + } + if len(bt.Usages) == 0 { + bt.Usages = constants.DefaultTokenUsages + } + + if len(bt.Groups) == 0 { + bt.Groups = constants.DefaultTokenGroups + } +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/types.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/types.go index f1ec6f51079..9d755d5f8ed 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/types.go @@ -47,15 +47,9 @@ type MasterConfiguration struct { // KubernetesVersion is the target version of the control plane. KubernetesVersion string `json:"kubernetesVersion"` - // Token is used for establishing bidirectional trust between nodes and masters. - // Used for joining nodes in the cluster. - Token string `json:"token"` - // TokenTTL defines the ttl for Token. Defaults to 24h. - TokenTTL *metav1.Duration `json:"tokenTTL,omitempty"` - // TokenUsages describes the ways in which this token can be used. - TokenUsages []string `json:"tokenUsages,omitempty"` - // Extra groups that this token will authenticate as when used for authentication - TokenGroups []string `json:"tokenGroups,omitempty"` + // BootstrapTokens is respected at `kubeadm init` time and describes a set of Bootstrap Tokens to create. + // This information IS NOT uploaded to the kubeadm cluster configmap, due to its sensitive nature + BootstrapTokens []BootstrapToken `json:"bootstrapTokens,omitempty"` // APIServerExtraArgs is a set of extra flags to pass to the API Server or override // default ones in form of =. @@ -145,18 +139,6 @@ type NodeRegistrationOptions struct { ExtraArgs map[string]string `json:"kubeletExtraArgs,omitempty"` } -// TokenDiscovery contains elements needed for token discovery. -type TokenDiscovery struct { - // ID is the first part of a bootstrap token. Considered public information. - // It is used when referring to a token without leaking the secret part. - ID string `json:"id"` - // Secret is the second part of a bootstrap token. Should only be shared - // with trusted parties. - Secret string `json:"secret"` - // TODO: Seems unused. Remove? - // Addresses []string `json:"addresses"` -} - // Networking contains elements describing cluster's networking configuration type Networking struct { // ServiceSubnet is the subnet used by k8s services. Defaults to "10.96.0.0/12". @@ -167,6 +149,28 @@ type Networking struct { DNSDomain string `json:"dnsDomain"` } +// BootstrapToken describes one bootstrap token, stored as a Secret in the cluster +type BootstrapToken struct { + // Token is used for establishing bidirectional trust between nodes and masters. + // Used for joining nodes in the cluster. + Token *BootstrapTokenString `json:"token"` + // Description sets a human-friendly message why this token exists and what it's used + // for, so other administrators can know its purpose. + Description string `json:"description,omitempty"` + // TTL defines the time to live for this token. Defaults to 24h. + // Expires and TTL are mutually exclusive. + TTL *metav1.Duration `json:"ttl,omitempty"` + // Expires specifies the timestamp when this token expires. Defaults to being set + // dynamically at runtime based on the TTL. Expires and TTL are mutually exclusive. + Expires *metav1.Time `json:"expires,omitempty"` + // Usages describes the ways in which this token can be used. Can by default be used + // for establishing bidirectional trust, but that can be changed here. + Usages []string `json:"usages,omitempty"` + // Groups specifies the extra groups that this token will authenticate as when/if + // used for authentication + Groups []string `json:"groups,omitempty"` +} + // Etcd contains elements describing Etcd configuration. type Etcd struct { diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index f51f539ac8f..53afcaeb4c6 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -35,7 +35,6 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/features" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" @@ -55,9 +54,7 @@ func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList allErrs = append(allErrs, ValidateCertSANs(c.APIServerCertSANs, field.NewPath("apiServerCertSANs"))...) allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificatesDir"))...) allErrs = append(allErrs, ValidateNodeRegistrationOptions(&c.NodeRegistration, field.NewPath("nodeRegistration"))...) - allErrs = append(allErrs, ValidateToken(c.Token, field.NewPath("token"))...) - allErrs = append(allErrs, ValidateTokenUsages(c.TokenUsages, field.NewPath("tokenUsages"))...) - allErrs = append(allErrs, ValidateTokenGroups(c.TokenUsages, c.TokenGroups, field.NewPath("tokenGroups"))...) + allErrs = append(allErrs, ValidateBootstrapTokens(c.BootstrapTokens, field.NewPath("bootstrapTokens"))...) allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("featureGates"))...) allErrs = append(allErrs, ValidateAPIEndpoint(&c.API, field.NewPath("api"))...) allErrs = append(allErrs, ValidateProxy(c.KubeProxy.Config, field.NewPath("kubeProxy").Child("config"))...) @@ -181,18 +178,29 @@ func ValidateDiscoveryFile(discoveryFile string, fldPath *field.Path) field.Erro return allErrs } -// ValidateToken validates token -func ValidateToken(t string, fldPath *field.Path) field.ErrorList { +func ValidateBootstrapTokens(bts []kubeadm.BootstrapToken, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, bt := range bts { + btPath := fldPath.Child(fmt.Sprintf("%d", i)) + allErrs = append(allErrs, ValidateToken(bt.Token.String(), btPath.Child("token"))...) + allErrs = append(allErrs, ValidateTokenUsages(bt.Usages, btPath.Child("usages"))...) + allErrs = append(allErrs, ValidateTokenGroups(bt.Usages, bt.Groups, btPath.Child("groups"))...) + + if bt.Expires != nil && bt.TTL != nil { + allErrs = append(allErrs, field.Invalid(btPath, "", "the BootstrapToken .TTL and .Expires fields are mutually exclusive")) + } + } + return allErrs +} + +// ValidateToken validates a Bootstrap Token +func ValidateToken(token string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - id, secret, err := tokenutil.ParseToken(t) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath, t, err.Error())) + if !bootstraputil.IsValidBootstrapToken(token) { + allErrs = append(allErrs, field.Invalid(fldPath, token, "the bootstrap token is invalid")) } - if len(id) == 0 || len(secret) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath, t, "token must be of form '[a-z0-9]{6}.[a-z0-9]{16}'")) - } return allErrs } diff --git a/cmd/kubeadm/app/cmd/config.go b/cmd/kubeadm/app/cmd/config.go index 249667e193e..c9b514201f0 100644 --- a/cmd/kubeadm/app/cmd/config.go +++ b/cmd/kubeadm/app/cmd/config.go @@ -48,10 +48,18 @@ const ( // TODO: Figure out how to get these constants from the API machinery masterConfig = "MasterConfiguration" nodeConfig = "NodeConfiguration" - sillyToken = "abcdef.0123456789abcdef" ) -var availableAPIObjects = []string{masterConfig, nodeConfig} +var ( + availableAPIObjects = []string{masterConfig, nodeConfig} + // sillyToken is only set statically to make kubeadm not randomize the token on every run + sillyToken = kubeadmapiv1alpha2.BootstrapToken{ + Token: &kubeadmapiv1alpha2.BootstrapTokenString{ + ID: "abcdef", + Secret: "0123456789abcdef", + }, + } +) // NewCmdConfig returns cobra.Command for "kubeadm config" command func NewCmdConfig(out io.Writer) *cobra.Command { @@ -123,7 +131,7 @@ func getDefaultAPIObjectBytes(apiObject string) ([]byte, error) { if apiObject == masterConfig { internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha2.MasterConfiguration{ - Token: sillyToken, + BootstrapTokens: []kubeadmapiv1alpha2.BootstrapToken{sillyToken}, }) kubeadmutil.CheckErr(err) @@ -131,7 +139,7 @@ func getDefaultAPIObjectBytes(apiObject string) ([]byte, error) { } if apiObject == nodeConfig { internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha2.NodeConfiguration{ - Token: sillyToken, + Token: sillyToken.Token.String(), DiscoveryTokenAPIServers: []string{"kube-apiserver:6443"}, DiscoveryTokenUnsafeSkipCAVerification: true, }) diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 68df174b7b9..28bffa628df 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -37,6 +37,7 @@ import ( kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/features" @@ -123,6 +124,9 @@ func NewCmdInit(out io.Writer) *cobra.Command { var dryRun bool var featureGatesString string var ignorePreflightErrors []string + // Create the options object for the bootstrap token-related flags, and override the default value for .Description + bto := options.NewBootstrapTokenOptions() + bto.Description = "The default bootstrap token generated by 'kubeadm init'." cmd := &cobra.Command{ Use: "init", @@ -142,6 +146,9 @@ func NewCmdInit(out io.Writer) *cobra.Command { err = validation.ValidateMixedArguments(cmd.Flags()) kubeadmutil.CheckErr(err) + err = bto.ApplyTo(externalcfg) + kubeadmutil.CheckErr(err) + i, err := NewInit(cfgPath, externalcfg, ignorePreflightErrorsSet, skipTokenPrint, dryRun) kubeadmutil.CheckErr(err) kubeadmutil.CheckErr(i.Run(out)) @@ -150,6 +157,8 @@ func NewCmdInit(out io.Writer) *cobra.Command { AddInitConfigFlags(cmd.PersistentFlags(), externalcfg, &featureGatesString) AddInitOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &skipTokenPrint, &dryRun, &ignorePreflightErrors) + bto.AddTokenFlag(cmd.PersistentFlags()) + bto.AddTTLFlag(cmd.PersistentFlags()) return cmd } @@ -192,21 +201,12 @@ func AddInitConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha2.MasterCon &cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name, `Specify the node name.`, ) - flagSet.StringVar( - &cfg.Token, "token", cfg.Token, - "The token to use for establishing bidirectional trust between nodes and masters. The format is [a-z0-9]{6}\\.[a-z0-9]{16} - e.g. abcdef.0123456789abcdef", - ) - flagSet.DurationVar( - &cfg.TokenTTL.Duration, "token-ttl", cfg.TokenTTL.Duration, - "The duration before the bootstrap token is automatically deleted. If set to '0', the token will never expire.", - ) flagSet.StringVar( &cfg.NodeRegistration.CRISocket, "cri-socket", cfg.NodeRegistration.CRISocket, `Specify the CRI socket to connect to.`, ) flagSet.StringVar(featureGatesString, "feature-gates", *featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+ "Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n")) - } // AddInitOtherFlags adds init flags that are not bound to a configuration file to the given flagset @@ -427,14 +427,21 @@ func (i *Init) Run(out io.Writer) error { } // PHASE 5: Set up the node bootstrap tokens + tokens := []string{} + for _, bt := range i.cfg.BootstrapTokens { + tokens = append(tokens, bt.Token.String()) + } if !i.skipTokenPrint { - glog.Infof("[bootstraptoken] using token: %s\n", i.cfg.Token) + if len(tokens) == 1 { + glog.Infof("[bootstraptoken] using token: %s\n", tokens[0]) + } else if len(tokens) > 1 { + glog.Infof("[bootstraptoken] using tokens: %v\n", tokens) + } } // Create the default node bootstrap token glog.V(1).Infof("[init] creating RBAC rules to generate default bootstrap token") - tokenDescription := "The default bootstrap token generated by 'kubeadm init'." - if err := nodebootstraptokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL.Duration, i.cfg.TokenUsages, i.cfg.TokenGroups, tokenDescription); err != nil { + if err := nodebootstraptokenphase.UpdateOrCreateTokens(client, false, i.cfg.BootstrapTokens); err != nil { return fmt.Errorf("error updating or creating token: %v", err) } // Create RBAC rules that makes the bootstrap tokens able to post CSRs @@ -491,10 +498,19 @@ func (i *Init) Run(out io.Writer) error { return nil } - // Gets the join command - joinCommand, err := cmdutil.GetJoinCommand(kubeadmconstants.GetAdminKubeConfigPath(), i.cfg.Token, i.skipTokenPrint) + // Prints the join command, multiple times in case the user has multiple tokens + for _, token := range tokens { + if err := printJoinCommand(out, adminKubeConfigPath, token, i.skipTokenPrint); err != nil { + return fmt.Errorf("failed to print join command: %v", err) + } + } + return nil +} + +func printJoinCommand(out io.Writer, adminKubeConfigPath, token string, skipTokenPrint bool) error { + joinCommand, err := cmdutil.GetJoinCommand(adminKubeConfigPath, token, skipTokenPrint) if err != nil { - return fmt.Errorf("failed to get join command: %v", err) + return err } ctx := map[string]string{ diff --git a/cmd/kubeadm/app/cmd/options/token.go b/cmd/kubeadm/app/cmd/options/token.go new file mode 100644 index 00000000000..204fdb4342c --- /dev/null +++ b/cmd/kubeadm/app/cmd/options/token.go @@ -0,0 +1,91 @@ +/* +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 options + +import ( + "fmt" + "strings" + + "github.com/spf13/pflag" + + bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" + kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" +) + +func NewBootstrapTokenOptions() *BootstrapTokenOptions { + bto := &BootstrapTokenOptions{&kubeadmapiv1alpha2.BootstrapToken{}, ""} + kubeadmapiv1alpha2.SetDefaults_BootstrapToken(bto.BootstrapToken) + return bto +} + +// BootstrapTokenOptions is a wrapper struct for adding bootstrap token-related flags to a FlagSet +// and applying the parsed flags to a MasterConfiguration object later at runtime +// TODO: In the future, we might want to group the flags in a better way than adding them all individually like this +type BootstrapTokenOptions struct { + *kubeadmapiv1alpha2.BootstrapToken + TokenStr string +} + +func (bto *BootstrapTokenOptions) AddTokenFlag(fs *pflag.FlagSet) { + fs.StringVar( + &bto.TokenStr, "token", "", + "The token to use for establishing bidirectional trust between nodes and masters. The format is [a-z0-9]{6}\\.[a-z0-9]{16} - e.g. abcdef.0123456789abcdef", + ) +} + +func (bto *BootstrapTokenOptions) AddTTLFlag(fs *pflag.FlagSet) { + fs.DurationVar( + &bto.TTL.Duration, "ttl", bto.TTL.Duration, + "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire", + ) +} + +func (bto *BootstrapTokenOptions) AddUsagesFlag(fs *pflag.FlagSet) { + fs.StringSliceVar( + &bto.Usages, "usages", bto.Usages, + fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s]", strings.Join(kubeadmconstants.DefaultTokenUsages, ",")), + ) +} + +func (bto *BootstrapTokenOptions) AddGroupsFlag(fs *pflag.FlagSet) { + fs.StringSliceVar( + &bto.Groups, "groups", bto.Groups, + fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q", bootstrapapi.BootstrapGroupPattern), + ) +} + +func (bto *BootstrapTokenOptions) AddDescriptionFlag(fs *pflag.FlagSet) { + fs.StringVar( + &bto.Description, "description", bto.Description, + "A human friendly description of how this token is used.", + ) +} + +func (bto *BootstrapTokenOptions) ApplyTo(cfg *kubeadmapiv1alpha2.MasterConfiguration) error { + if len(bto.TokenStr) > 0 { + var err error + bto.Token, err = kubeadmapiv1alpha2.NewBootstrapTokenString(bto.TokenStr) + if err != nil { + return err + } + } + + // Set the token specified by the flags as the first and only token to create in case --config is not specified + cfg.BootstrapTokens = []kubeadmapiv1alpha2.BootstrapToken{*bto.BootstrapToken} + return nil +} diff --git a/cmd/kubeadm/app/cmd/phases/bootstraptoken.go b/cmd/kubeadm/app/cmd/phases/bootstraptoken.go index 30c43652c86..bf6577dd52c 100644 --- a/cmd/kubeadm/app/cmd/phases/bootstraptoken.go +++ b/cmd/kubeadm/app/cmd/phases/bootstraptoken.go @@ -18,7 +18,6 @@ package phases import ( "fmt" - "strings" "github.com/golang/glog" "github.com/spf13/cobra" @@ -30,8 +29,8 @@ import ( kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" - kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" @@ -111,14 +110,15 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command { cfg := &kubeadmapiv1alpha2.MasterConfiguration{ // KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid // the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig - KubernetesVersion: "v1.9.0", + KubernetesVersion: "v1.10.0", } // Default values for the cobra help text kubeadmscheme.Scheme.Default(cfg) - var cfgPath, description string + var cfgPath string var skipTokenPrint bool + bto := options.NewBootstrapTokenOptions() cmd := &cobra.Command{ Use: "all", @@ -129,11 +129,14 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command { err := validation.ValidateMixedArguments(cmd.Flags()) kubeadmutil.CheckErr(err) + err = bto.ApplyTo(cfg) + kubeadmutil.CheckErr(err) + client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) kubeadmutil.CheckErr(err) // Creates the bootstap token - err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, skipTokenPrint) + err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, skipTokenPrint) kubeadmutil.CheckErr(err) // Create the cluster-info ConfigMap or update if it already exists @@ -159,7 +162,12 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command { } // Adds flags to the command - addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &skipTokenPrint) + addGenericFlags(cmd.Flags(), &cfgPath, &skipTokenPrint) + bto.AddTokenFlag(cmd.Flags()) + bto.AddTTLFlag(cmd.Flags()) + bto.AddUsagesFlag(cmd.Flags()) + bto.AddGroupsFlag(cmd.Flags()) + bto.AddDescriptionFlag(cmd.Flags()) return cmd } @@ -169,14 +177,15 @@ func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command { cfg := &kubeadmapiv1alpha2.MasterConfiguration{ // KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid // the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig - KubernetesVersion: "v1.9.0", + KubernetesVersion: "v1.10.0", } // Default values for the cobra help text kubeadmscheme.Scheme.Default(cfg) - var cfgPath, description string + var cfgPath string var skipTokenPrint bool + bto := options.NewBootstrapTokenOptions() cmd := &cobra.Command{ Use: "create", @@ -186,16 +195,24 @@ func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command { err := validation.ValidateMixedArguments(cmd.Flags()) kubeadmutil.CheckErr(err) + err = bto.ApplyTo(cfg) + kubeadmutil.CheckErr(err) + client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) kubeadmutil.CheckErr(err) - err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, skipTokenPrint) + err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, skipTokenPrint) kubeadmutil.CheckErr(err) }, } // Adds flags to the command - addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &skipTokenPrint) + addGenericFlags(cmd.Flags(), &cfgPath, &skipTokenPrint) + bto.AddTokenFlag(cmd.Flags()) + bto.AddTTLFlag(cmd.Flags()) + bto.AddUsagesFlag(cmd.Flags()) + bto.AddGroupsFlag(cmd.Flags()) + bto.AddDescriptionFlag(cmd.Flags()) return cmd } @@ -278,38 +295,18 @@ func NewSubCmdNodeBootstrapTokenAutoApprove(kubeConfigFile *string) *cobra.Comma return cmd } -func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiv1alpha2.MasterConfiguration, cfgPath, description *string, skipTokenPrint *bool) { +func addGenericFlags(flagSet *pflag.FlagSet, cfgPath *string, skipTokenPrint *bool) { flagSet.StringVar( cfgPath, "config", *cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental", ) - flagSet.StringVar( - &cfg.Token, "token", cfg.Token, - "The token to use for establishing bidirectional trust between nodes and masters", - ) - flagSet.DurationVar( - &cfg.TokenTTL.Duration, "ttl", cfg.TokenTTL.Duration, - "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire", - ) - flagSet.StringSliceVar( - &cfg.TokenUsages, "usages", cfg.TokenUsages, - fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s]", strings.Join(kubeadmconstants.DefaultTokenUsages, ",")), - ) - flagSet.StringSliceVar( - &cfg.TokenGroups, "groups", cfg.TokenGroups, - fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q", bootstrapapi.BootstrapGroupPattern), - ) - flagSet.StringVar( - description, "description", "The default bootstrap token generated by 'kubeadm init'.", - "A human friendly description of how this token is used.", - ) flagSet.BoolVar( skipTokenPrint, "skip-token-print", *skipTokenPrint, "Skip printing of the bootstrap token", ) } -func createBootstrapToken(kubeConfigFile string, client clientset.Interface, cfgPath string, cfg *kubeadmapiv1alpha2.MasterConfiguration, description string, skipTokenPrint bool) error { +func createBootstrapToken(kubeConfigFile string, client clientset.Interface, cfgPath string, cfg *kubeadmapiv1alpha2.MasterConfiguration, skipTokenPrint bool) error { // This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) @@ -319,18 +316,19 @@ func createBootstrapToken(kubeConfigFile string, client clientset.Interface, cfg glog.V(1).Infoln("[bootstraptoken] creating/updating token") // Creates or updates the token - if err := node.UpdateOrCreateToken(client, internalcfg.Token, false, internalcfg.TokenTTL.Duration, internalcfg.TokenUsages, internalcfg.TokenGroups, description); err != nil { + if err := node.UpdateOrCreateTokens(client, false, internalcfg.BootstrapTokens); err != nil { return err } glog.Infoln("[bootstraptoken] bootstrap token created") glog.Infoln("[bootstraptoken] you can now join any number of machines by running:") - joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.Token, skipTokenPrint) - if err != nil { - return fmt.Errorf("failed to get join command: %v", err) + if len(internalcfg.BootstrapTokens) > 0 { + joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.BootstrapTokens[0].Token.String(), skipTokenPrint) + if err != nil { + return fmt.Errorf("failed to get join command: %v", err) + } + glog.Infoln(joinCommand) } - glog.Infoln(joinCommand) - return nil } diff --git a/cmd/kubeadm/app/cmd/phases/markmaster.go b/cmd/kubeadm/app/cmd/phases/markmaster.go index 13ec0a314ce..9ba104e0839 100644 --- a/cmd/kubeadm/app/cmd/phases/markmaster.go +++ b/cmd/kubeadm/app/cmd/phases/markmaster.go @@ -50,7 +50,7 @@ func NewCmdMarkMaster() *cobra.Command { cfg := &kubeadmapiv1alpha2.MasterConfiguration{ // KubernetesVersion is not used by mark master, but we set this explicitly to avoid // the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig - KubernetesVersion: "v1.9.0", + KubernetesVersion: "v1.10.0", } // Default values for the cobra help text diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index 67e7e4608b1..4b52ecafe02 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -20,7 +20,6 @@ import ( "fmt" "io" "os" - "sort" "strings" "text/tabwriter" "time" @@ -29,26 +28,24 @@ import ( "github.com/renstrom/dedent" "github.com/spf13/cobra" - "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/duration" clientset "k8s.io/client-go/kubernetes" bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" + bootstraputil "k8s.io/client-go/tools/bootstrap/token/util" "k8s.io/client-go/tools/clientcmd" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" - kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" - tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" - api "k8s.io/kubernetes/pkg/apis/core" ) const defaultKubeConfig = "/etc/kubernetes/admin.conf" @@ -95,14 +92,16 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { cfg := &kubeadmapiv1alpha2.MasterConfiguration{ // KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid // the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig - KubernetesVersion: "v1.9.0", + KubernetesVersion: "v1.10.0", } // Default values for the cobra help text kubeadmscheme.Scheme.Default(cfg) - var cfgPath, description string + var cfgPath string var printJoinCommand bool + bto := options.NewBootstrapTokenOptions() + createCmd := &cobra.Command{ Use: "create [token]", DisableFlagsInUseLine: true, @@ -116,37 +115,35 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { If no [token] is given, kubeadm will generate a random token instead. `), Run: func(tokenCmd *cobra.Command, args []string) { - if len(args) != 0 { - cfg.Token = args[0] + if len(args) > 0 { + bto.TokenStr = args[0] } glog.V(1).Infoln("[token] validating mixed arguments") err := validation.ValidateMixedArguments(tokenCmd.Flags()) kubeadmutil.CheckErr(err) + err = bto.ApplyTo(cfg) + kubeadmutil.CheckErr(err) + glog.V(1).Infoln("[token] getting Clientsets from KubeConfig file") kubeConfigFile = findExistingKubeConfig(kubeConfigFile) client, err := getClientset(kubeConfigFile, dryRun) kubeadmutil.CheckErr(err) - err = RunCreateToken(out, client, cfgPath, cfg, description, printJoinCommand, kubeConfigFile) + err = RunCreateToken(out, client, cfgPath, cfg, printJoinCommand, kubeConfigFile) kubeadmutil.CheckErr(err) }, } createCmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") - createCmd.Flags().DurationVar(&cfg.TokenTTL.Duration, - "ttl", cfg.TokenTTL.Duration, "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire.") - createCmd.Flags().StringSliceVar(&cfg.TokenUsages, - "usages", cfg.TokenUsages, fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s].", strings.Join(kubeadmconstants.DefaultTokenUsages, ","))) - createCmd.Flags().StringSliceVar(&cfg.TokenGroups, - "groups", cfg.TokenGroups, - fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q.", bootstrapapi.BootstrapGroupPattern)) - createCmd.Flags().StringVar(&description, - "description", "", "A human friendly description of how this token is used.") createCmd.Flags().BoolVar(&printJoinCommand, "print-join-command", false, "Instead of printing only the token, print the full 'kubeadm join' flag needed to join the cluster using the token.") - tokenCmd.AddCommand(createCmd) + bto.AddTTLFlag(createCmd.Flags()) + bto.AddUsagesFlag(createCmd.Flags()) + bto.AddGroupsFlag(createCmd.Flags()) + bto.AddDescriptionFlag(createCmd.Flags()) + tokenCmd.AddCommand(createCmd) tokenCmd.AddCommand(NewCmdTokenGenerate(out)) listCmd := &cobra.Command{ @@ -178,7 +175,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command { `), Run: func(tokenCmd *cobra.Command, args []string) { if len(args) < 1 { - kubeadmutil.CheckErr(fmt.Errorf("missing subcommand; 'token delete' is missing token of form [%q]", tokenutil.TokenIDRegexpString)) + kubeadmutil.CheckErr(fmt.Errorf("missing subcommand; 'token delete' is missing token of form %q", bootstrapapi.BootstrapTokenIDPattern)) } kubeConfigFile = findExistingKubeConfig(kubeConfigFile) client, err := getClientset(kubeConfigFile, dryRun) @@ -217,7 +214,7 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command { } // RunCreateToken generates a new bootstrap token and stores it as a secret on the server. -func RunCreateToken(out io.Writer, client clientset.Interface, cfgPath string, cfg *kubeadmapiv1alpha2.MasterConfiguration, description string, printJoinCommand bool, kubeConfigFile string) error { +func RunCreateToken(out io.Writer, client clientset.Interface, cfgPath string, cfg *kubeadmapiv1alpha2.MasterConfiguration, printJoinCommand bool, kubeConfigFile string) error { // This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags glog.V(1).Infoln("[token] loading configurations") internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) @@ -226,21 +223,20 @@ func RunCreateToken(out io.Writer, client clientset.Interface, cfgPath string, c } glog.V(1).Infoln("[token] creating token") - err = tokenphase.CreateNewToken(client, internalcfg.Token, internalcfg.TokenTTL.Duration, internalcfg.TokenUsages, internalcfg.TokenGroups, description) - if err != nil { + if err := tokenphase.CreateNewTokens(client, internalcfg.BootstrapTokens); err != nil { return err } // if --print-join-command was specified, print the full `kubeadm join` command // otherwise, just print the token if printJoinCommand { - joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.Token, false) + joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.BootstrapTokens[0].Token.String(), false) if err != nil { return fmt.Errorf("failed to get join command: %v", err) } fmt.Fprintln(out, joinCommand) } else { - fmt.Fprintln(out, internalcfg.Token) + fmt.Fprintln(out, internalcfg.BootstrapTokens[0].Token.String()) } return nil @@ -248,8 +244,8 @@ func RunCreateToken(out io.Writer, client clientset.Interface, cfgPath string, c // RunGenerateToken just generates a random token for the user func RunGenerateToken(out io.Writer) error { - glog.V(1).Infoln("[token] generating randodm token") - token, err := tokenutil.GenerateToken() + glog.V(1).Infoln("[token] generating random token") + token, err := bootstraputil.GenerateBootstrapToken() if err != nil { return err } @@ -264,7 +260,10 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) er glog.V(1).Infoln("[token] preparing selector for bootstrap token") tokenSelector := fields.SelectorFromSet( map[string]string{ - api.SecretTypeField: string(bootstrapapi.SecretTypeBootstrapToken), + // TODO: We hard-code "type" here until `field_constants.go` that is + // currently in `pkg/apis/core/` exists in the external API, i.e. + // k8s.io/api/v1. Should be v1.SecretTypeField + "type": string(bootstrapapi.SecretTypeBootstrapToken), }, ) listOptions := metav1.ListOptions{ @@ -280,68 +279,17 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) er w := tabwriter.NewWriter(out, 10, 4, 3, ' ', 0) fmt.Fprintln(w, "TOKEN\tTTL\tEXPIRES\tUSAGES\tDESCRIPTION\tEXTRA GROUPS") for _, secret := range secrets.Items { - tokenID := getSecretString(&secret, bootstrapapi.BootstrapTokenIDKey) - if len(tokenID) == 0 { - fmt.Fprintf(errW, "bootstrap token has no token-id data: %s\n", secret.Name) + + // Get the BootstrapToken struct representation from the Secret object + token, err := kubeadmapi.BootstrapTokenFromSecret(&secret) + if err != nil { + fmt.Fprintf(errW, "%v", err) continue } - // enforce the right naming convention - if secret.Name != fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID) { - fmt.Fprintf(errW, "bootstrap token name is not of the form '%s(token-id)': %s\n", bootstrapapi.BootstrapTokenSecretPrefix, secret.Name) - continue - } - - tokenSecret := getSecretString(&secret, bootstrapapi.BootstrapTokenSecretKey) - if len(tokenSecret) == 0 { - fmt.Fprintf(errW, "bootstrap token has no token-secret data: %s\n", secret.Name) - continue - } - td := &kubeadmapi.TokenDiscovery{ID: tokenID, Secret: tokenSecret} - - // Expiration time is optional, if not specified this implies the token - // never expires. - ttl := "" - expires := "" - secretExpiration := getSecretString(&secret, bootstrapapi.BootstrapTokenExpirationKey) - if len(secretExpiration) > 0 { - expireTime, err := time.Parse(time.RFC3339, secretExpiration) - if err != nil { - fmt.Fprintf(errW, "can't parse expiration time of bootstrap token %s\n", secret.Name) - continue - } - ttl = duration.ShortHumanDuration(expireTime.Sub(time.Now())) - expires = expireTime.Format(time.RFC3339) - } - - usages := []string{} - for k, v := range secret.Data { - // Skip all fields that don't include this prefix - if !strings.HasPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix) { - continue - } - // Skip those that don't have this usage set to true - if string(v) != "true" { - continue - } - usages = append(usages, strings.TrimPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix)) - } - sort.Strings(usages) - usageString := strings.Join(usages, ",") - if len(usageString) == 0 { - usageString = "" - } - - description := getSecretString(&secret, bootstrapapi.BootstrapTokenDescriptionKey) - if len(description) == 0 { - description = "" - } - - groups := getSecretString(&secret, bootstrapapi.BootstrapTokenExtraGroupsKey) - if len(groups) == 0 { - groups = "" - } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", tokenutil.BearerToken(td), ttl, expires, usageString, description, groups) + // Get the human-friendly string representation for the token + humanFriendlyTokenOutput := humanReadableBootstrapToken(token) + fmt.Fprintln(w, humanFriendlyTokenOutput) } w.Flush() return nil @@ -352,13 +300,16 @@ func RunDeleteToken(out io.Writer, client clientset.Interface, tokenIDOrToken st // Assume the given first argument is a token id and try to parse it tokenID := tokenIDOrToken glog.V(1).Infoln("[token] parsing token ID") - if err := tokenutil.ParseTokenID(tokenIDOrToken); err != nil { - if tokenID, _, err = tokenutil.ParseToken(tokenIDOrToken); err != nil { - return fmt.Errorf("given token or token id %q didn't match pattern [%q] or [%q]", tokenIDOrToken, tokenutil.TokenIDRegexpString, tokenutil.TokenRegexpString) + if !bootstraputil.IsValidBootstrapTokenID(tokenIDOrToken) { + // Okay, the full token with both id and secret was probably passed. Parse it and extract the ID only + bts, err := kubeadmapiv1alpha2.NewBootstrapTokenString(tokenIDOrToken) + if err != nil { + return fmt.Errorf("given token or token id %q didn't match pattern %q or %q", tokenIDOrToken, bootstrapapi.BootstrapTokenIDPattern, bootstrapapi.BootstrapTokenIDPattern) } + tokenID = bts.ID } - tokenSecretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID) + tokenSecretName := bootstraputil.BootstrapTokenSecretName(tokenID) glog.V(1).Infoln("[token] deleting token") if err := client.CoreV1().Secrets(metav1.NamespaceSystem).Delete(tokenSecretName, nil); err != nil { return fmt.Errorf("failed to delete bootstrap token [%v]", err) @@ -367,14 +318,30 @@ func RunDeleteToken(out io.Writer, client clientset.Interface, tokenIDOrToken st return nil } -func getSecretString(secret *v1.Secret, key string) string { - if secret.Data == nil { - return "" +func humanReadableBootstrapToken(token *kubeadmapi.BootstrapToken) string { + description := token.Description + if len(description) == 0 { + description = "" } - if val, ok := secret.Data[key]; ok { - return string(val) + + ttl := "" + expires := "" + if token.Expires != nil { + ttl = duration.ShortHumanDuration(token.Expires.Sub(time.Now())) + expires = token.Expires.Format(time.RFC3339) } - return "" + + usagesString := strings.Join(token.Usages, ",") + if len(usagesString) == 0 { + usagesString = "" + } + + groupsString := strings.Join(token.Groups, ",") + if len(groupsString) == 0 { + groupsString = "" + } + + return fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\n", token.Token.String(), ttl, expires, usagesString, description, groupsString) } func getClientset(file string, dryRun bool) (clientset.Interface, error) { diff --git a/cmd/kubeadm/app/discovery/token/token.go b/cmd/kubeadm/app/discovery/token/token.go index a1c537a0b28..bd2d3b88a62 100644 --- a/cmd/kubeadm/app/discovery/token/token.go +++ b/cmd/kubeadm/app/discovery/token/token.go @@ -34,7 +34,6 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" - tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" "k8s.io/kubernetes/pkg/controller/bootstrap" ) @@ -45,7 +44,7 @@ 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 RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Cluster, error) { - tokenID, tokenSecret, err := tokenutil.ParseToken(cfg.DiscoveryToken) + token, err := kubeadmapi.NewBootstrapTokenString(cfg.DiscoveryToken) if err != nil { return nil, err } @@ -88,11 +87,11 @@ func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmd if !ok || len(insecureKubeconfigString) == 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 := insecureClusterInfo.Data[bootstrapapi.JWSSignatureKeyPrefix+tokenID] + detachedJWSToken, ok := insecureClusterInfo.Data[bootstrapapi.JWSSignatureKeyPrefix+token.ID] if !ok || len(detachedJWSToken) == 0 { - return nil, fmt.Errorf("token id %q is invalid for this cluster or it has expired. Use \"kubeadm token create\" on the master node to creating a new valid token", tokenID) + return nil, fmt.Errorf("token id %q is invalid for this cluster or it has expired. Use \"kubeadm token create\" on the master node to creating a new valid token", token.ID) } - if !bootstrap.DetachedTokenIsValid(detachedJWSToken, insecureKubeconfigString, tokenID, tokenSecret) { + if !bootstrap.DetachedTokenIsValid(detachedJWSToken, insecureKubeconfigString, token.ID, token.Secret) { return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object, can't trust this API Server") } insecureKubeconfigBytes := []byte(insecureKubeconfigString) diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/token.go b/cmd/kubeadm/app/phases/bootstraptoken/node/token.go index 02f63b7ce51..f24629865a6 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/token.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/token.go @@ -18,109 +18,43 @@ package node import ( "fmt" - "strings" - "time" - "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" - bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" bootstraputil "k8s.io/client-go/tools/bootstrap/token/util" - tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" ) -const tokenCreateRetries = 5 +// TODO(mattmoyer): Move CreateNewTokens, UpdateOrCreateTokens out of this package to client-go for a generic abstraction and client for a Bootstrap Token -// TODO(mattmoyer): Move CreateNewToken, UpdateOrCreateToken and encodeTokenSecretData out of this package to client-go for a generic abstraction and client for a Bootstrap Token - -// CreateNewToken tries to create a token and fails if one with the same ID already exists -func CreateNewToken(client clientset.Interface, token string, tokenDuration time.Duration, usages []string, extraGroups []string, description string) error { - return UpdateOrCreateToken(client, token, true, tokenDuration, usages, extraGroups, description) +// CreateNewTokens tries to create a token and fails if one with the same ID already exists +func CreateNewTokens(client clientset.Interface, tokens []kubeadmapi.BootstrapToken) error { + return UpdateOrCreateTokens(client, true, tokens) } -// UpdateOrCreateToken attempts to update a token with the given ID, or create if it does not already exist. -func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists bool, tokenDuration time.Duration, usages []string, extraGroups []string, description string) error { - tokenID, tokenSecret, err := tokenutil.ParseToken(token) - if err != nil { - return err - } - secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID) - var lastErr error - for i := 0; i < tokenCreateRetries; i++ { +// UpdateOrCreateTokens attempts to update a token with the given ID, or create if it does not already exist. +func UpdateOrCreateTokens(client clientset.Interface, failIfExists bool, tokens []kubeadmapi.BootstrapToken) error { + + for _, token := range tokens { + + secretName := bootstraputil.BootstrapTokenSecretName(token.Token.ID) secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{}) - if err == nil { - if failIfExists { - return fmt.Errorf("a token with id %q already exists", tokenID) - } - // Secret with this ID already exists, update it: - tokenSecretData, err := encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description) - if err != nil { - return err - } - secret.Data = tokenSecretData - if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Update(secret); err == nil { - return nil - } - lastErr = err - continue + if secret != nil && err == nil && failIfExists { + return fmt.Errorf("a token with id %q already exists", token.Token.ID) } - // Secret does not already exist: - if apierrors.IsNotFound(err) { - tokenSecretData, err := encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description) - if err != nil { - return err + updatedOrNewSecret := token.ToSecret() + // Try to create or update the token with an exponential backoff + err = apiclient.TryRunCommand(func() error { + if err := apiclient.CreateOrUpdateSecret(client, updatedOrNewSecret); err != nil { + return fmt.Errorf("failed to create or update bootstrap token with name %s: %v", secretName, err) } - - secret = &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - }, - Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken), - Data: tokenSecretData, - } - if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err == nil { - return nil - } - lastErr = err - continue + return nil + }, 5) + if err != nil { + return err } - } - return fmt.Errorf( - "unable to create bootstrap token after %d attempts [%v]", - tokenCreateRetries, - lastErr, - ) -} - -// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret -func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration, usages []string, extraGroups []string, description string) (map[string][]byte, error) { - data := map[string][]byte{ - bootstrapapi.BootstrapTokenIDKey: []byte(tokenID), - bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret), - } - - if len(extraGroups) > 0 { - data[bootstrapapi.BootstrapTokenExtraGroupsKey] = []byte(strings.Join(extraGroups, ",")) - } - - if duration > 0 { - // Get the current time, add the specified duration, and format it accordingly - durationString := time.Now().Add(duration).Format(time.RFC3339) - data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(durationString) - } - if len(description) > 0 { - data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(description) - } - - // validate usages - if err := bootstraputil.ValidateUsages(usages); err != nil { - return nil, err - } - for _, usage := range usages { - data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true") - } - return data, nil + return nil } diff --git a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go index 68a9391c99c..9ffe7a57afe 100644 --- a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go +++ b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go @@ -41,7 +41,7 @@ func UploadConfiguration(cfg *kubeadmapi.MasterConfiguration, client clientset.I kubeadmscheme.Scheme.Convert(cfg, externalcfg, nil) // Removes sensitive info from the data that will be stored in the config map - externalcfg.Token = "" + externalcfg.BootstrapTokens = nil cfgYaml, err := util.MarshalToYamlForCodecs(externalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, scheme.Codecs) if err != nil { diff --git a/cmd/kubeadm/app/util/config/masterconfig.go b/cmd/kubeadm/app/util/config/masterconfig.go index a78149e211c..1b5bb95e51d 100644 --- a/cmd/kubeadm/app/util/config/masterconfig.go +++ b/cmd/kubeadm/app/util/config/masterconfig.go @@ -26,6 +26,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" netutil "k8s.io/apimachinery/pkg/util/net" + bootstraputil "k8s.io/client-go/tools/bootstrap/token/util" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" @@ -33,7 +34,6 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" "k8s.io/kubernetes/pkg/util/node" "k8s.io/kubernetes/pkg/util/version" ) @@ -60,17 +60,29 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { cfg.KubeProxy.Config.BindAddress = kubeadmapiv1alpha2.DefaultProxyBindAddressv6 } // Resolve possible version labels and validate version string - err = NormalizeKubernetesVersion(cfg) - if err != nil { + if err := NormalizeKubernetesVersion(cfg); err != nil { return err } - if cfg.Token == "" { - var err error - cfg.Token, err = tokenutil.GenerateToken() + // Populate the .Token field with a random value if unset + // We do this at this layer, and not the API defaulting layer + // because of possible security concerns, and more practially + // because we can't return errors in the API object defaulting + // process but here we can. + for i, bt := range cfg.BootstrapTokens { + if bt.Token != nil && len(bt.Token.String()) > 0 { + continue + } + + tokenStr, err := bootstraputil.GenerateBootstrapToken() if err != nil { return fmt.Errorf("couldn't generate random token: %v", err) } + token, err := kubeadmapi.NewBootstrapTokenString(tokenStr) + if err != nil { + return err + } + cfg.BootstrapTokens[i].Token = token } cfg.NodeRegistration.Name = node.GetHostname(cfg.NodeRegistration.Name) diff --git a/cmd/kubeadm/app/util/token/tokens.go b/cmd/kubeadm/app/util/token/tokens.go deleted file mode 100644 index 6be9260005e..00000000000 --- a/cmd/kubeadm/app/util/token/tokens.go +++ /dev/null @@ -1,125 +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 ( - "bufio" - "crypto/rand" - "fmt" - "regexp" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" -) - -const ( - // TokenIDBytes defines a number of bytes used for a token id - TokenIDBytes = 6 - // TokenSecretBytes defines a number of bytes used for a secret - TokenSecretBytes = 16 -) - -var ( - // TokenIDRegexpString defines token's id regular expression pattern - TokenIDRegexpString = "^([a-z0-9]{6})$" - // TokenIDRegexp is a compiled regular expression of TokenIDRegexpString - TokenIDRegexp = regexp.MustCompile(TokenIDRegexpString) - // TokenRegexpString defines id.secret regular expression pattern - TokenRegexpString = "^([a-z0-9]{6})\\.([a-z0-9]{16})$" - // TokenRegexp is a compiled regular expression of TokenRegexpString - TokenRegexp = regexp.MustCompile(TokenRegexpString) -) - -const validBootstrapTokenChars = "0123456789abcdefghijklmnopqrstuvwxyz" - -func randBytes(length int) (string, error) { - // len("0123456789abcdefghijklmnopqrstuvwxyz") = 36 which doesn't evenly divide - // the possible values of a byte: 256 mod 36 = 4. Discard any random bytes we - // read that are >= 252 so the bytes we evenly divide the character set. - const maxByteValue = 252 - - var ( - b byte - err error - token = make([]byte, length) - ) - - reader := bufio.NewReaderSize(rand.Reader, length*2) - for i := range token { - for { - if b, err = reader.ReadByte(); err != nil { - return "", err - } - if b < maxByteValue { - break - } - } - - token[i] = validBootstrapTokenChars[int(b)%len(validBootstrapTokenChars)] - } - - return string(token), nil -} - -// 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() (string, error) { - tokenID, err := randBytes(TokenIDBytes) - if err != nil { - return "", err - } - - tokenSecret, err := randBytes(TokenSecretBytes) - if err != nil { - return "", err - } - - return fmt.Sprintf("%s.%s", tokenID, tokenSecret), nil -} - -// ParseTokenID tries and parse a valid token ID from a string. -// An error is returned in case of failure. -func ParseTokenID(s string) error { - if !TokenIDRegexp.MatchString(s) { - return fmt.Errorf("token ID [%q] was not of form [%q]", s, TokenIDRegexpString) - } - return nil -} - -// ParseToken tries and parse a valid token from a string. -// A token ID and token secret are returned in case of success, an error otherwise. -func ParseToken(s string) (string, string, error) { - split := TokenRegexp.FindStringSubmatch(s) - if len(split) != 3 { - return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, TokenRegexpString) - } - return split[1], split[2], nil -} - -// BearerToken returns a string representation of the passed token. -func BearerToken(d *kubeadmapi.TokenDiscovery) string { - return fmt.Sprintf("%s.%s", d.ID, d.Secret) -} - -// ValidateToken validates whether a token is well-formed. -// In case it's not, the corresponding error is returned as well. -func ValidateToken(d *kubeadmapi.TokenDiscovery) (bool, error) { - if _, _, err := ParseToken(d.ID + "." + d.Secret); err != nil { - return false, err - } - return true, nil -} From 17adbf9b085884ac3b028b3ae77754152788588a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 31 May 2018 22:19:47 +0300 Subject: [PATCH 3/5] Add unit tests for the new Bootstrap Token objects and functions --- .../kubeadm/bootstraptokenhelpers_test.go | 473 ++++++++++++++++++ .../apis/kubeadm/bootstraptokenstring_test.go | 236 +++++++++ .../v1alpha2/bootstraptokenstring_test.go | 236 +++++++++ .../kubeadm/validation/validation_test.go | 8 +- cmd/kubeadm/app/cmd/token_test.go | 42 +- cmd/kubeadm/app/cmd/upgrade/common_test.go | 2 - .../phases/bootstraptoken/node/token_test.go | 59 --- .../phases/uploadconfig/uploadconfig_test.go | 16 +- cmd/kubeadm/app/util/token/tokens_test.go | 173 ------- 9 files changed, 973 insertions(+), 272 deletions(-) create mode 100644 cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go create mode 100644 cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring_test.go create mode 100644 cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring_test.go delete mode 100644 cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go delete mode 100644 cmd/kubeadm/app/util/token/tokens_test.go diff --git a/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go new file mode 100644 index 00000000000..153d08e7c33 --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go @@ -0,0 +1,473 @@ +/* +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 kubeadm + +import ( + "encoding/json" + "reflect" + "testing" + "time" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// This timestamp is used as the reference value when computing expiration dates based on TTLs in these unit tests +var refTime = time.Date(1970, time.January, 1, 1, 1, 1, 0, time.UTC) + +func TestToSecret(t *testing.T) { + + var tests = []struct { + bt *BootstrapToken + secret *v1.Secret + }{ + { + &BootstrapToken{ // all together + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Description: "foo", + Expires: &metav1.Time{ + Time: refTime, + }, + Usages: []string{"signing", "authentication"}, + Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"}, + }, + &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bootstrap-token-abcdef", + Namespace: "kube-system", + }, + Type: v1.SecretType("bootstrap.kubernetes.io/token"), + Data: map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "description": []byte("foo"), + "expiration": []byte(refTime.Format(time.RFC3339)), + "usage-bootstrap-signing": []byte("true"), + "usage-bootstrap-authentication": []byte("true"), + "auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"), + }, + }, + }, + } + for _, rt := range tests { + actual := rt.bt.ToSecret() + if !reflect.DeepEqual(actual, rt.secret) { + t.Errorf( + "failed BootstrapToken.ToSecret():\n\texpected: %v\n\t actual: %v", + rt.secret, + actual, + ) + } + } +} + +func TestBootstrapTokenToSecretRoundtrip(t *testing.T) { + var tests = []struct { + bt *BootstrapToken + }{ + { + &BootstrapToken{ + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Description: "foo", + Expires: &metav1.Time{ + Time: refTime, + }, + Usages: []string{"authentication", "signing"}, + Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"}, + }, + }, + } + for _, rt := range tests { + actual, err := BootstrapTokenFromSecret(rt.bt.ToSecret()) + if err != nil { + t.Errorf("failed BootstrapToken to Secret roundtrip with error: %v", err) + } + if !reflect.DeepEqual(actual, rt.bt) { + t.Errorf( + "failed BootstrapToken to Secret roundtrip:\n\texpected: %v\n\t actual: %v", + rt.bt, + actual, + ) + } + } +} + +func TestEncodeTokenSecretData(t *testing.T) { + var tests = []struct { + bt *BootstrapToken + data map[string][]byte + }{ + { + &BootstrapToken{ // the minimum amount of information needed to be specified + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + }, + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + }, + }, + { + &BootstrapToken{ // adds description + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Description: "foo", + }, + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "description": []byte("foo"), + }, + }, + { + &BootstrapToken{ // adds ttl + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + TTL: &metav1.Duration{ + Duration: mustParseDuration("2h", t), + }, + }, + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "expiration": []byte(refTime.Add(mustParseDuration("2h", t)).Format(time.RFC3339)), + }, + }, + { + &BootstrapToken{ // adds expiration + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Expires: &metav1.Time{ + Time: refTime, + }, + }, + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "expiration": []byte(refTime.Format(time.RFC3339)), + }, + }, + { + &BootstrapToken{ // adds ttl and expiration, should favor expiration + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + TTL: &metav1.Duration{ + Duration: mustParseDuration("2h", t), + }, + Expires: &metav1.Time{ + Time: refTime, + }, + }, + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "expiration": []byte(refTime.Format(time.RFC3339)), + }, + }, + { + &BootstrapToken{ // adds usages + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Usages: []string{"authentication", "signing"}, + }, + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "usage-bootstrap-signing": []byte("true"), + "usage-bootstrap-authentication": []byte("true"), + }, + }, + { + &BootstrapToken{ // adds groups + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"}, + }, + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"), + }, + }, + { + &BootstrapToken{ // all together + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Description: "foo", + TTL: &metav1.Duration{ + Duration: mustParseDuration("2h", t), + }, + Expires: &metav1.Time{ + Time: refTime, + }, + Usages: []string{"authentication", "signing"}, + Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"}, + }, + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "description": []byte("foo"), + "expiration": []byte(refTime.Format(time.RFC3339)), + "usage-bootstrap-signing": []byte("true"), + "usage-bootstrap-authentication": []byte("true"), + "auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"), + }, + }, + } + for _, rt := range tests { + actual := encodeTokenSecretData(rt.bt, refTime) + if !reflect.DeepEqual(actual, rt.data) { + t.Errorf( + "failed encodeTokenSecretData:\n\texpected: %v\n\t actual: %v", + rt.data, + actual, + ) + } + } +} + +func mustParseDuration(durationStr string, t *testing.T) time.Duration { + d, err := time.ParseDuration(durationStr) + if err != nil { + t.Fatalf("couldn't parse duration %q: %v", durationStr, err) + } + return d +} + +func TestBootstrapTokenFromSecret(t *testing.T) { + var tests = []struct { + name string + data map[string][]byte + bt *BootstrapToken + expectedError bool + }{ + { // minimum information + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + }, + &BootstrapToken{ + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + }, + false, + }, + { // invalid token id + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdeF"), + "token-secret": []byte("abcdef0123456789"), + }, + nil, + true, + }, + { // invalid secret naming + "foo", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + }, + nil, + true, + }, + { // invalid token secret + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("ABCDEF0123456789"), + }, + nil, + true, + }, + { // adds description + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "description": []byte("foo"), + }, + &BootstrapToken{ + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Description: "foo", + }, + false, + }, + { // adds expiration + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "expiration": []byte(refTime.Format(time.RFC3339)), + }, + &BootstrapToken{ + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Expires: &metav1.Time{ + Time: refTime, + }, + }, + false, + }, + { // invalid expiration + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "expiration": []byte("invalid date"), + }, + nil, + true, + }, + { // adds usages + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "usage-bootstrap-signing": []byte("true"), + "usage-bootstrap-authentication": []byte("true"), + }, + &BootstrapToken{ + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Usages: []string{"authentication", "signing"}, + }, + false, + }, + { // should ignore usages that aren't set to true + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "usage-bootstrap-signing": []byte("true"), + "usage-bootstrap-authentication": []byte("true"), + "usage-bootstrap-foo": []byte("false"), + "usage-bootstrap-bar": []byte(""), + }, + &BootstrapToken{ + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Usages: []string{"authentication", "signing"}, + }, + false, + }, + { // adds groups + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"), + }, + &BootstrapToken{ + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"}, + }, + false, + }, + { // all fields set + "bootstrap-token-abcdef", + map[string][]byte{ + "token-id": []byte("abcdef"), + "token-secret": []byte("abcdef0123456789"), + "description": []byte("foo"), + "expiration": []byte(refTime.Format(time.RFC3339)), + "usage-bootstrap-signing": []byte("true"), + "usage-bootstrap-authentication": []byte("true"), + "auth-extra-groups": []byte("system:bootstrappers,system:bootstrappers:foo"), + }, + &BootstrapToken{ + Token: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, + Description: "foo", + Expires: &metav1.Time{ + Time: refTime, + }, + Usages: []string{"authentication", "signing"}, + Groups: []string{"system:bootstrappers", "system:bootstrappers:foo"}, + }, + false, + }, + } + for _, rt := range tests { + actual, err := BootstrapTokenFromSecret(&v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: rt.name, + Namespace: "kube-system", + }, + Type: v1.SecretType("bootstrap.kubernetes.io/token"), + Data: rt.data, + }) + if (err != nil) != rt.expectedError { + t.Errorf( + "failed BootstrapTokenFromSecret\n\texpected error: %t\n\t actual error: %v", + rt.expectedError, + err, + ) + } else { + if actual == nil && rt.bt == nil { + // if both pointers are nil, it's okay, just continue + continue + } + // If one of the pointers is defined but the other isn't, throw error. If both pointers are defined but unequal, throw error + if (actual == nil && rt.bt != nil) || (actual != nil && rt.bt == nil) || !reflect.DeepEqual(*actual, *rt.bt) { + t.Errorf( + "failed BootstrapTokenFromSecret\n\texpected: %s\n\t actual: %s", + jsonMarshal(rt.bt), + jsonMarshal(actual), + ) + } + } + } +} + +func jsonMarshal(bt *BootstrapToken) string { + b, _ := json.Marshal(*bt) + return string(b) +} + +func TestGetSecretString(t *testing.T) { + var tests = []struct { + secret *v1.Secret + key string + expectedVal string + }{ + { + secret: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Data: map[string][]byte{ + "foo": []byte("bar"), + }, + }, + key: "foo", + expectedVal: "bar", + }, + { + secret: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Data: map[string][]byte{ + "foo": []byte("bar"), + }, + }, + key: "baz", + expectedVal: "", + }, + { + secret: &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + }, + key: "foo", + expectedVal: "", + }, + } + for _, rt := range tests { + actual := getSecretString(rt.secret, rt.key) + if actual != rt.expectedVal { + t.Errorf( + "failed getSecretString:\n\texpected: %s\n\t actual: %s", + rt.expectedVal, + actual, + ) + } + } +} diff --git a/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring_test.go b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring_test.go new file mode 100644 index 00000000000..8a389f83ec1 --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring_test.go @@ -0,0 +1,236 @@ +/* +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 kubeadm + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" +) + +func TestMarshalJSON(t *testing.T) { + var tests = []struct { + bts BootstrapTokenString + expected string + }{ + {BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, `"abcdef.abcdef0123456789"`}, + {BootstrapTokenString{ID: "foo", Secret: "bar"}, `"foo.bar"`}, + {BootstrapTokenString{ID: "h", Secret: "b"}, `"h.b"`}, + } + for _, rt := range tests { + b, err := json.Marshal(rt.bts) + if err != nil { + t.Fatalf("json.Marshal returned an unexpected error: %v", err) + } + if string(b) != rt.expected { + t.Errorf( + "failed BootstrapTokenString.MarshalJSON:\n\texpected: %s\n\t actual: %s", + rt.expected, + string(b), + ) + } + } +} + +func TestUnmarshalJSON(t *testing.T) { + var tests = []struct { + input string + bts *BootstrapTokenString + expectedError bool + }{ + {`"f.s"`, &BootstrapTokenString{}, true}, + {`"abcdef."`, &BootstrapTokenString{}, true}, + {`"abcdef:abcdef0123456789"`, &BootstrapTokenString{}, true}, + {`abcdef.abcdef0123456789`, &BootstrapTokenString{}, true}, + {`"abcdef.abcdef0123456789`, &BootstrapTokenString{}, true}, + {`"abcdef.ABCDEF0123456789"`, &BootstrapTokenString{}, true}, + {`"abcdef.abcdef0123456789"`, &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, false}, + {`"123456.aabbccddeeffgghh"`, &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}, false}, + } + for _, rt := range tests { + newbts := &BootstrapTokenString{} + err := json.Unmarshal([]byte(rt.input), newbts) + if (err != nil) != rt.expectedError { + t.Errorf("failed BootstrapTokenString.UnmarshalJSON:\n\texpected error: %t\n\t actual error: %v", rt.expectedError, err) + } else if !reflect.DeepEqual(rt.bts, newbts) { + t.Errorf( + "failed BootstrapTokenString.UnmarshalJSON:\n\texpected: %v\n\t actual: %v", + rt.bts, + newbts, + ) + } + } +} + +func TestJSONRoundtrip(t *testing.T) { + var tests = []struct { + input string + bts *BootstrapTokenString + }{ + {`"abcdef.abcdef0123456789"`, nil}, + {"", &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}}, + } + for _, rt := range tests { + if err := roundtrip(rt.input, rt.bts); err != nil { + t.Errorf("failed BootstrapTokenString JSON roundtrip with error: %v", err) + } + } +} + +func roundtrip(input string, bts *BootstrapTokenString) error { + var b []byte + var err error + newbts := &BootstrapTokenString{} + // If string input was specified, roundtrip like this: string -> (unmarshal) -> object -> (marshal) -> string + if len(input) > 0 { + if err := json.Unmarshal([]byte(input), newbts); err != nil { + return fmt.Errorf("expected no unmarshal error, got error: %v", err) + } + if b, err = json.Marshal(newbts); err != nil { + return fmt.Errorf("expected no marshal error, got error: %v", err) + } + if input != string(b) { + return fmt.Errorf( + "expected token: %s\n\t actual: %s", + input, + string(b), + ) + } + } else { // Otherwise, roundtrip like this: object -> (marshal) -> string -> (unmarshal) -> object + if b, err = json.Marshal(bts); err != nil { + return fmt.Errorf("expected no marshal error, got error: %v", err) + } + if err := json.Unmarshal(b, newbts); err != nil { + return fmt.Errorf("expected no unmarshal error, got error: %v", err) + } + if !reflect.DeepEqual(bts, newbts) { + return fmt.Errorf( + "expected object: %v\n\t actual: %v", + bts, + newbts, + ) + } + } + return nil +} + +func TestTokenFromIDAndSecret(t *testing.T) { + var tests = []struct { + bts BootstrapTokenString + expected string + }{ + {BootstrapTokenString{ID: "foo", Secret: "bar"}, "foo.bar"}, + {BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, "abcdef.abcdef0123456789"}, + {BootstrapTokenString{ID: "h", Secret: "b"}, "h.b"}, + } + for _, rt := range tests { + actual := rt.bts.String() + if actual != rt.expected { + t.Errorf( + "failed BootstrapTokenString.String():\n\texpected: %s\n\t actual: %s", + rt.expected, + actual, + ) + } + } +} + +func TestNewBootstrapTokenString(t *testing.T) { + var tests = []struct { + token string + expectedError bool + bts *BootstrapTokenString + }{ + {token: "", expectedError: true, bts: nil}, + {token: ".", expectedError: true, bts: nil}, + {token: "1234567890123456789012", expectedError: true, bts: nil}, // invalid parcel size + {token: "12345.1234567890123456", expectedError: true, bts: nil}, // invalid parcel size + {token: ".1234567890123456", expectedError: true, bts: nil}, // invalid parcel size + {token: "123456.", expectedError: true, bts: nil}, // invalid parcel size + {token: "123456:1234567890.123456", expectedError: true, bts: nil}, // invalid separation + {token: "abcdef:1234567890123456", expectedError: true, bts: nil}, // invalid separation + {token: "Abcdef.1234567890123456", expectedError: true, bts: nil}, // invalid token id + {token: "123456.AABBCCDDEEFFGGHH", expectedError: true, bts: nil}, // invalid token secret + {token: "123456.AABBCCD-EEFFGGHH", expectedError: true, bts: nil}, // invalid character + {token: "abc*ef.1234567890123456", expectedError: true, bts: nil}, // invalid character + {token: "abcdef.1234567890123456", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "1234567890123456"}}, + {token: "123456.aabbccddeeffgghh", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}}, + {token: "abcdef.abcdef0123456789", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}}, + {token: "123456.1234560123456789", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "1234560123456789"}}, + } + for _, rt := range tests { + actual, err := NewBootstrapTokenString(rt.token) + if (err != nil) != rt.expectedError { + t.Errorf( + "failed NewBootstrapTokenString for the token %q\n\texpected error: %t\n\t actual error: %v", + rt.token, + rt.expectedError, + err, + ) + } else if !reflect.DeepEqual(actual, rt.bts) { + t.Errorf( + "failed NewBootstrapTokenString for the token %q\n\texpected: %v\n\t actual: %v", + rt.token, + rt.bts, + actual, + ) + } + } +} + +func TestNewBootstrapTokenStringFromIDAndSecret(t *testing.T) { + var tests = []struct { + id, secret string + expectedError bool + bts *BootstrapTokenString + }{ + {id: "", secret: "", expectedError: true, bts: nil}, + {id: "1234567890123456789012", secret: "", expectedError: true, bts: nil}, // invalid parcel size + {id: "12345", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid parcel size + {id: "", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid parcel size + {id: "123456", secret: "", expectedError: true, bts: nil}, // invalid parcel size + {id: "Abcdef", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid token id + {id: "123456", secret: "AABBCCDDEEFFGGHH", expectedError: true, bts: nil}, // invalid token secret + {id: "123456", secret: "AABBCCD-EEFFGGHH", expectedError: true, bts: nil}, // invalid character + {id: "abc*ef", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid character + {id: "abcdef", secret: "1234567890123456", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "1234567890123456"}}, + {id: "123456", secret: "aabbccddeeffgghh", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}}, + {id: "abcdef", secret: "abcdef0123456789", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}}, + {id: "123456", secret: "1234560123456789", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "1234560123456789"}}, + } + for _, rt := range tests { + actual, err := NewBootstrapTokenStringFromIDAndSecret(rt.id, rt.secret) + if (err != nil) != rt.expectedError { + t.Errorf( + "failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected error: %t\n\t actual error: %v", + rt.id, + rt.secret, + rt.expectedError, + err, + ) + } else if !reflect.DeepEqual(actual, rt.bts) { + t.Errorf( + "failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected: %v\n\t actual: %v", + rt.id, + rt.secret, + rt.bts, + actual, + ) + } + } +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring_test.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring_test.go new file mode 100644 index 00000000000..0d06bd153e3 --- /dev/null +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring_test.go @@ -0,0 +1,236 @@ +/* +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 v1alpha2 + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" +) + +func TestMarshalJSON(t *testing.T) { + var tests = []struct { + bts BootstrapTokenString + expected string + }{ + {BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, `"abcdef.abcdef0123456789"`}, + {BootstrapTokenString{ID: "foo", Secret: "bar"}, `"foo.bar"`}, + {BootstrapTokenString{ID: "h", Secret: "b"}, `"h.b"`}, + } + for _, rt := range tests { + b, err := json.Marshal(rt.bts) + if err != nil { + t.Fatalf("json.Marshal returned an unexpected error: %v", err) + } + if string(b) != rt.expected { + t.Errorf( + "failed BootstrapTokenString.MarshalJSON:\n\texpected: %s\n\t actual: %s", + rt.expected, + string(b), + ) + } + } +} + +func TestUnmarshalJSON(t *testing.T) { + var tests = []struct { + input string + bts *BootstrapTokenString + expectedError bool + }{ + {`"f.s"`, &BootstrapTokenString{}, true}, + {`"abcdef."`, &BootstrapTokenString{}, true}, + {`"abcdef:abcdef0123456789"`, &BootstrapTokenString{}, true}, + {`abcdef.abcdef0123456789`, &BootstrapTokenString{}, true}, + {`"abcdef.abcdef0123456789`, &BootstrapTokenString{}, true}, + {`"abcdef.ABCDEF0123456789"`, &BootstrapTokenString{}, true}, + {`"abcdef.abcdef0123456789"`, &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, false}, + {`"123456.aabbccddeeffgghh"`, &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}, false}, + } + for _, rt := range tests { + newbts := &BootstrapTokenString{} + err := json.Unmarshal([]byte(rt.input), newbts) + if (err != nil) != rt.expectedError { + t.Errorf("failed BootstrapTokenString.UnmarshalJSON:\n\texpected error: %t\n\t actual error: %v", rt.expectedError, err) + } else if !reflect.DeepEqual(rt.bts, newbts) { + t.Errorf( + "failed BootstrapTokenString.UnmarshalJSON:\n\texpected: %v\n\t actual: %v", + rt.bts, + newbts, + ) + } + } +} + +func TestJSONRoundtrip(t *testing.T) { + var tests = []struct { + input string + bts *BootstrapTokenString + }{ + {`"abcdef.abcdef0123456789"`, nil}, + {"", &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}}, + } + for _, rt := range tests { + if err := roundtrip(rt.input, rt.bts); err != nil { + t.Errorf("failed BootstrapTokenString JSON roundtrip with error: %v", err) + } + } +} + +func roundtrip(input string, bts *BootstrapTokenString) error { + var b []byte + var err error + newbts := &BootstrapTokenString{} + // If string input was specified, roundtrip like this: string -> (unmarshal) -> object -> (marshal) -> string + if len(input) > 0 { + if err := json.Unmarshal([]byte(input), newbts); err != nil { + return fmt.Errorf("expected no unmarshal error, got error: %v", err) + } + if b, err = json.Marshal(newbts); err != nil { + return fmt.Errorf("expected no marshal error, got error: %v", err) + } + if input != string(b) { + return fmt.Errorf( + "expected token: %s\n\t actual: %s", + input, + string(b), + ) + } + } else { // Otherwise, roundtrip like this: object -> (marshal) -> string -> (unmarshal) -> object + if b, err = json.Marshal(bts); err != nil { + return fmt.Errorf("expected no marshal error, got error: %v", err) + } + if err := json.Unmarshal(b, newbts); err != nil { + return fmt.Errorf("expected no unmarshal error, got error: %v", err) + } + if !reflect.DeepEqual(bts, newbts) { + return fmt.Errorf( + "expected object: %v\n\t actual: %v", + bts, + newbts, + ) + } + } + return nil +} + +func TestTokenFromIDAndSecret(t *testing.T) { + var tests = []struct { + bts BootstrapTokenString + expected string + }{ + {BootstrapTokenString{ID: "foo", Secret: "bar"}, "foo.bar"}, + {BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}, "abcdef.abcdef0123456789"}, + {BootstrapTokenString{ID: "h", Secret: "b"}, "h.b"}, + } + for _, rt := range tests { + actual := rt.bts.String() + if actual != rt.expected { + t.Errorf( + "failed BootstrapTokenString.String():\n\texpected: %s\n\t actual: %s", + rt.expected, + actual, + ) + } + } +} + +func TestNewBootstrapTokenString(t *testing.T) { + var tests = []struct { + token string + expectedError bool + bts *BootstrapTokenString + }{ + {token: "", expectedError: true, bts: nil}, + {token: ".", expectedError: true, bts: nil}, + {token: "1234567890123456789012", expectedError: true, bts: nil}, // invalid parcel size + {token: "12345.1234567890123456", expectedError: true, bts: nil}, // invalid parcel size + {token: ".1234567890123456", expectedError: true, bts: nil}, // invalid parcel size + {token: "123456.", expectedError: true, bts: nil}, // invalid parcel size + {token: "123456:1234567890.123456", expectedError: true, bts: nil}, // invalid separation + {token: "abcdef:1234567890123456", expectedError: true, bts: nil}, // invalid separation + {token: "Abcdef.1234567890123456", expectedError: true, bts: nil}, // invalid token id + {token: "123456.AABBCCDDEEFFGGHH", expectedError: true, bts: nil}, // invalid token secret + {token: "123456.AABBCCD-EEFFGGHH", expectedError: true, bts: nil}, // invalid character + {token: "abc*ef.1234567890123456", expectedError: true, bts: nil}, // invalid character + {token: "abcdef.1234567890123456", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "1234567890123456"}}, + {token: "123456.aabbccddeeffgghh", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}}, + {token: "abcdef.abcdef0123456789", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}}, + {token: "123456.1234560123456789", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "1234560123456789"}}, + } + for _, rt := range tests { + actual, err := NewBootstrapTokenString(rt.token) + if (err != nil) != rt.expectedError { + t.Errorf( + "failed NewBootstrapTokenString for the token %q\n\texpected error: %t\n\t actual error: %v", + rt.token, + rt.expectedError, + err, + ) + } else if !reflect.DeepEqual(actual, rt.bts) { + t.Errorf( + "failed NewBootstrapTokenString for the token %q\n\texpected: %v\n\t actual: %v", + rt.token, + rt.bts, + actual, + ) + } + } +} + +func TestNewBootstrapTokenStringFromIDAndSecret(t *testing.T) { + var tests = []struct { + id, secret string + expectedError bool + bts *BootstrapTokenString + }{ + {id: "", secret: "", expectedError: true, bts: nil}, + {id: "1234567890123456789012", secret: "", expectedError: true, bts: nil}, // invalid parcel size + {id: "12345", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid parcel size + {id: "", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid parcel size + {id: "123456", secret: "", expectedError: true, bts: nil}, // invalid parcel size + {id: "Abcdef", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid token id + {id: "123456", secret: "AABBCCDDEEFFGGHH", expectedError: true, bts: nil}, // invalid token secret + {id: "123456", secret: "AABBCCD-EEFFGGHH", expectedError: true, bts: nil}, // invalid character + {id: "abc*ef", secret: "1234567890123456", expectedError: true, bts: nil}, // invalid character + {id: "abcdef", secret: "1234567890123456", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "1234567890123456"}}, + {id: "123456", secret: "aabbccddeeffgghh", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "aabbccddeeffgghh"}}, + {id: "abcdef", secret: "abcdef0123456789", expectedError: false, bts: &BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"}}, + {id: "123456", secret: "1234560123456789", expectedError: false, bts: &BootstrapTokenString{ID: "123456", Secret: "1234560123456789"}}, + } + for _, rt := range tests { + actual, err := NewBootstrapTokenStringFromIDAndSecret(rt.id, rt.secret) + if (err != nil) != rt.expectedError { + t.Errorf( + "failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected error: %t\n\t actual error: %v", + rt.id, + rt.secret, + rt.expectedError, + err, + ) + } else if !reflect.DeepEqual(actual, rt.bts) { + t.Errorf( + "failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected: %v\n\t actual: %v", + rt.id, + rt.secret, + rt.bts, + actual, + ) + } + } +} diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go index 6876bd702de..d4454fb4655 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go @@ -33,7 +33,7 @@ import ( utilpointer "k8s.io/kubernetes/pkg/util/pointer" ) -func TestValidateTokenDiscovery(t *testing.T) { +func TestValidateToken(t *testing.T) { var tests = []struct { c *kubeadm.NodeConfiguration f *field.Path @@ -51,7 +51,7 @@ func TestValidateTokenDiscovery(t *testing.T) { err := ValidateToken(rt.c.Token, rt.f).ToAggregate() if (err == nil) != rt.expected { t.Errorf( - "failed ValidateTokenDiscovery:\n\texpected: %t\n\t actual: %t", + "failed ValidateToken:\n\texpected: %t\n\t actual: %t", rt.expected, (err == nil), ) @@ -434,7 +434,6 @@ func TestValidateMasterConfiguration(t *testing.T) { DNSDomain: "cluster.local", }, CertificatesDir: "/some/other/cert/dir", - Token: "abcdef.0123456789abcdef", }, false}, {"valid master configuration with incorrect IPv4 pod subnet", &kubeadm.MasterConfiguration{ @@ -448,7 +447,6 @@ func TestValidateMasterConfiguration(t *testing.T) { PodSubnet: "10.0.1.15", }, CertificatesDir: "/some/other/cert/dir", - Token: "abcdef.0123456789abcdef", NodeRegistration: kubeadm.NodeRegistrationOptions{Name: nodename, CRISocket: "/some/path"}, }, false}, {"valid master configuration with IPv4 service subnet", @@ -494,7 +492,6 @@ func TestValidateMasterConfiguration(t *testing.T) { PodSubnet: "10.0.1.15/16", }, CertificatesDir: "/some/other/cert/dir", - Token: "abcdef.0123456789abcdef", NodeRegistration: kubeadm.NodeRegistrationOptions{Name: nodename, CRISocket: "/some/path"}, }, true}, {"valid master configuration using IPv6 service subnet", @@ -539,7 +536,6 @@ func TestValidateMasterConfiguration(t *testing.T) { DNSDomain: "cluster.local", }, CertificatesDir: "/some/other/cert/dir", - Token: "abcdef.0123456789abcdef", NodeRegistration: kubeadm.NodeRegistrationOptions{Name: nodename, CRISocket: "/some/path"}, }, true}, } diff --git a/cmd/kubeadm/app/cmd/token_test.go b/cmd/kubeadm/app/cmd/token_test.go index 74b3bc8614c..c919583a918 100644 --- a/cmd/kubeadm/app/cmd/token_test.go +++ b/cmd/kubeadm/app/cmd/token_test.go @@ -135,13 +135,6 @@ func TestRunCreateToken(t *testing.T) { extraGroups: []string{}, expectedError: false, }, - { - name: "invalid: incorrect token", - token: "123456.AABBCCDDEEFFGGHH", - usages: []string{"signing", "authentication"}, - extraGroups: []string{}, - expectedError: true, - }, { name: "invalid: incorrect extraGroups", token: "abcdef.1234567890123456", @@ -180,18 +173,27 @@ func TestRunCreateToken(t *testing.T) { }, } for _, tc := range testCases { + bts, err := kubeadmapiv1alpha2.NewBootstrapTokenString(tc.token) + if err != nil && len(tc.token) != 0 { // if tc.token is "" it's okay as it will be generated later at runtime + t.Fatalf("token couldn't be parsed for testing: %v", err) + } + cfg := &kubeadmapiv1alpha2.MasterConfiguration{ // KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid // the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig KubernetesVersion: "v1.10.0", - Token: tc.token, - TokenTTL: &metav1.Duration{Duration: 0}, - TokenUsages: tc.usages, - TokenGroups: tc.extraGroups, + BootstrapTokens: []kubeadmapiv1alpha2.BootstrapToken{ + { + Token: bts, + TTL: &metav1.Duration{Duration: 0}, + Usages: tc.usages, + Groups: tc.extraGroups, + }, + }, } - err := RunCreateToken(&buf, fakeClient, "", cfg, "", tc.printJoin, "") + err = RunCreateToken(&buf, fakeClient, "", cfg, tc.printJoin, "") if (err != nil) != tc.expectedError { t.Errorf("Test case %s: RunCreateToken expected error: %v, saw: %v", tc.name, tc.expectedError, (err != nil)) } @@ -277,22 +279,6 @@ func TestNewCmdToken(t *testing.T) { } } -func TestGetSecretString(t *testing.T) { - secret := v1.Secret{} - key := "test-key" - if str := getSecretString(&secret, key); str != "" { - t.Errorf("getSecretString() did not return empty string for a nil v1.Secret.Data") - } - secret.Data = make(map[string][]byte) - if str := getSecretString(&secret, key); str != "" { - t.Errorf("getSecretString() did not return empty string for missing v1.Secret.Data key") - } - secret.Data[key] = []byte("test-value") - if str := getSecretString(&secret, key); str == "" { - t.Errorf("getSecretString() failed for a valid v1.Secret.Data key") - } -} - func TestGetClientset(t *testing.T) { testConfigTokenFile := "test-config-file" diff --git a/cmd/kubeadm/app/cmd/upgrade/common_test.go b/cmd/kubeadm/app/cmd/upgrade/common_test.go index 8745378b60f..19b32e09806 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/common_test.go @@ -68,7 +68,6 @@ func TestPrintConfiguration(t *testing.T) { nodeRegistration: criSocket: "" name: "" - token: "" unifiedControlPlaneImage: "" `), }, @@ -113,7 +112,6 @@ func TestPrintConfiguration(t *testing.T) { nodeRegistration: criSocket: "" name: "" - token: "" unifiedControlPlaneImage: "" `), }, diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go b/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go deleted file mode 100644 index 48bc8c1f168..00000000000 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go +++ /dev/null @@ -1,59 +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 node - -import ( - "bytes" - "testing" - "time" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" -) - -func TestEncodeTokenSecretData(t *testing.T) { - var tests = []struct { - token *kubeadmapi.TokenDiscovery - t time.Duration - }{ - {token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}}, // should use default - {token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default - } - for _, rt := range tests { - actual, _ := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, []string{}, "") - if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) { - t.Errorf( - "failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s", - rt.token.ID, - actual["token-id"], - ) - } - if !bytes.Equal(actual["token-secret"], []byte(rt.token.Secret)) { - t.Errorf( - "failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s", - rt.token.Secret, - actual["token-secret"], - ) - } - if rt.t > 0 { - if actual["expiration"] == nil { - t.Errorf( - "failed EncodeTokenSecretData, duration was not added to time", - ) - } - } - } -} diff --git a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go index e683bf3bbe7..accaff0904e 100644 --- a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go +++ b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go @@ -63,8 +63,15 @@ func TestUploadConfiguration(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cfg := &kubeadmapi.MasterConfiguration{ - KubernetesVersion: "1.7.3", - Token: "1234567", + KubernetesVersion: "v1.10.3", + BootstrapTokens: []kubeadmapi.BootstrapToken{ + { + Token: &kubeadmapi.BootstrapTokenString{ + ID: "abcdef", + Secret: "abcdef0123456789", + }, + }, + }, } client := clientsetfake.NewSimpleClientset() if tt.errOnCreate != nil { @@ -110,8 +117,9 @@ func TestUploadConfiguration(t *testing.T) { t.Errorf("Decoded value doesn't match, decoded = %#v, expected = %#v", decodedCfg.KubernetesVersion, cfg.KubernetesVersion) } - if decodedCfg.Token != "" { - t.Errorf("Decoded value contains token (sensitive info), decoded = %#v, expected = empty", decodedCfg.Token) + // If the decoded cfg has a BootstrapTokens array, verify the sensitive information we had isn't still there. + if len(decodedCfg.BootstrapTokens) > 0 && decodedCfg.BootstrapTokens[0].Token != nil && decodedCfg.BootstrapTokens[0].Token.String() == cfg.BootstrapTokens[0].Token.String() { + t.Errorf("Decoded value contains .BootstrapTokens (sensitive info), decoded = %#v, expected = empty", decodedCfg.BootstrapTokens) } if decodedExtCfg.Kind != "MasterConfiguration" { diff --git a/cmd/kubeadm/app/util/token/tokens_test.go b/cmd/kubeadm/app/util/token/tokens_test.go deleted file mode 100644 index 4146a027036..00000000000 --- a/cmd/kubeadm/app/util/token/tokens_test.go +++ /dev/null @@ -1,173 +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 ( - "testing" - - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" -) - -func TestTokenParse(t *testing.T) { - var tests = []struct { - token string - expected bool - }{ - {token: "1234567890123456789012", expected: false}, // invalid parcel size - {token: "12345.1234567890123456", expected: false}, // invalid parcel size - {token: ".1234567890123456", expected: false}, // invalid parcel size - {token: "123456:1234567890.123456", expected: false}, // invalid separation - {token: "abcdef:1234567890123456", expected: false}, // invalid separation - {token: "Abcdef.1234567890123456", expected: false}, // invalid token id - {token: "123456.AABBCCDDEEFFGGHH", expected: false}, // invalid token secret - {token: "abcdef.1234567890123456", expected: true}, - {token: "123456.aabbccddeeffgghh", expected: true}, - } - - for _, rt := range tests { - _, _, actual := ParseToken(rt.token) - if (actual == nil) != rt.expected { - t.Errorf( - "failed ParseToken for this token: [%s]\n\texpected: %t\n\t actual: %t", - rt.token, - rt.expected, - (actual == nil), - ) - } - } - -} - -func TestParseTokenID(t *testing.T) { - var tests = []struct { - tokenID string - expected bool - }{ - {tokenID: "", expected: false}, - {tokenID: "1234567890123456789012", expected: false}, - {tokenID: "12345", expected: false}, - {tokenID: "Abcdef", expected: false}, - {tokenID: "abcdef", expected: true}, - {tokenID: "123456", expected: true}, - } - for _, rt := range tests { - actual := ParseTokenID(rt.tokenID) - if (actual == nil) != rt.expected { - t.Errorf( - "failed ParseTokenID for this token ID: [%s]\n\texpected: %t\n\t actual: %t", - rt.tokenID, - rt.expected, - (actual == nil), - ) - } - } -} - -func TestValidateToken(t *testing.T) { - var tests = []struct { - token *kubeadmapi.TokenDiscovery - expected bool - }{ - {token: &kubeadmapi.TokenDiscovery{ID: "", Secret: ""}, expected: false}, - {token: &kubeadmapi.TokenDiscovery{ID: "1234567890123456789012", Secret: ""}, expected: false}, - {token: &kubeadmapi.TokenDiscovery{ID: "", Secret: "1234567890123456789012"}, expected: false}, - {token: &kubeadmapi.TokenDiscovery{ID: "12345", Secret: "1234567890123456"}, expected: false}, - {token: &kubeadmapi.TokenDiscovery{ID: "Abcdef", Secret: "1234567890123456"}, expected: false}, - {token: &kubeadmapi.TokenDiscovery{ID: "123456", Secret: "AABBCCDDEEFFGGHH"}, expected: false}, - {token: &kubeadmapi.TokenDiscovery{ID: "abc*ef", Secret: "1234567890123456"}, expected: false}, - {token: &kubeadmapi.TokenDiscovery{ID: "abcdef", Secret: "123456789*123456"}, expected: false}, - {token: &kubeadmapi.TokenDiscovery{ID: "abcdef", Secret: "1234567890123456"}, expected: true}, - {token: &kubeadmapi.TokenDiscovery{ID: "123456", Secret: "aabbccddeeffgghh"}, expected: true}, - {token: &kubeadmapi.TokenDiscovery{ID: "abc456", Secret: "1234567890123456"}, expected: true}, - {token: &kubeadmapi.TokenDiscovery{ID: "abcdef", Secret: "123456ddeeffgghh"}, expected: true}, - } - for _, rt := range tests { - valid, actual := ValidateToken(rt.token) - if (actual == nil) != rt.expected { - t.Errorf( - "failed ValidateToken for this token ID: [%s]\n\texpected: %t\n\t actual: %t", - rt.token, - rt.expected, - (actual == nil), - ) - } - if (valid == true) != rt.expected { - t.Errorf( - "failed ValidateToken for this token ID: [%s]\n\texpected: %t\n\t actual: %t", - rt.token, - rt.expected, - (actual == nil), - ) - } - } -} - -func TestGenerateToken(t *testing.T) { - token, err := GenerateToken() - if err != nil { - t.Fatalf("GenerateToken returned an unexpected error: %+v", err) - } - tokenID, tokenSecret, err := ParseToken(token) - if err != nil { - t.Fatalf("GenerateToken returned an unexpected error: %+v", err) - } - 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)) - } -} - -func TestRandBytes(t *testing.T) { - var randTest = []int{ - 0, - 1, - 2, - 3, - 100, - } - - for _, rt := range randTest { - actual, err := randBytes(rt) - if err != nil { - t.Errorf("failed randBytes: %v", err) - } - if len(actual) != rt { - t.Errorf("failed randBytes:\n\texpected: %d\n\t actual: %d\n", rt, len(actual)) - } - } -} - -func TestBearerToken(t *testing.T) { - var tests = []struct { - token *kubeadmapi.TokenDiscovery - expected string - }{ - {token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, expected: "foo.bar"}, // should use default - } - for _, rt := range tests { - actual := BearerToken(rt.token) - if actual != rt.expected { - t.Errorf( - "failed BearerToken:\n\texpected: %s\n\t actual: %s", - rt.expected, - actual, - ) - } - } -} From 67a9f1bfe71496c8f2dab1cc7ad9388f66ff9a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 31 May 2018 22:21:06 +0300 Subject: [PATCH 4/5] autogenerated --- cmd/kubeadm/app/apis/kubeadm/BUILD | 19 +++- cmd/kubeadm/app/apis/kubeadm/install/BUILD | 14 --- .../v1alpha1/zz_generated.conversion.go | 37 +----- cmd/kubeadm/app/apis/kubeadm/v1alpha2/BUILD | 11 +- .../v1alpha2/zz_generated.conversion.go | 106 +++++++++++------- .../kubeadm/v1alpha2/zz_generated.deepcopy.go | 106 ++++++++++++------ .../kubeadm/v1alpha2/zz_generated.defaults.go | 4 + cmd/kubeadm/app/apis/kubeadm/validation/BUILD | 1 - .../app/apis/kubeadm/zz_generated.deepcopy.go | 106 ++++++++++++------ cmd/kubeadm/app/cmd/BUILD | 6 +- cmd/kubeadm/app/cmd/options/BUILD | 28 +++++ cmd/kubeadm/app/cmd/phases/BUILD | 1 + cmd/kubeadm/app/discovery/token/BUILD | 1 - .../app/phases/bootstraptoken/node/BUILD | 13 +-- cmd/kubeadm/app/util/BUILD | 1 - cmd/kubeadm/app/util/config/BUILD | 2 +- .../testdata/conversion/master/internal.yaml | 17 +-- .../testdata/conversion/master/v1alpha2.yaml | 15 +-- .../testdata/defaulting/master/defaulted.yaml | 15 +-- .../defaulting/master/incomplete.yaml | 2 + cmd/kubeadm/app/util/token/BUILD | 34 ------ 21 files changed, 311 insertions(+), 228 deletions(-) delete mode 100644 cmd/kubeadm/app/apis/kubeadm/install/BUILD create mode 100644 cmd/kubeadm/app/cmd/options/BUILD delete mode 100644 cmd/kubeadm/app/util/token/BUILD diff --git a/cmd/kubeadm/app/apis/kubeadm/BUILD b/cmd/kubeadm/app/apis/kubeadm/BUILD index d37bb68e4a7..866e8826f5a 100644 --- a/cmd/kubeadm/app/apis/kubeadm/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/BUILD @@ -3,11 +3,14 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( name = "go_default_library", srcs = [ + "bootstraptokenhelpers.go", + "bootstraptokenstring.go", "doc.go", "register.go", "types.go", @@ -22,6 +25,8 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library", + "//vendor/k8s.io/client-go/tools/bootstrap/token/util:go_default_library", ], ) @@ -37,7 +42,6 @@ filegroup( srcs = [ ":package-srcs", "//cmd/kubeadm/app/apis/kubeadm/fuzzer:all-srcs", - "//cmd/kubeadm/app/apis/kubeadm/install:all-srcs", "//cmd/kubeadm/app/apis/kubeadm/scheme:all-srcs", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:all-srcs", "//cmd/kubeadm/app/apis/kubeadm/v1alpha2:all-srcs", @@ -45,3 +49,16 @@ filegroup( ], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = [ + "bootstraptokenhelpers_test.go", + "bootstraptokenstring_test.go", + ], + embed = [":go_default_library"], + deps = [ + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) diff --git a/cmd/kubeadm/app/apis/kubeadm/install/BUILD b/cmd/kubeadm/app/apis/kubeadm/install/BUILD deleted file mode 100644 index 7e76248ad95..00000000000 --- a/cmd/kubeadm/app/apis/kubeadm/install/BUILD +++ /dev/null @@ -1,14 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go index 6a979a82eae..81ab257038b 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go @@ -58,8 +58,6 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_kubeadm_Networking_To_v1alpha1_Networking, Convert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration, Convert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration, - Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery, - Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery, ) } @@ -221,10 +219,10 @@ func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in // WARNING: in.AuthorizationModes requires manual conversion: does not exist in peer-type // WARNING: in.NoTaintMaster requires manual conversion: does not exist in peer-type // WARNING: in.PrivilegedPods requires manual conversion: does not exist in peer-type - out.Token = in.Token - out.TokenTTL = (*meta_v1.Duration)(unsafe.Pointer(in.TokenTTL)) - out.TokenUsages = *(*[]string)(unsafe.Pointer(&in.TokenUsages)) - out.TokenGroups = *(*[]string)(unsafe.Pointer(&in.TokenGroups)) + // WARNING: in.Token requires manual conversion: does not exist in peer-type + // WARNING: in.TokenTTL requires manual conversion: does not exist in peer-type + // WARNING: in.TokenUsages requires manual conversion: does not exist in peer-type + // WARNING: in.TokenGroups requires manual conversion: does not exist in peer-type // WARNING: in.CRISocket requires manual conversion: does not exist in peer-type out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs)) out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs)) @@ -263,10 +261,7 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in } out.KubernetesVersion = in.KubernetesVersion // WARNING: in.NodeRegistration requires manual conversion: does not exist in peer-type - out.Token = in.Token - out.TokenTTL = (*meta_v1.Duration)(unsafe.Pointer(in.TokenTTL)) - out.TokenUsages = *(*[]string)(unsafe.Pointer(&in.TokenUsages)) - out.TokenGroups = *(*[]string)(unsafe.Pointer(&in.TokenGroups)) + // WARNING: in.BootstrapTokens requires manual conversion: does not exist in peer-type out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs)) out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs)) out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs)) @@ -342,25 +337,3 @@ func autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kub out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) return nil } - -func autoConvert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in *TokenDiscovery, out *kubeadm.TokenDiscovery, s conversion.Scope) error { - out.ID = in.ID - out.Secret = in.Secret - return nil -} - -// Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery is an autogenerated conversion function. -func Convert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in *TokenDiscovery, out *kubeadm.TokenDiscovery, s conversion.Scope) error { - return autoConvert_v1alpha1_TokenDiscovery_To_kubeadm_TokenDiscovery(in, out, s) -} - -func autoConvert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery(in *kubeadm.TokenDiscovery, out *TokenDiscovery, s conversion.Scope) error { - out.ID = in.ID - out.Secret = in.Secret - return nil -} - -// Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery is an autogenerated conversion function. -func Convert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery(in *kubeadm.TokenDiscovery, out *TokenDiscovery, s conversion.Scope) error { - return autoConvert_kubeadm_TokenDiscovery_To_v1alpha1_TokenDiscovery(in, out, s) -} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/BUILD b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/BUILD index 841c006d451..380b5b6d844 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/BUILD @@ -1,8 +1,9 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "bootstraptokenstring.go", "defaults.go", "doc.go", "register.go", @@ -61,6 +62,8 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library", + "//vendor/k8s.io/client-go/tools/bootstrap/token/util:go_default_library", ], ) @@ -77,3 +80,9 @@ filegroup( tags = ["automanaged"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_test", + srcs = ["bootstraptokenstring_test.go"], + embed = [":go_default_library"], +) diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.conversion.go index 888517af55d..07b5c5fc284 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.conversion.go @@ -23,8 +23,8 @@ package v1alpha2 import ( unsafe "unsafe" - v1 "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + core_v1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" @@ -44,6 +44,10 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_kubeadm_API_To_v1alpha2_API, Convert_v1alpha2_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration, Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha2_AuditPolicyConfiguration, + Convert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken, + Convert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken, + Convert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString, + Convert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString, Convert_v1alpha2_Etcd_To_kubeadm_Etcd, Convert_kubeadm_Etcd_To_v1alpha2_Etcd, Convert_v1alpha2_ExternalEtcd_To_kubeadm_ExternalEtcd, @@ -64,8 +68,6 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_kubeadm_NodeConfiguration_To_v1alpha2_NodeConfiguration, Convert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions, Convert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions, - Convert_v1alpha2_TokenDiscovery_To_kubeadm_TokenDiscovery, - Convert_kubeadm_TokenDiscovery_To_v1alpha2_TokenDiscovery, ) } @@ -117,6 +119,58 @@ func Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha2_AuditPolicyConfigurati return autoConvert_kubeadm_AuditPolicyConfiguration_To_v1alpha2_AuditPolicyConfiguration(in, out, s) } +func autoConvert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken(in *BootstrapToken, out *kubeadm.BootstrapToken, s conversion.Scope) error { + out.Token = (*kubeadm.BootstrapTokenString)(unsafe.Pointer(in.Token)) + out.Description = in.Description + out.TTL = (*v1.Duration)(unsafe.Pointer(in.TTL)) + out.Expires = (*v1.Time)(unsafe.Pointer(in.Expires)) + out.Usages = *(*[]string)(unsafe.Pointer(&in.Usages)) + out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups)) + return nil +} + +// Convert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken is an autogenerated conversion function. +func Convert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken(in *BootstrapToken, out *kubeadm.BootstrapToken, s conversion.Scope) error { + return autoConvert_v1alpha2_BootstrapToken_To_kubeadm_BootstrapToken(in, out, s) +} + +func autoConvert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken(in *kubeadm.BootstrapToken, out *BootstrapToken, s conversion.Scope) error { + out.Token = (*BootstrapTokenString)(unsafe.Pointer(in.Token)) + out.Description = in.Description + out.TTL = (*v1.Duration)(unsafe.Pointer(in.TTL)) + out.Expires = (*v1.Time)(unsafe.Pointer(in.Expires)) + out.Usages = *(*[]string)(unsafe.Pointer(&in.Usages)) + out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups)) + return nil +} + +// Convert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken is an autogenerated conversion function. +func Convert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken(in *kubeadm.BootstrapToken, out *BootstrapToken, s conversion.Scope) error { + return autoConvert_kubeadm_BootstrapToken_To_v1alpha2_BootstrapToken(in, out, s) +} + +func autoConvert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in *BootstrapTokenString, out *kubeadm.BootstrapTokenString, s conversion.Scope) error { + out.ID = in.ID + out.Secret = in.Secret + return nil +} + +// Convert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString is an autogenerated conversion function. +func Convert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in *BootstrapTokenString, out *kubeadm.BootstrapTokenString, s conversion.Scope) error { + return autoConvert_v1alpha2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in, out, s) +} + +func autoConvert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString(in *kubeadm.BootstrapTokenString, out *BootstrapTokenString, s conversion.Scope) error { + out.ID = in.ID + out.Secret = in.Secret + return nil +} + +// Convert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString is an autogenerated conversion function. +func Convert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString(in *kubeadm.BootstrapTokenString, out *BootstrapTokenString, s conversion.Scope) error { + return autoConvert_kubeadm_BootstrapTokenString_To_v1alpha2_BootstrapTokenString(in, out, s) +} + func autoConvert_v1alpha2_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)) @@ -170,7 +224,7 @@ func autoConvert_v1alpha2_HostPathMount_To_kubeadm_HostPathMount(in *HostPathMou out.HostPath = in.HostPath out.MountPath = in.MountPath out.Writable = in.Writable - out.PathType = v1.HostPathType(in.PathType) + out.PathType = core_v1.HostPathType(in.PathType) return nil } @@ -184,7 +238,7 @@ func autoConvert_kubeadm_HostPathMount_To_v1alpha2_HostPathMount(in *kubeadm.Hos out.HostPath = in.HostPath out.MountPath = in.MountPath out.Writable = in.Writable - out.PathType = v1.HostPathType(in.PathType) + out.PathType = core_v1.HostPathType(in.PathType) return nil } @@ -281,10 +335,7 @@ func autoConvert_v1alpha2_MasterConfiguration_To_kubeadm_MasterConfiguration(in return err } out.KubernetesVersion = in.KubernetesVersion - out.Token = in.Token - out.TokenTTL = (*meta_v1.Duration)(unsafe.Pointer(in.TokenTTL)) - out.TokenUsages = *(*[]string)(unsafe.Pointer(&in.TokenUsages)) - out.TokenGroups = *(*[]string)(unsafe.Pointer(&in.TokenGroups)) + out.BootstrapTokens = *(*[]kubeadm.BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens)) out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs)) out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs)) out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs)) @@ -328,10 +379,7 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha2_MasterConfiguration(in if err := Convert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions(&in.NodeRegistration, &out.NodeRegistration, s); err != nil { return err } - out.Token = in.Token - out.TokenTTL = (*meta_v1.Duration)(unsafe.Pointer(in.TokenTTL)) - out.TokenUsages = *(*[]string)(unsafe.Pointer(&in.TokenUsages)) - out.TokenGroups = *(*[]string)(unsafe.Pointer(&in.TokenGroups)) + out.BootstrapTokens = *(*[]BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens)) out.APIServerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.APIServerExtraArgs)) out.ControllerManagerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ControllerManagerExtraArgs)) out.SchedulerExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.SchedulerExtraArgs)) @@ -388,7 +436,7 @@ func autoConvert_v1alpha2_NodeConfiguration_To_kubeadm_NodeConfiguration(in *Nod out.DiscoveryFile = in.DiscoveryFile out.DiscoveryToken = in.DiscoveryToken out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers)) - out.DiscoveryTimeout = (*meta_v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout)) + out.DiscoveryTimeout = (*v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout)) out.TLSBootstrapToken = in.TLSBootstrapToken out.Token = in.Token out.ClusterName = in.ClusterName @@ -411,7 +459,7 @@ func autoConvert_kubeadm_NodeConfiguration_To_v1alpha2_NodeConfiguration(in *kub out.DiscoveryFile = in.DiscoveryFile out.DiscoveryToken = in.DiscoveryToken out.DiscoveryTokenAPIServers = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenAPIServers)) - out.DiscoveryTimeout = (*meta_v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout)) + out.DiscoveryTimeout = (*v1.Duration)(unsafe.Pointer(in.DiscoveryTimeout)) out.TLSBootstrapToken = in.TLSBootstrapToken out.Token = in.Token out.ClusterName = in.ClusterName @@ -429,7 +477,7 @@ func Convert_kubeadm_NodeConfiguration_To_v1alpha2_NodeConfiguration(in *kubeadm func autoConvert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(in *NodeRegistrationOptions, out *kubeadm.NodeRegistrationOptions, s conversion.Scope) error { out.Name = in.Name out.CRISocket = in.CRISocket - out.Taints = *(*[]v1.Taint)(unsafe.Pointer(&in.Taints)) + out.Taints = *(*[]core_v1.Taint)(unsafe.Pointer(&in.Taints)) out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs)) return nil } @@ -442,7 +490,7 @@ func Convert_v1alpha2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions func autoConvert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions(in *kubeadm.NodeRegistrationOptions, out *NodeRegistrationOptions, s conversion.Scope) error { out.Name = in.Name out.CRISocket = in.CRISocket - out.Taints = *(*[]v1.Taint)(unsafe.Pointer(&in.Taints)) + out.Taints = *(*[]core_v1.Taint)(unsafe.Pointer(&in.Taints)) out.ExtraArgs = *(*map[string]string)(unsafe.Pointer(&in.ExtraArgs)) return nil } @@ -451,25 +499,3 @@ func autoConvert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOpt func Convert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions(in *kubeadm.NodeRegistrationOptions, out *NodeRegistrationOptions, s conversion.Scope) error { return autoConvert_kubeadm_NodeRegistrationOptions_To_v1alpha2_NodeRegistrationOptions(in, out, s) } - -func autoConvert_v1alpha2_TokenDiscovery_To_kubeadm_TokenDiscovery(in *TokenDiscovery, out *kubeadm.TokenDiscovery, s conversion.Scope) error { - out.ID = in.ID - out.Secret = in.Secret - return nil -} - -// Convert_v1alpha2_TokenDiscovery_To_kubeadm_TokenDiscovery is an autogenerated conversion function. -func Convert_v1alpha2_TokenDiscovery_To_kubeadm_TokenDiscovery(in *TokenDiscovery, out *kubeadm.TokenDiscovery, s conversion.Scope) error { - return autoConvert_v1alpha2_TokenDiscovery_To_kubeadm_TokenDiscovery(in, out, s) -} - -func autoConvert_kubeadm_TokenDiscovery_To_v1alpha2_TokenDiscovery(in *kubeadm.TokenDiscovery, out *TokenDiscovery, s conversion.Scope) error { - out.ID = in.ID - out.Secret = in.Secret - return nil -} - -// Convert_kubeadm_TokenDiscovery_To_v1alpha2_TokenDiscovery is an autogenerated conversion function. -func Convert_kubeadm_TokenDiscovery_To_v1alpha2_TokenDiscovery(in *kubeadm.TokenDiscovery, out *TokenDiscovery, s conversion.Scope) error { - return autoConvert_kubeadm_TokenDiscovery_To_v1alpha2_TokenDiscovery(in, out, s) -} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.deepcopy.go index 9d5fac577c0..00d78c57efe 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.deepcopy.go @@ -69,6 +69,74 @@ func (in *AuditPolicyConfiguration) DeepCopy() *AuditPolicyConfiguration { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BootstrapToken) DeepCopyInto(out *BootstrapToken) { + *out = *in + if in.Token != nil { + in, out := &in.Token, &out.Token + if *in == nil { + *out = nil + } else { + *out = new(BootstrapTokenString) + **out = **in + } + } + if in.TTL != nil { + in, out := &in.TTL, &out.TTL + if *in == nil { + *out = nil + } else { + *out = new(v1.Duration) + **out = **in + } + } + if in.Expires != nil { + in, out := &in.Expires, &out.Expires + if *in == nil { + *out = nil + } else { + *out = (*in).DeepCopy() + } + } + if in.Usages != nil { + in, out := &in.Usages, &out.Usages + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapToken. +func (in *BootstrapToken) DeepCopy() *BootstrapToken { + if in == nil { + return nil + } + out := new(BootstrapToken) + 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 + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapTokenString. +func (in *BootstrapTokenString) DeepCopy() *BootstrapTokenString { + if in == nil { + return nil + } + out := new(BootstrapTokenString) + 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 @@ -233,25 +301,13 @@ func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { in.KubeletConfiguration.DeepCopyInto(&out.KubeletConfiguration) out.Networking = in.Networking in.NodeRegistration.DeepCopyInto(&out.NodeRegistration) - if in.TokenTTL != nil { - in, out := &in.TokenTTL, &out.TokenTTL - if *in == nil { - *out = nil - } else { - *out = new(v1.Duration) - **out = **in + if in.BootstrapTokens != nil { + in, out := &in.BootstrapTokens, &out.BootstrapTokens + *out = make([]BootstrapToken, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.TokenUsages != nil { - in, out := &in.TokenUsages, &out.TokenUsages - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.TokenGroups != nil { - in, out := &in.TokenGroups, &out.TokenGroups - *out = make([]string, len(*in)) - copy(*out, *in) - } if in.APIServerExtraArgs != nil { in, out := &in.APIServerExtraArgs, &out.APIServerExtraArgs *out = make(map[string]string, len(*in)) @@ -419,19 +475,3 @@ func (in *NodeRegistrationOptions) DeepCopy() *NodeRegistrationOptions { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TokenDiscovery) DeepCopyInto(out *TokenDiscovery) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TokenDiscovery. -func (in *TokenDiscovery) DeepCopy() *TokenDiscovery { - if in == nil { - return nil - } - out := new(TokenDiscovery) - in.DeepCopyInto(out) - return out -} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.defaults.go index 62ab2c8e971..46560609bcb 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/zz_generated.defaults.go @@ -44,6 +44,10 @@ func SetObjectDefaults_MasterConfiguration(in *MasterConfiguration) { v1beta1.SetDefaults_KubeletConfiguration(in.KubeletConfiguration.BaseConfig) } SetDefaults_NodeRegistrationOptions(&in.NodeRegistration) + for i := range in.BootstrapTokens { + a := &in.BootstrapTokens[i] + SetDefaults_BootstrapToken(a) + } } func SetObjectDefaults_NodeConfiguration(in *NodeConfiguration) { diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/BUILD b/cmd/kubeadm/app/apis/kubeadm/validation/BUILD index 12cb119844f..253e2a1c4fb 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/validation/BUILD @@ -10,7 +10,6 @@ go_library( "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/util:go_default_library", - "//cmd/kubeadm/app/util/token:go_default_library", "//pkg/apis/core/validation:go_default_library", "//pkg/kubelet/apis/kubeletconfig:go_default_library", "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", diff --git a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go index c7923fb1480..2c2a23323f8 100644 --- a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go @@ -69,6 +69,74 @@ func (in *AuditPolicyConfiguration) DeepCopy() *AuditPolicyConfiguration { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BootstrapToken) DeepCopyInto(out *BootstrapToken) { + *out = *in + if in.Token != nil { + in, out := &in.Token, &out.Token + if *in == nil { + *out = nil + } else { + *out = new(BootstrapTokenString) + **out = **in + } + } + if in.TTL != nil { + in, out := &in.TTL, &out.TTL + if *in == nil { + *out = nil + } else { + *out = new(v1.Duration) + **out = **in + } + } + if in.Expires != nil { + in, out := &in.Expires, &out.Expires + if *in == nil { + *out = nil + } else { + *out = (*in).DeepCopy() + } + } + if in.Usages != nil { + in, out := &in.Usages, &out.Usages + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapToken. +func (in *BootstrapToken) DeepCopy() *BootstrapToken { + if in == nil { + return nil + } + out := new(BootstrapToken) + 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 + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapTokenString. +func (in *BootstrapTokenString) DeepCopy() *BootstrapTokenString { + if in == nil { + return nil + } + out := new(BootstrapTokenString) + 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 @@ -233,25 +301,13 @@ func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { in.KubeletConfiguration.DeepCopyInto(&out.KubeletConfiguration) out.Networking = in.Networking in.NodeRegistration.DeepCopyInto(&out.NodeRegistration) - if in.TokenTTL != nil { - in, out := &in.TokenTTL, &out.TokenTTL - if *in == nil { - *out = nil - } else { - *out = new(v1.Duration) - **out = **in + if in.BootstrapTokens != nil { + in, out := &in.BootstrapTokens, &out.BootstrapTokens + *out = make([]BootstrapToken, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.TokenUsages != nil { - in, out := &in.TokenUsages, &out.TokenUsages - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.TokenGroups != nil { - in, out := &in.TokenGroups, &out.TokenGroups - *out = make([]string, len(*in)) - copy(*out, *in) - } if in.APIServerExtraArgs != nil { in, out := &in.APIServerExtraArgs, &out.APIServerExtraArgs *out = make(map[string]string, len(*in)) @@ -419,19 +475,3 @@ func (in *NodeRegistrationOptions) DeepCopy() *NodeRegistrationOptions { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TokenDiscovery) DeepCopyInto(out *TokenDiscovery) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TokenDiscovery. -func (in *TokenDiscovery) DeepCopy() *TokenDiscovery { - if in == nil { - return nil - } - out := new(TokenDiscovery) - in.DeepCopyInto(out) - return out -} diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 5ebe1029b11..cd3960edace 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -25,6 +25,7 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library", + "//cmd/kubeadm/app/cmd/options:go_default_library", "//cmd/kubeadm/app/cmd/phases:go_default_library", "//cmd/kubeadm/app/cmd/upgrade:go_default_library", "//cmd/kubeadm/app/cmd/util:go_default_library", @@ -51,8 +52,6 @@ go_library( "//cmd/kubeadm/app/util/config:go_default_library", "//cmd/kubeadm/app/util/dryrun:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", - "//cmd/kubeadm/app/util/token:go_default_library", - "//pkg/apis/core:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/util/initsystem:go_default_library", "//pkg/version:go_default_library", @@ -61,7 +60,6 @@ go_library( "//vendor/github.com/renstrom/dedent:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/duration:go_default_library", @@ -70,6 +68,7 @@ go_library( "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library", + "//vendor/k8s.io/client-go/tools/bootstrap/token/util:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/util/cert:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", @@ -119,6 +118,7 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//cmd/kubeadm/app/cmd/options:all-srcs", "//cmd/kubeadm/app/cmd/phases:all-srcs", "//cmd/kubeadm/app/cmd/upgrade:all-srcs", "//cmd/kubeadm/app/cmd/util:all-srcs", diff --git a/cmd/kubeadm/app/cmd/options/BUILD b/cmd/kubeadm/app/cmd/options/BUILD new file mode 100644 index 00000000000..6c2d59b5c07 --- /dev/null +++ b/cmd/kubeadm/app/cmd/options/BUILD @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["token.go"], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options", + visibility = ["//visibility:public"], + deps = [ + "//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library", + "//cmd/kubeadm/app/constants:go_default_library", + "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/cmd/kubeadm/app/cmd/phases/BUILD b/cmd/kubeadm/app/cmd/phases/BUILD index faa232fb266..4644b86b5e7 100644 --- a/cmd/kubeadm/app/cmd/phases/BUILD +++ b/cmd/kubeadm/app/cmd/phases/BUILD @@ -24,6 +24,7 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library", + "//cmd/kubeadm/app/cmd/options:go_default_library", "//cmd/kubeadm/app/cmd/util:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/features:go_default_library", diff --git a/cmd/kubeadm/app/discovery/token/BUILD b/cmd/kubeadm/app/discovery/token/BUILD index 5ddffb3e440..e5a9bba7797 100644 --- a/cmd/kubeadm/app/discovery/token/BUILD +++ b/cmd/kubeadm/app/discovery/token/BUILD @@ -15,7 +15,6 @@ go_library( "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//cmd/kubeadm/app/util/pubkeypin:go_default_library", - "//cmd/kubeadm/app/util/token:go_default_library", "//pkg/controller/bootstrap:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD b/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD index ea40478a792..4e7dfb68d0e 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD @@ -3,14 +3,6 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["token_test.go"], - embed = [":go_default_library"], - deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], ) go_library( @@ -21,16 +13,13 @@ go_library( ], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node", deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", - "//cmd/kubeadm/app/util/token:go_default_library", "//vendor/github.com/golang/glog:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", - "//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library", "//vendor/k8s.io/client-go/tools/bootstrap/token/util:go_default_library", ], ) diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index 9e04be1a2e6..19057966524 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -74,7 +74,6 @@ filegroup( "//cmd/kubeadm/app/util/kubeconfig:all-srcs", "//cmd/kubeadm/app/util/pubkeypin:all-srcs", "//cmd/kubeadm/app/util/staticpod:all-srcs", - "//cmd/kubeadm/app/util/token:all-srcs", ], tags = ["automanaged"], ) diff --git a/cmd/kubeadm/app/util/config/BUILD b/cmd/kubeadm/app/util/config/BUILD index 2bb14a8ded6..b73f62f00e7 100644 --- a/cmd/kubeadm/app/util/config/BUILD +++ b/cmd/kubeadm/app/util/config/BUILD @@ -22,7 +22,6 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", - "//cmd/kubeadm/app/util/token:go_default_library", "//pkg/util/node:go_default_library", "//pkg/util/version:go_default_library", "//vendor/github.com/golang/glog:go_default_library", @@ -32,6 +31,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/tools/bootstrap/token/util:go_default_library", ], ) diff --git a/cmd/kubeadm/app/util/config/testdata/conversion/master/internal.yaml b/cmd/kubeadm/app/util/config/testdata/conversion/master/internal.yaml index 5267a7bce5c..dc8f00274b0 100644 --- a/cmd/kubeadm/app/util/config/testdata/conversion/master/internal.yaml +++ b/cmd/kubeadm/app/util/config/testdata/conversion/master/internal.yaml @@ -10,6 +10,16 @@ AuditPolicyConfiguration: LogDir: /var/log/kubernetes/audit LogMaxAge: 2 Path: "" +BootstrapTokens: +- Description: "" + Expires: null + Groups: + - system:bootstrappers:kubeadm:default-node-token + TTL: 24h0m0s + Token: s73ybu.6tw6wnqgp5z0wb77 + Usages: + - signing + - authentication CIImageRepository: "" CertificatesDir: /etc/kubernetes/pki ClusterName: kubernetes @@ -148,11 +158,4 @@ NodeRegistration: key: node-role.kubernetes.io/master SchedulerExtraArgs: null SchedulerExtraVolumes: null -Token: s73ybu.6tw6wnqgp5z0wb77 -TokenGroups: -- system:bootstrappers:kubeadm:default-node-token -TokenTTL: 24h0m0s -TokenUsages: -- signing -- authentication UnifiedControlPlaneImage: "" diff --git a/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha2.yaml b/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha2.yaml index b6594689dc8..c83e9bc1471 100644 --- a/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha2.yaml +++ b/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha2.yaml @@ -9,6 +9,14 @@ auditPolicy: logDir: /var/log/kubernetes/audit logMaxAge: 2 path: "" +bootstrapTokens: +- groups: + - system:bootstrappers:kubeadm:default-node-token + token: s73ybu.6tw6wnqgp5z0wb77 + ttl: 24h0m0s + usages: + - signing + - authentication certificatesDir: /etc/kubernetes/pki clusterName: kubernetes etcd: @@ -137,11 +145,4 @@ nodeRegistration: taints: - effect: NoSchedule key: node-role.kubernetes.io/master -token: s73ybu.6tw6wnqgp5z0wb77 -tokenGroups: -- system:bootstrappers:kubeadm:default-node-token -tokenTTL: 24h0m0s -tokenUsages: -- signing -- authentication unifiedControlPlaneImage: "" diff --git a/cmd/kubeadm/app/util/config/testdata/defaulting/master/defaulted.yaml b/cmd/kubeadm/app/util/config/testdata/defaulting/master/defaulted.yaml index 42647648181..693080ec2d6 100644 --- a/cmd/kubeadm/app/util/config/testdata/defaulting/master/defaulted.yaml +++ b/cmd/kubeadm/app/util/config/testdata/defaulting/master/defaulted.yaml @@ -7,6 +7,14 @@ auditPolicy: logDir: /var/log/kubernetes/audit logMaxAge: 2 path: "" +bootstrapTokens: +- groups: + - system:bootstrappers:kubeadm:default-node-token + token: s73ybu.6tw6wnqgp5z0wb77 + ttl: 24h0m0s + usages: + - signing + - authentication certificatesDir: /var/lib/kubernetes/pki clusterName: kubernetes etcd: @@ -132,11 +140,4 @@ nodeRegistration: taints: - effect: NoSchedule key: node-role.kubernetes.io/master -token: s73ybu.6tw6wnqgp5z0wb77 -tokenGroups: -- system:bootstrappers:kubeadm:default-node-token -tokenTTL: 24h0m0s -tokenUsages: -- signing -- authentication unifiedControlPlaneImage: "" diff --git a/cmd/kubeadm/app/util/config/testdata/defaulting/master/incomplete.yaml b/cmd/kubeadm/app/util/config/testdata/defaulting/master/incomplete.yaml index 405046ee5d3..e2d6be82bbc 100644 --- a/cmd/kubeadm/app/util/config/testdata/defaulting/master/incomplete.yaml +++ b/cmd/kubeadm/app/util/config/testdata/defaulting/master/incomplete.yaml @@ -1,3 +1,5 @@ +# This file _should_ set TypeMeta, but at some point earlier we supported deserializing MasterConfigurations without TypeMeta, so we need to support that as long as we +# support the v1alpha1 API. In the meantime kubeadm will treat this as v1alpha1 automatically when unmarshalling. api: advertiseAddress: 192.168.2.2 bindPort: 6443 diff --git a/cmd/kubeadm/app/util/token/BUILD b/cmd/kubeadm/app/util/token/BUILD deleted file mode 100644 index d16eb5018d5..00000000000 --- a/cmd/kubeadm/app/util/token/BUILD +++ /dev/null @@ -1,34 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["tokens_test.go"], - embed = [":go_default_library"], - deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], -) - -go_library( - name = "go_default_library", - srcs = ["tokens.go"], - importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/token", - deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) From d01a7be2acfdf43a3b12d1d4231145e1107c633a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Fri, 1 Jun 2018 17:14:26 +0300 Subject: [PATCH 5/5] fix the verify job --- cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go | 10 +++++----- cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go | 3 ++- .../app/apis/kubeadm/v1alpha2/bootstraptokenstring.go | 3 ++- cmd/kubeadm/app/apis/kubeadm/validation/validation.go | 1 + cmd/kubeadm/app/cmd/options/token.go | 10 +++++++++- cmd/kubeadm/app/util/config/masterconfig.go | 2 +- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go index 3bc0ed5a190..fc18701c616 100644 --- a/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go +++ b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go @@ -84,24 +84,24 @@ func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) { // Get the Token ID field from the Secret data tokenID := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey) if len(tokenID) == 0 { - return nil, fmt.Errorf("Bootstrap Token Secret has no token-id data: %s\n", secret.Name) + return nil, fmt.Errorf("Bootstrap Token Secret has no token-id data: %s", secret.Name) } // Enforce the right naming convention if secret.Name != bootstraputil.BootstrapTokenSecretName(tokenID) { - return nil, fmt.Errorf("bootstrap token name is not of the form '%s(token-id)'. Actual: %q. Expected: %q\n", + return nil, fmt.Errorf("bootstrap token name is not of the form '%s(token-id)'. Actual: %q. Expected: %q", bootstrapapi.BootstrapTokenSecretPrefix, secret.Name, bootstraputil.BootstrapTokenSecretName(tokenID)) } tokenSecret := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey) if len(tokenSecret) == 0 { - return nil, fmt.Errorf("Bootstrap Token Secret has no token-secret data: %s\n", secret.Name) + return nil, fmt.Errorf("Bootstrap Token Secret has no token-secret data: %s", secret.Name) } // Create the BootstrapTokenString object based on the ID and Secret bts, err := NewBootstrapTokenStringFromIDAndSecret(tokenID, tokenSecret) if err != nil { - return nil, fmt.Errorf("Bootstrap Token Secret is invalid and couldn't be parsed: %v\n", err) + return nil, fmt.Errorf("Bootstrap Token Secret is invalid and couldn't be parsed: %v", err) } // Get the description (if any) from the Secret @@ -116,7 +116,7 @@ func BootstrapTokenFromSecret(secret *v1.Secret) (*BootstrapToken, error) { if err != nil { return nil, fmt.Errorf("can't parse expiration time of bootstrap token %q: %v", secret.Name, err) } - expires = &metav1.Time{expTime} + expires = &metav1.Time{Time: expTime} } // Build an usages string slice from the Secret data diff --git a/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go index 8bbd11ebada..440c2534a64 100644 --- a/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go +++ b/cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package kubeadm holds the internal kubeadm API types // Note: This file should be kept in sync with the similar one for the external API // TODO: The BootstrapTokenString object should move out to either k8s.io/client-go or k8s.io/api in the future // (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now. @@ -28,7 +29,7 @@ import ( ) // BootstrapTokenString is a token of the format abcdef.abcdef0123456789 that is used -// for both validation of the authenticy of the API server from a joining node's point +// for both validation of the practically of the API server from a joining node's point // of view and as an authentication method for the node in the bootstrap phase of // "kubeadm join". This token is and should be short-lived type BootstrapTokenString struct { diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring.go index 546b8f898b4..d62d5a7438c 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha2/bootstraptokenstring.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package v1alpha2 holds the external kubeadm API types of version v1alpha2 // Note: This file should be kept in sync with the similar one for the internal API // TODO: The BootstrapTokenString object should move out to either k8s.io/client-go or k8s.io/api in the future // (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now. @@ -28,7 +29,7 @@ import ( ) // BootstrapTokenString is a token of the format abcdef.abcdef0123456789 that is used -// for both validation of the authenticy of the API server from a joining node's point +// for both validation of the practically of the API server from a joining node's point // of view and as an authentication method for the node in the bootstrap phase of // "kubeadm join". This token is and should be short-lived type BootstrapTokenString struct { diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index 53afcaeb4c6..ad542f36f00 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -178,6 +178,7 @@ func ValidateDiscoveryFile(discoveryFile string, fldPath *field.Path) field.Erro return allErrs } +// ValidateBootstrapTokens validates a slice of BootstrapToken objects func ValidateBootstrapTokens(bts []kubeadm.BootstrapToken, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for i, bt := range bts { diff --git a/cmd/kubeadm/app/cmd/options/token.go b/cmd/kubeadm/app/cmd/options/token.go index 204fdb4342c..2b793f14322 100644 --- a/cmd/kubeadm/app/cmd/options/token.go +++ b/cmd/kubeadm/app/cmd/options/token.go @@ -27,6 +27,7 @@ import ( kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) +// NewBootstrapTokenOptions creates a new BootstrapTokenOptions object with the default values func NewBootstrapTokenOptions() *BootstrapTokenOptions { bto := &BootstrapTokenOptions{&kubeadmapiv1alpha2.BootstrapToken{}, ""} kubeadmapiv1alpha2.SetDefaults_BootstrapToken(bto.BootstrapToken) @@ -41,6 +42,7 @@ type BootstrapTokenOptions struct { TokenStr string } +// AddTokenFlag adds the --token flag to the given flagset func (bto *BootstrapTokenOptions) AddTokenFlag(fs *pflag.FlagSet) { fs.StringVar( &bto.TokenStr, "token", "", @@ -48,13 +50,15 @@ func (bto *BootstrapTokenOptions) AddTokenFlag(fs *pflag.FlagSet) { ) } +// AddTTLFlag adds the --token-ttl flag to the given flagset func (bto *BootstrapTokenOptions) AddTTLFlag(fs *pflag.FlagSet) { fs.DurationVar( - &bto.TTL.Duration, "ttl", bto.TTL.Duration, + &bto.TTL.Duration, "token-ttl", bto.TTL.Duration, "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire", ) } +// AddUsagesFlag adds the --usages flag to the given flagset func (bto *BootstrapTokenOptions) AddUsagesFlag(fs *pflag.FlagSet) { fs.StringSliceVar( &bto.Usages, "usages", bto.Usages, @@ -62,6 +66,7 @@ func (bto *BootstrapTokenOptions) AddUsagesFlag(fs *pflag.FlagSet) { ) } +// AddGroupsFlag adds the --groups flag to the given flagset func (bto *BootstrapTokenOptions) AddGroupsFlag(fs *pflag.FlagSet) { fs.StringSliceVar( &bto.Groups, "groups", bto.Groups, @@ -69,6 +74,7 @@ func (bto *BootstrapTokenOptions) AddGroupsFlag(fs *pflag.FlagSet) { ) } +// AddDescriptionFlag adds the --description flag to the given flagset func (bto *BootstrapTokenOptions) AddDescriptionFlag(fs *pflag.FlagSet) { fs.StringVar( &bto.Description, "description", bto.Description, @@ -76,6 +82,8 @@ func (bto *BootstrapTokenOptions) AddDescriptionFlag(fs *pflag.FlagSet) { ) } +// ApplyTo applies the values set internally in the BootstrapTokenOptions object to a MasterConfiguration object at runtime +// If --token was specified in the CLI (as a string), it's parsed and validated before it's added to the BootstrapToken object. func (bto *BootstrapTokenOptions) ApplyTo(cfg *kubeadmapiv1alpha2.MasterConfiguration) error { if len(bto.TokenStr) > 0 { var err error diff --git a/cmd/kubeadm/app/util/config/masterconfig.go b/cmd/kubeadm/app/util/config/masterconfig.go index 1b5bb95e51d..c74f93109ea 100644 --- a/cmd/kubeadm/app/util/config/masterconfig.go +++ b/cmd/kubeadm/app/util/config/masterconfig.go @@ -66,7 +66,7 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error { // Populate the .Token field with a random value if unset // We do this at this layer, and not the API defaulting layer - // because of possible security concerns, and more practially + // because of possible security concerns, and more practically // because we can't return errors in the API object defaulting // process but here we can. for i, bt := range cfg.BootstrapTokens {