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 }