Fresh dep ensure

This commit is contained in:
Mike Cronce
2018-11-26 13:23:56 -05:00
parent 93cb8a04d7
commit 407478ab9a
9016 changed files with 551394 additions and 279685 deletions

View File

@ -12,11 +12,13 @@ go_library(
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/discovery",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library",
"//cmd/kubeadm/app/discovery/file:go_default_library",
"//cmd/kubeadm/app/discovery/https:go_default_library",
"//cmd/kubeadm/app/discovery/token:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)

View File

@ -17,11 +17,13 @@ limitations under the License.
package discovery
import (
"fmt"
"net/url"
"github.com/pkg/errors"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/file"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/https"
"k8s.io/kubernetes/cmd/kubeadm/app/discovery/token"
@ -31,37 +33,42 @@ import (
// TokenUser defines token user
const TokenUser = "tls-bootstrap-token-user"
// For returns a KubeConfig object that can be used for doing the TLS Bootstrap with the right credentials
// For returns a kubeconfig object that can be used for doing the TLS Bootstrap with the right credentials
// Also, before returning anything, it makes sure it can trust the API Server
func For(cfg *kubeadmapi.NodeConfiguration) (*clientcmdapi.Config, error) {
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)
return nil, errors.Wrap(err, "couldn't validate the identity of the API Server")
}
if len(cfg.Discovery.TLSBootstrapToken) == 0 {
return config, nil
}
clusterinfo := kubeconfigutil.GetClusterFromKubeConfig(config)
return kubeconfigutil.CreateWithToken(
clusterinfo.Server,
cfg.ClusterName,
kubeadmapiv1beta1.DefaultClusterName,
TokenUser,
clusterinfo.CertificateAuthorityData,
cfg.TLSBootstrapToken,
cfg.Discovery.TLSBootstrapToken,
), nil
}
// GetValidatedClusterInfoObject returns a validated Cluster object that specifies where the cluster is and the CA cert to trust
func GetValidatedClusterInfoObject(cfg *kubeadmapi.NodeConfiguration) (*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)
case cfg.Discovery.File != nil:
kubeConfigPath := cfg.Discovery.File.KubeConfigPath
if isHTTPSURL(kubeConfigPath) {
return https.RetrieveValidatedConfigInfo(kubeConfigPath, kubeadmapiv1beta1.DefaultClusterName)
}
return file.RetrieveValidatedClusterInfo(cfg.DiscoveryFile, cfg.ClusterName)
case len(cfg.DiscoveryToken) != 0:
return token.RetrieveValidatedClusterInfo(cfg)
return file.RetrieveValidatedConfigInfo(kubeConfigPath, kubeadmapiv1beta1.DefaultClusterName)
case cfg.Discovery.BootstrapToken != nil:
return token.RetrieveValidatedConfigInfo(cfg)
default:
return nil, fmt.Errorf("couldn't find a valid discovery configuration")
return nil, errors.New("couldn't find a valid discovery configuration")
}
}

View File

@ -24,31 +24,37 @@ import (
func TestFor(t *testing.T) {
tests := []struct {
d kubeadm.NodeConfiguration
d kubeadm.JoinConfiguration
expect bool
}{
{d: kubeadm.NodeConfiguration{}, expect: false},
{d: kubeadm.JoinConfiguration{}, expect: false},
{
d: kubeadm.NodeConfiguration{
DiscoveryFile: "notnil",
d: kubeadm.JoinConfiguration{
Discovery: kubeadm.Discovery{
File: &kubeadm.FileDiscovery{
KubeConfigPath: "notnil",
},
},
},
expect: false,
},
{
d: kubeadm.NodeConfiguration{
DiscoveryFile: "https://localhost",
d: kubeadm.JoinConfiguration{
Discovery: kubeadm.Discovery{
File: &kubeadm.FileDiscovery{
KubeConfigPath: "https://localhost",
},
},
},
expect: false,
},
{
d: kubeadm.NodeConfiguration{
DiscoveryFile: "notnil",
},
expect: false,
},
{
d: kubeadm.NodeConfiguration{
DiscoveryToken: "foo.bar@foobar",
d: kubeadm.JoinConfiguration{
Discovery: kubeadm.Discovery{
BootstrapToken: &kubeadm.BootstrapTokenDiscovery{
Token: "foo.bar@foobar",
},
},
},
expect: false,
},

View File

@ -12,13 +12,14 @@ go_library(
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/api/core/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/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/cluster-bootstrap/token/api:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)

View File

@ -18,51 +18,87 @@ package file
import (
"fmt"
"io/ioutil"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
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)
// This is the cluster object we've got from the cluster-info kubeconfig file
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, errors.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, errors.New("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,42 +124,42 @@ 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
func tryParseClusterInfoFromConfigMap(cm *v1.ConfigMap) (*clientcmdapi.Config, error) {
kubeConfigString, ok := cm.Data[bootstrapapi.KubeConfigKey]
if !ok || len(kubeConfigString) == 0 {
return nil, fmt.Errorf("no %s key in ConfigMap", bootstrapapi.KubeConfigKey)
return nil, errors.Errorf("no %s key in ConfigMap", bootstrapapi.KubeConfigKey)
}
parsedKubeConfig, err := clientcmd.Load([]byte(kubeConfigString))
if err != nil {
return nil, fmt.Errorf("couldn't parse the kubeconfig file in the %s ConfigMap: %v", bootstrapapi.ConfigMapClusterInfo, err)
return nil, errors.Wrapf(err, "couldn't parse the kubeconfig file in the %s ConfigMap", bootstrapapi.ConfigMapClusterInfo)
}
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 {
return fmt.Errorf("the provided cluster-info KubeConfig file must have at least one Cluster defined")
// validateKubeConfig makes sure the user-provided kubeconfig file is valid
func validateKubeConfig(config *clientcmdapi.Config) error {
if len(config.Clusters) < 1 {
return errors.New("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 errors.New("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

@ -11,9 +11,9 @@ go_library(
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/discovery/https",
deps = [
"//cmd/kubeadm/app/discovery/file:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
],
)

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

@ -12,16 +12,18 @@ go_library(
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/discovery/token",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util/pubkeypin:go_default_library",
"//pkg/controller/bootstrap:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/api:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/cluster-bootstrap/token/api:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
@ -42,8 +44,5 @@ go_test(
name = "go_default_test",
srcs = ["token_test.go"],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
],
deps = ["//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library"],
)

View File

@ -24,13 +24,16 @@ import (
"sync"
"time"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin"
@ -40,27 +43,27 @@ 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.NodeConfiguration) (*clientcmdapi.Cluster, error) {
token, err := kubeadmapi.NewBootstrapTokenString(cfg.DiscoveryToken)
func RetrieveValidatedConfigInfo(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) {
token, err := kubeadmapi.NewBootstrapTokenString(cfg.Discovery.BootstrapToken.Token)
if err != nil {
return nil, err
}
// Load the cfg.DiscoveryTokenCACertHashes into a pubkeypin.Set
pubKeyPins := pubkeypin.NewSet()
err = pubKeyPins.Allow(cfg.DiscoveryTokenCACertHashes...)
err = pubKeyPins.Allow(cfg.Discovery.BootstrapToken.CACertHashes...)
if err != nil {
return nil, err
}
// The function below runs for every endpoint, and all endpoints races with each other.
// The endpoint that wins the race and completes the task first gets its kubeconfig returned below
baseKubeConfig, err := runForEndpointsAndReturnFirst(cfg.DiscoveryTokenAPIServers, cfg.DiscoveryTimeout.Duration, func(endpoint string) (*clientcmdapi.Config, error) {
baseKubeConfig, err := fetchKubeConfigWithTimeout(cfg.Discovery.BootstrapToken.APIServerEndpoint, cfg.Discovery.Timeout.Duration, func(endpoint string) (*clientcmdapi.Config, error) {
insecureBootstrapConfig := buildInsecureBootstrapKubeConfig(endpoint, cfg.ClusterName)
insecureBootstrapConfig := buildInsecureBootstrapKubeConfig(endpoint, kubeadmapiv1beta1.DefaultClusterName)
clusterName := insecureBootstrapConfig.Contexts[insecureBootstrapConfig.CurrentContext].Cluster
insecureClient, err := kubeconfigutil.ToClientSet(insecureBootstrapConfig)
@ -85,19 +88,20 @@ func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmd
// Validate the MAC on the kubeconfig from the ConfigMap and load it
insecureKubeconfigString, ok := insecureClusterInfo.Data[bootstrapapi.KubeConfigKey]
if !ok || len(insecureKubeconfigString) == 0 {
return nil, fmt.Errorf("there is no %s key in the %s ConfigMap. This API Server isn't set up for token bootstrapping, can't connect", bootstrapapi.KubeConfigKey, bootstrapapi.ConfigMapClusterInfo)
return nil, errors.Errorf("there is no %s key in the %s ConfigMap. This API Server isn't set up for token bootstrapping, can't connect",
bootstrapapi.KubeConfigKey, bootstrapapi.ConfigMapClusterInfo)
}
detachedJWSToken, ok := insecureClusterInfo.Data[bootstrapapi.JWSSignatureKeyPrefix+token.ID]
if !ok || len(detachedJWSToken) == 0 {
return nil, fmt.Errorf("token id %q is invalid for this cluster or it has expired. Use \"kubeadm token create\" on the master node to creating a new valid token", token.ID)
return nil, errors.Errorf("token id %q is invalid for this cluster or it has expired. Use \"kubeadm token create\" on the master node to creating a new valid token", token.ID)
}
if !bootstrap.DetachedTokenIsValid(detachedJWSToken, insecureKubeconfigString, token.ID, token.Secret) {
return nil, fmt.Errorf("failed to verify JWS signature of received cluster info object, can't trust this API Server")
return nil, errors.New("failed to verify JWS signature of received cluster info object, can't trust this API Server")
}
insecureKubeconfigBytes := []byte(insecureKubeconfigString)
insecureConfig, err := clientcmd.Load(insecureKubeconfigBytes)
if err != nil {
return nil, fmt.Errorf("couldn't parse the kubeconfig file in the %s configmap: %v", bootstrapapi.ConfigMapClusterInfo, err)
return nil, errors.Wrapf(err, "couldn't parse the kubeconfig file in the %s configmap", bootstrapapi.ConfigMapClusterInfo)
}
// If no TLS root CA pinning was specified, we're done
@ -108,7 +112,7 @@ func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmd
// Load the cluster CA from the Config
if len(insecureConfig.Clusters) != 1 {
return nil, fmt.Errorf("expected the kubeconfig file in the %s configmap to have a single cluster, but it had %d", bootstrapapi.ConfigMapClusterInfo, len(insecureConfig.Clusters))
return nil, errors.Errorf("expected the kubeconfig file in the %s configmap to have a single cluster, but it had %d", bootstrapapi.ConfigMapClusterInfo, len(insecureConfig.Clusters))
}
var clusterCABytes []byte
for _, cluster := range insecureConfig.Clusters {
@ -116,14 +120,14 @@ func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmd
}
clusterCA, err := parsePEMCert(clusterCABytes)
if err != nil {
return nil, fmt.Errorf("failed to parse cluster CA from the %s configmap: %v", bootstrapapi.ConfigMapClusterInfo, err)
return nil, errors.Wrapf(err, "failed to parse cluster CA from the %s configmap", bootstrapapi.ConfigMapClusterInfo)
}
// Validate the cluster CA public key against the pinned set
err = pubKeyPins.Check(clusterCA)
if err != nil {
return nil, fmt.Errorf("cluster CA found in %s configmap is invalid: %v", bootstrapapi.ConfigMapClusterInfo, err)
return nil, errors.Wrapf(err, "cluster CA found in %s configmap is invalid", bootstrapapi.ConfigMapClusterInfo)
}
// Now that we know the proported cluster CA, connect back a second time validating with that CA
@ -148,12 +152,12 @@ func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*clientcmd
// Pull the kubeconfig from the securely-obtained ConfigMap and validate that it's the same as what we found the first time
secureKubeconfigBytes := []byte(secureClusterInfo.Data[bootstrapapi.KubeConfigKey])
if !bytes.Equal(secureKubeconfigBytes, insecureKubeconfigBytes) {
return nil, fmt.Errorf("the second kubeconfig from the %s configmap (using validated TLS) was different from the first", bootstrapapi.ConfigMapClusterInfo)
return nil, errors.Errorf("the second kubeconfig from the %s configmap (using validated TLS) was different from the first", bootstrapapi.ConfigMapClusterInfo)
}
secureKubeconfig, err := clientcmd.Load(secureKubeconfigBytes)
if err != nil {
return nil, fmt.Errorf("couldn't parse the kubeconfig file in the %s configmap: %v", bootstrapapi.ConfigMapClusterInfo, err)
return nil, errors.Wrapf(err, "couldn't parse the kubeconfig file in the %s configmap", bootstrapapi.ConfigMapClusterInfo)
}
fmt.Printf("[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server %q\n", endpoint)
@ -163,10 +167,10 @@ func RetrieveValidatedClusterInfo(cfg *kubeadmapi.NodeConfiguration) (*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
// buildInsecureBootstrapKubeConfig makes a kubeconfig object that connects insecurely to the API Server for bootstrapping purposes
func buildInsecureBootstrapKubeConfig(endpoint, clustername string) *clientcmdapi.Config {
masterEndpoint := fmt.Sprintf("https://%s", endpoint)
bootstrapConfig := kubeconfigutil.CreateBasic(masterEndpoint, clustername, BootstrapUser, []byte{})
@ -174,44 +178,44 @@ func buildInsecureBootstrapKubeConfig(endpoint, clustername string) *clientcmdap
return bootstrapConfig
}
// buildSecureBootstrapKubeConfig makes a KubeConfig object that connects securely to the API Server for bootstrapping purposes (validating with the specified CA)
// buildSecureBootstrapKubeConfig makes a kubeconfig object that connects securely to the API Server for bootstrapping purposes (validating with the specified CA)
func buildSecureBootstrapKubeConfig(endpoint string, caCert []byte, clustername string) *clientcmdapi.Config {
masterEndpoint := fmt.Sprintf("https://%s", endpoint)
bootstrapConfig := kubeconfigutil.CreateBasic(masterEndpoint, clustername, BootstrapUser, caCert)
return bootstrapConfig
}
// runForEndpointsAndReturnFirst loops the endpoints slice and let's the endpoints race for connecting to the master
func runForEndpointsAndReturnFirst(endpoints []string, discoveryTimeout time.Duration, fetchKubeConfigFunc func(string) (*clientcmdapi.Config, error)) (*clientcmdapi.Config, error) {
// fetchKubeConfigWithTimeout tries to run fetchKubeConfigFunc on every DiscoveryRetryInterval, but until discoveryTimeout is reached
func fetchKubeConfigWithTimeout(apiEndpoint string, discoveryTimeout time.Duration, fetchKubeConfigFunc func(string) (*clientcmdapi.Config, error)) (*clientcmdapi.Config, error) {
stopChan := make(chan struct{})
var resultingKubeConfig *clientcmdapi.Config
var once sync.Once
var wg sync.WaitGroup
for _, endpoint := range endpoints {
wg.Add(1)
go func(apiEndpoint string) {
defer wg.Done()
wait.Until(func() {
fmt.Printf("[discovery] Trying to connect to API Server %q\n", apiEndpoint)
cfg, err := fetchKubeConfigFunc(apiEndpoint)
if err != nil {
fmt.Printf("[discovery] Failed to connect to API Server %q: %v\n", apiEndpoint, err)
return
}
fmt.Printf("[discovery] Successfully established connection with API Server %q\n", apiEndpoint)
// connection established, stop all wait threads
once.Do(func() {
close(stopChan)
resultingKubeConfig = cfg
})
}, constants.DiscoveryRetryInterval, stopChan)
}(endpoint)
}
wg.Add(1)
go func() {
defer wg.Done()
wait.Until(func() {
fmt.Printf("[discovery] Trying to connect to API Server %q\n", apiEndpoint)
cfg, err := fetchKubeConfigFunc(apiEndpoint)
if err != nil {
fmt.Printf("[discovery] Failed to connect to API Server %q: %v\n", apiEndpoint, err)
return
}
fmt.Printf("[discovery] Successfully established connection with API Server %q\n", apiEndpoint)
once.Do(func() {
resultingKubeConfig = cfg
close(stopChan)
})
}, constants.DiscoveryRetryInterval, stopChan)
}()
select {
case <-time.After(discoveryTimeout):
close(stopChan)
err := fmt.Errorf("abort connecting to API servers after timeout of %v", discoveryTimeout)
once.Do(func() {
close(stopChan)
})
err := errors.Errorf("abort connecting to API servers after timeout of %v", discoveryTimeout)
fmt.Printf("[discovery] %v\n", err)
wg.Wait()
return nil, err
@ -225,10 +229,10 @@ func runForEndpointsAndReturnFirst(endpoints []string, discoveryTimeout time.Dur
func parsePEMCert(certData []byte) (*x509.Certificate, error) {
pemBlock, trailingData := pem.Decode(certData)
if pemBlock == nil {
return nil, fmt.Errorf("invalid PEM data")
return nil, errors.New("invalid PEM data")
}
if len(trailingData) != 0 {
return nil, fmt.Errorf("trailing data after first PEM block")
return nil, errors.New("trailing data after first PEM block")
}
return x509.ParseCertificate(pemBlock.Bytes)
}

View File

@ -17,12 +17,11 @@ limitations under the License.
package token
import (
"strconv"
"fmt"
"testing"
"time"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
)
// testCertPEM is a simple self-signed test certificate issued with the openssl CLI:
@ -49,41 +48,49 @@ c1vuFqTnJBPcb7W//R/GI2Paicm1cmns9NLnPR35exHxFTy+D1yxmGokpoPMdife
aH+sfuxT8xeTPb3kjzF9eJTlnEquUDLM
-----END CERTIFICATE-----`
func TestRunForEndpointsAndReturnFirst(t *testing.T) {
func TestFetchKubeConfigWithTimeout(t *testing.T) {
const testAPIEndpoint = "sample-endpoint:1234"
tests := []struct {
endpoints []string
expectedEndpoint string
name string
discoveryTimeout time.Duration
shouldFail bool
}{
{
endpoints: []string{"1", "2", "3"},
expectedEndpoint: "1",
name: "Timeout if value is not returned on time",
discoveryTimeout: 1 * time.Second,
shouldFail: true,
},
{
endpoints: []string{"6", "5"},
expectedEndpoint: "5",
},
{
endpoints: []string{"10", "4"},
expectedEndpoint: "4",
name: "Don't timeout if value is returned on time",
discoveryTimeout: 5 * time.Second,
shouldFail: false,
},
}
for _, rt := range tests {
returnKubeConfig, err := runForEndpointsAndReturnFirst(rt.endpoints, 5*time.Minute, func(endpoint string) (*clientcmdapi.Config, error) {
timeout, _ := strconv.Atoi(endpoint)
time.Sleep(time.Second * time.Duration(timeout))
return kubeconfigutil.CreateBasic(endpoint, "foo", "foo", []byte{}), nil
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cfg, err := fetchKubeConfigWithTimeout(testAPIEndpoint, test.discoveryTimeout, func(apiEndpoint string) (*clientcmdapi.Config, error) {
if apiEndpoint != testAPIEndpoint {
return nil, fmt.Errorf("unexpected API server endpoint:\n\texpected: %q\n\tgot: %q", testAPIEndpoint, apiEndpoint)
}
time.Sleep(3 * time.Second)
return &clientcmdapi.Config{}, nil
})
if test.shouldFail {
if err == nil {
t.Fatal("unexpected success")
}
} else {
if err != nil {
t.Fatalf("unexpected failure: %v", err)
}
if cfg == nil {
t.Fatal("cfg is nil")
}
}
})
if err != nil {
t.Errorf("unexpected error: %v for endpoint %s", err, rt.expectedEndpoint)
}
endpoint := returnKubeConfig.Clusters[returnKubeConfig.Contexts[returnKubeConfig.CurrentContext].Cluster].Server
if endpoint != rt.expectedEndpoint {
t.Errorf(
"failed TestRunForEndpointsAndReturnFirst:\n\texpected: %s\n\t actual: %s",
endpoint,
rt.expectedEndpoint,
)
}
}
}