diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 0c548ba4000..85a9f87e156 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -19,10 +19,8 @@ package cmd import ( "fmt" "io" - "net" "os" "path/filepath" - "strconv" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -92,6 +90,7 @@ type initData struct { skipTokenPrint bool dryRun bool kubeconfig *clientcmdapi.Config + kubeconfigOriginal *clientcmdapi.Config kubeconfigDir string kubeconfigPath string ignorePreflightErrors sets.Set[string] @@ -462,6 +461,9 @@ func (d *initData) CertificateDir() string { } // KubeConfig returns a kubeconfig after loading it from KubeConfigPath(). +// If the default kubeconfig path is used (admin.conf), instead of constructing +// a kubeconfig that points to the control plane endpoint, make it point to the localAPIEndpoint. +// This would allow 'kubeadm init' to only talk to the local kube-apiserver instance. func (d *initData) KubeConfig() (*clientcmdapi.Config, error) { if d.kubeconfig != nil { return d.kubeconfig, nil @@ -472,10 +474,26 @@ func (d *initData) KubeConfig() (*clientcmdapi.Config, error) { if err != nil { return nil, err } + d.kubeconfigOriginal = d.kubeconfig.DeepCopy() + + if d.kubeconfigPath == kubeadmconstants.GetAdminKubeConfigPath() { + kubeconfigutil.PointKubeConfigToLocalAPIEndpoint(d.kubeconfig, &d.Cfg().LocalAPIEndpoint) + } return d.kubeconfig, nil } +// KubeConfigOriginal returns the original kubeconfig loaded from file, without any modifications. +func (d *initData) KubeConfigOriginal() (*clientcmdapi.Config, error) { + if d.kubeconfigOriginal == nil { + if _, err := d.KubeConfig(); err != nil { + return nil, err + } + } + + return d.kubeconfigOriginal, nil +} + // KubeConfigDir returns the path of the Kubernetes configuration folder or the temporary folder path in case of DryRun. func (d *initData) KubeConfigDir() string { if d.dryRun { @@ -520,8 +538,12 @@ func (d *initData) OutputWriter() io.Writer { // getDryRunClient creates a fake client that answers some GET calls in order to be able to do the full init flow in dry-run mode. func getDryRunClient(d *initData) (clientset.Interface, error) { + kubeconfig, err := d.KubeConfig() + if err != nil { + return nil, err + } dryRun := apiclient.NewDryRun() - if err := dryRun.WithKubeConfigFile(d.KubeConfigPath()); err != nil { + if err := dryRun.WithKubeConfig(kubeconfig); err != nil { return nil, err } dryRun.WithDefaultMarshalFunction(). @@ -549,7 +571,11 @@ func (d *initData) Client() (clientset.Interface, error) { // and if the bootstrapping was not already done if !d.adminKubeConfigBootstrapped && isDefaultKubeConfigPath { // Call EnsureAdminClusterRoleBinding() to obtain a working client from admin.conf. - d.client, err = kubeconfigphase.EnsureAdminClusterRoleBinding(kubeadmconstants.KubernetesDir, nil) + d.client, err = kubeconfigphase.EnsureAdminClusterRoleBinding( + kubeadmconstants.KubernetesDir, + &d.Cfg().LocalAPIEndpoint, + nil, + ) if err != nil { return nil, errors.Wrapf(err, "could not bootstrap the admin user in file %s", kubeadmconstants.AdminKubeConfigFileName) } @@ -570,30 +596,6 @@ func (d *initData) Client() (clientset.Interface, error) { return d.client, nil } -// WaitControlPlaneClient returns a basic client used for the purpose of waiting -// for control plane components to report 'ok' on their respective health check endpoints. -// It uses the admin.conf as the base, but modifies it to point at the local API server instead -// of the control plane endpoint. -func (d *initData) WaitControlPlaneClient() (clientset.Interface, error) { - config, err := clientcmd.LoadFromFile(d.KubeConfigPath()) - if err != nil { - return nil, err - } - for _, v := range config.Clusters { - v.Server = fmt.Sprintf("https://%s", - net.JoinHostPort( - d.Cfg().LocalAPIEndpoint.AdvertiseAddress, - strconv.Itoa(int(d.Cfg().LocalAPIEndpoint.BindPort)), - ), - ) - } - client, err := kubeconfigutil.ToClientSet(config) - if err != nil { - return nil, err - } - return client, nil -} - // Tokens returns an array of token strings. func (d *initData) Tokens() []string { tokens := []string{} diff --git a/cmd/kubeadm/app/cmd/phases/init/bootstraptoken.go b/cmd/kubeadm/app/cmd/phases/init/bootstraptoken.go index aa0ea810b27..8b5d0b90045 100644 --- a/cmd/kubeadm/app/cmd/phases/init/bootstraptoken.go +++ b/cmd/kubeadm/app/cmd/phases/init/bootstraptoken.go @@ -72,7 +72,7 @@ func runBootstrapToken(c workflow.RunData) error { if err != nil { return err } - kubeconfig, err := data.KubeConfig() + kubeconfig, err := data.KubeConfigOriginal() if err != nil { return err } diff --git a/cmd/kubeadm/app/cmd/phases/init/data.go b/cmd/kubeadm/app/cmd/phases/init/data.go index feaf95fd299..3af216f7474 100644 --- a/cmd/kubeadm/app/cmd/phases/init/data.go +++ b/cmd/kubeadm/app/cmd/phases/init/data.go @@ -40,6 +40,7 @@ type InitData interface { CertificateWriteDir() string CertificateDir() string KubeConfig() (*clientcmdapi.Config, error) + KubeConfigOriginal() (*clientcmdapi.Config, error) KubeConfigDir() string KubeConfigPath() string ManifestDir() string @@ -47,7 +48,6 @@ type InitData interface { ExternalCA() bool OutputWriter() io.Writer Client() (clientset.Interface, error) - WaitControlPlaneClient() (clientset.Interface, error) Tokens() []string PatchesDir() string } diff --git a/cmd/kubeadm/app/cmd/phases/init/data_test.go b/cmd/kubeadm/app/cmd/phases/init/data_test.go index afdbc100f25..b64eb4de8b8 100644 --- a/cmd/kubeadm/app/cmd/phases/init/data_test.go +++ b/cmd/kubeadm/app/cmd/phases/init/data_test.go @@ -32,24 +32,24 @@ type testInitData struct{} // testInitData must satisfy InitData. var _ InitData = &testInitData{} -func (t *testInitData) UploadCerts() bool { return false } -func (t *testInitData) CertificateKey() string { return "" } -func (t *testInitData) SetCertificateKey(key string) {} -func (t *testInitData) SkipCertificateKeyPrint() bool { return false } -func (t *testInitData) Cfg() *kubeadmapi.InitConfiguration { return nil } -func (t *testInitData) DryRun() bool { return false } -func (t *testInitData) SkipTokenPrint() bool { return false } -func (t *testInitData) IgnorePreflightErrors() sets.Set[string] { return nil } -func (t *testInitData) CertificateWriteDir() string { return "" } -func (t *testInitData) CertificateDir() string { return "" } -func (t *testInitData) KubeConfig() (*clientcmdapi.Config, error) { return nil, nil } -func (t *testInitData) KubeConfigDir() string { return "" } -func (t *testInitData) KubeConfigPath() string { return "" } -func (t *testInitData) ManifestDir() string { return "" } -func (t *testInitData) KubeletDir() string { return "" } -func (t *testInitData) ExternalCA() bool { return false } -func (t *testInitData) OutputWriter() io.Writer { return nil } -func (t *testInitData) Client() (clientset.Interface, error) { return nil, nil } -func (t *testInitData) WaitControlPlaneClient() (clientset.Interface, error) { return nil, nil } -func (t *testInitData) Tokens() []string { return nil } -func (t *testInitData) PatchesDir() string { return "" } +func (t *testInitData) UploadCerts() bool { return false } +func (t *testInitData) CertificateKey() string { return "" } +func (t *testInitData) SetCertificateKey(key string) {} +func (t *testInitData) SkipCertificateKeyPrint() bool { return false } +func (t *testInitData) Cfg() *kubeadmapi.InitConfiguration { return nil } +func (t *testInitData) DryRun() bool { return false } +func (t *testInitData) SkipTokenPrint() bool { return false } +func (t *testInitData) IgnorePreflightErrors() sets.Set[string] { return nil } +func (t *testInitData) CertificateWriteDir() string { return "" } +func (t *testInitData) CertificateDir() string { return "" } +func (t *testInitData) KubeConfig() (*clientcmdapi.Config, error) { return nil, nil } +func (t *testInitData) KubeConfigOriginal() (*clientcmdapi.Config, error) { return nil, nil } +func (t *testInitData) KubeConfigDir() string { return "" } +func (t *testInitData) KubeConfigPath() string { return "" } +func (t *testInitData) ManifestDir() string { return "" } +func (t *testInitData) KubeletDir() string { return "" } +func (t *testInitData) ExternalCA() bool { return false } +func (t *testInitData) OutputWriter() io.Writer { return nil } +func (t *testInitData) Client() (clientset.Interface, error) { return nil, nil } +func (t *testInitData) Tokens() []string { return nil } +func (t *testInitData) PatchesDir() string { return "" } diff --git a/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go b/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go index 50b89d3ac1b..7bb691bfd73 100644 --- a/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go +++ b/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go @@ -63,7 +63,7 @@ func runWaitControlPlanePhase(c workflow.RunData) error { } } - client, err := data.WaitControlPlaneClient() + client, err := data.Client() if err != nil { return errors.Wrap(err, "cannot obtain client without bootstrap") } diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index c1fcca242b7..7af15be2dea 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -623,14 +623,19 @@ type EnsureRBACFunc func(context.Context, clientset.Interface, clientset.Interfa // constructs a client from super-admin.conf if the file exists. It then proceeds // to pass the clients to EnsureAdminClusterRoleBindingImpl. The function returns a // usable client from admin.conf with RBAC properly constructed or an error. -func EnsureAdminClusterRoleBinding(outDir string, ensureRBACFunc EnsureRBACFunc) (clientset.Interface, error) { +func EnsureAdminClusterRoleBinding(outDir string, lae *kubeadmapi.APIEndpoint, ensureRBACFunc EnsureRBACFunc) (clientset.Interface, error) { var ( err error adminClient, superAdminClient clientset.Interface ) // Create a client from admin.conf. - adminClient, err = kubeconfigutil.ClientSetFromFile(filepath.Join(outDir, kubeadmconstants.AdminKubeConfigFileName)) + kubeconfig, err := clientcmd.LoadFromFile(filepath.Join(outDir, kubeadmconstants.AdminKubeConfigFileName)) + if err != nil { + return nil, err + } + kubeconfigutil.PointKubeConfigToLocalAPIEndpoint(kubeconfig, lae) + adminClient, err = kubeconfigutil.ToClientSet(kubeconfig) if err != nil { return nil, err } @@ -638,7 +643,12 @@ func EnsureAdminClusterRoleBinding(outDir string, ensureRBACFunc EnsureRBACFunc) // Create a client from super-admin.conf. superAdminPath := filepath.Join(outDir, kubeadmconstants.SuperAdminKubeConfigFileName) if _, err := os.Stat(superAdminPath); err == nil { - superAdminClient, err = kubeconfigutil.ClientSetFromFile(superAdminPath) + kubeconfig, err := clientcmd.LoadFromFile(superAdminPath) + if err != nil { + return nil, err + } + kubeconfigutil.PointKubeConfigToLocalAPIEndpoint(kubeconfig, lae) + superAdminClient, err = kubeconfigutil.ToClientSet(kubeconfig) if err != nil { return nil, err } diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go index 0418beca5b5..17a6fc600e1 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go @@ -991,7 +991,7 @@ func TestEnsureAdminClusterRoleBinding(t *testing.T) { } } - client, err := EnsureAdminClusterRoleBinding(dir, ensureRBACFunc) + client, err := EnsureAdminClusterRoleBinding(dir, &kubeadmapi.APIEndpoint{}, ensureRBACFunc) if (err != nil) != tc.expectedError { t.Fatalf("expected error: %v, got: %v, error: %v", err != nil, tc.expectedError, err) } diff --git a/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go index f7b465ed7da..518c187962f 100644 --- a/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go @@ -18,13 +18,17 @@ package kubeconfig import ( "fmt" + "net" "os" + "strconv" "github.com/pkg/errors" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" ) // CreateBasic creates a basic, general KubeConfig object that then can be extended @@ -121,6 +125,18 @@ func GetClusterFromKubeConfig(config *clientcmdapi.Config) (string, *clientcmdap return "", nil, errors.Errorf("the current context is invalid: %s", config.CurrentContext) } +// PointKubeConfigToLocalAPIEndpoint modifies the provided kubeconfig to point to the given APIEndpoint. +func PointKubeConfigToLocalAPIEndpoint(config *clientcmdapi.Config, lae *kubeadmapi.APIEndpoint) { + for _, v := range config.Clusters { + v.Server = fmt.Sprintf("https://%s", + net.JoinHostPort( + lae.AdvertiseAddress, + strconv.Itoa(int(lae.BindPort)), + ), + ) + } +} + // HasAuthenticationCredentials returns true if the current user has valid authentication credentials for // token authentication, basic authentication or X509 authentication func HasAuthenticationCredentials(config *clientcmdapi.Config) bool {