mirror of https://github.com/k3s-io/k3s
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 @errordeveloperpull/6/head
commit
b2765427a2
|
@ -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"],
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"],
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue