kubeadm: Generate kubeconfig files for controller-manager and scheduler and use them; secures the control plane communication

This commit is contained in:
Lucas Käldström 2017-02-23 21:28:03 +02:00
parent 17375fc59f
commit 42cb8c8cb0
No known key found for this signature in database
GPG Key ID: 3FA3783D77751514
5 changed files with 122 additions and 51 deletions

View File

@ -49,12 +49,23 @@ const (
FrontProxyClientCertName = "front-proxy-client.crt"
FrontProxyClientKeyName = "front-proxy-client.key"
AdminKubeConfigFileName = "admin.conf"
KubeletKubeConfigFileName = "kubelet.conf"
AdminKubeConfigFileName = "admin.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
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
KubeDNSServiceAccountName = "kube-dns"
KubeProxyServiceAccountName = "kube-proxy"

View File

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

View File

@ -483,7 +483,7 @@ func TestGetControllerManagerCommand(t *testing.T) {
"kube-controller-manager",
"--address=127.0.0.1",
"--leader-elect",
"--master=127.0.0.1:8080",
"--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/controller-manager.conf",
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key",
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
@ -498,7 +498,7 @@ func TestGetControllerManagerCommand(t *testing.T) {
"kube-controller-manager",
"--address=127.0.0.1",
"--leader-elect",
"--master=127.0.0.1:8080",
"--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/controller-manager.conf",
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key",
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
@ -514,7 +514,7 @@ func TestGetControllerManagerCommand(t *testing.T) {
"kube-controller-manager",
"--address=127.0.0.1",
"--leader-elect",
"--master=127.0.0.1:8080",
"--kubeconfig=" + kubeadmapi.GlobalEnvParams.KubernetesDir + "/controller-manager.conf",
"--root-ca-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
"--service-account-private-key-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/sa.key",
"--cluster-signing-cert-file=" + kubeadmapi.GlobalEnvParams.HostPKIPath + "/ca.crt",
@ -552,7 +552,7 @@ func TestGetSchedulerCommand(t *testing.T) {
"kube-scheduler",
"--address=127.0.0.1",
"--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
config := certutil.Config{
CommonName: "kube-apiserver-kubelet-client",
Organization: []string{"system:masters"},
Organization: []string{kubeadmconstants.MastersGroup},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)

View File

@ -18,7 +18,6 @@ package kubeconfig
import (
"bytes"
"crypto/rsa"
"crypto/x509"
"fmt"
"os"
@ -32,6 +31,15 @@ import (
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
)
type KubeConfigProperties 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
// and makes sure that those two phases work well together...
@ -42,61 +50,112 @@ 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 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
func CreateAdminAndKubeletKubeConfig(masterEndpoint, pkiDir, outDir string) error {
// CreateInitKubeConfigFiles is called from the main init and does the work for the default phase behaviour
func CreateInitKubeConfigFiles(masterEndpoint, pkiDir, outDir string) error {
// Try to load ca.crt and ca.key from the PKI directory
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, kubeadmconstants.CACertAndKeyBaseName)
hostname, err := os.Hostname()
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
// TODO: Add test case that make sure this cert has the x509.ExtKeyUsageClientAuth flag
adminCertConfig := certutil.Config{
CommonName: "kubernetes-admin",
Organization: []string{"system:masters"},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
adminKubeConfigFilePath := filepath.Join(outDir, kubeadmconstants.AdminKubeConfigFileName)
if err := createKubeConfigFileForClient(masterEndpoint, adminKubeConfigFilePath, adminCertConfig, caCert, caKey); err != nil {
return fmt.Errorf("couldn't create config for the admin: %v", err)
// Create a lightweight specification for what the files should look like
filesToCreateFromSpec := map[string]KubeConfigProperties{
kubeadmconstants.AdminKubeConfigFileName: {
ClientName: "kubernetes-admin",
APIServer: masterEndpoint,
CertDir: pkiDir,
Organization: []string{kubeadmconstants.MastersGroup},
MakeClientCerts: true,
},
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
// and we do need that in the bootstrap phase, but we should swap it out after the control plane is up
// TODO: Add test case that make sure this cert has the x509.ExtKeyUsageClientAuth flag
kubeletCertConfig := certutil.Config{
CommonName: "kubelet",
Organization: []string{"system:nodes"},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
kubeletKubeConfigFilePath := filepath.Join(outDir, kubeadmconstants.KubeletKubeConfigFileName)
if err := createKubeConfigFileForClient(masterEndpoint, kubeletKubeConfigFilePath, kubeletCertConfig, caCert, caKey); err != nil {
return fmt.Errorf("couldn't create a kubeconfig file for the kubelet: %v", err)
// Loop through all specs for kubeconfig files and create them if necessary
for filename, config := range filesToCreateFromSpec {
kubeconfig, err := buildKubeConfig(config)
if err != nil {
return err
}
kubeConfigFilePath := filepath.Join(outDir, filename)
err = writeKubeconfigToDiskIfNotExists(kubeConfigFilePath, kubeconfig)
if err != nil {
return err
}
}
// TODO make credentials for the controller-manager and scheduler
return nil
}
func createKubeConfigFileForClient(masterEndpoint, kubeConfigFilePath string, config certutil.Config, caCert *x509.Certificate, caKey *rsa.PrivateKey) error {
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, config)
func GetKubeConfigBytesFromSpec(config KubeConfigProperties) ([]byte, error) {
kubeconfig, err := buildKubeConfig(config)
if err != nil {
return fmt.Errorf("failure while creating %s client certificate [%v]", config.CommonName, err)
return []byte{}, err
}
kubeconfig := kubeconfigutil.CreateWithCerts(
masterEndpoint,
"kubernetes",
config.CommonName,
certutil.EncodeCertPEM(caCert),
certutil.EncodePrivateKeyPEM(key),
certutil.EncodeCertPEM(cert),
)
kubeConfigBytes, err := clientcmd.Write(*kubeconfig)
if err != nil {
return []byte{}, err
}
return kubeConfigBytes, nil
}
// Write it now to a file if there already isn't a valid one
return writeKubeconfigToDiskIfNotExists(kubeConfigFilePath, kubeconfig)
// buildKubeConfig creates a kubeconfig object from some commonly specified properties in the struct above
func buildKubeConfig(config KubeConfigProperties) (*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