Merge pull request #64408 from luxas/kubeadm_refactor_bt

Automatic merge from submit-queue (batch tested with PRs 64057, 63223, 64346, 64562, 64408). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

kubeadm: Refactor the Bootstrap Tokens usage in the API types

**What this PR does / why we need it**:
This PR:
 - Moves some common, generic Bootstrap Token helpers and constants from `k8s.io/kubernetes/cmd/kubeadm/app/util/token` to `k8s.io/client-go/tools/bootstrap/token/`
 - Breaks out the top-level Bootstrap Token fields to a dedicated `BootstrapToken` struct with helper functions.
 - Instead of representing the Bootstrap Token as a plain `string`, there is now a wrapper struct `BootstrapTokenString` that can marshal/unmarshal correctly and supports validation on create, and splitting up the full token in the ID/Secret parts automatically.
 - Makes kubeadm support multiple Bootstrap Tokens automatically by supporting a slice of `BootstrapToken` in the `MasterConfiguration` API object
 - Consolidates the place for kubeadm to create token-related flags in an `options` package
 - Supports automatic conversion from the `v1alpha1` to `v1alpha2` API
 - Adds support to set token expiration directly instead of setting a TTL (Expiration and TTL are mutually exclusive)
 - Removes the old `TokenDiscovery` struct we're not using anymore inside of kubeadm

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Related to https://github.com/kubernetes/community/pull/2131

**Special notes for your reviewer**:
This is work in progress. Please only review the first two commits for now.
I will work on splitting up this PR in smaller chunks.
I will also write unit tests tomorrow.

**Release note**:

```release-note
[action required] kubeadm: The Token-related fields in the `MasterConfiguration` object have now been refactored. Instead of the top-level `.Token`, `.TokenTTL`, `.TokenUsages`, `.TokenGroups` fields, there is now a `BootstrapTokens` slice of `BootstrapToken` objects that support the same features under the `.Token`, `.TTL`, `.Usages`, `.Groups` fields.
```
@kubernetes/sig-cluster-lifecycle-pr-reviews @mattmoyer @liztio
This commit is contained in:
Kubernetes Submit Queue 2018-06-02 02:10:18 -07:00 committed by GitHub
commit c7b71ebca9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 2338 additions and 987 deletions

View File

@ -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",
],
)

View File

@ -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", 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",
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", 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", 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{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 ""
}

View File

@ -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,
)
}
}
}

View File

@ -0,0 +1,90 @@
/*
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 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.
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 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 {
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))
}

View File

@ -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,
)
}
}
}

View File

@ -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"

View File

@ -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"],
)

View File

@ -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 <flagname>=<value>.
@ -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 {

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"],
)

View File

@ -0,0 +1,90 @@
/*
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 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.
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 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 {
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))
}

View File

@ -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,
)
}
}
}

View File

@ -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
}
}

View File

@ -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 <flagname>=<value>.
@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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",

View File

@ -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,30 @@ func ValidateDiscoveryFile(discoveryFile string, fldPath *field.Path) field.Erro
return allErrs
}
// ValidateToken validates token
func ValidateToken(t string, fldPath *field.Path) field.ErrorList {
// 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 {
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
}

View File

@ -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},
}

View File

@ -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
}

View File

@ -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",

View File

@ -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,
})

View File

@ -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{

View File

@ -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"],
)

View File

@ -0,0 +1,99 @@
/*
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"
)
// NewBootstrapTokenOptions creates a new BootstrapTokenOptions object with the default values
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
}
// AddTokenFlag adds the --token flag to the given flagset
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",
)
}
// AddTTLFlag adds the --token-ttl flag to the given flagset
func (bto *BootstrapTokenOptions) AddTTLFlag(fs *pflag.FlagSet) {
fs.DurationVar(
&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,
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, ",")),
)
}
// AddGroupsFlag adds the --groups flag to the given flagset
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),
)
}
// AddDescriptionFlag adds the --description flag to the given flagset
func (bto *BootstrapTokenOptions) AddDescriptionFlag(fs *pflag.FlagSet) {
fs.StringVar(
&bto.Description, "description", bto.Description,
"A human friendly description of how this token is used.",
)
}
// 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
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
}

View File

@ -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",

View File

@ -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
}

View File

@ -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

View File

@ -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 := "<forever>"
expires := "<never>"
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 = "<none>"
}
description := getSecretString(&secret, bootstrapapi.BootstrapTokenDescriptionKey)
if len(description) == 0 {
description = "<none>"
}
groups := getSecretString(&secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
if len(groups) == 0 {
groups = "<none>"
}
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 = "<none>"
}
if val, ok := secret.Data[key]; ok {
return string(val)
ttl := "<forever>"
expires := "<never>"
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 = "<none>"
}
groupsString := strings.Join(token.Groups, ",")
if len(groupsString) == 0 {
groupsString = "<none>"
}
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) {

View File

@ -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"

View File

@ -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: ""
`),
},

View File

@ -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",

View File

@ -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)

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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",
)
}
}
}
}

View File

@ -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 {

View File

@ -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" {

View File

@ -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"],
)

View File

@ -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",
],
)

View File

@ -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 practically
// 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)

View File

@ -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: ""

View File

@ -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: ""

View File

@ -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: ""

View File

@ -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

View File

@ -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"],
)

View File

@ -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
}

View File

@ -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,
)
}
}
}

View File

@ -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"

View File

@ -86,14 +86,26 @@ const (
// authenticate as. The full username given is "system:bootstrap:<token-id>".
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.

View File

@ -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
}

View File

@ -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