mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 04:06:03 +00:00
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:
commit
c7b71ebca9
@ -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",
|
||||
],
|
||||
)
|
||||
|
168
cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go
Normal file
168
cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers.go
Normal 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 ""
|
||||
}
|
473
cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go
Normal file
473
cmd/kubeadm/app/apis/kubeadm/bootstraptokenhelpers_test.go
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
90
cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go
Normal file
90
cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go
Normal 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))
|
||||
}
|
236
cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring_test.go
Normal file
236
cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring_test.go
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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"],
|
||||
)
|
@ -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 {
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"],
|
||||
)
|
||||
|
@ -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))
|
||||
}
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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},
|
||||
}
|
||||
|
106
cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go
generated
106
cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go
generated
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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{
|
||||
|
28
cmd/kubeadm/app/cmd/options/BUILD
Normal file
28
cmd/kubeadm/app/cmd/options/BUILD
Normal 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"],
|
||||
)
|
99
cmd/kubeadm/app/cmd/options/token.go
Normal file
99
cmd/kubeadm/app/cmd/options/token.go
Normal 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
|
||||
}
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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: ""
|
||||
`),
|
||||
},
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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" {
|
||||
|
@ -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"],
|
||||
)
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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: ""
|
||||
|
@ -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: ""
|
||||
|
@ -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: ""
|
||||
|
@ -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
|
||||
|
@ -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"],
|
||||
)
|
@ -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
|
||||
}
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user