kubeadm create token using config file

pull/6/head
fabriziopandini 2018-02-25 11:02:04 +01:00
parent 46dc023f93
commit e247752ef9
11 changed files with 169 additions and 81 deletions

View File

@ -36,7 +36,6 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
c.FuzzNoCustom(obj)
obj.KubernetesVersion = "v10"
obj.API.BindPort = 20
obj.TokenTTL = &metav1.Duration{Duration: 1 * time.Hour}
obj.API.AdvertiseAddress = "foo"
obj.Networking.ServiceSubnet = "foo"
obj.Networking.DNSDomain = "foo"
@ -47,6 +46,9 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
obj.Etcd.PeerCertSANs = []string{"foo"}
obj.Token = "foo"
obj.CRISocket = "foo"
obj.TokenTTL = &metav1.Duration{Duration: 1 * time.Hour}
obj.TokenUsages = []string{"foo"}
obj.TokenGroups = []string{"foo"}
obj.Etcd.Image = "foo"
obj.Etcd.DataDir = "foo"
obj.ImageRepository = "foo"

View File

@ -64,8 +64,12 @@ type MasterConfiguration struct {
// Token is used for establishing bidirectional trust between nodes and masters.
// Used for joining nodes in the cluster.
Token string
// TokenTTL is a ttl for Token. Defaults to 24h.
// 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
// CRISocket is used to retrieve container runtime info.
CRISocket string

View File

@ -116,6 +116,14 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) {
obj.CRISocket = DefaultCRISocket
}
if len(obj.TokenUsages) == 0 {
obj.TokenUsages = constants.DefaultTokenUsages
}
if len(obj.TokenGroups) == 0 {
obj.TokenGroups = constants.DefaultTokenGroups
}
if obj.ImageRepository == "" {
obj.ImageRepository = DefaultImageRepository
}

View File

@ -64,8 +64,12 @@ type MasterConfiguration struct {
// Token is used for establishing bidirectional trust between nodes and masters.
// Used for joining nodes in the cluster.
Token string `json:"token"`
// TokenTTL is a ttl for Token. Defaults to 24h.
// 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"`
// CRISocket is used to retrieve container runtime info.
CRISocket string `json:"criSocket,omitempty"`

View File

@ -29,6 +29,8 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
@ -78,6 +80,8 @@ func ValidateMasterConfiguration(c *kubeadm.MasterConfiguration) field.ErrorList
allErrs = append(allErrs, ValidateAbsolutePath(c.CertificatesDir, field.NewPath("certificates-dir"))...)
allErrs = append(allErrs, ValidateNodeName(c.NodeName, field.NewPath("node-name"))...)
allErrs = append(allErrs, ValidateToken(c.Token, field.NewPath("token"))...)
allErrs = append(allErrs, ValidateTokenUsages(c.TokenUsages, field.NewPath("tokenUsages"))...)
allErrs = append(allErrs, ValidateTokenGroups(c.TokenUsages, c.TokenGroups, field.NewPath("tokenGroups"))...)
allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("feature-gates"))...)
allErrs = append(allErrs, ValidateAPIEndpoint(c, field.NewPath("api-endpoint"))...)
allErrs = append(allErrs, ValidateProxy(c, field.NewPath("kube-proxy"))...)
@ -230,6 +234,39 @@ func ValidateToken(t string, fldPath *field.Path) field.ErrorList {
return allErrs
}
// ValidateTokenGroups validates token groups
func ValidateTokenGroups(usages []string, groups []string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// adding groups only makes sense for authentication
usagesSet := sets.NewString(usages...)
usageAuthentication := strings.TrimPrefix(bootstrapapi.BootstrapTokenUsageAuthentication, bootstrapapi.BootstrapTokenUsagePrefix)
if len(groups) > 0 && !usagesSet.Has(usageAuthentication) {
allErrs = append(allErrs, field.Invalid(fldPath, groups, fmt.Sprintf("token groups cannot be specified unless --usages includes %q", usageAuthentication)))
}
// validate any extra group names
for _, group := range groups {
if err := bootstraputil.ValidateBootstrapGroupName(group); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, groups, err.Error()))
}
}
return allErrs
}
// ValidateTokenUsages validates token usages
func ValidateTokenUsages(usages []string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// validate usages
if err := bootstraputil.ValidateUsages(usages); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, usages, err.Error()))
}
return allErrs
}
// ValidateCertSANs validates alternative names
func ValidateCertSANs(altnames []string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

View File

@ -59,6 +59,51 @@ func TestValidateTokenDiscovery(t *testing.T) {
}
}
func TestValidateValidateTokenUsages(t *testing.T) {
var tests = []struct {
u []string
f *field.Path
expected bool
}{
{[]string{}, nil, true}, // supported (no usages)
{[]string{"signing", "authentication"}, nil, true}, // supported
{[]string{"something else"}, nil, false}, // usage not supported
}
for _, rt := range tests {
actual := ValidateTokenUsages(rt.u, rt.f)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"failed ValidateTokenUsages:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
)
}
}
}
func TestValidateTokenGroups(t *testing.T) {
var tests = []struct {
u []string
g []string
f *field.Path
expected bool
}{
{[]string{"some usage"}, []string{"some group"}, nil, false}, // groups doesn't makes sense if usage authentication
{[]string{"authentication"}, []string{"some group"}, nil, false}, // group not supported
{[]string{"authentication"}, []string{"system:bootstrappers:anygroup"}, nil, true}, // supported
}
for _, rt := range tests {
actual := ValidateTokenGroups(rt.u, rt.g, rt.f)
if (len(actual) == 0) != rt.expected {
t.Errorf(
"failed ValidateTokenGroups:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
)
}
}
}
func TestValidateAuthorizationModes(t *testing.T) {
var tests = []struct {
s []string

View File

@ -413,7 +413,7 @@ func (i *Init) Run(out io.Writer) error {
// Create the default node bootstrap token
tokenDescription := "The default bootstrap token generated by 'kubeadm init'."
if err := nodebootstraptokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL.Duration, kubeadmconstants.DefaultTokenUsages, []string{kubeadmconstants.NodeBootstrapTokenAuthGroup}, tokenDescription); 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)
}
// Create RBAC rules that makes the bootstrap tokens able to post CSRs

View File

@ -24,10 +24,8 @@ import (
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
@ -119,7 +117,6 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command {
legacyscheme.Scheme.Default(cfg)
var cfgPath, description string
var usages, extraGroups []string
var skipTokenPrint bool
cmd := &cobra.Command{
@ -135,7 +132,7 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command {
kubeadmutil.CheckErr(err)
// Creates the bootstap token
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, usages, extraGroups, skipTokenPrint)
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, skipTokenPrint)
kubeadmutil.CheckErr(err)
// Create the cluster-info ConfigMap or update if it already exists
@ -161,7 +158,7 @@ func NewSubCmdBootstrapTokenAll(kubeConfigFile *string) *cobra.Command {
}
// Adds flags to the command
addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &usages, &extraGroups, &skipTokenPrint)
addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &skipTokenPrint)
return cmd
}
@ -178,7 +175,6 @@ func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command {
legacyscheme.Scheme.Default(cfg)
var cfgPath, description string
var usages, extraGroups []string
var skipTokenPrint bool
cmd := &cobra.Command{
@ -192,13 +188,13 @@ func NewSubCmdBootstrapToken(kubeConfigFile *string) *cobra.Command {
client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile)
kubeadmutil.CheckErr(err)
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, usages, extraGroups, skipTokenPrint)
err = createBootstrapToken(*kubeConfigFile, client, cfgPath, cfg, description, skipTokenPrint)
kubeadmutil.CheckErr(err)
},
}
// Adds flags to the command
addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &usages, &extraGroups, &skipTokenPrint)
addBootstrapTokenFlags(cmd.Flags(), cfg, &cfgPath, &description, &skipTokenPrint)
return cmd
}
@ -281,7 +277,7 @@ func NewSubCmdNodeBootstrapTokenAutoApprove(kubeConfigFile *string) *cobra.Comma
return cmd
}
func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterConfiguration, cfgPath, description *string, usages, extraGroups *[]string, skipTokenPrint *bool) {
func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterConfiguration, cfgPath, description *string, skipTokenPrint *bool) {
flagSet.StringVar(
cfgPath, "config", *cfgPath,
"Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)",
@ -291,15 +287,15 @@ func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterCon
"The token to use for establishing bidirectional trust between nodes and masters",
)
flagSet.DurationVar(
&cfg.TokenTTL.Duration, "ttl", kubeadmconstants.DefaultTokenDuration,
&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(
usages, "usages", kubeadmconstants.DefaultTokenUsages,
&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(
extraGroups, "groups", []string{kubeadmconstants.NodeBootstrapTokenAuthGroup},
&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(
@ -312,27 +308,16 @@ func addBootstrapTokenFlags(flagSet *pflag.FlagSet, cfg *kubeadmapiext.MasterCon
)
}
func createBootstrapToken(kubeConfigFile string, client clientset.Interface, cfgPath string, cfg *kubeadmapiext.MasterConfiguration, description string, usages, extraGroups []string, skipTokenPrint bool) error {
// adding groups only makes sense for authentication
usagesSet := sets.NewString(usages...)
usageAuthentication := strings.TrimPrefix(bootstrapapi.BootstrapTokenUsageAuthentication, bootstrapapi.BootstrapTokenUsagePrefix)
if len(extraGroups) > 0 && !usagesSet.Has(usageAuthentication) {
return fmt.Errorf("--groups cannot be specified unless --usages includes %q", usageAuthentication)
}
// validate any extra group names
for _, group := range extraGroups {
if err := bootstraputil.ValidateBootstrapGroupName(group); err != nil {
return err
}
}
func createBootstrapToken(kubeConfigFile string, client clientset.Interface, cfgPath string, cfg *kubeadmapiext.MasterConfiguration, description string, skipTokenPrint bool) error {
// This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
kubeadmutil.CheckErr(err)
if err != nil {
return err
}
// Creates or updates the token
if err := node.UpdateOrCreateToken(client, internalcfg.Token, false, internalcfg.TokenTTL.Duration, usages, extraGroups, description); err != nil {
if err := node.UpdateOrCreateToken(client, internalcfg.Token, false, internalcfg.TokenTTL.Duration, internalcfg.TokenUsages, internalcfg.TokenGroups, description); err != nil {
return err
}

View File

@ -32,18 +32,20 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
)
@ -86,10 +88,16 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
tokenCmd.PersistentFlags().BoolVar(&dryRun,
"dry-run", dryRun, "Whether to enable dry-run mode or not")
var usages []string
var extraGroups []string
var tokenDuration time.Duration
var description string
cfg := &kubeadmapiext.MasterConfiguration{
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
// the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig
KubernetesVersion: "v1.9.0",
}
// Default values for the cobra help text
legacyscheme.Scheme.Default(cfg)
var cfgPath, description string
var printJoinCommand bool
createCmd := &cobra.Command{
Use: "create [token]",
@ -104,23 +112,28 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
If no [token] is given, kubeadm will generate a random token instead.
`),
Run: func(tokenCmd *cobra.Command, args []string) {
token := ""
if len(args) != 0 {
token = args[0]
cfg.Token = args[0]
}
err := validation.ValidateMixedArguments(tokenCmd.Flags())
kubeadmutil.CheckErr(err)
client, err := getClientset(kubeConfigFile, dryRun)
kubeadmutil.CheckErr(err)
err = RunCreateToken(out, client, token, tokenDuration, usages, extraGroups, description, printJoinCommand, kubeConfigFile)
err = RunCreateToken(out, client, cfgPath, cfg, description, printJoinCommand, kubeConfigFile)
kubeadmutil.CheckErr(err)
},
}
createCmd.Flags().DurationVar(&tokenDuration,
"ttl", kubeadmconstants.DefaultTokenDuration, "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(&usages,
"usages", kubeadmconstants.DefaultTokenUsages, 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(&extraGroups,
"groups", []string{kubeadmconstants.NodeBootstrapTokenAuthGroup},
createCmd.Flags().StringVar(&cfgPath,
"config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
createCmd.Flags().DurationVar(&cfg.TokenTTL.Duration,
"ttl", cfg.TokenTTL.Duration, "The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire.")
createCmd.Flags().StringSliceVar(&cfg.TokenUsages,
"usages", cfg.TokenUsages, fmt.Sprintf("Describes the ways in which this token can be used. You can pass --usages multiple times or provide a comma separated list of options. Valid options: [%s].", strings.Join(kubeadmconstants.DefaultTokenUsages, ",")))
createCmd.Flags().StringSliceVar(&cfg.TokenGroups,
"groups", cfg.TokenGroups,
fmt.Sprintf("Extra groups that this token will authenticate as when used for authentication. Must match %q.", bootstrapapi.BootstrapGroupPattern))
createCmd.Flags().StringVar(&description,
"description", "", "A human friendly description of how this token is used.")
@ -196,40 +209,14 @@ func NewCmdTokenGenerate(out io.Writer) *cobra.Command {
}
// RunCreateToken generates a new bootstrap token and stores it as a secret on the server.
func RunCreateToken(out io.Writer, client clientset.Interface, token string, tokenDuration time.Duration, usages []string, extraGroups []string, description string, printJoinCommand bool, kubeConfigFile string) error {
if len(token) == 0 {
var err error
token, err = tokenutil.GenerateToken()
if err != nil {
return err
}
} else {
_, _, err := tokenutil.ParseToken(token)
if err != nil {
return err
}
}
// adding groups only makes sense for authentication
usagesSet := sets.NewString(usages...)
usageAuthentication := strings.TrimPrefix(bootstrapapi.BootstrapTokenUsageAuthentication, bootstrapapi.BootstrapTokenUsagePrefix)
if len(extraGroups) > 0 && !usagesSet.Has(usageAuthentication) {
return fmt.Errorf("--groups cannot be specified unless --usages includes %q", usageAuthentication)
}
// validate any extra group names
for _, group := range extraGroups {
if err := bootstraputil.ValidateBootstrapGroupName(group); err != nil {
return err
}
}
// validate usages
if err := bootstraputil.ValidateUsages(usages); err != nil {
func RunCreateToken(out io.Writer, client clientset.Interface, cfgPath string, cfg *kubeadmapiext.MasterConfiguration, description string, 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
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
if err != nil {
return err
}
err := tokenphase.CreateNewToken(client, token, tokenDuration, usages, extraGroups, description)
err = tokenphase.CreateNewToken(client, internalcfg.Token, internalcfg.TokenTTL.Duration, internalcfg.TokenUsages, internalcfg.TokenGroups, description)
if err != nil {
return err
}
@ -237,13 +224,13 @@ func RunCreateToken(out io.Writer, client clientset.Interface, token string, tok
// if --print-join-command was specified, print the full `kubeadm join` command
// otherwise, just print the token
if printJoinCommand {
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, token, false)
joinCommand, err := cmdutil.GetJoinCommand(kubeConfigFile, internalcfg.Token, false)
if err != nil {
return fmt.Errorf("failed to get join command: %v", err)
}
fmt.Fprintln(out, joinCommand)
} else {
fmt.Fprintln(out, token)
fmt.Fprintln(out, internalcfg.Token)
}
return nil

View File

@ -23,9 +23,11 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
)
const (
@ -123,7 +125,18 @@ func TestRunCreateToken(t *testing.T) {
},
}
for _, tc := range testCases {
err := RunCreateToken(&buf, fakeClient, tc.token, 0, tc.usages, tc.extraGroups, "", false, "")
cfg := &kubeadmapiext.MasterConfiguration{
// KubernetesVersion is not used by bootstrap-token, but we set this explicitly to avoid
// the lookup of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig
KubernetesVersion: "v1.9.0",
Token: tc.token,
TokenTTL: &metav1.Duration{Duration: 0},
TokenUsages: tc.usages,
TokenGroups: tc.extraGroups,
}
err := RunCreateToken(&buf, fakeClient, "", cfg, "", false, "")
if (err != nil) != tc.expectedError {
t.Errorf("Test case %s: RunCreateToken expected error: %v, saw: %v", tc.name, tc.expectedError, (err != nil))
}

View File

@ -266,6 +266,9 @@ var (
// DefaultTokenUsages specifies the default functions a token will get
DefaultTokenUsages = bootstrapapi.KnownTokenUsages
// DefaultTokenGroups specifies the default groups that this token will authenticate as when used for authentication
DefaultTokenGroups = []string{NodeBootstrapTokenAuthGroup}
// MasterComponents defines the master component names
MasterComponents = []string{KubeAPIServer, KubeControllerManager, KubeScheduler}