From 997a612206b8f625f03bcb7ebe9c4a0be8e2d9db Mon Sep 17 00:00:00 2001 From: Di Xu Date: Sun, 22 Jul 2018 15:18:38 +0800 Subject: [PATCH] kubeadm join the cluster with pre-existing client certificate --- cmd/kubeadm/app/discovery/discovery.go | 16 ++-- cmd/kubeadm/app/discovery/file/file.go | 74 ++++++++++++++----- cmd/kubeadm/app/discovery/https/https.go | 8 +- cmd/kubeadm/app/discovery/token/token.go | 6 +- cmd/kubeadm/app/util/kubeconfig/kubeconfig.go | 2 +- 5 files changed, 72 insertions(+), 34 deletions(-) diff --git a/cmd/kubeadm/app/discovery/discovery.go b/cmd/kubeadm/app/discovery/discovery.go index 8d50137ef8b..8b2bb599d9b 100644 --- a/cmd/kubeadm/app/discovery/discovery.go +++ b/cmd/kubeadm/app/discovery/discovery.go @@ -36,11 +36,15 @@ const TokenUser = "tls-bootstrap-token-user" func For(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { // TODO: Print summary info about the CA certificate, along with the checksum signature // we also need an ability for the user to configure the client to validate received CA cert against a checksum - clusterinfo, err := GetValidatedClusterInfoObject(cfg) + config, err := DiscoverValidatedKubeConfig(cfg) if err != nil { return nil, fmt.Errorf("couldn't validate the identity of the API Server: %v", err) } + if len(cfg.TLSBootstrapToken) == 0 { + return config, nil + } + clusterinfo := kubeconfigutil.GetClusterFromKubeConfig(config) return kubeconfigutil.CreateWithToken( clusterinfo.Server, cfg.ClusterName, @@ -50,16 +54,16 @@ func For(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { ), nil } -// GetValidatedClusterInfoObject returns a validated Cluster object that specifies where the cluster is and the CA cert to trust -func GetValidatedClusterInfoObject(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Cluster, error) { +// DiscoverValidatedKubeConfig returns a validated Config object that specifies where the cluster is and the CA cert to trust +func DiscoverValidatedKubeConfig(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { switch { case len(cfg.DiscoveryFile) != 0: if isHTTPSURL(cfg.DiscoveryFile) { - return https.RetrieveValidatedClusterInfo(cfg.DiscoveryFile, cfg.ClusterName) + return https.RetrieveValidatedConfigInfo(cfg.DiscoveryFile, cfg.ClusterName) } - return file.RetrieveValidatedClusterInfo(cfg.DiscoveryFile, cfg.ClusterName) + return file.RetrieveValidatedConfigInfo(cfg.DiscoveryFile, cfg.ClusterName) case len(cfg.DiscoveryToken) != 0: - return token.RetrieveValidatedClusterInfo(cfg) + return token.RetrieveValidatedConfigInfo(cfg) default: return nil, fmt.Errorf("couldn't find a valid discovery configuration") } diff --git a/cmd/kubeadm/app/discovery/file/file.go b/cmd/kubeadm/app/discovery/file/file.go index f8ef38d2e86..f96c0310e4c 100644 --- a/cmd/kubeadm/app/discovery/file/file.go +++ b/cmd/kubeadm/app/discovery/file/file.go @@ -18,7 +18,7 @@ package file import ( "fmt" - + "io/ioutil" "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -30,39 +30,73 @@ import ( kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" ) -// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk +// RetrieveValidatedConfigInfo connects to the API Server and makes sure it can talk // securely to the API Server using the provided CA cert and // optionally refreshes the cluster-info information from the cluster-info ConfigMap -func RetrieveValidatedClusterInfo(filepath, clustername string) (*clientcmdapi.Cluster, error) { - clusterinfo, err := clientcmd.LoadFromFile(filepath) +func RetrieveValidatedConfigInfo(filepath, clustername string) (*clientcmdapi.Config, error) { + config, err := clientcmd.LoadFromFile(filepath) if err != nil { return nil, err } - return ValidateClusterInfo(clusterinfo, clustername) + return ValidateConfigInfo(config, clustername) } -// ValidateClusterInfo connects to the API Server and makes sure it can talk -// securely to the API Server using the provided CA cert and +// ValidateConfigInfo connects to the API Server and makes sure it can talk +// securely to the API Server using the provided CA cert/client certificates and // optionally refreshes the cluster-info information from the cluster-info ConfigMap -func ValidateClusterInfo(clusterinfo *clientcmdapi.Config, clustername string) (*clientcmdapi.Cluster, error) { - err := validateClusterInfoKubeConfig(clusterinfo) +func ValidateConfigInfo(config *clientcmdapi.Config, clustername string) (*clientcmdapi.Config, error) { + err := validateKubeConfig(config) if err != nil { return nil, err } // This is the cluster object we've got from the cluster-info KubeConfig file - defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(clusterinfo) + defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(config) // Create a new kubeconfig object from the given, just copy over the server and the CA cert // We do this in order to not pick up other possible misconfigurations in the clusterinfo file - configFromClusterInfo := kubeconfigutil.CreateBasic( + kubeconfig := kubeconfigutil.CreateBasic( defaultCluster.Server, clustername, "", // no user provided defaultCluster.CertificateAuthorityData, ) + // load pre-existing client certificates + if config.Contexts[config.CurrentContext] != nil && len(config.AuthInfos) > 0 { + user := config.Contexts[config.CurrentContext].AuthInfo + authInfo, ok := config.AuthInfos[user] + if !ok || authInfo == nil { + return nil, fmt.Errorf("empty settings for user %q", user) + } + if len(authInfo.ClientCertificateData) == 0 && len(authInfo.ClientCertificate) != 0 { + clientCert, err := ioutil.ReadFile(authInfo.ClientCertificate) + if err != nil { + return nil, err + } + authInfo.ClientCertificateData = clientCert + } + if len(authInfo.ClientKeyData) == 0 && len(authInfo.ClientKey) != 0 { + clientKey, err := ioutil.ReadFile(authInfo.ClientKey) + if err != nil { + return nil, err + } + authInfo.ClientKeyData = clientKey + } - client, err := kubeconfigutil.ToClientSet(configFromClusterInfo) + if len(authInfo.ClientCertificateData) == 0 || len(authInfo.ClientKeyData) == 0 { + return nil, fmt.Errorf("couldn't read authentication info from the given kubeconfig file") + } + kubeconfig = kubeconfigutil.CreateWithCerts( + defaultCluster.Server, + clustername, + "", // no user provided + defaultCluster.CertificateAuthorityData, + authInfo.ClientKeyData, + authInfo.ClientCertificateData, + ) + } + + client, err := kubeconfigutil.ToClientSet(kubeconfig) if err != nil { return nil, err } @@ -88,19 +122,19 @@ func ValidateClusterInfo(clusterinfo *clientcmdapi.Config, clustername string) ( // If we couldn't fetch the cluster-info ConfigMap, just return the cluster-info object the user provided if clusterinfoCM == nil { - return defaultCluster, nil + return kubeconfig, nil } // We somehow got hold of the ConfigMap, try to read some data from it. If we can't, fallback on the user-provided file refreshedBaseKubeConfig, err := tryParseClusterInfoFromConfigMap(clusterinfoCM) if err != nil { fmt.Printf("[discovery] The %s ConfigMap isn't set up properly (%v), but the TLS cert is valid so proceeding...\n", bootstrapapi.ConfigMapClusterInfo, err) - return defaultCluster, nil + return kubeconfig, nil } fmt.Println("[discovery] Synced cluster-info information from the API Server so we have got the latest information") // In an HA world in the future, this will make more sense, because now we've got new information, possibly about new API Servers to talk to - return kubeconfigutil.GetClusterFromKubeConfig(refreshedBaseKubeConfig), nil + return refreshedBaseKubeConfig, nil } // tryParseClusterInfoFromConfigMap tries to parse a kubeconfig file from a ConfigMap key @@ -116,14 +150,14 @@ func tryParseClusterInfoFromConfigMap(cm *v1.ConfigMap) (*clientcmdapi.Config, e return parsedKubeConfig, nil } -// validateClusterInfoKubeConfig makes sure the user-provided cluster-info KubeConfig file is valid -func validateClusterInfoKubeConfig(clusterinfo *clientcmdapi.Config) error { - if len(clusterinfo.Clusters) < 1 { +// validateKubeConfig makes sure the user-provided KubeConfig file is valid +func validateKubeConfig(config *clientcmdapi.Config) error { + if len(config.Clusters) < 1 { return fmt.Errorf("the provided cluster-info KubeConfig file must have at least one Cluster defined") } - defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(clusterinfo) + defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(config) if defaultCluster == nil { return fmt.Errorf("the provided cluster-info KubeConfig file must have an unnamed Cluster or a CurrentContext that specifies a non-nil Cluster") } - return nil + return clientcmd.Validate(*config) } diff --git a/cmd/kubeadm/app/discovery/https/https.go b/cmd/kubeadm/app/discovery/https/https.go index 23f154e573a..82d342ec74c 100644 --- a/cmd/kubeadm/app/discovery/https/https.go +++ b/cmd/kubeadm/app/discovery/https/https.go @@ -26,10 +26,10 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/discovery/file" ) -// RetrieveValidatedClusterInfo connects to the API Server and makes sure it can talk +// RetrieveValidatedConfigInfo connects to the API Server and makes sure it can talk // securely to the API Server using the provided CA cert and // optionally refreshes the cluster-info information from the cluster-info ConfigMap -func RetrieveValidatedClusterInfo(httpsURL, clustername string) (*clientcmdapi.Cluster, error) { +func RetrieveValidatedConfigInfo(httpsURL, clustername string) (*clientcmdapi.Config, error) { client := &http.Client{Transport: netutil.SetOldTransportDefaults(&http.Transport{})} response, err := client.Get(httpsURL) if err != nil { @@ -42,9 +42,9 @@ func RetrieveValidatedClusterInfo(httpsURL, clustername string) (*clientcmdapi.C return nil, err } - clusterinfo, err := clientcmd.Load(kubeconfig) + config, err := clientcmd.Load(kubeconfig) if err != nil { return nil, err } - return file.ValidateClusterInfo(clusterinfo, clustername) + return file.ValidateConfigInfo(config, clustername) } diff --git a/cmd/kubeadm/app/discovery/token/token.go b/cmd/kubeadm/app/discovery/token/token.go index 37832433dac..8fc75952f16 100644 --- a/cmd/kubeadm/app/discovery/token/token.go +++ b/cmd/kubeadm/app/discovery/token/token.go @@ -40,10 +40,10 @@ import ( // BootstrapUser defines bootstrap user name const BootstrapUser = "token-bootstrap-client" -// RetrieveValidatedClusterInfo connects to the API Server and tries to fetch the cluster-info ConfigMap +// RetrieveValidatedConfigInfo connects to the API Server and tries to fetch the cluster-info ConfigMap // It then makes sure it can trust the API Server by looking at the JWS-signed tokens and (if cfg.DiscoveryTokenCACertHashes is not empty) // validating the cluster CA against a set of pinned public keys -func RetrieveValidatedClusterInfo(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Cluster, error) { +func RetrieveValidatedConfigInfo(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { token, err := kubeadmapi.NewBootstrapTokenString(cfg.DiscoveryToken) if err != nil { return nil, err @@ -163,7 +163,7 @@ func RetrieveValidatedClusterInfo(cfg *kubeadmapi.JoinConfiguration) (*clientcmd return nil, err } - return kubeconfigutil.GetClusterFromKubeConfig(baseKubeConfig), nil + return baseKubeConfig, nil } // buildInsecureBootstrapKubeConfig makes a KubeConfig object that connects insecurely to the API Server for bootstrapping purposes diff --git a/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go index e44b0391d96..f2ef0203fa5 100644 --- a/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/util/kubeconfig/kubeconfig.go @@ -25,7 +25,7 @@ import ( ) // CreateBasic creates a basic, general KubeConfig object that then can be extended -func CreateBasic(serverURL string, clusterName string, userName string, caCert []byte) *clientcmdapi.Config { +func CreateBasic(serverURL, clusterName, userName string, caCert []byte) *clientcmdapi.Config { // Use the cluster and the username as the context name contextName := fmt.Sprintf("%s@%s", userName, clusterName)