Merge pull request #41897 from luxas/kubeadm_secure_controlplane

Automatic merge from submit-queue (batch tested with PRs 41701, 41818, 41897, 41119, 41562)

kubeadm: Secure the control plane communication and add the kubeconfig phase command

**What this PR does / why we need it**:

This generates kubeconfig files for the controller-manager and the scheduler, ref: https://github.com/kubernetes/kubeadm/issues/172

The second commit adds the `kubeadm alpha phase kubeconfig` command as described in the design doc: https://github.com/kubernetes/kubeadm/pull/156

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #

**Special notes for your reviewer**:

@dmmcquay What kind of tests would you like for the kubeconfig phase command?

**Release note**:

```release-note
```
@jbeda @mikedanese @dmmcquay @pires @liggitt @deads2k @errordeveloper
pull/6/head
Kubernetes Submit Queue 2017-02-26 14:02:52 -08:00 committed by GitHub
commit b2765427a2
13 changed files with 339 additions and 53 deletions

View File

@ -24,6 +24,7 @@ go_library(
"//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/cmd/phases:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/discovery:go_default_library", "//cmd/kubeadm/app/discovery:go_default_library",
"//cmd/kubeadm/app/master:go_default_library", "//cmd/kubeadm/app/master:go_default_library",
@ -78,6 +79,9 @@ filegroup(
filegroup( filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [":package-srcs"], srcs = [
":package-srcs",
"//cmd/kubeadm/app/cmd/phases:all-srcs",
],
tags = ["automanaged"], tags = ["automanaged"],
) )

View File

@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apiserver/pkg/util/flag" "k8s.io/apiserver/pkg/util/flag"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
) )
@ -88,6 +89,7 @@ func NewKubeadmCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
Short: "Experimental sub-commands not yet fully functional.", Short: "Experimental sub-commands not yet fully functional.",
} }
experimentalCmd.AddCommand(NewCmdToken(out, err)) experimentalCmd.AddCommand(NewCmdToken(out, err))
experimentalCmd.AddCommand(phases.NewCmdPhase(out))
cmds.AddCommand(experimentalCmd) cmds.AddCommand(experimentalCmd)
return cmds return cmds

View File

@ -190,7 +190,7 @@ func (i *Init) Run(out io.Writer) error {
// so we'll pick the first one, there is much of chance to have an empty // so we'll pick the first one, there is much of chance to have an empty
// slice by the time this gets called // slice by the time this gets called
masterEndpoint := fmt.Sprintf("https://%s:%d", i.cfg.API.AdvertiseAddresses[0], i.cfg.API.Port) masterEndpoint := fmt.Sprintf("https://%s:%d", i.cfg.API.AdvertiseAddresses[0], i.cfg.API.Port)
err = kubeconfigphase.CreateAdminAndKubeletKubeConfig(masterEndpoint, kubeadmapi.GlobalEnvParams.HostPKIPath, kubeadmapi.GlobalEnvParams.KubernetesDir) err = kubeconfigphase.CreateInitKubeConfigFiles(masterEndpoint, kubeadmapi.GlobalEnvParams.HostPKIPath, kubeadmapi.GlobalEnvParams.KubernetesDir)
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,36 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"kubeconfig.go",
"phase.go",
],
tags = ["automanaged"],
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//vendor:github.com/spf13/cobra",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,119 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package phases
import (
"fmt"
"io"
"github.com/spf13/cobra"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
func NewCmdKubeConfig(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "kubeconfig",
Short: "Create KubeConfig files from given credentials.",
RunE: subCmdRunE("kubeconfig"),
}
cmd.AddCommand(NewCmdToken(out))
cmd.AddCommand(NewCmdClientCerts(out))
return cmd
}
func NewCmdToken(out io.Writer) *cobra.Command {
config := &kubeconfigphase.BuildConfigProperties{
MakeClientCerts: false,
}
cmd := &cobra.Command{
Use: "token",
Short: "Output a valid KubeConfig file to STDOUT with a token as the authentication method.",
Run: func(cmd *cobra.Command, args []string) {
err := RunCreateWithToken(out, config)
kubeadmutil.CheckErr(err)
},
}
addCommonFlags(cmd, config)
cmd.Flags().StringVar(&config.Token, "token", "", "The path to the directory where the certificates are.")
return cmd
}
func NewCmdClientCerts(out io.Writer) *cobra.Command {
config := &kubeconfigphase.BuildConfigProperties{
MakeClientCerts: true,
}
cmd := &cobra.Command{
Use: "client-certs",
Short: "Output a valid KubeConfig file to STDOUT with a client certificates as the authentication method.",
Run: func(cmd *cobra.Command, args []string) {
err := RunCreateWithClientCerts(out, config)
kubeadmutil.CheckErr(err)
},
}
addCommonFlags(cmd, config)
cmd.Flags().StringSliceVar(&config.Organization, "organization", []string{}, "The organization (group) the certificate should be in.")
return cmd
}
func addCommonFlags(cmd *cobra.Command, config *kubeconfigphase.BuildConfigProperties) {
cmd.Flags().StringVar(&config.CertDir, "cert-dir", kubeadmconstants.DefaultCertDir, "The path to the directory where the certificates are.")
cmd.Flags().StringVar(&config.ClientName, "client-name", "", "The name of the client for which the KubeConfig file will be generated.")
cmd.Flags().StringVar(&config.APIServer, "server", "", "The location of the api server.")
}
func validateCommonFlags(config *kubeconfigphase.BuildConfigProperties) error {
if len(config.ClientName) == 0 {
return fmt.Errorf("The --client-name flag is required")
}
if len(config.APIServer) == 0 {
return fmt.Errorf("The --server flag is required")
}
return nil
}
// RunCreateWithToken generates a kubeconfig file from with a token as the authentication mechanism
func RunCreateWithToken(out io.Writer, config *kubeconfigphase.BuildConfigProperties) error {
if len(config.Token) == 0 {
return fmt.Errorf("The --token flag is required")
}
if err := validateCommonFlags(config); err != nil {
return err
}
kubeConfigBytes, err := kubeconfigphase.GetKubeConfigBytesFromSpec(*config)
if err != nil {
return err
}
fmt.Fprintln(out, string(kubeConfigBytes))
return nil
}
// RunCreateWithClientCerts generates a kubeconfig file from with client certs as the authentication mechanism
func RunCreateWithClientCerts(out io.Writer, config *kubeconfigphase.BuildConfigProperties) error {
if err := validateCommonFlags(config); err != nil {
return err
}
kubeConfigBytes, err := kubeconfigphase.GetKubeConfigBytesFromSpec(*config)
if err != nil {
return err
}
fmt.Fprintln(out, string(kubeConfigBytes))
return nil
}

View File

@ -0,0 +1,49 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package phases
import (
"fmt"
"io"
"github.com/spf13/cobra"
)
func NewCmdPhase(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "phase",
Short: "Invoke subsets of kubeadm functions separately for a manual install.",
RunE: subCmdRunE("phase"),
}
cmd.AddCommand(NewCmdKubeConfig(out))
return cmd
}
// subCmdRunE returns a function that handles a case where a subcommand must be specified
// Without this callback, if a user runs just the command without a subcommand,
// or with an invalid subcommand, cobra will print usage information, but still exit cleanly.
// We want to return an error code in these cases so that the
// user knows that their command was invalid.
func subCmdRunE(name string) func(*cobra.Command, []string) error {
return func(_ *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("missing subcommand; %q is not meant to be run on its own", name)
} else {
return fmt.Errorf("invalid subcommand: %q", args[0])
}
}
}

View File

@ -223,6 +223,8 @@ func resetConfigDir(configPathDir, pkiPathDir string) {
filesToClean := []string{ filesToClean := []string{
filepath.Join(configPathDir, kubeadmconstants.AdminKubeConfigFileName), filepath.Join(configPathDir, kubeadmconstants.AdminKubeConfigFileName),
filepath.Join(configPathDir, kubeadmconstants.KubeletKubeConfigFileName), filepath.Join(configPathDir, kubeadmconstants.KubeletKubeConfigFileName),
filepath.Join(configPathDir, kubeadmconstants.ControllerManagerKubeConfigFileName),
filepath.Join(configPathDir, kubeadmconstants.SchedulerKubeConfigFileName),
} }
fmt.Printf("[reset] Deleting files: %v\n", filesToClean) fmt.Printf("[reset] Deleting files: %v\n", filesToClean)
for _, path := range filesToClean { for _, path := range filesToClean {

View File

@ -51,12 +51,23 @@ const (
FrontProxyClientCertName = "front-proxy-client.crt" FrontProxyClientCertName = "front-proxy-client.crt"
FrontProxyClientKeyName = "front-proxy-client.key" FrontProxyClientKeyName = "front-proxy-client.key"
AdminKubeConfigFileName = "admin.conf" AdminKubeConfigFileName = "admin.conf"
KubeletKubeConfigFileName = "kubelet.conf" KubeletKubeConfigFileName = "kubelet.conf"
ControllerManagerKubeConfigFileName = "controller-manager.conf"
SchedulerKubeConfigFileName = "scheduler.conf"
DefaultCertDir = "/etc/kubernetes/pki"
// Important: a "v"-prefix shouldn't exist here; semver doesn't allow that // Important: a "v"-prefix shouldn't exist here; semver doesn't allow that
MinimumControlPlaneVersion = "1.6.0-alpha.2" MinimumControlPlaneVersion = "1.6.0-alpha.2"
// Some well-known users and groups in the core Kubernetes authorization system
ControllerManagerUser = "system:kube-controller-manager"
SchedulerUser = "system:kube-scheduler"
MastersGroup = "system:masters"
NodesGroup = "system:nodes"
// Constants for what we name our ServiceAccounts with limited access to the cluster in case of RBAC // Constants for what we name our ServiceAccounts with limited access to the cluster in case of RBAC
KubeDNSServiceAccountName = "kube-dns" KubeDNSServiceAccountName = "kube-dns"
KubeProxyServiceAccountName = "kube-proxy" KubeProxyServiceAccountName = "kube-proxy"

View File

@ -91,10 +91,11 @@ func WriteStaticPodManifests(cfg *kubeadmapi.MasterConfiguration) error {
Name: kubeScheduler, Name: kubeScheduler,
Image: images.GetCoreImage(images.KubeSchedulerImage, cfg, kubeadmapi.GlobalEnvParams.HyperkubeImage), Image: images.GetCoreImage(images.KubeSchedulerImage, cfg, kubeadmapi.GlobalEnvParams.HyperkubeImage),
Command: getSchedulerCommand(cfg, false), Command: getSchedulerCommand(cfg, false),
VolumeMounts: []api.VolumeMount{k8sVolumeMount()},
LivenessProbe: componentProbe(10251, "/healthz"), LivenessProbe: componentProbe(10251, "/healthz"),
Resources: componentResources("100m"), Resources: componentResources("100m"),
Env: getProxyEnvVars(), Env: getProxyEnvVars(),
}), }, k8sVolume(cfg)),
} }
// Add etcd static pod spec only if external etcd is not configured // Add etcd static pod spec only if external etcd is not configured
@ -378,7 +379,7 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted
command = append(getComponentBaseCommand(controllerManager), command = append(getComponentBaseCommand(controllerManager),
"--address=127.0.0.1", "--address=127.0.0.1",
"--leader-elect", "--leader-elect",
"--master=127.0.0.1:8080", "--kubeconfig="+path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName),
"--root-ca-file="+getCertFilePath(kubeadmconstants.CACertName), "--root-ca-file="+getCertFilePath(kubeadmconstants.CACertName),
"--service-account-private-key-file="+getCertFilePath(kubeadmconstants.ServiceAccountPrivateKeyName), "--service-account-private-key-file="+getCertFilePath(kubeadmconstants.ServiceAccountPrivateKeyName),
"--cluster-signing-cert-file="+getCertFilePath(kubeadmconstants.CACertName), "--cluster-signing-cert-file="+getCertFilePath(kubeadmconstants.CACertName),
@ -416,7 +417,7 @@ func getSchedulerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted bool) [
command = append(getComponentBaseCommand(scheduler), command = append(getComponentBaseCommand(scheduler),
"--address=127.0.0.1", "--address=127.0.0.1",
"--leader-elect", "--leader-elect",
"--master=127.0.0.1:8080", "--kubeconfig="+path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName),
) )
return command return command

View File

@ -483,7 +483,7 @@ func TestGetControllerManagerCommand(t *testing.T) {
"kube-controller-manager", "kube-controller-manager",
"--address=127.0.0.1", "--address=127.0.0.1",
"--leader-elect", "--leader-elect",
"--master=127.0.0.1:8080", "--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/controller-manager.conf",
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt", "--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key", "--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key",
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt", "--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
@ -498,7 +498,7 @@ func TestGetControllerManagerCommand(t *testing.T) {
"kube-controller-manager", "kube-controller-manager",
"--address=127.0.0.1", "--address=127.0.0.1",
"--leader-elect", "--leader-elect",
"--master=127.0.0.1:8080", "--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/controller-manager.conf",
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt", "--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key", "--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key",
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt", "--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
@ -514,7 +514,7 @@ func TestGetControllerManagerCommand(t *testing.T) {
"kube-controller-manager", "kube-controller-manager",
"--address=127.0.0.1", "--address=127.0.0.1",
"--leader-elect", "--leader-elect",
"--master=127.0.0.1:8080", "--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/controller-manager.conf",
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt", "--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key", "--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key",
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt", "--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
@ -552,7 +552,7 @@ func TestGetSchedulerCommand(t *testing.T) {
"kube-scheduler", "kube-scheduler",
"--address=127.0.0.1", "--address=127.0.0.1",
"--leader-elect", "--leader-elect",
"--master=127.0.0.1:8080", "--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/scheduler.conf",
}, },
}, },
} }

View File

@ -151,7 +151,7 @@ func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration, pkiDir string) error {
// TODO: Add a test case to verify that this cert has the x509.ExtKeyUsageClientAuth flag // TODO: Add a test case to verify that this cert has the x509.ExtKeyUsageClientAuth flag
config := certutil.Config{ config := certutil.Config{
CommonName: "kube-apiserver-kubelet-client", CommonName: "kube-apiserver-kubelet-client",
Organization: []string{"system:masters"}, Organization: []string{kubeadmconstants.MastersGroup},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
} }
apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(caCert, caKey, config) apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)

View File

@ -18,7 +18,6 @@ package kubeconfig
import ( import (
"bytes" "bytes"
"crypto/rsa"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"os" "os"
@ -32,6 +31,16 @@ import (
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
) )
// BuildConfigProperties holds some simple information about how this phase should build the KubeConfig object
type BuildConfigProperties struct {
CertDir string
ClientName string
Organization []string
APIServer string
Token string
MakeClientCerts bool
}
// TODO: Make an integration test for this function that runs after the certificates phase // TODO: Make an integration test for this function that runs after the certificates phase
// and makes sure that those two phases work well together... // and makes sure that those two phases work well together...
@ -42,61 +51,113 @@ import (
// /etc/kubernetes/{admin,kubelet}.conf exist but the CA cert doesn't match what's in the pki dir => error // /etc/kubernetes/{admin,kubelet}.conf exist but the CA cert doesn't match what's in the pki dir => error
// /etc/kubernetes/{admin,kubelet}.conf exist but not certs => certs will be generated and conflict with the kubeconfig files => error // /etc/kubernetes/{admin,kubelet}.conf exist but not certs => certs will be generated and conflict with the kubeconfig files => error
// CreateAdminAndKubeletKubeConfig is called from the main init and does the work for the default phase behaviour // CreateInitKubeConfigFiles is called from the main init and does the work for the default phase behaviour
func CreateAdminAndKubeletKubeConfig(masterEndpoint, pkiDir, outDir string) error { func CreateInitKubeConfigFiles(masterEndpoint, pkiDir, outDir string) error {
// Try to load ca.crt and ca.key from the PKI directory hostname, err := os.Hostname()
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.CACertAndKeyBaseName)
if err != nil { if err != nil {
return fmt.Errorf("couldn't create a kubeconfig; the CA files couldn't be loaded: %v", err) return err
} }
// User admin should have full access to the cluster // Create a lightweight specification for what the files should look like
// TODO: Add test case that make sure this cert has the x509.ExtKeyUsageClientAuth flag filesToCreateFromSpec := map[string]BuildConfigProperties{
adminCertConfig := certutil.Config{ kubeadmconstants.AdminKubeConfigFileName: {
CommonName: "kubernetes-admin", ClientName: "kubernetes-admin",
Organization: []string{"system:masters"}, APIServer: masterEndpoint,
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, CertDir: pkiDir,
} Organization: []string{kubeadmconstants.MastersGroup},
adminKubeConfigFilePath := filepath.Join(outDir, kubeadmconstants.AdminKubeConfigFileName) MakeClientCerts: true,
if err := createKubeConfigFileForClient(masterEndpoint, adminKubeConfigFilePath, adminCertConfig, caCert, caKey); err != nil { },
return fmt.Errorf("couldn't create config for the admin: %v", err) kubeadmconstants.KubeletKubeConfigFileName: {
ClientName: fmt.Sprintf("system:node:%s", hostname),
APIServer: masterEndpoint,
CertDir: pkiDir,
Organization: []string{kubeadmconstants.NodesGroup},
MakeClientCerts: true,
},
kubeadmconstants.ControllerManagerKubeConfigFileName: {
ClientName: kubeadmconstants.ControllerManagerUser,
APIServer: masterEndpoint,
CertDir: pkiDir,
MakeClientCerts: true,
},
kubeadmconstants.SchedulerKubeConfigFileName: {
ClientName: kubeadmconstants.SchedulerUser,
APIServer: masterEndpoint,
CertDir: pkiDir,
MakeClientCerts: true,
},
} }
// TODO: The kubelet should have limited access to the cluster. Right now, this gives kubelet basically root access // Loop through all specs for kubeconfig files and create them if necessary
// and we do need that in the bootstrap phase, but we should swap it out after the control plane is up for filename, config := range filesToCreateFromSpec {
// TODO: Add test case that make sure this cert has the x509.ExtKeyUsageClientAuth flag kubeconfig, err := buildKubeConfig(config)
kubeletCertConfig := certutil.Config{ if err != nil {
CommonName: "kubelet", return err
Organization: []string{"system:nodes"}, }
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
} kubeConfigFilePath := filepath.Join(outDir, filename)
kubeletKubeConfigFilePath := filepath.Join(outDir, kubeadmconstants.KubeletKubeConfigFileName) err = writeKubeconfigToDiskIfNotExists(kubeConfigFilePath, kubeconfig)
if err := createKubeConfigFileForClient(masterEndpoint, kubeletKubeConfigFilePath, kubeletCertConfig, caCert, caKey); err != nil { if err != nil {
return fmt.Errorf("couldn't create a kubeconfig file for the kubelet: %v", err) return err
}
} }
// TODO make credentials for the controller-manager and scheduler
return nil return nil
} }
func createKubeConfigFileForClient(masterEndpoint, kubeConfigFilePath string, config certutil.Config, caCert *x509.Certificate, caKey *rsa.PrivateKey) error { // GetKubeConfigBytesFromSpec takes properties how to build a KubeConfig file and then returns the bytes of that file
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, config) func GetKubeConfigBytesFromSpec(config BuildConfigProperties) ([]byte, error) {
kubeconfig, err := buildKubeConfig(config)
if err != nil { if err != nil {
return fmt.Errorf("failure while creating %s client certificate [%v]", config.CommonName, err) return []byte{}, err
} }
kubeconfig := kubeconfigutil.CreateWithCerts( kubeConfigBytes, err := clientcmd.Write(*kubeconfig)
masterEndpoint, if err != nil {
"kubernetes", return []byte{}, err
config.CommonName, }
certutil.EncodeCertPEM(caCert), return kubeConfigBytes, nil
certutil.EncodePrivateKeyPEM(key), }
certutil.EncodeCertPEM(cert),
)
// Write it now to a file if there already isn't a valid one // buildKubeConfig creates a kubeconfig object from some commonly specified properties in the struct above
return writeKubeconfigToDiskIfNotExists(kubeConfigFilePath, kubeconfig) func buildKubeConfig(config BuildConfigProperties) (*clientcmdapi.Config, error) {
// Try to load ca.crt and ca.key from the PKI directory
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(config.CertDir, kubeadmconstants.CACertAndKeyBaseName)
if err != nil {
return nil, fmt.Errorf("couldn't create a kubeconfig; the CA files couldn't be loaded: %v", err)
}
// If this file should have client certs, generate one from the spec
if config.MakeClientCerts {
certConfig := certutil.Config{
CommonName: config.ClientName,
Organization: config.Organization,
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, certConfig)
if err != nil {
return nil, fmt.Errorf("failure while creating %s client certificate [%v]", certConfig.CommonName, err)
}
return kubeconfigutil.CreateWithCerts(
config.APIServer,
"kubernetes",
config.ClientName,
certutil.EncodeCertPEM(caCert),
certutil.EncodePrivateKeyPEM(key),
certutil.EncodeCertPEM(cert),
), nil
}
// otherwise, create a kubeconfig with a token
return kubeconfigutil.CreateWithToken(
config.APIServer,
"kubernetes",
config.ClientName,
certutil.EncodeCertPEM(caCert),
config.Token,
), nil
} }
// writeKubeconfigToDiskIfNotExists saves the KubeConfig struct to disk if there isn't any file at the given path // writeKubeconfigToDiskIfNotExists saves the KubeConfig struct to disk if there isn't any file at the given path

View File

@ -74,6 +74,7 @@ cleanup-iptables
client-ca-file client-ca-file
client-certificate client-certificate
client-key client-key
client-name
clientset-api-path clientset-api-path
clientset-name clientset-name
clientset-only clientset-only