mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
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:
commit
f7641e8710
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user