mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 21:17:23 +00:00
kubeadm: Initial refactor of the Bootstrap Tokens. Add the new API objects, add/move helpers and start using the new flow in the code
This commit is contained in:
parent
33f59e438e
commit
c473039580
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\n", secret.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce the right naming convention
|
||||||
|
if secret.Name != bootstraputil.BootstrapTokenSecretName(tokenID) {
|
||||||
|
return nil, fmt.Errorf("bootstrap token name is not of the form '%s(token-id)'. Actual: %q. Expected: %q\n",
|
||||||
|
bootstrapapi.BootstrapTokenSecretPrefix, secret.Name, bootstraputil.BootstrapTokenSecretName(tokenID))
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenSecret := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
|
||||||
|
if len(tokenSecret) == 0 {
|
||||||
|
return nil, fmt.Errorf("Bootstrap Token Secret has no token-secret data: %s\n", secret.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the BootstrapTokenString object based on the ID and Secret
|
||||||
|
bts, err := NewBootstrapTokenStringFromIDAndSecret(tokenID, tokenSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Bootstrap Token Secret is invalid and couldn't be parsed: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the description (if any) from the Secret
|
||||||
|
description := getSecretString(secret, bootstrapapi.BootstrapTokenDescriptionKey)
|
||||||
|
|
||||||
|
// Expiration time is optional, if not specified this implies the token
|
||||||
|
// never expires.
|
||||||
|
secretExpiration := getSecretString(secret, bootstrapapi.BootstrapTokenExpirationKey)
|
||||||
|
var expires *metav1.Time
|
||||||
|
if len(secretExpiration) > 0 {
|
||||||
|
expTime, err := time.Parse(time.RFC3339, secretExpiration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't parse expiration time of bootstrap token %q: %v", secret.Name, err)
|
||||||
|
}
|
||||||
|
expires = &metav1.Time{expTime}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build an usages string slice from the Secret data
|
||||||
|
var usages []string
|
||||||
|
for k, v := range secret.Data {
|
||||||
|
// Skip all fields that don't include this prefix
|
||||||
|
if !strings.HasPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Skip those that don't have this usage set to true
|
||||||
|
if string(v) != "true" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
usages = append(usages, strings.TrimPrefix(k, bootstrapapi.BootstrapTokenUsagePrefix))
|
||||||
|
}
|
||||||
|
// Only sort the slice if defined
|
||||||
|
if usages != nil {
|
||||||
|
sort.Strings(usages)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the extra groups information from the Secret
|
||||||
|
// It's done this way to make .Groups be nil in case there is no items, rather than an
|
||||||
|
// empty slice or an empty slice with a "" string only
|
||||||
|
var groups []string
|
||||||
|
groupsString := getSecretString(secret, bootstrapapi.BootstrapTokenExtraGroupsKey)
|
||||||
|
g := strings.Split(groupsString, ",")
|
||||||
|
if len(g) > 0 && len(g[0]) > 0 {
|
||||||
|
groups = g
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BootstrapToken{
|
||||||
|
Token: bts,
|
||||||
|
Description: description,
|
||||||
|
Expires: expires,
|
||||||
|
Usages: usages,
|
||||||
|
Groups: groups,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSecretString returns the string value for the given key in the specified Secret
|
||||||
|
func getSecretString(secret *v1.Secret, key string) string {
|
||||||
|
if secret.Data == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if val, ok := secret.Data[key]; ok {
|
||||||
|
return string(val)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
89
cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go
Normal file
89
cmd/kubeadm/app/apis/kubeadm/bootstraptokenstring.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Note: This file should be kept in sync with the similar one for the external API
|
||||||
|
// TODO: The BootstrapTokenString object should move out to either k8s.io/client-go or k8s.io/api in the future
|
||||||
|
// (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now.
|
||||||
|
package kubeadm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
|
||||||
|
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BootstrapTokenString is a token of the format abcdef.abcdef0123456789 that is used
|
||||||
|
// for both validation of the authenticy of the API server from a joining node's point
|
||||||
|
// of view and as an authentication method for the node in the bootstrap phase of
|
||||||
|
// "kubeadm join". This token is and should be short-lived
|
||||||
|
type BootstrapTokenString struct {
|
||||||
|
ID string
|
||||||
|
Secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (bts BootstrapTokenString) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(fmt.Sprintf(`"%s"`, bts.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaller interface.
|
||||||
|
func (bts *BootstrapTokenString) UnmarshalJSON(b []byte) error {
|
||||||
|
// If the token is represented as "", just return quickly without an error
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove unnecessary " characters coming from the JSON parser
|
||||||
|
token := strings.Replace(string(b), `"`, ``, -1)
|
||||||
|
// Convert the string Token to a BootstrapTokenString object
|
||||||
|
newbts, err := NewBootstrapTokenString(token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bts.ID = newbts.ID
|
||||||
|
bts.Secret = newbts.Secret
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the BootstrapTokenString
|
||||||
|
func (bts BootstrapTokenString) String() string {
|
||||||
|
if len(bts.ID) > 0 && len(bts.Secret) > 0 {
|
||||||
|
return bootstraputil.TokenFromIDAndSecret(bts.ID, bts.Secret)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBootstrapTokenString converts the given Bootstrap Token as a string
|
||||||
|
// to the BootstrapTokenString object used for serialization/deserialization
|
||||||
|
// and internal usage. It also automatically validates that the given token
|
||||||
|
// is of the right format
|
||||||
|
func NewBootstrapTokenString(token string) (*BootstrapTokenString, error) {
|
||||||
|
substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token)
|
||||||
|
// TODO: Add a constant for the 3 value here, and explain better why it's needed (other than because how the regexp parsin works)
|
||||||
|
if len(substrs) != 3 {
|
||||||
|
return nil, fmt.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBootstrapTokenStringFromIDAndSecret is a wrapper around NewBootstrapTokenString
|
||||||
|
// that allows the caller to specify the ID and Secret separately
|
||||||
|
func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) {
|
||||||
|
return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret))
|
||||||
|
}
|
@ -43,10 +43,17 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
|
|||||||
obj.CertificatesDir = "foo"
|
obj.CertificatesDir = "foo"
|
||||||
obj.APIServerCertSANs = []string{"foo"}
|
obj.APIServerCertSANs = []string{"foo"}
|
||||||
|
|
||||||
obj.Token = "foo"
|
obj.BootstrapTokens = []kubeadm.BootstrapToken{
|
||||||
obj.TokenTTL = &metav1.Duration{Duration: 1 * time.Hour}
|
{
|
||||||
obj.TokenUsages = []string{"foo"}
|
Token: &kubeadm.BootstrapTokenString{
|
||||||
obj.TokenGroups = []string{"foo"}
|
ID: "abcdef",
|
||||||
|
Secret: "abcdef0123456789",
|
||||||
|
},
|
||||||
|
TTL: &metav1.Duration{Duration: 1 * time.Hour},
|
||||||
|
Usages: []string{"foo"},
|
||||||
|
Groups: []string{"foo"},
|
||||||
|
},
|
||||||
|
}
|
||||||
obj.ImageRepository = "foo"
|
obj.ImageRepository = "foo"
|
||||||
obj.CIImageRepository = ""
|
obj.CIImageRepository = ""
|
||||||
obj.UnifiedControlPlaneImage = "foo"
|
obj.UnifiedControlPlaneImage = "foo"
|
||||||
|
@ -48,15 +48,9 @@ type MasterConfiguration struct {
|
|||||||
// NodeRegistration holds fields that relate to registering the new master node to the cluster
|
// NodeRegistration holds fields that relate to registering the new master node to the cluster
|
||||||
NodeRegistration NodeRegistrationOptions
|
NodeRegistration NodeRegistrationOptions
|
||||||
|
|
||||||
// Token is used for establishing bidirectional trust between nodes and masters.
|
// BootstrapTokens is respected at `kubeadm init` time and describes a set of Bootstrap Tokens to create.
|
||||||
// Used for joining nodes in the cluster.
|
// This information IS NOT uploaded to the kubeadm cluster configmap, due to its sensitive nature
|
||||||
Token string
|
BootstrapTokens []BootstrapToken
|
||||||
// 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
|
|
||||||
|
|
||||||
// APIServerExtraArgs is a set of extra flags to pass to the API Server or override
|
// APIServerExtraArgs is a set of extra flags to pass to the API Server or override
|
||||||
// default ones in form of <flagname>=<value>.
|
// default ones in form of <flagname>=<value>.
|
||||||
@ -153,18 +147,6 @@ type NodeRegistrationOptions struct {
|
|||||||
ExtraArgs map[string]string
|
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.
|
// Networking contains elements describing cluster's networking configuration.
|
||||||
type Networking struct {
|
type Networking struct {
|
||||||
// ServiceSubnet is the subnet used by k8s services. Defaults to "10.96.0.0/12".
|
// ServiceSubnet is the subnet used by k8s services. Defaults to "10.96.0.0/12".
|
||||||
@ -175,6 +157,30 @@ type Networking struct {
|
|||||||
DNSDomain string
|
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.
|
// Etcd contains elements describing Etcd configuration.
|
||||||
type Etcd struct {
|
type Etcd struct {
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -54,6 +55,9 @@ func Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in *Mas
|
|||||||
UpgradeCloudProvider(in, out)
|
UpgradeCloudProvider(in, out)
|
||||||
UpgradeAuthorizationModes(in, out)
|
UpgradeAuthorizationModes(in, out)
|
||||||
UpgradeNodeRegistrationOptionsForMaster(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 .PrivilegedPods field which was removed in v1alpha2
|
||||||
// We don't support migrating information from the .ImagePullPolicy 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
|
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
|
// UpgradeCloudProvider handles the removal of .CloudProvider as smoothly as possible
|
||||||
func UpgradeCloudProvider(in *MasterConfiguration, out *kubeadm.MasterConfiguration) {
|
func UpgradeCloudProvider(in *MasterConfiguration, out *kubeadm.MasterConfiguration) {
|
||||||
if len(in.CloudProvider) != 0 {
|
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
|
// 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 {
|
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 {
|
if err := autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in, out, s); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -172,9 +179,17 @@ func Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in *kub
|
|||||||
out.CRISocket = in.NodeRegistration.CRISocket
|
out.CRISocket = in.NodeRegistration.CRISocket
|
||||||
out.NoTaintMaster = in.NodeRegistration.Taints != nil && len(in.NodeRegistration.Taints) == 0
|
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
|
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 {
|
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 {
|
if err := autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in, out, s); err != nil {
|
||||||
return err
|
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.
|
// 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.NodeName = in.NodeRegistration.Name
|
||||||
out.CRISocket = in.NodeRegistration.CRISocket
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Note: This file should be kept in sync with the similar one for the internal API
|
||||||
|
// TODO: The BootstrapTokenString object should move out to either k8s.io/client-go or k8s.io/api in the future
|
||||||
|
// (probably as part of Bootstrap Tokens going GA). It should not be staged under the kubeadm API as it is now.
|
||||||
|
package v1alpha2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
|
||||||
|
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BootstrapTokenString is a token of the format abcdef.abcdef0123456789 that is used
|
||||||
|
// for both validation of the authenticy of the API server from a joining node's point
|
||||||
|
// of view and as an authentication method for the node in the bootstrap phase of
|
||||||
|
// "kubeadm join". This token is and should be short-lived
|
||||||
|
type BootstrapTokenString struct {
|
||||||
|
ID string
|
||||||
|
Secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (bts BootstrapTokenString) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(fmt.Sprintf(`"%s"`, bts.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaller interface.
|
||||||
|
func (bts *BootstrapTokenString) UnmarshalJSON(b []byte) error {
|
||||||
|
// If the token is represented as "", just return quickly without an error
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove unnecessary " characters coming from the JSON parser
|
||||||
|
token := strings.Replace(string(b), `"`, ``, -1)
|
||||||
|
// Convert the string Token to a BootstrapTokenString object
|
||||||
|
newbts, err := NewBootstrapTokenString(token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bts.ID = newbts.ID
|
||||||
|
bts.Secret = newbts.Secret
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the BootstrapTokenString
|
||||||
|
func (bts BootstrapTokenString) String() string {
|
||||||
|
if len(bts.ID) > 0 && len(bts.Secret) > 0 {
|
||||||
|
return bootstraputil.TokenFromIDAndSecret(bts.ID, bts.Secret)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBootstrapTokenString converts the given Bootstrap Token as a string
|
||||||
|
// to the BootstrapTokenString object used for serialization/deserialization
|
||||||
|
// and internal usage. It also automatically validates that the given token
|
||||||
|
// is of the right format
|
||||||
|
func NewBootstrapTokenString(token string) (*BootstrapTokenString, error) {
|
||||||
|
substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(token)
|
||||||
|
// TODO: Add a constant for the 3 value here, and explain better why it's needed (other than because how the regexp parsin works)
|
||||||
|
if len(substrs) != 3 {
|
||||||
|
return nil, fmt.Errorf("the bootstrap token %q was not of the form %q", token, bootstrapapi.BootstrapTokenPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BootstrapTokenString{ID: substrs[1], Secret: substrs[2]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBootstrapTokenStringFromIDAndSecret is a wrapper around NewBootstrapTokenString
|
||||||
|
// that allows the caller to specify the ID and Secret separately
|
||||||
|
func NewBootstrapTokenStringFromIDAndSecret(id, secret string) (*BootstrapTokenString, error) {
|
||||||
|
return NewBootstrapTokenString(bootstraputil.TokenFromIDAndSecret(id, secret))
|
||||||
|
}
|
@ -97,20 +97,6 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
|
|||||||
obj.CertificatesDir = DefaultCertificatesDir
|
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 == "" {
|
if obj.ImageRepository == "" {
|
||||||
obj.ImageRepository = DefaultImageRepository
|
obj.ImageRepository = DefaultImageRepository
|
||||||
}
|
}
|
||||||
@ -120,6 +106,7 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetDefaults_NodeRegistrationOptions(&obj.NodeRegistration)
|
SetDefaults_NodeRegistrationOptions(&obj.NodeRegistration)
|
||||||
|
SetDefaults_BootstrapTokens(obj)
|
||||||
SetDefaults_KubeletConfiguration(obj)
|
SetDefaults_KubeletConfiguration(obj)
|
||||||
SetDefaults_Etcd(obj)
|
SetDefaults_Etcd(obj)
|
||||||
SetDefaults_ProxyConfiguration(obj)
|
SetDefaults_ProxyConfiguration(obj)
|
||||||
@ -248,3 +235,35 @@ func SetDefaults_AuditPolicyConfiguration(obj *MasterConfiguration) {
|
|||||||
obj.AuditPolicyConfiguration.LogMaxAge = &DefaultAuditPolicyLogMaxAge
|
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 is the target version of the control plane.
|
||||||
KubernetesVersion string `json:"kubernetesVersion"`
|
KubernetesVersion string `json:"kubernetesVersion"`
|
||||||
|
|
||||||
// Token is used for establishing bidirectional trust between nodes and masters.
|
// BootstrapTokens is respected at `kubeadm init` time and describes a set of Bootstrap Tokens to create.
|
||||||
// Used for joining nodes in the cluster.
|
// This information IS NOT uploaded to the kubeadm cluster configmap, due to its sensitive nature
|
||||||
Token string `json:"token"`
|
BootstrapTokens []BootstrapToken `json:"bootstrapTokens,omitempty"`
|
||||||
// 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"`
|
|
||||||
|
|
||||||
// APIServerExtraArgs is a set of extra flags to pass to the API Server or override
|
// APIServerExtraArgs is a set of extra flags to pass to the API Server or override
|
||||||
// default ones in form of <flagname>=<value>.
|
// default ones in form of <flagname>=<value>.
|
||||||
@ -145,18 +139,6 @@ type NodeRegistrationOptions struct {
|
|||||||
ExtraArgs map[string]string `json:"kubeletExtraArgs,omitempty"`
|
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
|
// Networking contains elements describing cluster's networking configuration
|
||||||
type Networking struct {
|
type Networking struct {
|
||||||
// ServiceSubnet is the subnet used by k8s services. Defaults to "10.96.0.0/12".
|
// 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"`
|
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.
|
// Etcd contains elements describing Etcd configuration.
|
||||||
type Etcd struct {
|
type Etcd struct {
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ import (
|
|||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
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"
|
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
|
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
|
||||||
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
|
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, ValidateCertSANs(c.APIServerCertSANs, field.NewPath("apiServerCertSANs"))...)
|
||||||
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificatesDir"))...)
|
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificatesDir"))...)
|
||||||
allErrs = append(allErrs, ValidateNodeRegistrationOptions(&c.NodeRegistration, field.NewPath("nodeRegistration"))...)
|
allErrs = append(allErrs, ValidateNodeRegistrationOptions(&c.NodeRegistration, field.NewPath("nodeRegistration"))...)
|
||||||
allErrs = append(allErrs, ValidateToken(c.Token, field.NewPath("token"))...)
|
allErrs = append(allErrs, ValidateBootstrapTokens(c.BootstrapTokens, field.NewPath("bootstrapTokens"))...)
|
||||||
allErrs = append(allErrs, ValidateTokenUsages(c.TokenUsages, field.NewPath("tokenUsages"))...)
|
|
||||||
allErrs = append(allErrs, ValidateTokenGroups(c.TokenUsages, c.TokenGroups, field.NewPath("tokenGroups"))...)
|
|
||||||
allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("featureGates"))...)
|
allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("featureGates"))...)
|
||||||
allErrs = append(allErrs, ValidateAPIEndpoint(&c.API, field.NewPath("api"))...)
|
allErrs = append(allErrs, ValidateAPIEndpoint(&c.API, field.NewPath("api"))...)
|
||||||
allErrs = append(allErrs, ValidateProxy(c.KubeProxy.Config, field.NewPath("kubeProxy").Child("config"))...)
|
allErrs = append(allErrs, ValidateProxy(c.KubeProxy.Config, field.NewPath("kubeProxy").Child("config"))...)
|
||||||
@ -181,18 +178,29 @@ func ValidateDiscoveryFile(discoveryFile string, fldPath *field.Path) field.Erro
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateToken validates token
|
func ValidateBootstrapTokens(bts []kubeadm.BootstrapToken, fldPath *field.Path) field.ErrorList {
|
||||||
func ValidateToken(t string, 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{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
id, secret, err := tokenutil.ParseToken(t)
|
if !bootstraputil.IsValidBootstrapToken(token) {
|
||||||
if err != nil {
|
allErrs = append(allErrs, field.Invalid(fldPath, token, "the bootstrap token is invalid"))
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, t, err.Error()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +48,18 @@ const (
|
|||||||
// TODO: Figure out how to get these constants from the API machinery
|
// TODO: Figure out how to get these constants from the API machinery
|
||||||
masterConfig = "MasterConfiguration"
|
masterConfig = "MasterConfiguration"
|
||||||
nodeConfig = "NodeConfiguration"
|
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
|
// NewCmdConfig returns cobra.Command for "kubeadm config" command
|
||||||
func NewCmdConfig(out io.Writer) *cobra.Command {
|
func NewCmdConfig(out io.Writer) *cobra.Command {
|
||||||
@ -123,7 +131,7 @@ func getDefaultAPIObjectBytes(apiObject string) ([]byte, error) {
|
|||||||
if apiObject == masterConfig {
|
if apiObject == masterConfig {
|
||||||
|
|
||||||
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha2.MasterConfiguration{
|
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha2.MasterConfiguration{
|
||||||
Token: sillyToken,
|
BootstrapTokens: []kubeadmapiv1alpha2.BootstrapToken{sillyToken},
|
||||||
})
|
})
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
@ -131,7 +139,7 @@ func getDefaultAPIObjectBytes(apiObject string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
if apiObject == nodeConfig {
|
if apiObject == nodeConfig {
|
||||||
internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha2.NodeConfiguration{
|
internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig("", &kubeadmapiv1alpha2.NodeConfiguration{
|
||||||
Token: sillyToken,
|
Token: sillyToken.Token.String(),
|
||||||
DiscoveryTokenAPIServers: []string{"kube-apiserver:6443"},
|
DiscoveryTokenAPIServers: []string{"kube-apiserver:6443"},
|
||||||
DiscoveryTokenUnsafeSkipCAVerification: true,
|
DiscoveryTokenUnsafeSkipCAVerification: true,
|
||||||
})
|
})
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||||
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
|
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/apis/kubeadm/validation"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
|
||||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
||||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||||
@ -123,6 +124,9 @@ func NewCmdInit(out io.Writer) *cobra.Command {
|
|||||||
var dryRun bool
|
var dryRun bool
|
||||||
var featureGatesString string
|
var featureGatesString string
|
||||||
var ignorePreflightErrors []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{
|
cmd := &cobra.Command{
|
||||||
Use: "init",
|
Use: "init",
|
||||||
@ -142,6 +146,9 @@ func NewCmdInit(out io.Writer) *cobra.Command {
|
|||||||
err = validation.ValidateMixedArguments(cmd.Flags())
|
err = validation.ValidateMixedArguments(cmd.Flags())
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
|
err = bto.ApplyTo(externalcfg)
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
i, err := NewInit(cfgPath, externalcfg, ignorePreflightErrorsSet, skipTokenPrint, dryRun)
|
i, err := NewInit(cfgPath, externalcfg, ignorePreflightErrorsSet, skipTokenPrint, dryRun)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
kubeadmutil.CheckErr(i.Run(out))
|
kubeadmutil.CheckErr(i.Run(out))
|
||||||
@ -150,6 +157,8 @@ func NewCmdInit(out io.Writer) *cobra.Command {
|
|||||||
|
|
||||||
AddInitConfigFlags(cmd.PersistentFlags(), externalcfg, &featureGatesString)
|
AddInitConfigFlags(cmd.PersistentFlags(), externalcfg, &featureGatesString)
|
||||||
AddInitOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &skipTokenPrint, &dryRun, &ignorePreflightErrors)
|
AddInitOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &skipTokenPrint, &dryRun, &ignorePreflightErrors)
|
||||||
|
bto.AddTokenFlag(cmd.PersistentFlags())
|
||||||
|
bto.AddTTLFlag(cmd.PersistentFlags())
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@ -192,21 +201,12 @@ func AddInitConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1alpha2.MasterCon
|
|||||||
&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name,
|
&cfg.NodeRegistration.Name, "node-name", cfg.NodeRegistration.Name,
|
||||||
`Specify the node 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(
|
flagSet.StringVar(
|
||||||
&cfg.NodeRegistration.CRISocket, "cri-socket", cfg.NodeRegistration.CRISocket,
|
&cfg.NodeRegistration.CRISocket, "cri-socket", cfg.NodeRegistration.CRISocket,
|
||||||
`Specify the CRI socket to connect to.`,
|
`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. "+
|
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"))
|
"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
|
// 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
|
// 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 {
|
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
|
// Create the default node bootstrap token
|
||||||
glog.V(1).Infof("[init] creating RBAC rules to generate default 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.UpdateOrCreateTokens(client, false, i.cfg.BootstrapTokens); err != nil {
|
||||||
if err := nodebootstraptokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL.Duration, i.cfg.TokenUsages, i.cfg.TokenGroups, tokenDescription); err != nil {
|
|
||||||
return fmt.Errorf("error updating or creating token: %v", err)
|
return fmt.Errorf("error updating or creating token: %v", err)
|
||||||
}
|
}
|
||||||
// Create RBAC rules that makes the bootstrap tokens able to post CSRs
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the join command
|
// Prints the join command, multiple times in case the user has multiple tokens
|
||||||
joinCommand, err := cmdutil.GetJoinCommand(kubeadmconstants.GetAdminKubeConfigPath(), i.cfg.Token, i.skipTokenPrint)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get join command: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := map[string]string{
|
ctx := map[string]string{
|
||||||
|
91
cmd/kubeadm/app/cmd/options/token.go
Normal file
91
cmd/kubeadm/app/cmd/options/token.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
|
||||||
|
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
|
||||||
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBootstrapTokenOptions() *BootstrapTokenOptions {
|
||||||
|
bto := &BootstrapTokenOptions{&kubeadmapiv1alpha2.BootstrapToken{}, ""}
|
||||||
|
kubeadmapiv1alpha2.SetDefaults_BootstrapToken(bto.BootstrapToken)
|
||||||
|
return bto
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapTokenOptions is a wrapper struct for adding bootstrap token-related flags to a FlagSet
|
||||||
|
// and applying the parsed flags to a MasterConfiguration object later at runtime
|
||||||
|
// TODO: In the future, we might want to group the flags in a better way than adding them all individually like this
|
||||||
|
type BootstrapTokenOptions struct {
|
||||||
|
*kubeadmapiv1alpha2.BootstrapToken
|
||||||
|
TokenStr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bto *BootstrapTokenOptions) AddTokenFlag(fs *pflag.FlagSet) {
|
||||||
|
fs.StringVar(
|
||||||
|
&bto.TokenStr, "token", "",
|
||||||
|
"The token to use for establishing bidirectional trust between nodes and masters. The format is [a-z0-9]{6}\\.[a-z0-9]{16} - e.g. abcdef.0123456789abcdef",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bto *BootstrapTokenOptions) AddTTLFlag(fs *pflag.FlagSet) {
|
||||||
|
fs.DurationVar(
|
||||||
|
&bto.TTL.Duration, "ttl", bto.TTL.Duration,
|
||||||
|
"The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bto *BootstrapTokenOptions) AddUsagesFlag(fs *pflag.FlagSet) {
|
||||||
|
fs.StringSliceVar(
|
||||||
|
&bto.Usages, "usages", bto.Usages,
|
||||||
|
fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s]", strings.Join(kubeadmconstants.DefaultTokenUsages, ",")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bto *BootstrapTokenOptions) AddGroupsFlag(fs *pflag.FlagSet) {
|
||||||
|
fs.StringSliceVar(
|
||||||
|
&bto.Groups, "groups", bto.Groups,
|
||||||
|
fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q", bootstrapapi.BootstrapGroupPattern),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bto *BootstrapTokenOptions) AddDescriptionFlag(fs *pflag.FlagSet) {
|
||||||
|
fs.StringVar(
|
||||||
|
&bto.Description, "description", bto.Description,
|
||||||
|
"A human friendly description of how this token is used.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bto *BootstrapTokenOptions) ApplyTo(cfg *kubeadmapiv1alpha2.MasterConfiguration) error {
|
||||||
|
if len(bto.TokenStr) > 0 {
|
||||||
|
var err error
|
||||||
|
bto.Token, err = kubeadmapiv1alpha2.NewBootstrapTokenString(bto.TokenStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the token specified by the flags as the first and only token to create in case --config is not specified
|
||||||
|
cfg.BootstrapTokens = []kubeadmapiv1alpha2.BootstrapToken{*bto.BootstrapToken}
|
||||||
|
return nil
|
||||||
|
}
|
@ -18,7 +18,6 @@ package phases
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -30,8 +29,8 @@ import (
|
|||||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||||
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
|
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/apis/kubeadm/validation"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
|
||||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
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/clusterinfo"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
|
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||||
@ -111,14 +110,15 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command {
|
|||||||
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
|
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
|
||||||
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
|
// 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
|
// 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
|
// Default values for the cobra help text
|
||||||
kubeadmscheme.Scheme.Default(cfg)
|
kubeadmscheme.Scheme.Default(cfg)
|
||||||
|
|
||||||
var cfgPath, description string
|
var cfgPath string
|
||||||
var skipTokenPrint bool
|
var skipTokenPrint bool
|
||||||
|
bto := options.NewBootstrapTokenOptions()
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "all",
|
Use: "all",
|
||||||
@ -129,11 +129,14 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command {
|
|||||||
err := validation.ValidateMixedArguments(cmd.Flags())
|
err := validation.ValidateMixedArguments(cmd.Flags())
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
|
err = bto.ApplyTo(cfg)
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
|
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
// Creates the bootstap token
|
// Creates the bootstap token
|
||||||
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, skipTokenPrint)
|
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, skipTokenPrint)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
// Create the cluster-info ConfigMap or update if it already exists
|
// 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
|
// 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
|
return cmd
|
||||||
}
|
}
|
||||||
@ -169,14 +177,15 @@ func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command {
|
|||||||
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
|
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
|
||||||
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
|
// 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
|
// 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
|
// Default values for the cobra help text
|
||||||
kubeadmscheme.Scheme.Default(cfg)
|
kubeadmscheme.Scheme.Default(cfg)
|
||||||
|
|
||||||
var cfgPath, description string
|
var cfgPath string
|
||||||
var skipTokenPrint bool
|
var skipTokenPrint bool
|
||||||
|
bto := options.NewBootstrapTokenOptions()
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "create",
|
Use: "create",
|
||||||
@ -186,16 +195,24 @@ func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command {
|
|||||||
err := validation.ValidateMixedArguments(cmd.Flags())
|
err := validation.ValidateMixedArguments(cmd.Flags())
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
|
err = bto.ApplyTo(cfg)
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
|
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, skipTokenPrint)
|
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, skipTokenPrint)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds flags to the 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
|
return cmd
|
||||||
}
|
}
|
||||||
@ -278,38 +295,18 @@ func NewSubCmdNodeBootstrapTokenAutoApprove(kubeConfigFile *string) *cobra.Comma
|
|||||||
return cmd
|
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(
|
flagSet.StringVar(
|
||||||
cfgPath, "config", *cfgPath,
|
cfgPath, "config", *cfgPath,
|
||||||
"Path to kubeadm config file. WARNING: Usage of a configuration file is experimental",
|
"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(
|
flagSet.BoolVar(
|
||||||
skipTokenPrint, "skip-token-print", *skipTokenPrint,
|
skipTokenPrint, "skip-token-print", *skipTokenPrint,
|
||||||
"Skip printing of the bootstrap token",
|
"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
|
// 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)
|
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")
|
glog.V(1).Infoln("[bootstraptoken] creating/updating token")
|
||||||
// Creates or updates the 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.Infoln("[bootstraptoken] bootstrap token created")
|
glog.Infoln("[bootstraptoken] bootstrap token created")
|
||||||
glog.Infoln("[bootstraptoken] you can now join any number of machines by running:")
|
glog.Infoln("[bootstraptoken] you can now join any number of machines by running:")
|
||||||
|
|
||||||
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.Token, skipTokenPrint)
|
if len(internalcfg.BootstrapTokens) > 0 {
|
||||||
if err != nil {
|
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.BootstrapTokens[0].Token.String(), skipTokenPrint)
|
||||||
return fmt.Errorf("failed to get join command: %v", err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get join command: %v", err)
|
||||||
|
}
|
||||||
|
glog.Infoln(joinCommand)
|
||||||
}
|
}
|
||||||
glog.Infoln(joinCommand)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ func NewCmdMarkMaster() *cobra.Command {
|
|||||||
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
|
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
|
||||||
// KubernetesVersion is not used by mark master, but we set this explicitly to avoid
|
// 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
|
// 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
|
// Default values for the cobra help text
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
@ -29,26 +28,24 @@ import (
|
|||||||
"github.com/renstrom/dedent"
|
"github.com/renstrom/dedent"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/util/duration"
|
"k8s.io/apimachinery/pkg/util/duration"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
|
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
|
||||||
|
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||||
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
|
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/apis/kubeadm/validation"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
|
||||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
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"
|
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
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"
|
const defaultKubeConfig = "/etc/kubernetes/admin.conf"
|
||||||
@ -95,14 +92,16 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
|
|||||||
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
|
cfg := &kubeadmapiv1alpha2.MasterConfiguration{
|
||||||
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
|
// 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
|
// 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
|
// Default values for the cobra help text
|
||||||
kubeadmscheme.Scheme.Default(cfg)
|
kubeadmscheme.Scheme.Default(cfg)
|
||||||
|
|
||||||
var cfgPath, description string
|
var cfgPath string
|
||||||
var printJoinCommand bool
|
var printJoinCommand bool
|
||||||
|
bto := options.NewBootstrapTokenOptions()
|
||||||
|
|
||||||
createCmd := &cobra.Command{
|
createCmd := &cobra.Command{
|
||||||
Use: "create [token]",
|
Use: "create [token]",
|
||||||
DisableFlagsInUseLine: true,
|
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.
|
If no [token] is given, kubeadm will generate a random token instead.
|
||||||
`),
|
`),
|
||||||
Run: func(tokenCmd *cobra.Command, args []string) {
|
Run: func(tokenCmd *cobra.Command, args []string) {
|
||||||
if len(args) != 0 {
|
if len(args) > 0 {
|
||||||
cfg.Token = args[0]
|
bto.TokenStr = args[0]
|
||||||
}
|
}
|
||||||
glog.V(1).Infoln("[token] validating mixed arguments")
|
glog.V(1).Infoln("[token] validating mixed arguments")
|
||||||
err := validation.ValidateMixedArguments(tokenCmd.Flags())
|
err := validation.ValidateMixedArguments(tokenCmd.Flags())
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
|
err = bto.ApplyTo(cfg)
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
glog.V(1).Infoln("[token] getting Clientsets from KubeConfig file")
|
glog.V(1).Infoln("[token] getting Clientsets from KubeConfig file")
|
||||||
kubeConfigFile = findExistingKubeConfig(kubeConfigFile)
|
kubeConfigFile = findExistingKubeConfig(kubeConfigFile)
|
||||||
client, err := getClientset(kubeConfigFile, dryRun)
|
client, err := getClientset(kubeConfigFile, dryRun)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
err = RunCreateToken(out, client, cfgPath, cfg, description, printJoinCommand, kubeConfigFile)
|
err = RunCreateToken(out, client, cfgPath, cfg, printJoinCommand, kubeConfigFile)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
createCmd.Flags().StringVar(&cfgPath,
|
createCmd.Flags().StringVar(&cfgPath,
|
||||||
"config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
|
"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,
|
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.")
|
"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))
|
tokenCmd.AddCommand(NewCmdTokenGenerate(out))
|
||||||
|
|
||||||
listCmd := &cobra.Command{
|
listCmd := &cobra.Command{
|
||||||
@ -178,7 +175,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
|
|||||||
`),
|
`),
|
||||||
Run: func(tokenCmd *cobra.Command, args []string) {
|
Run: func(tokenCmd *cobra.Command, args []string) {
|
||||||
if len(args) < 1 {
|
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)
|
kubeConfigFile = findExistingKubeConfig(kubeConfigFile)
|
||||||
client, err := getClientset(kubeConfigFile, dryRun)
|
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.
|
// 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
|
// 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")
|
glog.V(1).Infoln("[token] loading configurations")
|
||||||
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
|
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")
|
glog.V(1).Infoln("[token] creating token")
|
||||||
err = tokenphase.CreateNewToken(client, internalcfg.Token, internalcfg.TokenTTL.Duration, internalcfg.TokenUsages, internalcfg.TokenGroups, description)
|
if err := tokenphase.CreateNewTokens(client, internalcfg.BootstrapTokens); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if --print-join-command was specified, print the full `kubeadm join` command
|
// if --print-join-command was specified, print the full `kubeadm join` command
|
||||||
// otherwise, just print the token
|
// otherwise, just print the token
|
||||||
if printJoinCommand {
|
if printJoinCommand {
|
||||||
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.Token, false)
|
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.BootstrapTokens[0].Token.String(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get join command: %v", err)
|
return fmt.Errorf("failed to get join command: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Fprintln(out, joinCommand)
|
fmt.Fprintln(out, joinCommand)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(out, internalcfg.Token)
|
fmt.Fprintln(out, internalcfg.BootstrapTokens[0].Token.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
// RunGenerateToken just generates a random token for the user
|
||||||
func RunGenerateToken(out io.Writer) error {
|
func RunGenerateToken(out io.Writer) error {
|
||||||
glog.V(1).Infoln("[token] generating randodm token")
|
glog.V(1).Infoln("[token] generating random token")
|
||||||
token, err := tokenutil.GenerateToken()
|
token, err := bootstraputil.GenerateBootstrapToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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")
|
glog.V(1).Infoln("[token] preparing selector for bootstrap token")
|
||||||
tokenSelector := fields.SelectorFromSet(
|
tokenSelector := fields.SelectorFromSet(
|
||||||
map[string]string{
|
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{
|
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)
|
w := tabwriter.NewWriter(out, 10, 4, 3, ' ', 0)
|
||||||
fmt.Fprintln(w, "TOKEN\tTTL\tEXPIRES\tUSAGES\tDESCRIPTION\tEXTRA GROUPS")
|
fmt.Fprintln(w, "TOKEN\tTTL\tEXPIRES\tUSAGES\tDESCRIPTION\tEXTRA GROUPS")
|
||||||
for _, secret := range secrets.Items {
|
for _, secret := range secrets.Items {
|
||||||
tokenID := getSecretString(&secret, bootstrapapi.BootstrapTokenIDKey)
|
|
||||||
if len(tokenID) == 0 {
|
// Get the BootstrapToken struct representation from the Secret object
|
||||||
fmt.Fprintf(errW, "bootstrap token has no token-id data: %s\n", secret.Name)
|
token, err := kubeadmapi.BootstrapTokenFromSecret(&secret)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(errW, "%v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// enforce the right naming convention
|
// Get the human-friendly string representation for the token
|
||||||
if secret.Name != fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID) {
|
humanFriendlyTokenOutput := humanReadableBootstrapToken(token)
|
||||||
fmt.Fprintf(errW, "bootstrap token name is not of the form '%s(token-id)': %s\n", bootstrapapi.BootstrapTokenSecretPrefix, secret.Name)
|
fmt.Fprintln(w, humanFriendlyTokenOutput)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
w.Flush()
|
w.Flush()
|
||||||
return nil
|
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
|
// Assume the given first argument is a token id and try to parse it
|
||||||
tokenID := tokenIDOrToken
|
tokenID := tokenIDOrToken
|
||||||
glog.V(1).Infoln("[token] parsing token ID")
|
glog.V(1).Infoln("[token] parsing token ID")
|
||||||
if err := tokenutil.ParseTokenID(tokenIDOrToken); err != nil {
|
if !bootstraputil.IsValidBootstrapTokenID(tokenIDOrToken) {
|
||||||
if tokenID, _, err = tokenutil.ParseToken(tokenIDOrToken); err != nil {
|
// Okay, the full token with both id and secret was probably passed. Parse it and extract the ID only
|
||||||
return fmt.Errorf("given token or token id %q didn't match pattern [%q] or [%q]", tokenIDOrToken, tokenutil.TokenIDRegexpString, tokenutil.TokenRegexpString)
|
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")
|
glog.V(1).Infoln("[token] deleting token")
|
||||||
if err := client.CoreV1().Secrets(metav1.NamespaceSystem).Delete(tokenSecretName, nil); err != nil {
|
if err := client.CoreV1().Secrets(metav1.NamespaceSystem).Delete(tokenSecretName, nil); err != nil {
|
||||||
return fmt.Errorf("failed to delete bootstrap token [%v]", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSecretString(secret *v1.Secret, key string) string {
|
func humanReadableBootstrapToken(token *kubeadmapi.BootstrapToken) string {
|
||||||
if secret.Data == nil {
|
description := token.Description
|
||||||
return ""
|
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) {
|
func getClientset(file string, dryRun bool) (clientset.Interface, error) {
|
||||||
|
@ -34,7 +34,6 @@ import (
|
|||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin"
|
"k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin"
|
||||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
|
||||||
"k8s.io/kubernetes/pkg/controller/bootstrap"
|
"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)
|
// 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
|
// validating the cluster CA against a set of pinned public keys
|
||||||
func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Cluster, error) {
|
func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Cluster, error) {
|
||||||
tokenID, tokenSecret, err := tokenutil.ParseToken(cfg.DiscoveryToken)
|
token, err := kubeadmapi.NewBootstrapTokenString(cfg.DiscoveryToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -88,11 +87,11 @@ func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmd
|
|||||||
if !ok || len(insecureKubeconfigString) == 0 {
|
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)
|
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 {
|
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")
|
return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object, can't trust this API Server")
|
||||||
}
|
}
|
||||||
insecureKubeconfigBytes := []byte(insecureKubeconfigString)
|
insecureKubeconfigBytes := []byte(insecureKubeconfigString)
|
||||||
|
@ -18,109 +18,43 @@ package node
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
|
|
||||||
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
|
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
|
// 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 {
|
||||||
// CreateNewToken tries to create a token and fails if one with the same ID already exists
|
return UpdateOrCreateTokens(client, true, tokens)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateOrCreateToken attempts to update a token with the given ID, or create if it does not already exist.
|
// UpdateOrCreateTokens 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 {
|
func UpdateOrCreateTokens(client clientset.Interface, failIfExists bool, tokens []kubeadmapi.BootstrapToken) error {
|
||||||
tokenID, tokenSecret, err := tokenutil.ParseToken(token)
|
|
||||||
if err != nil {
|
for _, token := range tokens {
|
||||||
return err
|
|
||||||
}
|
secretName := bootstraputil.BootstrapTokenSecretName(token.Token.ID)
|
||||||
secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID)
|
|
||||||
var lastErr error
|
|
||||||
for i := 0; i < tokenCreateRetries; i++ {
|
|
||||||
secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
||||||
if err == nil {
|
if secret != nil && err == nil && failIfExists {
|
||||||
if failIfExists {
|
return fmt.Errorf("a token with id %q already exists", token.Token.ID)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret does not already exist:
|
updatedOrNewSecret := token.ToSecret()
|
||||||
if apierrors.IsNotFound(err) {
|
// Try to create or update the token with an exponential backoff
|
||||||
tokenSecretData, err := encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description)
|
err = apiclient.TryRunCommand(func() error {
|
||||||
if err != nil {
|
if err := apiclient.CreateOrUpdateSecret(client, updatedOrNewSecret); err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to create or update bootstrap token with name %s: %v", secretName, err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
secret = &v1.Secret{
|
}, 5)
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
if err != nil {
|
||||||
Name: secretName,
|
return err
|
||||||
},
|
|
||||||
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken),
|
|
||||||
Data: tokenSecretData,
|
|
||||||
}
|
|
||||||
if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return fmt.Errorf(
|
return nil
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ func UploadConfiguration(cfg *kubeadmapi.MasterConfiguration, client clientset.I
|
|||||||
kubeadmscheme.Scheme.Convert(cfg, externalcfg, nil)
|
kubeadmscheme.Scheme.Convert(cfg, externalcfg, nil)
|
||||||
|
|
||||||
// Removes sensitive info from the data that will be stored in the config map
|
// 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)
|
cfgYaml, err := util.MarshalToYamlForCodecs(externalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, scheme.Codecs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
netutil "k8s.io/apimachinery/pkg/util/net"
|
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"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||||
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
@ -33,7 +34,6 @@ import (
|
|||||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
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/node"
|
||||||
"k8s.io/kubernetes/pkg/util/version"
|
"k8s.io/kubernetes/pkg/util/version"
|
||||||
)
|
)
|
||||||
@ -60,17 +60,29 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
|||||||
cfg.KubeProxy.Config.BindAddress = kubeadmapiv1alpha2.DefaultProxyBindAddressv6
|
cfg.KubeProxy.Config.BindAddress = kubeadmapiv1alpha2.DefaultProxyBindAddressv6
|
||||||
}
|
}
|
||||||
// Resolve possible version labels and validate version string
|
// Resolve possible version labels and validate version string
|
||||||
err = NormalizeKubernetesVersion(cfg)
|
if err := NormalizeKubernetesVersion(cfg); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Token == "" {
|
// Populate the .Token field with a random value if unset
|
||||||
var err error
|
// We do this at this layer, and not the API defaulting layer
|
||||||
cfg.Token, err = tokenutil.GenerateToken()
|
// because of possible security concerns, and more practially
|
||||||
|
// because we can't return errors in the API object defaulting
|
||||||
|
// process but here we can.
|
||||||
|
for i, bt := range cfg.BootstrapTokens {
|
||||||
|
if bt.Token != nil && len(bt.Token.String()) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenStr, err := bootstraputil.GenerateBootstrapToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("couldn't generate random token: %v", err)
|
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)
|
cfg.NodeRegistration.Name = node.GetHostname(cfg.NodeRegistration.Name)
|
||||||
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user