mirror of https://github.com/k3s-io/k3s
fix-file-discovery
parent
56e0f22f1a
commit
911f6ea43e
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/klog"
|
||||
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"
|
||||
|
@ -43,17 +44,29 @@ func For(cfg *kubeadmapi.JoinConfiguration) (*clientcmdapi.Config, error) {
|
|||
return nil, errors.Wrap(err, "couldn't validate the identity of the API Server")
|
||||
}
|
||||
|
||||
if len(cfg.Discovery.TLSBootstrapToken) == 0 {
|
||||
// If the users has provided a TLSBootstrapToken use it for the join process.
|
||||
// This is usually the case of Token discovery, but it can also be used with a discovery file
|
||||
// without embedded authentication credentials.
|
||||
if len(cfg.Discovery.TLSBootstrapToken) != 0 {
|
||||
klog.V(1).Info("[discovery] Using provided TLSBootstrapToken as authentication credentials for the join process")
|
||||
|
||||
clusterinfo := kubeconfigutil.GetClusterFromKubeConfig(config)
|
||||
return kubeconfigutil.CreateWithToken(
|
||||
clusterinfo.Server,
|
||||
kubeadmapiv1beta2.DefaultClusterName,
|
||||
TokenUser,
|
||||
clusterinfo.CertificateAuthorityData,
|
||||
cfg.Discovery.TLSBootstrapToken,
|
||||
), nil
|
||||
}
|
||||
|
||||
// if the config returned from discovery has authentication credentials, proceed with the TLS boostrap process
|
||||
if kubeconfigutil.HasAuthenticationCredentials(config) {
|
||||
return config, nil
|
||||
}
|
||||
clusterinfo := kubeconfigutil.GetClusterFromKubeConfig(config)
|
||||
return kubeconfigutil.CreateWithToken(
|
||||
clusterinfo.Server,
|
||||
kubeadmapiv1beta1.DefaultClusterName,
|
||||
TokenUser,
|
||||
clusterinfo.CertificateAuthorityData,
|
||||
cfg.Discovery.TLSBootstrapToken,
|
||||
), nil
|
||||
|
||||
// if there are no authentication credentials (nor in the config returned from discovery, nor in the TLSBootstrapToken), fail
|
||||
return nil, errors.New("couldn't find authentication credentials for the TLS boostrap process. Please use Token discovery, a discovery file with embedded authentication credentials or a discovery file without authentication credentials and the TLSBootstrapToken flag")
|
||||
}
|
||||
|
||||
// DiscoverValidatedKubeConfig returns a validated Config object that specifies where the cluster is and the CA cert to trust
|
||||
|
|
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||
package file
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
|
@ -53,58 +51,44 @@ func ValidateConfigInfo(config *clientcmdapi.Config, clustername string) (*clien
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// This is the cluster object we've got from the cluster-info kubeconfig file
|
||||
defaultCluster := kubeconfigutil.GetClusterFromKubeConfig(config)
|
||||
var kubeconfig *clientcmdapi.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
|
||||
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
|
||||
}
|
||||
// If the discovery file config contains a authentication credentials
|
||||
if kubeconfigutil.HasAuthenticationCredentials(config) {
|
||||
klog.V(1).Info("[discovery] Using authentication credentials from the discovery file for validating TLS connection")
|
||||
|
||||
if len(authInfo.ClientCertificateData) == 0 || len(authInfo.ClientKeyData) == 0 {
|
||||
return nil, errors.New("couldn't read authentication info from the given kubeconfig file")
|
||||
// Use the discovery file config for starting the join process
|
||||
kubeconfig = config
|
||||
|
||||
// We should ensure that all the authentication info are embedded in config file, so everything will work also when
|
||||
// the kubeconfig file will be stored in /etc/kubernetes/boostrap-kubelet.conf
|
||||
if err := kubeconfigutil.EnsureAuthenticationInfoAreEmbedded(kubeconfig); err != nil {
|
||||
return nil, errors.Wrap(err, "error while reading client cert file or client key file")
|
||||
}
|
||||
kubeconfig = kubeconfigutil.CreateWithCerts(
|
||||
defaultCluster.Server,
|
||||
} else {
|
||||
// If the discovery file config does not contains authentication credentials
|
||||
klog.V(1).Info("[discovery] Discovery file does not contains authentication credentials, using unauthenticated request for validating TLS connection")
|
||||
|
||||
// Create a new kubeconfig object from the discovery file config, with only the server and the CA cert.
|
||||
// NB. We do this in order to not pick up other possible misconfigurations in the clusterinfo file
|
||||
var fileCluster = kubeconfigutil.GetClusterFromKubeConfig(config)
|
||||
kubeconfig = kubeconfigutil.CreateBasic(
|
||||
fileCluster.Server,
|
||||
clustername,
|
||||
"", // no user provided
|
||||
defaultCluster.CertificateAuthorityData,
|
||||
authInfo.ClientKeyData,
|
||||
authInfo.ClientCertificateData,
|
||||
fileCluster.CertificateAuthorityData,
|
||||
)
|
||||
}
|
||||
|
||||
// Try to read the cluster-info config map; this step was required by the original design in order
|
||||
// to validate the TLS connection to the server early in the process
|
||||
client, err := kubeconfigutil.ToClientSet(kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
klog.V(1).Infof("[discovery] Created cluster-info discovery client, requesting info from %q\n", defaultCluster.Server)
|
||||
var currentCluster = kubeconfigutil.GetClusterFromKubeConfig(kubeconfig)
|
||||
klog.V(1).Infof("[discovery] Created cluster-info discovery client, requesting info from %q\n", currentCluster.Server)
|
||||
|
||||
var clusterinfoCM *v1.ConfigMap
|
||||
wait.PollInfinite(constants.DiscoveryRetryInterval, func() (bool, error) {
|
||||
|
@ -113,11 +97,11 @@ func ValidateConfigInfo(config *clientcmdapi.Config, clustername string) (*clien
|
|||
if err != nil {
|
||||
if apierrors.IsForbidden(err) {
|
||||
// If the request is unauthorized, the cluster admin has not granted access to the cluster info configmap for unauthenticated users
|
||||
// In that case, trust the cluster admin and do not refresh the cluster-info credentials
|
||||
// In that case, trust the cluster admin and do not refresh the cluster-info data
|
||||
klog.Warningf("[discovery] Could not access the %s ConfigMap for refreshing the cluster-info information, but the TLS cert is valid so proceeding...\n", bootstrapapi.ConfigMapClusterInfo)
|
||||
return true, nil
|
||||
}
|
||||
klog.V(1).Infof("[discovery] Failed to validate the API Server's identity, will try again: [%v]\n", err)
|
||||
klog.V(1).Infof("[discovery] Error reading the %s ConfigMap, will try again: [%v]\n", bootstrapapi.ConfigMapClusterInfo, err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
|
@ -135,9 +119,12 @@ func ValidateConfigInfo(config *clientcmdapi.Config, clustername string) (*clien
|
|||
return kubeconfig, nil
|
||||
}
|
||||
|
||||
klog.V(1).Infoln("[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 refreshedBaseKubeConfig, nil
|
||||
var refreshedCluster = kubeconfigutil.GetClusterFromKubeConfig(refreshedBaseKubeConfig)
|
||||
currentCluster.Server = refreshedCluster.Server
|
||||
currentCluster.CertificateAuthorityData = refreshedCluster.CertificateAuthorityData
|
||||
|
||||
klog.V(1).Infof("[discovery] Synced server and CA from the %s ConfigMap so we have got the latest information", bootstrapapi.ConfigMapClusterInfo)
|
||||
return kubeconfig, nil
|
||||
}
|
||||
|
||||
// tryParseClusterInfoFromConfigMap tries to parse a kubeconfig file from a ConfigMap key
|
||||
|
|
|
@ -18,6 +18,7 @@ package kubeconfig
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
@ -112,3 +113,76 @@ func GetClusterFromKubeConfig(config *clientcmdapi.Config) *clientcmdapi.Cluster
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasAuthenticationCredentials returns true if the current user has valid authentication credentials for
|
||||
// token authentication, basic authentication or X509 authentication
|
||||
func HasAuthenticationCredentials(config *clientcmdapi.Config) bool {
|
||||
authInfo := getCurrentAuthInfo(config)
|
||||
if authInfo == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// token authentication
|
||||
if len(authInfo.Token) != 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// basic authentication
|
||||
if len(authInfo.Username) != 0 && len(authInfo.Password) != 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// X509 authentication
|
||||
if (len(authInfo.ClientCertificate) != 0 || len(authInfo.ClientCertificateData) != 0) &&
|
||||
(len(authInfo.ClientKey) != 0 || len(authInfo.ClientKeyData) != 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// EnsureAuthenticationInfoAreEmbedded check if some authentication info are provided as external key/certificate
|
||||
// files, and eventually embeds such files into the kubeconfig file
|
||||
func EnsureAuthenticationInfoAreEmbedded(config *clientcmdapi.Config) error {
|
||||
authInfo := getCurrentAuthInfo(config)
|
||||
if authInfo == nil {
|
||||
return errors.New("invalid kubeconfig file. AuthInfo is not defined for the current user")
|
||||
}
|
||||
|
||||
if len(authInfo.ClientCertificateData) == 0 && len(authInfo.ClientCertificate) != 0 {
|
||||
clientCert, err := ioutil.ReadFile(authInfo.ClientCertificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authInfo.ClientCertificateData = clientCert
|
||||
authInfo.ClientCertificate = ""
|
||||
}
|
||||
if len(authInfo.ClientKeyData) == 0 && len(authInfo.ClientKey) != 0 {
|
||||
clientKey, err := ioutil.ReadFile(authInfo.ClientKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authInfo.ClientKeyData = clientKey
|
||||
authInfo.ClientKey = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCurrentAuthInfo returns current authInfo, if defined
|
||||
func getCurrentAuthInfo(config *clientcmdapi.Config) *clientcmdapi.AuthInfo {
|
||||
if config == nil || config.CurrentContext == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(config.Contexts) == 0 || config.Contexts[config.CurrentContext] == nil {
|
||||
return nil
|
||||
}
|
||||
user := config.Contexts[config.CurrentContext].AuthInfo
|
||||
|
||||
if user == "" || len(config.AuthInfos) == 0 || config.AuthInfos[user] == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return config.AuthInfos[user]
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -186,3 +188,143 @@ func TestWriteKubeconfigToDisk(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrentAuthInfo(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
config *clientcmdapi.Config
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nil context",
|
||||
config: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no CurrentContext value",
|
||||
config: &clientcmdapi.Config{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no CurrentContext object 1",
|
||||
config: &clientcmdapi.Config{CurrentContext: "kubernetes"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no CurrentContext object ",
|
||||
config: &clientcmdapi.Config{
|
||||
CurrentContext: "kubernetes",
|
||||
Contexts: map[string]*clientcmdapi.Context{"NOTkubernetes": {}},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no AuthInfo value",
|
||||
config: &clientcmdapi.Config{
|
||||
CurrentContext: "kubernetes",
|
||||
Contexts: map[string]*clientcmdapi.Context{"kubernetes": {}},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no AuthInfo object 1",
|
||||
config: &clientcmdapi.Config{
|
||||
CurrentContext: "kubernetes",
|
||||
Contexts: map[string]*clientcmdapi.Context{"kubernetes": {AuthInfo: "kubernetes"}},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no AuthInfo object 2",
|
||||
config: &clientcmdapi.Config{
|
||||
CurrentContext: "kubernetes",
|
||||
Contexts: map[string]*clientcmdapi.Context{"kubernetes": {AuthInfo: "kubernetes"}},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{"NOTkubernetes": {}},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "authInfo",
|
||||
config: &clientcmdapi.Config{
|
||||
CurrentContext: "kubernetes",
|
||||
Contexts: map[string]*clientcmdapi.Context{"kubernetes": {AuthInfo: "kubernetes"}},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{"kubernetes": {}},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range testCases {
|
||||
t.Run(rt.name, func(t *testing.T) {
|
||||
r := getCurrentAuthInfo(rt.config)
|
||||
if rt.expected != (r != nil) {
|
||||
t.Errorf(
|
||||
"failed TestHasCredentials:\n\texpected: %v\n\t actual: %v",
|
||||
rt.expected,
|
||||
r,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasCredentials(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
config *clientcmdapi.Config
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "no authInfo",
|
||||
config: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no credentials",
|
||||
config: &clientcmdapi.Config{
|
||||
CurrentContext: "kubernetes",
|
||||
Contexts: map[string]*clientcmdapi.Context{"kubernetes": {AuthInfo: "kubernetes"}},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{"kubernetes": {}},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "token authentication credentials",
|
||||
config: &clientcmdapi.Config{
|
||||
CurrentContext: "kubernetes",
|
||||
Contexts: map[string]*clientcmdapi.Context{"kubernetes": {AuthInfo: "kubernetes"}},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{"kubernetes": {Token: "123"}},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "basic authentication credentials",
|
||||
config: &clientcmdapi.Config{
|
||||
CurrentContext: "kubernetes",
|
||||
Contexts: map[string]*clientcmdapi.Context{"kubernetes": {AuthInfo: "kubernetes"}},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{"kubernetes": {Username: "A", Password: "B"}},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "X509 authentication credentials",
|
||||
config: &clientcmdapi.Config{
|
||||
CurrentContext: "kubernetes",
|
||||
Contexts: map[string]*clientcmdapi.Context{"kubernetes": {AuthInfo: "kubernetes"}},
|
||||
AuthInfos: map[string]*clientcmdapi.AuthInfo{"kubernetes": {ClientKey: "A", ClientCertificate: "B"}},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range testCases {
|
||||
t.Run(rt.name, func(t *testing.T) {
|
||||
r := HasAuthenticationCredentials(rt.config)
|
||||
if rt.expected != r {
|
||||
t.Errorf(
|
||||
"failed TestHasCredentials:\n\texpected: %v\n\t actual: %v",
|
||||
rt.expected,
|
||||
r,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue