k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package kubeconfig
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto"
    23  	"crypto/x509"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	"github.com/pkg/errors"
    31  
    32  	rbac "k8s.io/api/rbac/v1"
    33  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	clientset "k8s.io/client-go/kubernetes"
    37  	"k8s.io/client-go/tools/clientcmd"
    38  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    39  	certutil "k8s.io/client-go/util/cert"
    40  	"k8s.io/client-go/util/keyutil"
    41  	"k8s.io/klog/v2"
    42  
    43  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    44  	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
    45  	certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
    46  	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
    47  	kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
    48  	"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
    49  )
    50  
    51  const (
    52  	errInvalid = "invalid argument"
    53  	errExist   = "file already exists"
    54  )
    55  
    56  // clientCertAuth struct holds info required to build a client certificate to provide authentication info in a kubeconfig object
    57  type clientCertAuth struct {
    58  	CAKey         crypto.Signer
    59  	Organizations []string
    60  }
    61  
    62  // tokenAuth struct holds info required to use a token to provide authentication info in a kubeconfig object
    63  type tokenAuth struct {
    64  	Token string `datapolicy:"token"`
    65  }
    66  
    67  // kubeConfigSpec struct holds info required to build a KubeConfig object
    68  type kubeConfigSpec struct {
    69  	CACert             *x509.Certificate
    70  	APIServer          string
    71  	ClientName         string
    72  	ClientCertNotAfter time.Time
    73  	TokenAuth          *tokenAuth      `datapolicy:"token"`
    74  	ClientCertAuth     *clientCertAuth `datapolicy:"security-key"`
    75  }
    76  
    77  // CreateJoinControlPlaneKubeConfigFiles will create and write to disk the kubeconfig files required by kubeadm
    78  // join --control-plane workflow, plus the admin kubeconfig file used by the administrator and kubeadm itself; the
    79  // kubelet.conf file must not be created because it will be created and signed by the kubelet TLS bootstrap process.
    80  // When not using external CA mode, if a kubeconfig file already exists it is used only if evaluated equal,
    81  // otherwise an error is returned. For external CA mode, the creation of kubeconfig files is skipped.
    82  func CreateJoinControlPlaneKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration) error {
    83  	var externalCA bool
    84  	caKeyPath := filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName)
    85  	if _, err := os.Stat(caKeyPath); os.IsNotExist(err) {
    86  		externalCA = true
    87  	}
    88  
    89  	files := []string{
    90  		kubeadmconstants.AdminKubeConfigFileName,
    91  		kubeadmconstants.ControllerManagerKubeConfigFileName,
    92  		kubeadmconstants.SchedulerKubeConfigFileName,
    93  	}
    94  
    95  	for _, file := range files {
    96  		if externalCA {
    97  			fmt.Printf("[kubeconfig] External CA mode: Using user provided %s\n", file)
    98  			continue
    99  		}
   100  		if err := createKubeConfigFiles(outDir, cfg, file); err != nil {
   101  			return err
   102  		}
   103  	}
   104  	return nil
   105  }
   106  
   107  // CreateKubeConfigFile creates a kubeconfig file.
   108  // If the kubeconfig file already exists, it is used only if evaluated equal; otherwise an error is returned.
   109  func CreateKubeConfigFile(kubeConfigFileName string, outDir string, cfg *kubeadmapi.InitConfiguration) error {
   110  	klog.V(1).Infof("creating kubeconfig file for %s", kubeConfigFileName)
   111  	return createKubeConfigFiles(outDir, cfg, kubeConfigFileName)
   112  }
   113  
   114  // createKubeConfigFiles creates all the requested kubeconfig files.
   115  // If kubeconfig files already exists, they are used only if evaluated equal; otherwise an error is returned.
   116  func createKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration, kubeConfigFileNames ...string) error {
   117  
   118  	// gets the KubeConfigSpecs, actualized for the current InitConfiguration
   119  	specs, err := getKubeConfigSpecs(cfg)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	for _, kubeConfigFileName := range kubeConfigFileNames {
   125  		// retrieves the KubeConfigSpec for given kubeConfigFileName
   126  		spec, exists := specs[kubeConfigFileName]
   127  		if !exists {
   128  			return errors.Errorf("couldn't retrieve KubeConfigSpec for %s", kubeConfigFileName)
   129  		}
   130  
   131  		// builds the KubeConfig object
   132  		config, err := buildKubeConfigFromSpec(spec, cfg.ClusterName)
   133  		if err != nil {
   134  			return err
   135  		}
   136  
   137  		// writes the kubeconfig to disk if it does not exist
   138  		if err = createKubeConfigFileIfNotExists(outDir, kubeConfigFileName, config); err != nil {
   139  			return err
   140  		}
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  // getKubeConfigSpecs returns all KubeConfigSpecs actualized to the context of the current InitConfiguration
   147  // NB. this method holds the information about how kubeadm creates kubeconfig files.
   148  func getKubeConfigSpecs(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConfigSpec, error) {
   149  	caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
   150  	if os.IsNotExist(errors.Cause(err)) {
   151  		return nil, errors.Wrap(err, "the CA files do not exist, please run `kubeadm init phase certs ca` to generate it")
   152  	}
   153  	if err != nil {
   154  		return nil, errors.Wrap(err, "couldn't create a kubeconfig; the CA files couldn't be loaded")
   155  	}
   156  	// Validate period
   157  	certsphase.CheckCertificatePeriodValidity(kubeadmconstants.CACertAndKeyBaseName, caCert)
   158  
   159  	configs, err := getKubeConfigSpecsBase(cfg)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	for _, spec := range configs {
   164  		spec.CACert = caCert
   165  		spec.ClientCertAuth.CAKey = caKey
   166  	}
   167  	return configs, nil
   168  }
   169  
   170  // buildKubeConfigFromSpec creates a kubeconfig object for the given kubeConfigSpec
   171  func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string) (*clientcmdapi.Config, error) {
   172  
   173  	// If this kubeconfig should use token
   174  	if spec.TokenAuth != nil {
   175  		// create a kubeconfig with a token
   176  		return kubeconfigutil.CreateWithToken(
   177  			spec.APIServer,
   178  			clustername,
   179  			spec.ClientName,
   180  			pkiutil.EncodeCertPEM(spec.CACert),
   181  			spec.TokenAuth.Token,
   182  		), nil
   183  	}
   184  
   185  	// otherwise, create a client cert
   186  	clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec)
   187  
   188  	clientCert, clientKey, err := pkiutil.NewCertAndKey(spec.CACert, spec.ClientCertAuth.CAKey, &clientCertConfig)
   189  	if err != nil {
   190  		return nil, errors.Wrapf(err, "failure while creating %s client certificate", spec.ClientName)
   191  	}
   192  
   193  	encodedClientKey, err := keyutil.MarshalPrivateKeyToPEM(clientKey)
   194  	if err != nil {
   195  		return nil, errors.Wrapf(err, "failed to marshal private key to PEM")
   196  	}
   197  	// create a kubeconfig with the client certs
   198  	return kubeconfigutil.CreateWithCerts(
   199  		spec.APIServer,
   200  		clustername,
   201  		spec.ClientName,
   202  		pkiutil.EncodeCertPEM(spec.CACert),
   203  		encodedClientKey,
   204  		pkiutil.EncodeCertPEM(clientCert),
   205  	), nil
   206  }
   207  
   208  func newClientCertConfigFromKubeConfigSpec(spec *kubeConfigSpec) pkiutil.CertConfig {
   209  	return pkiutil.CertConfig{
   210  		Config: certutil.Config{
   211  			CommonName:   spec.ClientName,
   212  			Organization: spec.ClientCertAuth.Organizations,
   213  			Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
   214  		},
   215  		NotAfter: spec.ClientCertNotAfter,
   216  	}
   217  }
   218  
   219  // validateKubeConfig check if the kubeconfig file exist and has the expected CA and server URL
   220  func validateKubeConfig(outDir, filename string, config *clientcmdapi.Config) error {
   221  	kubeConfigFilePath := filepath.Join(outDir, filename)
   222  
   223  	if _, err := os.Stat(kubeConfigFilePath); err != nil {
   224  		return err
   225  	}
   226  
   227  	// The kubeconfig already exists, let's check if it has got the same CA and server URL
   228  	currentConfig, err := clientcmd.LoadFromFile(kubeConfigFilePath)
   229  	if err != nil {
   230  		return errors.Wrapf(err, "failed to load kubeconfig file %s that already exists on disk", kubeConfigFilePath)
   231  	}
   232  
   233  	expectedCtx, exists := config.Contexts[config.CurrentContext]
   234  	if !exists {
   235  		return errors.Errorf("failed to find expected context %s", config.CurrentContext)
   236  	}
   237  	expectedCluster := expectedCtx.Cluster
   238  	currentCtx, exists := currentConfig.Contexts[currentConfig.CurrentContext]
   239  	if !exists {
   240  		return errors.Errorf("failed to find CurrentContext in Contexts of the kubeconfig file %s", kubeConfigFilePath)
   241  	}
   242  	currentCluster := currentCtx.Cluster
   243  	if currentConfig.Clusters[currentCluster] == nil {
   244  		return errors.Errorf("failed to find the given CurrentContext Cluster in Clusters of the kubeconfig file %s", kubeConfigFilePath)
   245  	}
   246  
   247  	// Make sure the compared CAs are whitespace-trimmed. The function clientcmd.LoadFromFile() just decodes
   248  	// the base64 CA and places it raw in the v1.Config object. In case the user has extra whitespace
   249  	// in the CA they used to create a kubeconfig this comparison to a generated v1.Config will otherwise fail.
   250  	caCurrent := bytes.TrimSpace(currentConfig.Clusters[currentCluster].CertificateAuthorityData)
   251  	if len(caCurrent) == 0 {
   252  		// fallback to load CA cert data from external CA file
   253  		clusterCAFilePath := currentConfig.Clusters[currentCluster].CertificateAuthority
   254  		if len(clusterCAFilePath) > 0 {
   255  			clusterCABytes, err := os.ReadFile(clusterCAFilePath)
   256  			if err != nil {
   257  				klog.Warningf("failed to load CA cert from %q for kubeconfig %q, %v", clusterCAFilePath, kubeConfigFilePath, err)
   258  			} else {
   259  				caCurrent = bytes.TrimSpace(clusterCABytes)
   260  			}
   261  		}
   262  	}
   263  	caExpected := bytes.TrimSpace(config.Clusters[expectedCluster].CertificateAuthorityData)
   264  
   265  	// If the current CA cert on disk doesn't match the expected CA cert, error out because we have a file, but it's stale
   266  	if !bytes.Equal(caCurrent, caExpected) {
   267  		return errors.Errorf("a kubeconfig file %q exists already but has got the wrong CA cert", kubeConfigFilePath)
   268  	}
   269  	// If the current API Server location on disk doesn't match the expected API server, show a warning
   270  	if currentConfig.Clusters[currentCluster].Server != config.Clusters[expectedCluster].Server {
   271  		klog.Warningf("a kubeconfig file %q exists already but has an unexpected API Server URL: expected: %s, got: %s",
   272  			kubeConfigFilePath, config.Clusters[expectedCluster].Server, currentConfig.Clusters[currentCluster].Server)
   273  	}
   274  
   275  	return nil
   276  }
   277  
   278  // createKubeConfigFileIfNotExists saves the KubeConfig object into a file if there isn't any file at the given path.
   279  // If there already is a kubeconfig file at the given path; kubeadm tries to load it and check if the values in the
   280  // existing and the expected config equals. If they do; kubeadm will just skip writing the file as it's up-to-date,
   281  // but if a file exists but has old content or isn't a kubeconfig file, this function returns an error.
   282  func createKubeConfigFileIfNotExists(outDir, filename string, config *clientcmdapi.Config) error {
   283  	kubeConfigFilePath := filepath.Join(outDir, filename)
   284  
   285  	err := validateKubeConfig(outDir, filename, config)
   286  	if err != nil {
   287  		// Check if the file exist, and if it doesn't, just write it to disk
   288  		if !os.IsNotExist(err) {
   289  			return err
   290  		}
   291  		fmt.Printf("[kubeconfig] Writing %q kubeconfig file\n", filename)
   292  		err = kubeconfigutil.WriteToDisk(kubeConfigFilePath, config)
   293  		return errors.Wrapf(err, "failed to save kubeconfig file %q on disk", kubeConfigFilePath)
   294  	}
   295  	// kubeadm doesn't validate the existing kubeconfig file more than this (kubeadm trusts the client certs to be valid)
   296  	// Basically, if we find a kubeconfig file with the same path; the same CA cert and the same server URL;
   297  	// kubeadm thinks those files are equal and doesn't bother writing a new file
   298  	fmt.Printf("[kubeconfig] Using existing kubeconfig file: %q\n", kubeConfigFilePath)
   299  
   300  	return nil
   301  }
   302  
   303  // WriteKubeConfigWithClientCert writes a kubeconfig file - with a client certificate as authentication info  - to the given writer.
   304  func WriteKubeConfigWithClientCert(out io.Writer, cfg *kubeadmapi.InitConfiguration, clientName string, organizations []string, notAfter time.Time) error {
   305  
   306  	// creates the KubeConfigSpecs, actualized for the current InitConfiguration
   307  	caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
   308  	if err != nil {
   309  		return errors.Wrap(err, "couldn't create a kubeconfig; the CA files couldn't be loaded")
   310  	}
   311  	// Validate period
   312  	certsphase.CheckCertificatePeriodValidity(kubeadmconstants.CACertAndKeyBaseName, caCert)
   313  
   314  	controlPlaneEndpoint, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint)
   315  	if err != nil {
   316  		return err
   317  	}
   318  
   319  	spec := &kubeConfigSpec{
   320  		ClientName: clientName,
   321  		APIServer:  controlPlaneEndpoint,
   322  		CACert:     caCert,
   323  		ClientCertAuth: &clientCertAuth{
   324  			CAKey:         caKey,
   325  			Organizations: organizations,
   326  		},
   327  		ClientCertNotAfter: notAfter,
   328  	}
   329  
   330  	return writeKubeConfigFromSpec(out, spec, cfg.ClusterName)
   331  }
   332  
   333  // WriteKubeConfigWithToken writes a kubeconfig file - with a token as client authentication info - to the given writer.
   334  func WriteKubeConfigWithToken(out io.Writer, cfg *kubeadmapi.InitConfiguration, clientName, token string, notAfter time.Time) error {
   335  
   336  	// creates the KubeConfigSpecs, actualized for the current InitConfiguration
   337  	caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
   338  	if err != nil {
   339  		return errors.Wrap(err, "couldn't create a kubeconfig; the CA files couldn't be loaded")
   340  	}
   341  	// Validate period
   342  	certsphase.CheckCertificatePeriodValidity(kubeadmconstants.CACertAndKeyBaseName, caCert)
   343  
   344  	controlPlaneEndpoint, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint)
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	spec := &kubeConfigSpec{
   350  		ClientName: clientName,
   351  		APIServer:  controlPlaneEndpoint,
   352  		CACert:     caCert,
   353  		TokenAuth: &tokenAuth{
   354  			Token: token,
   355  		},
   356  		ClientCertNotAfter: notAfter,
   357  	}
   358  
   359  	return writeKubeConfigFromSpec(out, spec, cfg.ClusterName)
   360  }
   361  
   362  // writeKubeConfigFromSpec creates a kubeconfig object from a kubeConfigSpec and writes it to the given writer.
   363  func writeKubeConfigFromSpec(out io.Writer, spec *kubeConfigSpec, clustername string) error {
   364  
   365  	// builds the KubeConfig object
   366  	config, err := buildKubeConfigFromSpec(spec, clustername)
   367  	if err != nil {
   368  		return err
   369  	}
   370  
   371  	// writes the kubeconfig to disk if it not exists
   372  	configBytes, err := clientcmd.Write(*config)
   373  	if err != nil {
   374  		return errors.Wrap(err, "failure while serializing admin kubeconfig")
   375  	}
   376  
   377  	fmt.Fprintln(out, string(configBytes))
   378  	return nil
   379  }
   380  
   381  // ValidateKubeconfigsForExternalCA check if the kubeconfig file exist and has the expected CA and server URL using kubeadmapi.InitConfiguration.
   382  func ValidateKubeconfigsForExternalCA(outDir string, cfg *kubeadmapi.InitConfiguration) error {
   383  	// Creates a kubeconfig file with the target CA and server URL
   384  	// to be used as a input for validating user provided kubeconfig files
   385  	caCert, err := pkiutil.TryLoadCertFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
   386  	if err != nil {
   387  		return errors.Wrapf(err, "the CA file couldn't be loaded")
   388  	}
   389  	// Validate period
   390  	certsphase.CheckCertificatePeriodValidity(kubeadmconstants.CACertAndKeyBaseName, caCert)
   391  
   392  	// validate user provided kubeconfig files for the scheduler and controller-manager
   393  	localAPIEndpoint, err := kubeadmutil.GetLocalAPIEndpoint(&cfg.LocalAPIEndpoint)
   394  	if err != nil {
   395  		return err
   396  	}
   397  
   398  	validationConfigLocal := kubeconfigutil.CreateBasic(localAPIEndpoint, "dummy", "dummy", pkiutil.EncodeCertPEM(caCert))
   399  	kubeConfigFileNamesLocal := []string{
   400  		kubeadmconstants.ControllerManagerKubeConfigFileName,
   401  		kubeadmconstants.SchedulerKubeConfigFileName,
   402  	}
   403  
   404  	for _, kubeConfigFileName := range kubeConfigFileNamesLocal {
   405  		if err = validateKubeConfig(outDir, kubeConfigFileName, validationConfigLocal); err != nil {
   406  			return errors.Wrapf(err, "the %s file does not exists or it is not valid", kubeConfigFileName)
   407  		}
   408  	}
   409  
   410  	// validate user provided kubeconfig files for the kubelet and admin
   411  	controlPlaneEndpoint, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint)
   412  	if err != nil {
   413  		return err
   414  	}
   415  
   416  	validationConfigCPE := kubeconfigutil.CreateBasic(controlPlaneEndpoint, "dummy", "dummy", pkiutil.EncodeCertPEM(caCert))
   417  	kubeConfigFileNamesCPE := []string{
   418  		kubeadmconstants.AdminKubeConfigFileName,
   419  		kubeadmconstants.SuperAdminKubeConfigFileName,
   420  		kubeadmconstants.KubeletKubeConfigFileName,
   421  	}
   422  
   423  	for _, kubeConfigFileName := range kubeConfigFileNamesCPE {
   424  		if err = validateKubeConfig(outDir, kubeConfigFileName, validationConfigCPE); err != nil {
   425  			return errors.Wrapf(err, "the %s file does not exists or it is not valid", kubeConfigFileName)
   426  		}
   427  	}
   428  
   429  	return nil
   430  }
   431  
   432  func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConfigSpec, error) {
   433  	controlPlaneEndpoint, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  	localAPIEndpoint, err := kubeadmutil.GetLocalAPIEndpoint(&cfg.LocalAPIEndpoint)
   438  	if err != nil {
   439  		return nil, err
   440  	}
   441  
   442  	startTime := kubeadmutil.StartTimeUTC()
   443  	notAfter := startTime.Add(kubeadmconstants.CertificateValidityPeriod)
   444  	if cfg.ClusterConfiguration.CertificateValidityPeriod != nil {
   445  		notAfter = startTime.Add(cfg.ClusterConfiguration.CertificateValidityPeriod.Duration)
   446  	}
   447  
   448  	return map[string]*kubeConfigSpec{
   449  		kubeadmconstants.AdminKubeConfigFileName: {
   450  			APIServer:  controlPlaneEndpoint,
   451  			ClientName: "kubernetes-admin",
   452  			ClientCertAuth: &clientCertAuth{
   453  				Organizations: []string{kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding},
   454  			},
   455  			ClientCertNotAfter: notAfter,
   456  		},
   457  		kubeadmconstants.SuperAdminKubeConfigFileName: {
   458  			APIServer:  controlPlaneEndpoint,
   459  			ClientName: "kubernetes-super-admin",
   460  			ClientCertAuth: &clientCertAuth{
   461  				Organizations: []string{kubeadmconstants.SystemPrivilegedGroup},
   462  			},
   463  			ClientCertNotAfter: notAfter,
   464  		},
   465  		kubeadmconstants.KubeletKubeConfigFileName: {
   466  			APIServer:  controlPlaneEndpoint,
   467  			ClientName: fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name),
   468  			ClientCertAuth: &clientCertAuth{
   469  				Organizations: []string{kubeadmconstants.NodesGroup},
   470  			},
   471  			ClientCertNotAfter: notAfter,
   472  		},
   473  		kubeadmconstants.ControllerManagerKubeConfigFileName: {
   474  			APIServer:          localAPIEndpoint,
   475  			ClientName:         kubeadmconstants.ControllerManagerUser,
   476  			ClientCertAuth:     &clientCertAuth{},
   477  			ClientCertNotAfter: notAfter,
   478  		},
   479  		kubeadmconstants.SchedulerKubeConfigFileName: {
   480  			APIServer:          localAPIEndpoint,
   481  			ClientName:         kubeadmconstants.SchedulerUser,
   482  			ClientCertAuth:     &clientCertAuth{},
   483  			ClientCertNotAfter: notAfter,
   484  		},
   485  	}, nil
   486  }
   487  
   488  func createKubeConfigAndCSR(kubeConfigDir string, kubeadmConfig *kubeadmapi.InitConfiguration, name string, spec *kubeConfigSpec) error {
   489  	if kubeConfigDir == "" {
   490  		return errors.Errorf("%s: kubeConfigDir was empty", errInvalid)
   491  	}
   492  	if kubeadmConfig == nil {
   493  		return errors.Errorf("%s: kubeadmConfig was nil", errInvalid)
   494  	}
   495  	if name == "" {
   496  		return errors.Errorf("%s: name was empty", errInvalid)
   497  	}
   498  	if spec == nil {
   499  		return errors.Errorf("%s: spec was nil", errInvalid)
   500  	}
   501  	kubeConfigPath := filepath.Join(kubeConfigDir, name)
   502  	if _, err := os.Stat(kubeConfigPath); err == nil {
   503  		return errors.Errorf("%s: kube config: %s", errExist, kubeConfigPath)
   504  	} else if !os.IsNotExist(err) {
   505  		return errors.Wrapf(err, "unexpected error while checking if file exists: %s", kubeConfigPath)
   506  	}
   507  	if pkiutil.CSROrKeyExist(kubeConfigDir, name) {
   508  		return errors.Errorf("%s: csr: %s", errExist, kubeConfigPath)
   509  	}
   510  
   511  	clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec)
   512  
   513  	clientKey, err := pkiutil.NewPrivateKey(clientCertConfig.EncryptionAlgorithm)
   514  	if err != nil {
   515  		return err
   516  	}
   517  	clientCSR, err := pkiutil.NewCSR(clientCertConfig, clientKey)
   518  	if err != nil {
   519  		return err
   520  	}
   521  
   522  	encodedClientKey, err := keyutil.MarshalPrivateKeyToPEM(clientKey)
   523  	if err != nil {
   524  		return err
   525  	}
   526  
   527  	var (
   528  		emptyCACert     []byte
   529  		emptyClientCert []byte
   530  	)
   531  
   532  	// create a kubeconfig with the client certs
   533  	config := kubeconfigutil.CreateWithCerts(
   534  		spec.APIServer,
   535  		kubeadmConfig.ClusterName,
   536  		spec.ClientName,
   537  		emptyCACert,
   538  		encodedClientKey,
   539  		emptyClientCert,
   540  	)
   541  
   542  	if err := kubeconfigutil.WriteToDisk(kubeConfigPath, config); err != nil {
   543  		return err
   544  	}
   545  	// Write CSR to disk
   546  	if err := pkiutil.WriteCSR(kubeConfigDir, name, clientCSR); err != nil {
   547  		return err
   548  	}
   549  	return nil
   550  }
   551  
   552  // CreateDefaultKubeConfigsAndCSRFiles is used in ExternalCA mode to create
   553  // kubeconfig files and adjacent CSR files.
   554  func CreateDefaultKubeConfigsAndCSRFiles(out io.Writer, kubeConfigDir string, kubeadmConfig *kubeadmapi.InitConfiguration) error {
   555  	kubeConfigs, err := getKubeConfigSpecsBase(kubeadmConfig)
   556  	if err != nil {
   557  		return err
   558  	}
   559  	if out != nil {
   560  		fmt.Fprintf(out, "generating keys and CSRs in %s\n", kubeConfigDir)
   561  	}
   562  	for name, spec := range kubeConfigs {
   563  		if err := createKubeConfigAndCSR(kubeConfigDir, kubeadmConfig, name, spec); err != nil {
   564  			return err
   565  		}
   566  		if out != nil {
   567  			fmt.Fprintf(out, "  %s\n", name)
   568  		}
   569  	}
   570  	return nil
   571  }
   572  
   573  // EnsureRBACFunc defines a function type that can be passed to EnsureAdminClusterRoleBinding().
   574  type EnsureRBACFunc func(context.Context, clientset.Interface, clientset.Interface, time.Duration, time.Duration) (clientset.Interface, error)
   575  
   576  // EnsureAdminClusterRoleBinding constructs a client from admin.conf and optionally
   577  // constructs a client from super-admin.conf if the file exists. It then proceeds
   578  // to pass the clients to EnsureAdminClusterRoleBindingImpl. The function returns a
   579  // usable client from admin.conf with RBAC properly constructed or an error.
   580  func EnsureAdminClusterRoleBinding(outDir string, ensureRBACFunc EnsureRBACFunc) (clientset.Interface, error) {
   581  	var (
   582  		err                           error
   583  		adminClient, superAdminClient clientset.Interface
   584  	)
   585  
   586  	// Create a client from admin.conf.
   587  	adminClient, err = kubeconfigutil.ClientSetFromFile(filepath.Join(outDir, kubeadmconstants.AdminKubeConfigFileName))
   588  	if err != nil {
   589  		return nil, err
   590  	}
   591  
   592  	// Create a client from super-admin.conf.
   593  	superAdminPath := filepath.Join(outDir, kubeadmconstants.SuperAdminKubeConfigFileName)
   594  	if _, err := os.Stat(superAdminPath); err == nil {
   595  		superAdminClient, err = kubeconfigutil.ClientSetFromFile(superAdminPath)
   596  		if err != nil {
   597  			return nil, err
   598  		}
   599  	}
   600  
   601  	if ensureRBACFunc == nil {
   602  		ensureRBACFunc = EnsureAdminClusterRoleBindingImpl
   603  	}
   604  
   605  	ctx := context.Background()
   606  	return ensureRBACFunc(
   607  		ctx, adminClient, superAdminClient,
   608  		kubeadmconstants.KubernetesAPICallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration,
   609  	)
   610  }
   611  
   612  // EnsureAdminClusterRoleBindingImpl first attempts to see if the ClusterRoleBinding
   613  // kubeadm:cluster-admins exists by using adminClient. If it already exists,
   614  // it would mean the adminClient is usable. If it does not, attempt to create
   615  // the ClusterRoleBinding by using superAdminClient.
   616  func EnsureAdminClusterRoleBindingImpl(ctx context.Context, adminClient, superAdminClient clientset.Interface,
   617  	retryInterval, retryTimeout time.Duration) (clientset.Interface, error) {
   618  
   619  	klog.V(1).Infof("ensuring that the ClusterRoleBinding for the %s Group exists",
   620  		kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding)
   621  
   622  	var (
   623  		err, lastError     error
   624  		crbExists          bool
   625  		clusterRoleBinding = &rbac.ClusterRoleBinding{
   626  			ObjectMeta: metav1.ObjectMeta{
   627  				Name: kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding,
   628  			},
   629  			RoleRef: rbac.RoleRef{
   630  				APIGroup: rbac.GroupName,
   631  				Kind:     "ClusterRole",
   632  				Name:     "cluster-admin",
   633  			},
   634  			Subjects: []rbac.Subject{
   635  				{
   636  					Kind: rbac.GroupKind,
   637  					Name: kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding,
   638  				},
   639  			},
   640  		}
   641  	)
   642  
   643  	// First try to create the CRB with the admin.conf client. If the admin.conf contains a User bound
   644  	// to the built-in super-user group, this will pass. In all other cases an error will be returned.
   645  	// The poll here is required to ensure the API server is reachable during "kubeadm init" workflows.
   646  	err = wait.PollUntilContextTimeout(
   647  		ctx,
   648  		retryInterval,
   649  		retryTimeout,
   650  		true, func(ctx context.Context) (bool, error) {
   651  			if _, err := adminClient.RbacV1().ClusterRoleBindings().Create(
   652  				ctx,
   653  				clusterRoleBinding,
   654  				metav1.CreateOptions{},
   655  			); err != nil {
   656  				if apierrors.IsForbidden(err) {
   657  					// If it encounters a forbidden error this means that the API server was reached
   658  					// but the CRB is missing - i.e. the admin.conf user does not have permissions
   659  					// to create its own permission RBAC yet.
   660  					return true, nil
   661  				} else if apierrors.IsAlreadyExists(err) {
   662  					// If the CRB exists it means the admin.conf already has the right
   663  					// permissions; return.
   664  					crbExists = true
   665  					return true, nil
   666  				} else {
   667  					// Retry on any other error type.
   668  					lastError = errors.Wrap(err, "unable to create ClusterRoleBinding")
   669  					return false, nil
   670  				}
   671  			}
   672  			crbExists = true
   673  			return true, nil
   674  		})
   675  	if err != nil {
   676  		return nil, lastError
   677  	}
   678  
   679  	// The CRB was created or already existed; return the admin.conf client.
   680  	if crbExists {
   681  		return adminClient, nil
   682  	}
   683  
   684  	// If the superAdminClient is nil at this point we cannot proceed creating the CRB; return an error.
   685  	if superAdminClient == nil {
   686  		return nil, errors.Errorf("the ClusterRoleBinding for the %s Group is missing but there is no %s to create it",
   687  			kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding,
   688  			kubeadmconstants.SuperAdminKubeConfigFileName)
   689  	}
   690  
   691  	// Create the ClusterRoleBinding with the super-admin.conf client.
   692  	klog.V(1).Infof("creating the ClusterRoleBinding for the %s Group by using %s",
   693  		kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding,
   694  		kubeadmconstants.SuperAdminKubeConfigFileName)
   695  
   696  	err = wait.PollUntilContextTimeout(
   697  		ctx,
   698  		retryInterval,
   699  		retryTimeout,
   700  		true, func(ctx context.Context) (bool, error) {
   701  			if _, err := superAdminClient.RbacV1().ClusterRoleBindings().Create(
   702  				ctx,
   703  				clusterRoleBinding,
   704  				metav1.CreateOptions{},
   705  			); err != nil {
   706  				lastError = err
   707  				if apierrors.IsAlreadyExists(err) {
   708  					klog.V(5).Infof("ClusterRoleBinding %s already exists.", kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding)
   709  					return true, nil
   710  				}
   711  				// Retry on any other type of error.
   712  				return false, nil
   713  			}
   714  			return true, nil
   715  		})
   716  	if err != nil {
   717  		return nil, errors.Wrapf(lastError, "unable to create the %s ClusterRoleBinding by using %s",
   718  			kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding,
   719  			kubeadmconstants.SuperAdminKubeConfigFileName)
   720  	}
   721  
   722  	// Once the CRB is in place, start using the admin.conf client.
   723  	return adminClient, nil
   724  }