From 42cb8c8cb0340d1bfeb96dfb81cfeba277fc356d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 23 Feb 2017 21:28:03 +0200 Subject: [PATCH 1/3] kubeadm: Generate kubeconfig files for controller-manager and scheduler and use them; secures the control plane communication --- cmd/kubeadm/app/constants/constants.go | 15 +- cmd/kubeadm/app/master/manifests.go | 7 +- cmd/kubeadm/app/master/manifests_test.go | 8 +- cmd/kubeadm/app/phases/certs/certs.go | 2 +- .../app/phases/kubeconfig/kubeconfig.go | 141 +++++++++++++----- 5 files changed, 122 insertions(+), 51 deletions(-) diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index a7b4b70151..93a7a5b9e5 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -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" diff --git a/cmd/kubeadm/app/master/manifests.go b/cmd/kubeadm/app/master/manifests.go index 03d0dead9d..925303720b 100644 --- a/cmd/kubeadm/app/master/manifests.go +++ b/cmd/kubeadm/app/master/manifests.go @@ -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 diff --git a/cmd/kubeadm/app/master/manifests_test.go b/cmd/kubeadm/app/master/manifests_test.go index 375ecb244a..b469553dbd 100644 --- a/cmd/kubeadm/app/master/manifests_test.go +++ b/cmd/kubeadm/app/master/manifests_test.go @@ -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", }, }, } diff --git a/cmd/kubeadm/app/phases/certs/certs.go b/cmd/kubeadm/app/phases/certs/certs.go index abba646d45..1311035a9f 100644 --- a/cmd/kubeadm/app/phases/certs/certs.go +++ b/cmd/kubeadm/app/phases/certs/certs.go @@ -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) diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index 5c2caac16a..d04879e2a7 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -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 From 69c24afc20cfaf56f9a6ee6249c83ce6746ed9c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 23 Feb 2017 21:28:18 +0200 Subject: [PATCH 2/3] kubeadm: Add a 'kubeadm alpha phase kubeconfig command' --- cmd/kubeadm/app/cmd/BUILD | 6 +- cmd/kubeadm/app/cmd/cmd.go | 2 + cmd/kubeadm/app/cmd/init.go | 2 +- cmd/kubeadm/app/cmd/phases/BUILD | 36 +++++++ cmd/kubeadm/app/cmd/phases/kubeconfig.go | 119 +++++++++++++++++++++++ cmd/kubeadm/app/cmd/phases/phase.go | 49 ++++++++++ cmd/kubeadm/app/cmd/reset.go | 2 + hack/verify-flags/known-flags.txt | 1 + 8 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 cmd/kubeadm/app/cmd/phases/BUILD create mode 100644 cmd/kubeadm/app/cmd/phases/kubeconfig.go create mode 100644 cmd/kubeadm/app/cmd/phases/phase.go diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 83d8e2b9db..34aea5b735 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -24,6 +24,7 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1: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/discovery:go_default_library", "//cmd/kubeadm/app/master:go_default_library", @@ -78,6 +79,9 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//cmd/kubeadm/app/cmd/phases:all-srcs", + ], tags = ["automanaged"], ) diff --git a/cmd/kubeadm/app/cmd/cmd.go b/cmd/kubeadm/app/cmd/cmd.go index acc753c371..65e07b7f57 100644 --- a/cmd/kubeadm/app/cmd/cmd.go +++ b/cmd/kubeadm/app/cmd/cmd.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "k8s.io/apiserver/pkg/util/flag" + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases" 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.", } experimentalCmd.AddCommand(NewCmdToken(out, err)) + experimentalCmd.AddCommand(phases.NewCmdPhase(out)) cmds.AddCommand(experimentalCmd) return cmds diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 1da11c70ca..2bd0ff1212 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -197,7 +197,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 // slice by the time this gets called 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 { return err } diff --git a/cmd/kubeadm/app/cmd/phases/BUILD b/cmd/kubeadm/app/cmd/phases/BUILD new file mode 100644 index 0000000000..21bdfd8e5d --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/BUILD @@ -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"], +) diff --git a/cmd/kubeadm/app/cmd/phases/kubeconfig.go b/cmd/kubeadm/app/cmd/phases/kubeconfig.go new file mode 100644 index 0000000000..af38df0c44 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/kubeconfig.go @@ -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.KubeConfigProperties{ + 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.KubeConfigProperties{ + 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.KubeConfigProperties) { + 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.KubeConfigProperties) 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.KubeConfigProperties) 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.KubeConfigProperties) 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 +} diff --git a/cmd/kubeadm/app/cmd/phases/phase.go b/cmd/kubeadm/app/cmd/phases/phase.go new file mode 100644 index 0000000000..2ef206a1a9 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/phase.go @@ -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]) + } + } +} diff --git a/cmd/kubeadm/app/cmd/reset.go b/cmd/kubeadm/app/cmd/reset.go index 4df78e00d7..3721af2f18 100644 --- a/cmd/kubeadm/app/cmd/reset.go +++ b/cmd/kubeadm/app/cmd/reset.go @@ -223,6 +223,8 @@ func resetConfigDir(configPathDir, pkiPathDir string) { filesToClean := []string{ filepath.Join(configPathDir, kubeadmconstants.AdminKubeConfigFileName), filepath.Join(configPathDir, kubeadmconstants.KubeletKubeConfigFileName), + filepath.Join(configPathDir, kubeadmconstants.ControllerManagerKubeConfigFileName), + filepath.Join(configPathDir, kubeadmconstants.SchedulerKubeConfigFileName), } fmt.Printf("[reset] Deleting files: %v\n", filesToClean) for _, path := range filesToClean { diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index 1a18d7dd9a..4d4f40ef9f 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -72,6 +72,7 @@ cleanup-iptables client-ca-file client-certificate client-key +client-name clientset-api-path clientset-name clientset-only From 036463dd17ac20d459dc4143f5c08368766500ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Thu, 23 Feb 2017 23:44:46 +0200 Subject: [PATCH 3/3] Fix golint issues --- cmd/kubeadm/app/cmd/phases/kubeconfig.go | 12 ++++++------ cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go | 10 ++++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cmd/kubeadm/app/cmd/phases/kubeconfig.go b/cmd/kubeadm/app/cmd/phases/kubeconfig.go index af38df0c44..47583e3fbe 100644 --- a/cmd/kubeadm/app/cmd/phases/kubeconfig.go +++ b/cmd/kubeadm/app/cmd/phases/kubeconfig.go @@ -40,7 +40,7 @@ func NewCmdKubeConfig(out io.Writer) *cobra.Command { } func NewCmdToken(out io.Writer) *cobra.Command { - config := &kubeconfigphase.KubeConfigProperties{ + config := &kubeconfigphase.BuildConfigProperties{ MakeClientCerts: false, } cmd := &cobra.Command{ @@ -57,7 +57,7 @@ func NewCmdToken(out io.Writer) *cobra.Command { } func NewCmdClientCerts(out io.Writer) *cobra.Command { - config := &kubeconfigphase.KubeConfigProperties{ + config := &kubeconfigphase.BuildConfigProperties{ MakeClientCerts: true, } cmd := &cobra.Command{ @@ -73,13 +73,13 @@ func NewCmdClientCerts(out io.Writer) *cobra.Command { return cmd } -func addCommonFlags(cmd *cobra.Command, config *kubeconfigphase.KubeConfigProperties) { +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.KubeConfigProperties) error { +func validateCommonFlags(config *kubeconfigphase.BuildConfigProperties) error { if len(config.ClientName) == 0 { return fmt.Errorf("The --client-name flag is required") } @@ -90,7 +90,7 @@ func validateCommonFlags(config *kubeconfigphase.KubeConfigProperties) error { } // RunCreateWithToken generates a kubeconfig file from with a token as the authentication mechanism -func RunCreateWithToken(out io.Writer, config *kubeconfigphase.KubeConfigProperties) error { +func RunCreateWithToken(out io.Writer, config *kubeconfigphase.BuildConfigProperties) error { if len(config.Token) == 0 { return fmt.Errorf("The --token flag is required") } @@ -106,7 +106,7 @@ func RunCreateWithToken(out io.Writer, config *kubeconfigphase.KubeConfigPropert } // RunCreateWithClientCerts generates a kubeconfig file from with client certs as the authentication mechanism -func RunCreateWithClientCerts(out io.Writer, config *kubeconfigphase.KubeConfigProperties) error { +func RunCreateWithClientCerts(out io.Writer, config *kubeconfigphase.BuildConfigProperties) error { if err := validateCommonFlags(config); err != nil { return err } diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index d04879e2a7..652b79e125 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -31,7 +31,8 @@ import ( kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" ) -type KubeConfigProperties struct { +// BuildConfigProperties holds some simple information about how this phase should build the KubeConfig object +type BuildConfigProperties struct { CertDir string ClientName string Organization []string @@ -59,7 +60,7 @@ func CreateInitKubeConfigFiles(masterEndpoint, pkiDir, outDir string) error { } // Create a lightweight specification for what the files should look like - filesToCreateFromSpec := map[string]KubeConfigProperties{ + filesToCreateFromSpec := map[string]BuildConfigProperties{ kubeadmconstants.AdminKubeConfigFileName: { ClientName: "kubernetes-admin", APIServer: masterEndpoint, @@ -105,7 +106,8 @@ func CreateInitKubeConfigFiles(masterEndpoint, pkiDir, outDir string) error { return nil } -func GetKubeConfigBytesFromSpec(config KubeConfigProperties) ([]byte, error) { +// GetKubeConfigBytesFromSpec takes properties how to build a KubeConfig file and then returns the bytes of that file +func GetKubeConfigBytesFromSpec(config BuildConfigProperties) ([]byte, error) { kubeconfig, err := buildKubeConfig(config) if err != nil { return []byte{}, err @@ -119,7 +121,7 @@ func GetKubeConfigBytesFromSpec(config KubeConfigProperties) ([]byte, error) { } // buildKubeConfig creates a kubeconfig object from some commonly specified properties in the struct above -func buildKubeConfig(config KubeConfigProperties) (*clientcmdapi.Config, error) { +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)