diff --git a/cmd/kubeadm/app/BUILD b/cmd/kubeadm/app/BUILD index 3f3441f41e..7a1263a587 100644 --- a/cmd/kubeadm/app/BUILD +++ b/cmd/kubeadm/app/BUILD @@ -41,6 +41,7 @@ filegroup( "//cmd/kubeadm/app/phases/bootstraptoken/node:all-srcs", "//cmd/kubeadm/app/phases/certs:all-srcs", "//cmd/kubeadm/app/phases/controlplane:all-srcs", + "//cmd/kubeadm/app/phases/copycerts:all-srcs", "//cmd/kubeadm/app/phases/etcd:all-srcs", "//cmd/kubeadm/app/phases/kubeconfig:all-srcs", "//cmd/kubeadm/app/phases/kubelet:all-srcs", @@ -48,7 +49,6 @@ filegroup( "//cmd/kubeadm/app/phases/patchnode:all-srcs", "//cmd/kubeadm/app/phases/selfhosting:all-srcs", "//cmd/kubeadm/app/phases/upgrade:all-srcs", - "//cmd/kubeadm/app/phases/uploadcerts:all-srcs", "//cmd/kubeadm/app/phases/uploadconfig:all-srcs", "//cmd/kubeadm/app/preflight:all-srcs", "//cmd/kubeadm/app/util:all-srcs", diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index af44803a5e..f8331e42bb 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -428,6 +428,7 @@ func isAllowedFlag(flagName string) bool { kubeadmcmdoptions.NodeCRISocket, kubeadmcmdoptions.KubeconfigDir, kubeadmcmdoptions.UploadCerts, + kubeadmcmdoptions.CertificateKey, "print-join-command", "rootfs", "v") if knownFlags.Has(flagName) { return true diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index c4533bb8a4..8376245e74 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -127,6 +127,7 @@ type joinOptions struct { controlPlane bool ignorePreflightErrors []string externalcfg *kubeadmapiv1beta1.JoinConfiguration + certificateKey string } // compile-time assert that the local data object satisfies the phases data interface. @@ -142,6 +143,7 @@ type joinData struct { clientSets map[string]*clientset.Clientset ignorePreflightErrors sets.String outputWriter io.Writer + certificateKey string } // NewCmdJoin returns "kubeadm join" command. @@ -192,7 +194,7 @@ func NewCmdJoin(out io.Writer, joinOptions *joinOptions) *cobra.Command { } addJoinConfigFlags(cmd.Flags(), joinOptions.externalcfg) - addJoinOtherFlags(cmd.Flags(), &joinOptions.cfgPath, &joinOptions.ignorePreflightErrors, &joinOptions.controlPlane, &joinOptions.token) + addJoinOtherFlags(cmd.Flags(), &joinOptions.cfgPath, &joinOptions.ignorePreflightErrors, &joinOptions.controlPlane, &joinOptions.token, &joinOptions.certificateKey) joinRunner.AppendPhase(phases.NewPreflightPhase()) joinRunner.AppendPhase(phases.NewControlPlanePreparePhase()) @@ -254,7 +256,14 @@ func addJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta1.JoinConfig } // addJoinOtherFlags adds join flags that are not bound to a configuration file to the given flagset -func addJoinOtherFlags(flagSet *flag.FlagSet, cfgPath *string, ignorePreflightErrors *[]string, controlPlane *bool, token *string) { +func addJoinOtherFlags( + flagSet *flag.FlagSet, + cfgPath *string, + ignorePreflightErrors *[]string, + controlPlane *bool, + token *string, + certificateKey *string, +) { flagSet.StringVar( cfgPath, options.CfgPath, *cfgPath, "Path to kubeadm config file.", @@ -271,6 +280,10 @@ func addJoinOtherFlags(flagSet *flag.FlagSet, cfgPath *string, ignorePreflightEr controlPlane, options.ControlPlane, *controlPlane, "Create a new control plane instance on this node", ) + flagSet.StringVar( + certificateKey, options.CertificateKey, "", + "Use this key to decrypt the certificate secrets uploaded by init.", + ) } // newJoinOptions returns a struct ready for being used for creating cmd join flags. @@ -375,9 +388,15 @@ func newJoinData(cmd *cobra.Command, args []string, options *joinOptions, out io clientSets: map[string]*clientset.Clientset{}, ignorePreflightErrors: ignorePreflightErrorsSet, outputWriter: out, + certificateKey: options.certificateKey, }, nil } +// CertificateKey returns the key used to encrypt the certs. +func (j *joinData) CertificateKey() string { + return j.certificateKey +} + // Cfg returns the JoinConfiguration. func (j *joinData) Cfg() *kubeadmapi.JoinConfiguration { return j.cfg diff --git a/cmd/kubeadm/app/cmd/options/constant.go b/cmd/kubeadm/app/cmd/options/constant.go index 2386a20edd..f89b6dfab0 100644 --- a/cmd/kubeadm/app/cmd/options/constant.go +++ b/cmd/kubeadm/app/cmd/options/constant.go @@ -121,4 +121,7 @@ const ( // UploadCerts flag instruct kubeadm to upload certificates UploadCerts = "experimental-upload-certs" + + // CertificateKey flag sets the key used to encrypt and decrypt certificate secrets + CertificateKey = "certificate-key" ) diff --git a/cmd/kubeadm/app/cmd/phases/init/BUILD b/cmd/kubeadm/app/cmd/phases/init/BUILD index 22033bbf72..eb9eaf416a 100644 --- a/cmd/kubeadm/app/cmd/phases/init/BUILD +++ b/cmd/kubeadm/app/cmd/phases/init/BUILD @@ -33,12 +33,12 @@ go_library( "//cmd/kubeadm/app/phases/bootstraptoken/node:go_default_library", "//cmd/kubeadm/app/phases/certs:go_default_library", "//cmd/kubeadm/app/phases/controlplane:go_default_library", + "//cmd/kubeadm/app/phases/copycerts:go_default_library", "//cmd/kubeadm/app/phases/etcd:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/phases/kubelet:go_default_library", "//cmd/kubeadm/app/phases/markcontrolplane:go_default_library", "//cmd/kubeadm/app/phases/patchnode:go_default_library", - "//cmd/kubeadm/app/phases/uploadcerts:go_default_library", "//cmd/kubeadm/app/phases/uploadconfig:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/util:go_default_library", diff --git a/cmd/kubeadm/app/cmd/phases/init/uploadcerts.go b/cmd/kubeadm/app/cmd/phases/init/uploadcerts.go index 447f56bf2f..3c0fb5c4b0 100644 --- a/cmd/kubeadm/app/cmd/phases/init/uploadcerts.go +++ b/cmd/kubeadm/app/cmd/phases/init/uploadcerts.go @@ -26,7 +26,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadcerts" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/copycerts" ) // NewUploadCertsPhase returns the uploadCerts phase @@ -59,14 +59,14 @@ func runUploadCerts(c workflow.RunData) error { } if len(data.CertificateKey()) == 0 { - certificateKey, err := uploadcerts.CreateCertificateKey() + certificateKey, err := copycerts.CreateCertificateKey() if err != nil { return err } data.SetCertificateKey(certificateKey) } - if err := uploadcerts.UploadCerts(client, data.Cfg(), data.CertificateKey()); err != nil { + if err := copycerts.UploadCerts(client, data.Cfg(), data.CertificateKey()); err != nil { return errors.Wrap(err, "error uploading certs") } return nil diff --git a/cmd/kubeadm/app/cmd/phases/join/BUILD b/cmd/kubeadm/app/cmd/phases/join/BUILD index 6eb6d73fdb..f9eed0bb11 100644 --- a/cmd/kubeadm/app/cmd/phases/join/BUILD +++ b/cmd/kubeadm/app/cmd/phases/join/BUILD @@ -20,6 +20,7 @@ go_library( "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/phases/certs:go_default_library", "//cmd/kubeadm/app/phases/controlplane:go_default_library", + "//cmd/kubeadm/app/phases/copycerts:go_default_library", "//cmd/kubeadm/app/phases/etcd:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", "//cmd/kubeadm/app/phases/kubelet:go_default_library", diff --git a/cmd/kubeadm/app/cmd/phases/join/controlplaneprepare.go b/cmd/kubeadm/app/cmd/phases/join/controlplaneprepare.go index c006df16aa..858115b5b4 100644 --- a/cmd/kubeadm/app/cmd/phases/join/controlplaneprepare.go +++ b/cmd/kubeadm/app/cmd/phases/join/controlplaneprepare.go @@ -21,13 +21,17 @@ import ( "github.com/pkg/errors" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/klog" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs" "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/copycerts" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" ) // NewControlPlanePreparePhase creates a kubeadm workflow phase that implements the preparation of the node to serve a control plane @@ -35,7 +39,6 @@ func NewControlPlanePreparePhase() workflow.Phase { return workflow.Phase{ Name: "control-plane-prepare", Short: "Prepares the machine for serving a control plane.", - Long: cmdutil.MacroCommandLongDescription, Phases: []workflow.Phase{ { Name: "all", @@ -43,6 +46,7 @@ func NewControlPlanePreparePhase() workflow.Phase { InheritFlags: getControlPlanePreparePhaseFlags(), RunAllSiblings: true, }, + newControlPlanePrepareDownloadCertsSubphase(), newControlPlanePrepareCertsSubphase(), newControlPlanePrepareKubeconfigSubphase(), newControlPlanePrepareManifestsSubphases(), @@ -60,6 +64,20 @@ func getControlPlanePreparePhaseFlags() []string { options.TokenDiscovery, options.TokenDiscoveryCAHash, options.TokenDiscoverySkipCAHash, + options.CertificateKey, + } +} + +func newControlPlanePrepareDownloadCertsSubphase() workflow.Phase { + return workflow.Phase{ + Name: "download-certs", + Short: fmt.Sprintf("Download certificates from %s", kubeadmconstants.KubeadmCertsSecret), + Long: cmdutil.MacroCommandLongDescription, + Run: runControlPlanePrepareDownloadCertsPhaseLocal, + InheritFlags: []string{ + options.CfgPath, + options.CertificateKey, + }, } } @@ -110,6 +128,33 @@ func runControlPlanePrepareManifestsSubphase(c workflow.RunData) error { return controlplane.CreateInitStaticPodManifestFiles(kubeadmconstants.GetStaticPodDirectory(), cfg) } +func runControlPlanePrepareDownloadCertsPhaseLocal(c workflow.RunData) error { + data, ok := c.(JoinData) + if !ok { + return errors.New("download-certs phase invoked with an invalid data struct") + } + + if data.Cfg().ControlPlane == nil || len(data.CertificateKey()) == 0 { + klog.V(1).Infoln("[download-certs] Skipping certs download") + return nil + } + + cfg, err := data.InitCfg() + if err != nil { + return err + } + + client, err := bootstrapClient(data) + if err != nil { + return err + } + + if err := copycerts.DownloadCerts(client, cfg, data.CertificateKey()); err != nil { + return errors.Wrap(err, "error downloading certs") + } + return nil +} + func runControlPlanePrepareCertsPhaseLocal(c workflow.RunData) error { data, ok := c.(JoinData) if !ok { @@ -157,3 +202,15 @@ func runControlPlanePrepareKubeconfigPhaseLocal(c workflow.RunData) error { return nil } + +func bootstrapClient(data JoinData) (clientset.Interface, error) { + tlsBootstrapCfg, err := data.TLSBootstrapCfg() + if err != nil { + return nil, errors.Wrap(err, "unable to access the cluster") + } + client, err := kubeconfigutil.ToClientSet(tlsBootstrapCfg) + if err != nil { + return nil, errors.Wrap(err, "unable to access the cluster") + } + return client, nil +} diff --git a/cmd/kubeadm/app/cmd/phases/join/data.go b/cmd/kubeadm/app/cmd/phases/join/data.go index 1955419a9f..617dea2b00 100644 --- a/cmd/kubeadm/app/cmd/phases/join/data.go +++ b/cmd/kubeadm/app/cmd/phases/join/data.go @@ -28,6 +28,7 @@ import ( // JoinData is the interface to use for join phases. // The "joinData" type from "cmd/join.go" must satisfy this interface. type JoinData interface { + CertificateKey() string Cfg() *kubeadmapi.JoinConfiguration KubeConfigPath() string TLSBootstrapCfg() (*clientcmdapi.Config, error) diff --git a/cmd/kubeadm/app/cmd/phases/join/data_test.go b/cmd/kubeadm/app/cmd/phases/join/data_test.go index 8de201b393..ee10d91186 100644 --- a/cmd/kubeadm/app/cmd/phases/join/data_test.go +++ b/cmd/kubeadm/app/cmd/phases/join/data_test.go @@ -31,6 +31,7 @@ type testJoinData struct{} // testJoinData must satisfy JoinData. var _ JoinData = &testJoinData{} +func (j *testJoinData) CertificateKey() string { return "" } func (j *testJoinData) Cfg() *kubeadmapi.JoinConfiguration { return nil } func (j *testJoinData) KubeConfigPath() string { return "" } func (j *testJoinData) TLSBootstrapCfg() (*clientcmdapi.Config, error) { return nil, nil } diff --git a/cmd/kubeadm/app/cmd/phases/join/preflight.go b/cmd/kubeadm/app/cmd/phases/join/preflight.go index 6a4137f097..a0a43fb387 100644 --- a/cmd/kubeadm/app/cmd/phases/join/preflight.go +++ b/cmd/kubeadm/app/cmd/phases/join/preflight.go @@ -39,7 +39,7 @@ var ( kubeadm join phase preflight --config kubeadm-config.yml `) - notReadyToJoinControPlaneTemp = template.Must(template.New("join").Parse(dedent.Dedent(` + notReadyToJoinControlPlaneTemp = template.Must(template.New("join").Parse(dedent.Dedent(` One or more conditions for hosting a new control plane instance is not satisfied. {{.Error}} @@ -105,14 +105,15 @@ func runPreflight(c workflow.RunData) error { if j.Cfg().ControlPlane != nil { // Checks if the cluster configuration supports // joining a new control plane instance and if all the necessary certificates are provided - if err := checkIfReadyForAdditionalControlPlane(&initCfg.ClusterConfiguration); err != nil { + hasCertificateKey := len(j.CertificateKey()) > 0 + if err := checkIfReadyForAdditionalControlPlane(&initCfg.ClusterConfiguration, hasCertificateKey); err != nil { // outputs the not ready for hosting a new control plane instance message ctx := map[string]string{ "Error": err.Error(), } var msg bytes.Buffer - notReadyToJoinControPlaneTemp.Execute(&msg, ctx) + notReadyToJoinControlPlaneTemp.Execute(&msg, ctx) return errors.New(msg.String()) } @@ -134,15 +135,17 @@ func runPreflight(c workflow.RunData) error { // checkIfReadyForAdditionalControlPlane ensures that the cluster is in a state that supports // joining an additional control plane instance and if the node is ready to preflight -func checkIfReadyForAdditionalControlPlane(initConfiguration *kubeadmapi.ClusterConfiguration) error { +func checkIfReadyForAdditionalControlPlane(initConfiguration *kubeadmapi.ClusterConfiguration, hasCertificateKey bool) error { // blocks if the cluster was created without a stable control plane endpoint if initConfiguration.ControlPlaneEndpoint == "" { return errors.New("unable to add a new control plane instance a cluster that doesn't have a stable controlPlaneEndpoint address") } - // checks if the certificates that must be equal across contolplane instances are provided - if ret, err := certs.SharedCertificateExists(initConfiguration); !ret { - return err + if !hasCertificateKey { + // checks if the certificates that must be equal across controlplane instances are provided + if ret, err := certs.SharedCertificateExists(initConfiguration); !ret { + return err + } } return nil diff --git a/cmd/kubeadm/app/phases/uploadcerts/BUILD b/cmd/kubeadm/app/phases/copycerts/BUILD similarity index 82% rename from cmd/kubeadm/app/phases/uploadcerts/BUILD rename to cmd/kubeadm/app/phases/copycerts/BUILD index 89d6cbc038..09699b6114 100644 --- a/cmd/kubeadm/app/phases/uploadcerts/BUILD +++ b/cmd/kubeadm/app/phases/copycerts/BUILD @@ -2,8 +2,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = ["uploadcerts.go"], - importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadcerts", + srcs = ["copycerts.go"], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/copycerts", visibility = ["//visibility:public"], deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", @@ -14,9 +14,12 @@ go_library( "//pkg/apis/rbac/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/rbac/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", + "//staging/src/k8s.io/client-go/util/cert:go_default_library", + "//staging/src/k8s.io/client-go/util/keyutil:go_default_library", "//staging/src/k8s.io/cluster-bootstrap/token/util:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", ], @@ -38,7 +41,7 @@ filegroup( go_test( name = "go_default_test", - srcs = ["uploadcerts_test.go"], + srcs = ["copycerts_test.go"], embed = [":go_default_library"], deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", diff --git a/cmd/kubeadm/app/phases/uploadcerts/uploadcerts.go b/cmd/kubeadm/app/phases/copycerts/copycerts.go similarity index 67% rename from cmd/kubeadm/app/phases/uploadcerts/uploadcerts.go rename to cmd/kubeadm/app/phases/copycerts/copycerts.go index 9e5b1f0e1d..229be0a22f 100644 --- a/cmd/kubeadm/app/phases/uploadcerts/uploadcerts.go +++ b/cmd/kubeadm/app/phases/copycerts/copycerts.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package uploadcerts +package copycerts import ( "encoding/hex" @@ -28,9 +28,12 @@ import ( v1 "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" clientset "k8s.io/client-go/kubernetes" + certutil "k8s.io/client-go/util/cert" + keyutil "k8s.io/client-go/util/keyutil" bootstraputil "k8s.io/cluster-bootstrap/token/util" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -92,7 +95,7 @@ func UploadCerts(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, return err } - secretData, err := getSecretData(cfg, decodedKey) + secretData, err := getDataFromDisk(cfg, decodedKey) if err != nil { return err } @@ -169,7 +172,7 @@ func loadAndEncryptCert(certPath string, key []byte) ([]byte, error) { return cryptoutil.EncryptBytes(cert, key) } -func certsToUpload(cfg *kubeadmapi.InitConfiguration) map[string]string { +func certsToTransfer(cfg *kubeadmapi.InitConfiguration) map[string]string { certsDir := cfg.CertificatesDir certs := map[string]string{ kubeadmconstants.CACertName: path.Join(certsDir, kubeadmconstants.CACertName), @@ -191,15 +194,85 @@ func certsToUpload(cfg *kubeadmapi.InitConfiguration) map[string]string { return certs } -func getSecretData(cfg *kubeadmapi.InitConfiguration, key []byte) (map[string][]byte, error) { +func getDataFromDisk(cfg *kubeadmapi.InitConfiguration, key []byte) (map[string][]byte, error) { secretData := map[string][]byte{} - for certName, certPath := range certsToUpload(cfg) { + for certName, certPath := range certsToTransfer(cfg) { cert, err := loadAndEncryptCert(certPath, key) if err == nil || (err != nil && os.IsNotExist(err)) { - secretData[strings.Replace(certName, "/", "-", -1)] = cert + secretData[certOrKeyNameToSecretName(certName)] = cert } else { return nil, err } } return secretData, nil } + +// DownloadCerts downloads the certificates needed to join a new control plane. +func DownloadCerts(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, key string) error { + fmt.Printf("[download-certs] downloading the certificates in Secret %q in the %q Namespace\n", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem) + + decodedKey, err := hex.DecodeString(key) + if err != nil { + return errors.Wrap(err, "error decoding certificate key") + } + + secret, err := getSecret(client) + if err != nil { + return errors.Wrap(err, "error downloading the secret") + } + + secretData, err := getDataFromSecret(secret, decodedKey) + if err != nil { + return errors.Wrap(err, "error decoding secret data with provided key") + } + + for certOrKeyName, certOrKeyPath := range certsToTransfer(cfg) { + certOrKeyData, found := secretData[certOrKeyNameToSecretName(certOrKeyName)] + if !found { + return errors.New("couldn't find required certificate or key in Secret") + } + if err := writeCertOrKey(certOrKeyPath, certOrKeyData); err != nil { + return err + } + } + + return nil +} + +func writeCertOrKey(certOrKeyPath string, certOrKeyData []byte) error { + if _, err := keyutil.ParsePublicKeysPEM(certOrKeyData); err == nil { + return keyutil.WriteKey(certOrKeyPath, certOrKeyData) + } else if _, err := certutil.ParseCertsPEM(certOrKeyData); err == nil { + return certutil.WriteCert(certOrKeyPath, certOrKeyData) + } + return errors.New("unknown data found in Secret entry") +} + +func getSecret(client clientset.Interface) (*v1.Secret, error) { + secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(kubeadmconstants.KubeadmCertsSecret, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, errors.Errorf("Secret %q was not found in the %q Namespace. This Secret might have expired. Please, run `kubeadm init phase upload-certs` on a control plane to generate a new one", kubeadmconstants.KubeadmCertsSecret, metav1.NamespaceSystem) + } + return nil, err + } + return secret, nil +} + +func getDataFromSecret(secret *v1.Secret, key []byte) (map[string][]byte, error) { + secretData := map[string][]byte{} + for certName, encryptedCert := range secret.Data { + cert, err := cryptoutil.DecryptBytes(encryptedCert, key) + if err != nil { + // If any of the decrypt operations fail do not return a partial result, + // return an empty result immediately + return map[string][]byte{}, err + } + secretData[certName] = cert + } + return secretData, nil +} + +func certOrKeyNameToSecretName(certOrKeyName string) string { + return strings.Replace(certOrKeyName, "/", "-", -1) +} diff --git a/cmd/kubeadm/app/phases/uploadcerts/uploadcerts_test.go b/cmd/kubeadm/app/phases/copycerts/copycerts_test.go similarity index 66% rename from cmd/kubeadm/app/phases/uploadcerts/uploadcerts_test.go rename to cmd/kubeadm/app/phases/copycerts/copycerts_test.go index a8d2faf250..3cdd762d8a 100644 --- a/cmd/kubeadm/app/phases/uploadcerts/uploadcerts_test.go +++ b/cmd/kubeadm/app/phases/copycerts/copycerts_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package uploadcerts +package copycerts import ( "encoding/hex" @@ -38,7 +38,7 @@ func TestUploadCerts(t *testing.T) { } //teste cert name, teste cert can be decrypted -func TestGetSecretData(t *testing.T) { +func TestGetDataFromInitConfig(t *testing.T) { certData := []byte("cert-data") tmpdir := testutil.SetupTempDir(t) defer os.RemoveAll(tmpdir) @@ -58,14 +58,14 @@ func TestGetSecretData(t *testing.T) { t.Fatalf(dedent.Dedent("failed to create etcd cert dir.\nfatal error: %v"), err) } - certs := certsToUpload(cfg) + certs := certsToTransfer(cfg) for name, path := range certs { if err := ioutil.WriteFile(path, certData, 0644); err != nil { t.Fatalf(dedent.Dedent("failed to write cert: %s\nfatal error: %v"), name, err) } } - secretData, err := getSecretData(cfg, decodedKey) + secretData, err := getDataFromDisk(cfg, decodedKey) if err != nil { t.Fatalf("failed to get secret data. fatal error: %v", err) } @@ -83,29 +83,44 @@ func TestGetSecretData(t *testing.T) { } } -func TestCertsToUpload(t *testing.T) { +func TestCertsToTransfer(t *testing.T) { localEtcdCfg := &kubeadmapi.InitConfiguration{} externalEtcdCfg := &kubeadmapi.InitConfiguration{} externalEtcdCfg.Etcd = kubeadmapi.Etcd{} externalEtcdCfg.Etcd.External = &kubeadmapi.ExternalEtcd{} + commonExpectedCerts := []string{ + kubeadmconstants.CACertName, + kubeadmconstants.CAKeyName, + kubeadmconstants.FrontProxyCACertName, + kubeadmconstants.FrontProxyCAKeyName, + kubeadmconstants.ServiceAccountPublicKeyName, + kubeadmconstants.ServiceAccountPrivateKeyName, + } + tests := map[string]struct { config *kubeadmapi.InitConfiguration expectedCerts []string }{ "local etcd": { - config: localEtcdCfg, - expectedCerts: []string{kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName}, + config: localEtcdCfg, + expectedCerts: append( + []string{kubeadmconstants.EtcdCACertName, kubeadmconstants.EtcdCAKeyName}, + commonExpectedCerts..., + ), }, "external etcd": { - config: externalEtcdCfg, - expectedCerts: []string{externalEtcdCA, externalEtcdCert, externalEtcdKey}, + config: externalEtcdCfg, + expectedCerts: append( + []string{externalEtcdCA, externalEtcdCert, externalEtcdKey}, + commonExpectedCerts..., + ), }, } for name, test := range tests { t.Run(name, func(t2 *testing.T) { - certList := certsToUpload(test.config) + certList := certsToTransfer(test.config) for _, cert := range test.expectedCerts { if _, found := certList[cert]; !found { t2.Fatalf(dedent.Dedent("failed to get list of certs to upload\ncert %s not found"), cert) @@ -114,3 +129,30 @@ func TestCertsToUpload(t *testing.T) { }) } } + +func TestCertOrKeyNameToSecretName(t *testing.T) { + tests := []struct { + keyName string + expectedSecretName string + }{ + { + keyName: "apiserver-kubelet-client.crt", + expectedSecretName: "apiserver-kubelet-client.crt", + }, + { + keyName: "etcd/ca.crt", + expectedSecretName: "etcd-ca.crt", + }, + { + keyName: "etcd/healthcheck-client.crt", + expectedSecretName: "etcd-healthcheck-client.crt", + }, + } + + for _, tc := range tests { + secretName := certOrKeyNameToSecretName(tc.keyName) + if secretName != tc.expectedSecretName { + t.Fatalf("secret name %s didn't match expected name %s", secretName, tc.expectedSecretName) + } + } +}