Merge pull request #66482 from dixudx/kubeadm_use_existing_config

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

kubeadm join the cluster with pre-existing client certificate if provided

**What this PR does / why we need it**:
support `kubeadm join` with a pre-existing client certificate

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes kubernetes/kubeadm#945

**Special notes for your reviewer**:
/cc @luxas @timothysc  @kubernetes/sig-cluster-lifecycle-pr-reviews 

**Release note**:

```release-note
kubeadm now can join the cluster with pre-existing client certificate if provided
```
This commit is contained in:
Kubernetes Submit Queue 2018-07-27 05:54:27 -07:00 committed by GitHub
commit f7641e8710
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 34 deletions

View File

@ -36,11 +36,15 @@ const TokenUser = "tls-bootstrap-token-user"
func For(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) { func For(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) {
// TODO: Print summary info about the CA certificate, along with the checksum signature // 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 // 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 { if err != nil {
return nil, fmt.Errorf("couldn't validate the identity of the API Server: %v", err) 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( return kubeconfigutil.CreateWithToken(
clusterinfo.Server, clusterinfo.Server,
cfg.ClusterName, cfg.ClusterName,
@ -50,16 +54,16 @@ func For(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) {
), nil ), nil
} }
// GetValidatedClusterInfoObject returns a validated Cluster object that specifies where the cluster is and the CA cert to trust // DiscoverValidatedKubeConfig returns a validated Config object that specifies where the cluster is and the CA cert to trust
func GetValidatedClusterInfoObject(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Cluster, error) { func DiscoverValidatedKubeConfig(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) {
switch { switch {
case len(cfg.DiscoveryFile) != 0: case len(cfg.DiscoveryFile) != 0:
if isHTTPSURL(cfg.DiscoveryFile) { 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: case len(cfg.DiscoveryToken) != 0:
return token.RetrieveValidatedClusterInfo(cfg) return token.RetrieveValidatedConfigInfo(cfg)
default: default:
return nil, fmt.Errorf("couldn't find a valid discovery configuration") return nil, fmt.Errorf("couldn't find a valid discovery configuration")
} }

View File

@ -18,7 +18,7 @@ package file
import ( import (
"fmt" "fmt"
"io/ioutil"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -30,39 +30,73 @@ import (
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" 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 // securely to the API Server using the provided CA cert and
// optionally refreshes the cluster-info information from the cluster-info ConfigMap // optionally refreshes the cluster-info information from the cluster-info ConfigMap
func RetrieveValidatedClusterInfo(filepath, clustername string) (*clientcmdapi.Cluster, error) { func RetrieveValidatedConfigInfo(filepath, clustername string) (*clientcmdapi.Config, error) {
clusterinfo, err := clientcmd.LoadFromFile(filepath) config, err := clientcmd.LoadFromFile(filepath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ValidateClusterInfo(clusterinfo, clustername) return ValidateConfigInfo(config, clustername)
} }
// ValidateClusterInfo connects to the API Server and makes sure it can talk // ValidateConfigInfo connects to the API Server and makes sure it can talk
// securely to the API Server using the provided CA cert and // securely to the API Server using the provided CA cert/client certificates and
// optionally refreshes the cluster-info information from the cluster-info ConfigMap // optionally refreshes the cluster-info information from the cluster-info ConfigMap
func ValidateClusterInfo(clusterinfo *clientcmdapi.Config, clustername string) (*clientcmdapi.Cluster, error) { func ValidateConfigInfo(config *clientcmdapi.Config, clustername string) (*clientcmdapi.Config, error) {
err := validateClusterInfoKubeConfig(clusterinfo) err := validateKubeConfig(config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// This is the cluster object we've got from the cluster-info KubeConfig file // 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 // 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 // We do this in order to not pick up other possible misconfigurations in the clusterinfo file
configFromClusterInfo := kubeconfigutil.CreateBasic( kubeconfig := kubeconfigutil.CreateBasic(
defaultCluster.Server, defaultCluster.Server,
clustername, clustername,
"", // no user provided "", // no user provided
defaultCluster.CertificateAuthorityData, 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 { if err != nil {
return nil, err 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 we couldn't fetch the cluster-info ConfigMap, just return the cluster-info object the user provided
if clusterinfoCM == nil { 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 // 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) refreshedBaseKubeConfig, err := tryParseClusterInfoFromConfigMap(clusterinfoCM)
if err != nil { 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) 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") 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 // 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 // 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 return parsedKubeConfig, nil
} }
// validateClusterInfoKubeConfig makes sure the user-provided cluster-info KubeConfig file is valid // validateKubeConfig makes sure the user-provided KubeConfig file is valid
func validateClusterInfoKubeConfig(clusterinfo *clientcmdapi.Config) error { func validateKubeConfig(config *clientcmdapi.Config) error {
if len(clusterinfo.Clusters) < 1 { if len(config.Clusters) < 1 {
return fmt.Errorf("the provided cluster-info KubeConfig file must have at least one Cluster defined") 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 { 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 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)
} }

View File

@ -26,10 +26,10 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/file" "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 // securely to the API Server using the provided CA cert and
// optionally refreshes the cluster-info information from the cluster-info ConfigMap // 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{})} client := &http.Client{Transport: netutil.SetOldTransportDefaults(&http.Transport{})}
response, err := client.Get(httpsURL) response, err := client.Get(httpsURL)
if err != nil { if err != nil {
@ -42,9 +42,9 @@ func RetrieveValidatedClusterInfo(httpsURL, clustername string) (*clientcmdapi.C
return nil, err return nil, err
} }
clusterinfo, err := clientcmd.Load(kubeconfig) config, err := clientcmd.Load(kubeconfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return file.ValidateClusterInfo(clusterinfo, clustername) return file.ValidateConfigInfo(config, clustername)
} }

View File

@ -40,10 +40,10 @@ import (
// BootstrapUser defines bootstrap user name // BootstrapUser defines bootstrap user name
const BootstrapUser = "token-bootstrap-client" 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) // 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 // 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) token, err := kubeadmapi.NewBootstrapTokenString(cfg.DiscoveryToken)
if err != nil { if err != nil {
return nil, err return nil, err
@ -163,7 +163,7 @@ func RetrieveValidatedClusterInfo(cfg *kubeadmapi.JoinConfiguration) (*clientcmd
return nil, err 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 // buildInsecureBootstrapKubeConfig makes a KubeConfig object that connects insecurely to the API Server for bootstrapping purposes

View File

@ -25,7 +25,7 @@ import (
) )
// CreateBasic creates a basic, general KubeConfig object that then can be extended // 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 // Use the cluster and the username as the context name
contextName := fmt.Sprintf("%s@%s", userName, clusterName) contextName := fmt.Sprintf("%s@%s", userName, clusterName)