kubeadm join the cluster with pre-existing client certificate

This commit is contained in:
Di Xu 2018-07-22 15:18:38 +08:00
parent 4797c8df8f
commit 997a612206
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) {
// 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")
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)