Merge pull request #102964 from neolit123/1.22-decouple-bootstraptoken-api

kubeadm: decouple the bootstraptoken API from the kubeadm API
This commit is contained in:
Kubernetes Prow Robot 2021-07-02 07:58:13 -07:00 committed by GitHub
commit ce3bf862ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 492 additions and 1041 deletions

View File

@ -0,0 +1,21 @@
/*
Copyright 2021 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.
*/
// +groupName=bootstraptoken.kubeadm.k8s.io
// Package bootstraptoken contains an API and utilities wrapping the
// "bootstrap.kubernetes.io/token" Secret type to ease its usage in kubeadm.
package bootstraptoken // import "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken"

View File

@ -0,0 +1,58 @@
/*
Copyright 2021 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 v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// BootstrapToken describes one bootstrap token, stored as a Secret in the cluster
// +k8s:deepcopy-gen=true
type BootstrapToken struct {
// Token is used for establishing bidirectional trust between nodes and control-planes.
// Used for joining nodes in the cluster.
Token *BootstrapTokenString `json:"token" datapolicy:"token"`
// Description sets a human-friendly message why this token exists and what it's used
// for, so other administrators can know its purpose.
// +optional
Description string `json:"description,omitempty"`
// TTL defines the time to live for this token. Defaults to 24h.
// Expires and TTL are mutually exclusive.
// +optional
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.
// +optional
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.
// +optional
Usages []string `json:"usages,omitempty"`
// Groups specifies the extra groups that this token will authenticate as when/if
// used for authentication
// +optional
Groups []string `json:"groups,omitempty"`
}
// 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 `json:"-"`
Secret string `json:"-" datapolicy:"token"`
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2018 The Kubernetes Authors.
Copyright 2021 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.
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package kubeadm
package v1
import (
"fmt"
"sort"
"strings"
"time"
@ -30,9 +31,61 @@ import (
bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets"
)
// ToSecret converts the given BootstrapToken object to its Secret representation that
// 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, errors.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))
}
// BootstrapTokenToSecret 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 {
func BootstrapTokenToSecret(bt *BootstrapToken) *v1.Secret {
return &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: bootstraputil.BootstrapTokenSecretName(bt.Token.ID),

View File

@ -1,5 +1,5 @@
/*
Copyright 2018 The Kubernetes Authors.
Copyright 2021 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.
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package kubeadm
package v1
import (
"encoding/json"
@ -22,15 +22,239 @@ import (
"testing"
"time"
"k8s.io/api/core/v1"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
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 {
t.Run(rt.bts.ID, func(t *testing.T) {
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 {
t.Run(rt.input, func(t *testing.T) {
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 {
t.Run(rt.input, func(t *testing.T) {
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 errors.Wrap(err, "expected no unmarshal error, got error")
}
if b, err = json.Marshal(newbts); err != nil {
return errors.Wrap(err, "expected no marshal error, got error")
}
if input != string(b) {
return errors.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 errors.Wrap(err, "expected no marshal error, got error")
}
if err := json.Unmarshal(b, newbts); err != nil {
return errors.Wrap(err, "expected no unmarshal error, got error")
}
if !reflect.DeepEqual(bts, newbts) {
return errors.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 {
t.Run(rt.bts.ID, func(t *testing.T) {
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 {
t.Run(rt.token, func(t *testing.T) {
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 {
t.Run(rt.id, func(t *testing.T) {
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,
)
}
})
}
}
// 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) {
func TestBootstrapTokenToSecret(t *testing.T) {
var tests = []struct {
bt *BootstrapToken
secret *v1.Secret
@ -65,10 +289,10 @@ func TestToSecret(t *testing.T) {
}
for _, rt := range tests {
t.Run(rt.bt.Token.ID, func(t *testing.T) {
actual := rt.bt.ToSecret()
actual := BootstrapTokenToSecret(rt.bt)
if !reflect.DeepEqual(actual, rt.secret) {
t.Errorf(
"failed BootstrapToken.ToSecret():\n\texpected: %v\n\t actual: %v",
"failed BootstrapTokenToSecret():\n\texpected: %v\n\t actual: %v",
rt.secret,
actual,
)
@ -95,7 +319,7 @@ func TestBootstrapTokenToSecretRoundtrip(t *testing.T) {
}
for _, rt := range tests {
t.Run(rt.bt.Token.ID, func(t *testing.T) {
actual, err := BootstrapTokenFromSecret(rt.bt.ToSecret())
actual, err := BootstrapTokenFromSecret(BootstrapTokenToSecret(rt.bt))
if err != nil {
t.Errorf("failed BootstrapToken to Secret roundtrip with error: %v", err)
}

View File

@ -0,0 +1,65 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// 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
*out = new(BootstrapTokenString)
**out = **in
}
if in.TTL != nil {
in, out := &in.TTL, &out.TTL
*out = new(metav1.Duration)
**out = **in
}
if in.Expires != nil {
in, out := &in.Expires, &out.Expires
*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
}

View File

@ -1,92 +0,0 @@
/*
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"
"github.com/pkg/errors"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
bootstraputil "k8s.io/cluster-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, errors.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern)
}
return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil
}
// NewBootstrapTokenStringFromIDAndSecret is a wrapper around NewBootstrapTokenString
// that allows the caller to specify the ID and Secret separately
func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) {
return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret))
}

View File

@ -1,249 +0,0 @@
/*
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"
"github.com/pkg/errors"
)
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 {
t.Run(rt.bts.ID, func(t *testing.T) {
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 {
t.Run(rt.input, func(t *testing.T) {
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 {
t.Run(rt.input, func(t *testing.T) {
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 errors.Wrap(err, "expected no unmarshal error, got error")
}
if b, err = json.Marshal(newbts); err != nil {
return errors.Wrap(err, "expected no marshal error, got error")
}
if input != string(b) {
return errors.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 errors.Wrap(err, "expected no marshal error, got error")
}
if err := json.Unmarshal(b, newbts); err != nil {
return errors.Wrap(err, "expected no unmarshal error, got error")
}
if !reflect.DeepEqual(bts, newbts) {
return errors.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 {
t.Run(rt.bts.ID, func(t *testing.T) {
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 {
t.Run(rt.token, func(t *testing.T) {
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 {
t.Run(rt.id, func(t *testing.T) {
actual, err := NewBootstrapTokenStringFromIDAndSecret(rt.id, rt.secret)
if (err != nil) != rt.expectedError {
t.Errorf(
"failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected error: %t\n\t actual error: %v",
rt.id,
rt.secret,
rt.expectedError,
err,
)
} else if !reflect.DeepEqual(actual, rt.bts) {
t.Errorf(
"failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected: %v\n\t actual: %v",
rt.id,
rt.secret,
rt.bts,
actual,
)
}
})
}
}

View File

@ -20,6 +20,7 @@ import (
fuzz "github.com/google/gofuzz"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
@ -49,7 +50,7 @@ func fuzzInitConfiguration(obj *kubeadm.InitConfiguration, c fuzz.Continue) {
obj.ClusterConfiguration = kubeadm.ClusterConfiguration{}
// Adds the default bootstrap token to get the round trip working
obj.BootstrapTokens = []kubeadm.BootstrapToken{
obj.BootstrapTokens = []bootstraptokenv1.BootstrapToken{
{
Groups: []string{"foo"},
Usages: []string{"foo"},

View File

@ -21,9 +21,9 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/apimachinery/pkg/runtime/schema"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -41,7 +41,7 @@ type InitConfiguration struct {
ClusterConfiguration `json:"-"`
// BootstrapTokens is respected at `kubeadm init` time and describes a set of Bootstrap Tokens to create.
BootstrapTokens []BootstrapToken
BootstrapTokens []bootstraptokenv1.BootstrapToken
// NodeRegistration holds fields that relate to registering the new control-plane node to the cluster
NodeRegistration NodeRegistrationOptions
@ -242,30 +242,6 @@ 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 control-planes.
// Used for joining nodes in the cluster.
Token *BootstrapTokenString
// Description sets a human-friendly message why this token exists and what it's used
// for, so other administrators can know its purpose.
Description string
// TTL defines the time to live for this token. Defaults to 24h.
// Expires and TTL are mutually exclusive.
TTL *metav1.Duration
// Expires specifies the timestamp when this token expires. Defaults to being set
// dynamically at runtime based on the TTL. Expires and TTL are mutually exclusive.
Expires *metav1.Time
// Usages describes the ways in which this token can be used. Can by default be used
// for establishing bidirectional trust, but that can be changed here.
Usages []string
// Groups specifies the extra groups that this token will authenticate as when/if
// used for authentication
Groups []string
}
// Etcd contains elements describing Etcd configuration.
type Etcd struct {

View File

@ -27,6 +27,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
@ -57,16 +58,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*BootstrapToken)(nil), (*kubeadm.BootstrapToken)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta2_BootstrapToken_To_kubeadm_BootstrapToken(a.(*BootstrapToken), b.(*kubeadm.BootstrapToken), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*kubeadm.BootstrapToken)(nil), (*BootstrapToken)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_BootstrapToken_To_v1beta2_BootstrapToken(a.(*kubeadm.BootstrapToken), b.(*BootstrapToken), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*BootstrapTokenDiscovery)(nil), (*kubeadm.BootstrapTokenDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta2_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(a.(*BootstrapTokenDiscovery), b.(*kubeadm.BootstrapTokenDiscovery), scope)
}); err != nil {
@ -77,16 +68,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*BootstrapTokenString)(nil), (*kubeadm.BootstrapTokenString)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(a.(*BootstrapTokenString), b.(*kubeadm.BootstrapTokenString), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*kubeadm.BootstrapTokenString)(nil), (*BootstrapTokenString)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_BootstrapTokenString_To_v1beta2_BootstrapTokenString(a.(*kubeadm.BootstrapTokenString), b.(*BootstrapTokenString), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*kubeadm.ClusterConfiguration)(nil), (*ClusterConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_ClusterConfiguration_To_v1beta2_ClusterConfiguration(a.(*kubeadm.ClusterConfiguration), b.(*ClusterConfiguration), scope)
}); err != nil {
@ -290,36 +271,6 @@ func Convert_kubeadm_APIServer_To_v1beta2_APIServer(in *kubeadm.APIServer, out *
return autoConvert_kubeadm_APIServer_To_v1beta2_APIServer(in, out, s)
}
func autoConvert_v1beta2_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_v1beta2_BootstrapToken_To_kubeadm_BootstrapToken is an autogenerated conversion function.
func Convert_v1beta2_BootstrapToken_To_kubeadm_BootstrapToken(in *BootstrapToken, out *kubeadm.BootstrapToken, s conversion.Scope) error {
return autoConvert_v1beta2_BootstrapToken_To_kubeadm_BootstrapToken(in, out, s)
}
func autoConvert_kubeadm_BootstrapToken_To_v1beta2_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_v1beta2_BootstrapToken is an autogenerated conversion function.
func Convert_kubeadm_BootstrapToken_To_v1beta2_BootstrapToken(in *kubeadm.BootstrapToken, out *BootstrapToken, s conversion.Scope) error {
return autoConvert_kubeadm_BootstrapToken_To_v1beta2_BootstrapToken(in, out, s)
}
func autoConvert_v1beta2_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(in *BootstrapTokenDiscovery, out *kubeadm.BootstrapTokenDiscovery, s conversion.Scope) error {
out.Token = in.Token
out.APIServerEndpoint = in.APIServerEndpoint
@ -346,28 +297,6 @@ func Convert_kubeadm_BootstrapTokenDiscovery_To_v1beta2_BootstrapTokenDiscovery(
return autoConvert_kubeadm_BootstrapTokenDiscovery_To_v1beta2_BootstrapTokenDiscovery(in, out, s)
}
func autoConvert_v1beta2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in *BootstrapTokenString, out *kubeadm.BootstrapTokenString, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
return nil
}
// Convert_v1beta2_BootstrapTokenString_To_kubeadm_BootstrapTokenString is an autogenerated conversion function.
func Convert_v1beta2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in *BootstrapTokenString, out *kubeadm.BootstrapTokenString, s conversion.Scope) error {
return autoConvert_v1beta2_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in, out, s)
}
func autoConvert_kubeadm_BootstrapTokenString_To_v1beta2_BootstrapTokenString(in *kubeadm.BootstrapTokenString, out *BootstrapTokenString, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
return nil
}
// Convert_kubeadm_BootstrapTokenString_To_v1beta2_BootstrapTokenString is an autogenerated conversion function.
func Convert_kubeadm_BootstrapTokenString_To_v1beta2_BootstrapTokenString(in *kubeadm.BootstrapTokenString, out *BootstrapTokenString, s conversion.Scope) error {
return autoConvert_kubeadm_BootstrapTokenString_To_v1beta2_BootstrapTokenString(in, out, s)
}
func autoConvert_v1beta2_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in *ClusterConfiguration, out *kubeadm.ClusterConfiguration, s conversion.Scope) error {
if err := Convert_v1beta2_Etcd_To_kubeadm_Etcd(&in.Etcd, &out.Etcd, s); err != nil {
return err
@ -625,7 +554,7 @@ func Convert_kubeadm_ImageMeta_To_v1beta2_ImageMeta(in *kubeadm.ImageMeta, out *
}
func autoConvert_v1beta2_InitConfiguration_To_kubeadm_InitConfiguration(in *InitConfiguration, out *kubeadm.InitConfiguration, s conversion.Scope) error {
out.BootstrapTokens = *(*[]kubeadm.BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens))
out.BootstrapTokens = *(*[]bootstraptokenv1.BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens))
if err := Convert_v1beta2_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(&in.NodeRegistration, &out.NodeRegistration, s); err != nil {
return err
}

View File

@ -1,88 +0,0 @@
/*
Copyright 2021 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 v1beta3
import (
"fmt"
"strings"
"github.com/pkg/errors"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
bootstraputil "k8s.io/cluster-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 `json:"-"`
Secret string `json:"-" datapolicy:"token"`
}
// 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, errors.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern)
}
return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil
}
// NewBootstrapTokenStringFromIDAndSecret is a wrapper around NewBootstrapTokenString
// that allows the caller to specify the ID and Secret separately
func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) {
return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret))
}

View File

@ -1,249 +0,0 @@
/*
Copyright 2021 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 v1beta3
import (
"encoding/json"
"reflect"
"testing"
"github.com/pkg/errors"
)
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 {
t.Run(rt.bts.ID, func(t *testing.T) {
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 {
t.Run(rt.input, func(t *testing.T) {
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 {
t.Run(rt.input, func(t *testing.T) {
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 errors.Wrap(err, "expected no unmarshal error, got error")
}
if b, err = json.Marshal(newbts); err != nil {
return errors.Wrap(err, "expected no marshal error, got error")
}
if input != string(b) {
return errors.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 errors.Wrap(err, "expected no marshal error, got error")
}
if err := json.Unmarshal(b, newbts); err != nil {
return errors.Wrap(err, "expected no unmarshal error, got error")
}
if !reflect.DeepEqual(bts, newbts) {
return errors.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 {
t.Run(rt.bts.ID, func(t *testing.T) {
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 {
t.Run(rt.token, func(t *testing.T) {
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 {
t.Run(rt.id, func(t *testing.T) {
actual, err := NewBootstrapTokenStringFromIDAndSecret(rt.id, rt.secret)
if (err != nil) != rt.expectedError {
t.Errorf(
"failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected error: %t\n\t actual error: %v",
rt.id,
rt.secret,
rt.expectedError,
err,
)
} else if !reflect.DeepEqual(actual, rt.bts) {
t.Errorf(
"failed NewBootstrapTokenStringFromIDAndSecret for the token with id %q and secret %q\n\texpected: %v\n\t actual: %v",
rt.id,
rt.secret,
rt.bts,
actual,
)
}
})
}
}

View File

@ -22,6 +22,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
@ -174,7 +175,7 @@ func SetDefaults_FileDiscovery(obj *FileDiscovery) {
func SetDefaults_BootstrapTokens(obj *InitConfiguration) {
if obj.BootstrapTokens == nil || len(obj.BootstrapTokens) == 0 {
obj.BootstrapTokens = []BootstrapToken{{}}
obj.BootstrapTokens = []bootstraptokenv1.BootstrapToken{{}}
}
for i := range obj.BootstrapTokens {
@ -183,7 +184,7 @@ func SetDefaults_BootstrapTokens(obj *InitConfiguration) {
}
// SetDefaults_BootstrapToken sets the defaults for an individual Bootstrap Token
func SetDefaults_BootstrapToken(bt *BootstrapToken) {
func SetDefaults_BootstrapToken(bt *bootstraptokenv1.BootstrapToken) {
if bt.TTL == nil {
bt.TTL = &metav1.Duration{
Duration: constants.DefaultTokenDuration,

View File

@ -36,6 +36,8 @@ limitations under the License.
// "IfNotPresent". "IfNotPresent" is the default, which has been the existing behavior prior to this addition.
// - Add "InitConfiguration.Patches.Directory", "JoinConfiguration.Patches.Directory" to allow
// the user to configure a directory from which to take patches for components deployed by kubeadm.
// - Move the BootstrapToken* API and related utilities out of the "kubeadm" API group to a new group
// "bootstraptoken". The kubeadm API version v1beta3 no longer contains the BootstrapToken* structures.
//
// Migration from old kubeadm config versions
//

View File

@ -17,8 +17,9 @@ limitations under the License.
package v1beta3
import (
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -35,7 +36,7 @@ type InitConfiguration struct {
// 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, partly because of its sensitive nature
// +optional
BootstrapTokens []BootstrapToken `json:"bootstrapTokens,omitempty"`
BootstrapTokens []bootstraptokenv1.BootstrapToken `json:"bootstrapTokens,omitempty"`
// NodeRegistration holds fields that relate to registering the new control-plane node to the cluster
// +optional
@ -216,7 +217,7 @@ type NodeRegistrationOptions struct {
// Taints specifies the taints the Node API object should be registered with. If this field is unset, i.e. nil, in the `kubeadm init` process
// it will be defaulted to []v1.Taint{'node-role.kubernetes.io/master=""'}. If you don't want to taint your control-plane node, set this field to an
// empty slice, i.e. `taints: []` in the YAML file. This field is solely used for Node registration.
Taints []v1.Taint `json:"taints"`
Taints []corev1.Taint `json:"taints"`
// KubeletExtraArgs passes through extra arguments to the kubelet. The arguments here are passed to the kubelet command line via the environment file
// kubeadm writes at runtime for the kubelet to source. This overrides the generic base-level configuration in the kubelet-config-1.X ConfigMap
@ -234,7 +235,7 @@ type NodeRegistrationOptions struct {
// The value of this field must be one of "Always", "IfNotPresent" or "Never".
// If this field is unset kubeadm will default it to "IfNotPresent", or pull the required images if not present on the host.
// +optional
ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty"`
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`
}
// Networking contains elements describing cluster's networking configuration
@ -250,33 +251,6 @@ type Networking struct {
DNSDomain string `json:"dnsDomain,omitempty"`
}
// 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 control-planes.
// Used for joining nodes in the cluster.
Token *BootstrapTokenString `json:"token" datapolicy:"token"`
// Description sets a human-friendly message why this token exists and what it's used
// for, so other administrators can know its purpose.
// +optional
Description string `json:"description,omitempty"`
// TTL defines the time to live for this token. Defaults to 24h.
// Expires and TTL are mutually exclusive.
// +optional
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.
// +optional
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.
// +optional
Usages []string `json:"usages,omitempty"`
// Groups specifies the extra groups that this token will authenticate as when/if
// used for authentication
// +optional
Groups []string `json:"groups,omitempty"`
}
// Etcd contains elements describing Etcd configuration.
type Etcd struct {
@ -453,7 +427,7 @@ type HostPathMount struct {
ReadOnly bool `json:"readOnly,omitempty"`
// PathType is the type of the HostPath.
// +optional
PathType v1.HostPathType `json:"pathType,omitempty"`
PathType corev1.HostPathType `json:"pathType,omitempty"`
}
// Patches contains options related to applying patches to components deployed by kubeadm.

View File

@ -27,6 +27,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
@ -57,16 +58,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*BootstrapToken)(nil), (*kubeadm.BootstrapToken)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta3_BootstrapToken_To_kubeadm_BootstrapToken(a.(*BootstrapToken), b.(*kubeadm.BootstrapToken), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*kubeadm.BootstrapToken)(nil), (*BootstrapToken)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_BootstrapToken_To_v1beta3_BootstrapToken(a.(*kubeadm.BootstrapToken), b.(*BootstrapToken), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*BootstrapTokenDiscovery)(nil), (*kubeadm.BootstrapTokenDiscovery)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta3_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(a.(*BootstrapTokenDiscovery), b.(*kubeadm.BootstrapTokenDiscovery), scope)
}); err != nil {
@ -77,16 +68,6 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*BootstrapTokenString)(nil), (*kubeadm.BootstrapTokenString)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta3_BootstrapTokenString_To_kubeadm_BootstrapTokenString(a.(*BootstrapTokenString), b.(*kubeadm.BootstrapTokenString), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*kubeadm.BootstrapTokenString)(nil), (*BootstrapTokenString)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_BootstrapTokenString_To_v1beta3_BootstrapTokenString(a.(*kubeadm.BootstrapTokenString), b.(*BootstrapTokenString), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*kubeadm.ClusterConfiguration)(nil), (*ClusterConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_kubeadm_ClusterConfiguration_To_v1beta3_ClusterConfiguration(a.(*kubeadm.ClusterConfiguration), b.(*ClusterConfiguration), scope)
}); err != nil {
@ -300,36 +281,6 @@ func Convert_kubeadm_APIServer_To_v1beta3_APIServer(in *kubeadm.APIServer, out *
return autoConvert_kubeadm_APIServer_To_v1beta3_APIServer(in, out, s)
}
func autoConvert_v1beta3_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_v1beta3_BootstrapToken_To_kubeadm_BootstrapToken is an autogenerated conversion function.
func Convert_v1beta3_BootstrapToken_To_kubeadm_BootstrapToken(in *BootstrapToken, out *kubeadm.BootstrapToken, s conversion.Scope) error {
return autoConvert_v1beta3_BootstrapToken_To_kubeadm_BootstrapToken(in, out, s)
}
func autoConvert_kubeadm_BootstrapToken_To_v1beta3_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_v1beta3_BootstrapToken is an autogenerated conversion function.
func Convert_kubeadm_BootstrapToken_To_v1beta3_BootstrapToken(in *kubeadm.BootstrapToken, out *BootstrapToken, s conversion.Scope) error {
return autoConvert_kubeadm_BootstrapToken_To_v1beta3_BootstrapToken(in, out, s)
}
func autoConvert_v1beta3_BootstrapTokenDiscovery_To_kubeadm_BootstrapTokenDiscovery(in *BootstrapTokenDiscovery, out *kubeadm.BootstrapTokenDiscovery, s conversion.Scope) error {
out.Token = in.Token
out.APIServerEndpoint = in.APIServerEndpoint
@ -356,28 +307,6 @@ func Convert_kubeadm_BootstrapTokenDiscovery_To_v1beta3_BootstrapTokenDiscovery(
return autoConvert_kubeadm_BootstrapTokenDiscovery_To_v1beta3_BootstrapTokenDiscovery(in, out, s)
}
func autoConvert_v1beta3_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in *BootstrapTokenString, out *kubeadm.BootstrapTokenString, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
return nil
}
// Convert_v1beta3_BootstrapTokenString_To_kubeadm_BootstrapTokenString is an autogenerated conversion function.
func Convert_v1beta3_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in *BootstrapTokenString, out *kubeadm.BootstrapTokenString, s conversion.Scope) error {
return autoConvert_v1beta3_BootstrapTokenString_To_kubeadm_BootstrapTokenString(in, out, s)
}
func autoConvert_kubeadm_BootstrapTokenString_To_v1beta3_BootstrapTokenString(in *kubeadm.BootstrapTokenString, out *BootstrapTokenString, s conversion.Scope) error {
out.ID = in.ID
out.Secret = in.Secret
return nil
}
// Convert_kubeadm_BootstrapTokenString_To_v1beta3_BootstrapTokenString is an autogenerated conversion function.
func Convert_kubeadm_BootstrapTokenString_To_v1beta3_BootstrapTokenString(in *kubeadm.BootstrapTokenString, out *BootstrapTokenString, s conversion.Scope) error {
return autoConvert_kubeadm_BootstrapTokenString_To_v1beta3_BootstrapTokenString(in, out, s)
}
func autoConvert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in *ClusterConfiguration, out *kubeadm.ClusterConfiguration, s conversion.Scope) error {
if err := Convert_v1beta3_Etcd_To_kubeadm_Etcd(&in.Etcd, &out.Etcd, s); err != nil {
return err
@ -628,7 +557,7 @@ func Convert_kubeadm_ImageMeta_To_v1beta3_ImageMeta(in *kubeadm.ImageMeta, out *
}
func autoConvert_v1beta3_InitConfiguration_To_kubeadm_InitConfiguration(in *InitConfiguration, out *kubeadm.InitConfiguration, s conversion.Scope) error {
out.BootstrapTokens = *(*[]kubeadm.BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens))
out.BootstrapTokens = *(*[]bootstraptokenv1.BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens))
if err := Convert_v1beta3_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(&in.NodeRegistration, &out.NodeRegistration, s); err != nil {
return err
}
@ -643,7 +572,7 @@ func autoConvert_v1beta3_InitConfiguration_To_kubeadm_InitConfiguration(in *Init
func autoConvert_kubeadm_InitConfiguration_To_v1beta3_InitConfiguration(in *kubeadm.InitConfiguration, out *InitConfiguration, s conversion.Scope) error {
// WARNING: in.ClusterConfiguration requires manual conversion: does not exist in peer-type
out.BootstrapTokens = *(*[]BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens))
out.BootstrapTokens = *(*[]bootstraptokenv1.BootstrapToken)(unsafe.Pointer(&in.BootstrapTokens))
if err := Convert_kubeadm_NodeRegistrationOptions_To_v1beta3_NodeRegistrationOptions(&in.NodeRegistration, &out.NodeRegistration, s); err != nil {
return err
}

View File

@ -24,6 +24,7 @@ import (
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -69,46 +70,6 @@ func (in *APIServer) DeepCopy() *APIServer {
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
*out = new(BootstrapTokenString)
**out = **in
}
if in.TTL != nil {
in, out := &in.TTL, &out.TTL
*out = new(v1.Duration)
**out = **in
}
if in.Expires != nil {
in, out := &in.Expires, &out.Expires
*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 *BootstrapTokenDiscovery) DeepCopyInto(out *BootstrapTokenDiscovery) {
*out = *in
@ -130,22 +91,6 @@ func (in *BootstrapTokenDiscovery) DeepCopy() *BootstrapTokenDiscovery {
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 *ClusterConfiguration) DeepCopyInto(out *ClusterConfiguration) {
*out = *in
@ -361,7 +306,7 @@ func (in *InitConfiguration) DeepCopyInto(out *InitConfiguration) {
out.TypeMeta = in.TypeMeta
if in.BootstrapTokens != nil {
in, out := &in.BootstrapTokens, &out.BootstrapTokens
*out = make([]BootstrapToken, len(*in))
*out = make([]bootstraptokenv1.BootstrapToken, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}

View File

@ -33,6 +33,7 @@ import (
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
"k8s.io/klog/v2"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
kubeadmcmdoptions "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
@ -203,7 +204,7 @@ func ValidateDiscoveryKubeConfigPath(discoveryFile string, fldPath *field.Path)
}
// ValidateBootstrapTokens validates a slice of BootstrapToken objects
func ValidateBootstrapTokens(bts []kubeadm.BootstrapToken, fldPath *field.Path) field.ErrorList {
func ValidateBootstrapTokens(bts []bootstraptokenv1.BootstrapToken, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for i, bt := range bts {
btPath := fldPath.Child(fmt.Sprintf("%d", i))

View File

@ -24,6 +24,7 @@ import (
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -69,46 +70,6 @@ func (in *APIServer) DeepCopy() *APIServer {
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
*out = new(BootstrapTokenString)
**out = **in
}
if in.TTL != nil {
in, out := &in.TTL, &out.TTL
*out = new(v1.Duration)
**out = **in
}
if in.Expires != nil {
in, out := &in.Expires, &out.Expires
*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 *BootstrapTokenDiscovery) DeepCopyInto(out *BootstrapTokenDiscovery) {
*out = *in
@ -130,22 +91,6 @@ func (in *BootstrapTokenDiscovery) DeepCopy() *BootstrapTokenDiscovery {
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 *ClusterConfiguration) DeepCopyInto(out *ClusterConfiguration) {
*out = *in
@ -391,7 +336,7 @@ func (in *InitConfiguration) DeepCopyInto(out *InitConfiguration) {
in.ClusterConfiguration.DeepCopyInto(&out.ClusterConfiguration)
if in.BootstrapTokens != nil {
in, out := &in.BootstrapTokens, &out.BootstrapTokens
*out = make([]BootstrapToken, len(*in))
*out = make([]bootstraptokenv1.BootstrapToken, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}

View File

@ -23,13 +23,14 @@ import (
"github.com/spf13/pflag"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
// NewBootstrapTokenOptions creates a new BootstrapTokenOptions object with the default values
func NewBootstrapTokenOptions() *BootstrapTokenOptions {
bto := &BootstrapTokenOptions{&kubeadmapiv1.BootstrapToken{}, ""}
bto := &BootstrapTokenOptions{&bootstraptokenv1.BootstrapToken{}, ""}
kubeadmapiv1.SetDefaults_BootstrapToken(bto.BootstrapToken)
return bto
}
@ -38,7 +39,7 @@ func NewBootstrapTokenOptions() *BootstrapTokenOptions {
// and applying the parsed flags to a InitConfiguration 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 {
*kubeadmapiv1.BootstrapToken
*bootstraptokenv1.BootstrapToken
TokenStr string `datapolicy:"token"`
}
@ -92,13 +93,13 @@ func (bto *BootstrapTokenOptions) AddDescriptionFlag(fs *pflag.FlagSet) {
func (bto *BootstrapTokenOptions) ApplyTo(cfg *kubeadmapiv1.InitConfiguration) error {
if len(bto.TokenStr) > 0 {
var err error
bto.Token, err = kubeadmapiv1.NewBootstrapTokenString(bto.TokenStr)
bto.Token, err = bootstraptokenv1.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 = []kubeadmapiv1.BootstrapToken{*bto.BootstrapToken}
cfg.BootstrapTokens = []bootstraptokenv1.BootstrapToken{*bto.BootstrapToken}
return nil
}

View File

@ -38,7 +38,7 @@ import (
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
"k8s.io/klog/v2"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
@ -383,7 +383,7 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface, pr
for _, secret := range secrets.Items {
// Get the BootstrapToken struct representation from the Secret object
token, err := kubeadmapi.BootstrapTokenFromSecret(&secret)
token, err := bootstraptokenv1.BootstrapTokenFromSecret(&secret)
if err != nil {
fmt.Fprintf(errW, "%v", err)
continue
@ -416,7 +416,7 @@ func RunDeleteTokens(out io.Writer, client clientset.Interface, tokenIDsOrTokens
klog.V(1).Info("[token] parsing token")
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 := kubeadmapiv1.NewBootstrapTokenString(tokenIDOrToken)
bts, err := bootstraptokenv1.NewBootstrapTokenString(tokenIDOrToken)
if err != nil {
return errors.Errorf("given token didn't match pattern %q or %q",
bootstrapapi.BootstrapTokenIDPattern, bootstrapapi.BootstrapTokenIDPattern)

View File

@ -31,6 +31,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/clientcmd"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
outputapischeme "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/scheme"
@ -157,13 +158,13 @@ func TestRunCreateToken(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
bts, err := kubeadmapiv1.NewBootstrapTokenString(tc.token)
bts, err := bootstraptokenv1.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 := &kubeadmapiv1.InitConfiguration{
BootstrapTokens: []kubeadmapiv1.BootstrapToken{
BootstrapTokens: []bootstraptokenv1.BootstrapToken{
{
Token: bts,
TTL: &metav1.Duration{Duration: 0},

View File

@ -34,6 +34,7 @@ import (
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
bootstrap "k8s.io/cluster-bootstrap/token/jws"
"k8s.io/klog/v2"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -54,7 +55,7 @@ func RetrieveValidatedConfigInfo(cfg *kubeadmapi.Discovery) (*clientcmdapi.Confi
// retrieveValidatedConfigInfo is a private implementation of RetrieveValidatedConfigInfo.
// It accepts an optional clientset that can be used for testing purposes.
func retrieveValidatedConfigInfo(client clientset.Interface, cfg *kubeadmapi.Discovery, interval time.Duration) (*clientcmdapi.Config, error) {
token, err := kubeadmapi.NewBootstrapTokenString(cfg.BootstrapToken.Token)
token, err := bootstraptokenv1.NewBootstrapTokenString(cfg.BootstrapToken.Token)
if err != nil {
return nil, err
}
@ -151,7 +152,7 @@ func buildSecureBootstrapKubeConfig(endpoint string, caCert []byte, clustername
}
// validateClusterInfoToken validates that the JWS token present in the cluster info ConfigMap is valid
func validateClusterInfoToken(insecureClusterInfo *v1.ConfigMap, token *kubeadmapi.BootstrapTokenString) ([]byte, error) {
func validateClusterInfoToken(insecureClusterInfo *v1.ConfigMap, token *bootstraptokenv1.BootstrapTokenString) ([]byte, error) {
insecureKubeconfigString, ok := insecureClusterInfo.Data[bootstrapapi.KubeConfigKey]
if !ok || len(insecureKubeconfigString) == 0 {
return nil, errors.Errorf("there is no %s key in the %s ConfigMap. This API Server isn't set up for token bootstrapping, can't connect",
@ -194,7 +195,7 @@ func validateClusterCA(insecureConfig *clientcmdapi.Config, pubKeyPins *pubkeypi
// getClusterInfo creates a client from the given kubeconfig if the given client is nil,
// and requests the cluster info ConfigMap using PollImmediate.
// If a client is provided it will be used instead.
func getClusterInfo(client clientset.Interface, kubeconfig *clientcmdapi.Config, token *kubeadmapi.BootstrapTokenString, interval, duration time.Duration) (*v1.ConfigMap, error) {
func getClusterInfo(client clientset.Interface, kubeconfig *clientcmdapi.Config, token *bootstraptokenv1.BootstrapTokenString, interval, duration time.Duration) (*v1.ConfigMap, error) {
var cm *v1.ConfigMap
var err error

View File

@ -18,24 +18,23 @@ package node
import (
"context"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
)
// TODO(mattmoyer): Move CreateNewTokens, UpdateOrCreateTokens out of this package to client-go for a generic abstraction and client for a Bootstrap Token
// 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 {
func CreateNewTokens(client clientset.Interface, tokens []bootstraptokenv1.BootstrapToken) error {
return UpdateOrCreateTokens(client, true, tokens)
}
// 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 {
func UpdateOrCreateTokens(client clientset.Interface, failIfExists bool, tokens []bootstraptokenv1.BootstrapToken) error {
for _, token := range tokens {
@ -45,7 +44,7 @@ func UpdateOrCreateTokens(client clientset.Interface, failIfExists bool, tokens
return errors.Errorf("a token with id %q already exists", token.Token.ID)
}
updatedOrNewSecret := token.ToSecret()
updatedOrNewSecret := bootstraptokenv1.BootstrapTokenToSecret(&token)
// Try to create or update the token with an exponential backoff
err = apiclient.TryRunCommand(func() error {
if err := apiclient.CreateOrUpdateSecret(client, updatedOrNewSecret); err != nil {

View File

@ -37,6 +37,7 @@ import (
keyutil "k8s.io/client-go/util/keyutil"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
"k8s.io/klog/v2"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
nodebootstraptokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
@ -57,11 +58,11 @@ func createShortLivedBootstrapToken(client clientset.Interface) (string, error)
if err != nil {
return "", errors.Wrap(err, "error generating token to upload certs")
}
token, err := kubeadmapi.NewBootstrapTokenString(tokenStr)
token, err := bootstraptokenv1.NewBootstrapTokenString(tokenStr)
if err != nil {
return "", errors.Wrap(err, "error creating upload certs token")
}
tokens := []kubeadmapi.BootstrapToken{{
tokens := []bootstraptokenv1.BootstrapToken{{
Token: token,
Description: "Proxy for managing TTL for the kubeadm-certs secret",
TTL: &metav1.Duration{

View File

@ -31,6 +31,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
netutil "k8s.io/apimachinery/pkg/util/net"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
@ -44,8 +45,8 @@ import (
var (
// PlaceholderToken is only set statically to make kubeadm not randomize the token on every run
PlaceholderToken = kubeadmapiv1.BootstrapToken{
Token: &kubeadmapiv1.BootstrapTokenString{
PlaceholderToken = bootstraptokenv1.BootstrapToken{
Token: &bootstraptokenv1.BootstrapTokenString{
ID: "abcdef",
Secret: "0123456789abcdef",
},
@ -67,7 +68,7 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.InitConfiguration) error {
}
// SetBootstrapTokensDynamicDefaults checks and sets configuration values for the BootstrapTokens object
func SetBootstrapTokensDynamicDefaults(cfg *[]kubeadmapi.BootstrapToken) error {
func SetBootstrapTokensDynamicDefaults(cfg *[]bootstraptokenv1.BootstrapToken) error {
// Populate the .Token field with a random value if unset
// We do this at this layer, and not the API defaulting layer
// because of possible security concerns, and more practically
@ -82,7 +83,7 @@ func SetBootstrapTokensDynamicDefaults(cfg *[]kubeadmapi.BootstrapToken) error {
if err != nil {
return errors.Wrap(err, "couldn't generate random token")
}
token, err := kubeadmapi.NewBootstrapTokenString(tokenStr)
token, err := bootstraptokenv1.NewBootstrapTokenString(tokenStr)
if err != nil {
return err
}
@ -183,7 +184,7 @@ func SetClusterDynamicDefaults(cfg *kubeadmapi.ClusterConfiguration, localAPIEnd
func DefaultedStaticInitConfiguration() (*kubeadmapi.InitConfiguration, error) {
versionedInitCfg := &kubeadmapiv1.InitConfiguration{
LocalAPIEndpoint: kubeadmapiv1.APIEndpoint{AdvertiseAddress: "1.2.3.4"},
BootstrapTokens: []kubeadmapiv1.BootstrapToken{PlaceholderToken},
BootstrapTokens: []bootstraptokenv1.BootstrapToken{PlaceholderToken},
NodeRegistration: kubeadmapiv1.NodeRegistrationOptions{
CRISocket: kubeadmconstants.DefaultDockerCRISocket, // avoid CRI detection
Name: "node",

View File

@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -119,9 +120,9 @@ func TestMarshalUnmarshalToYamlForCodecs(t *testing.T) {
Name: "testNode",
CRISocket: "/var/run/cri.sock",
},
BootstrapTokens: []kubeadmapiv1.BootstrapToken{
BootstrapTokens: []bootstraptokenv1.BootstrapToken{
{
Token: &kubeadmapiv1.BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
Token: &bootstraptokenv1.BootstrapTokenString{ID: "abcdef", Secret: "abcdef0123456789"},
},
},
// NOTE: Using MarshalToYamlForCodecs and UnmarshalFromYamlForCodecs for ClusterConfiguration fields here won't work