sigs.k8s.io/cluster-api@v1.7.1/util/kubeconfig/kubeconfig.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package kubeconfig implements utilities for working with kubeconfigs.
    18  package kubeconfig
    19  
    20  import (
    21  	"context"
    22  	"crypto"
    23  	"crypto/x509"
    24  	"fmt"
    25  	"time"
    26  
    27  	"github.com/pkg/errors"
    28  	corev1 "k8s.io/api/core/v1"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/client-go/tools/clientcmd"
    32  	"k8s.io/client-go/tools/clientcmd/api"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  
    35  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    36  	"sigs.k8s.io/cluster-api/util"
    37  	"sigs.k8s.io/cluster-api/util/certs"
    38  	"sigs.k8s.io/cluster-api/util/secret"
    39  )
    40  
    41  var (
    42  	// ErrDependentCertificateNotFound signals that a CA secret could not be found.
    43  	ErrDependentCertificateNotFound = errors.New("could not find secret ca")
    44  )
    45  
    46  // FromSecret fetches the Kubeconfig for a Cluster.
    47  func FromSecret(ctx context.Context, c client.Reader, cluster client.ObjectKey) ([]byte, error) {
    48  	out, err := secret.Get(ctx, c, cluster, secret.Kubeconfig)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	return toKubeconfigBytes(out)
    53  }
    54  
    55  // New creates a new Kubeconfig using the cluster name and specified endpoint.
    56  func New(clusterName, endpoint string, caCert *x509.Certificate, caKey crypto.Signer) (*api.Config, error) {
    57  	cfg := &certs.Config{
    58  		CommonName:   "kubernetes-admin",
    59  		Organization: []string{"system:masters"},
    60  		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
    61  	}
    62  
    63  	clientKey, err := certs.NewPrivateKey()
    64  	if err != nil {
    65  		return nil, errors.Wrap(err, "unable to create private key")
    66  	}
    67  
    68  	clientCert, err := cfg.NewSignedCert(clientKey, caCert, caKey)
    69  	if err != nil {
    70  		return nil, errors.Wrap(err, "unable to sign certificate")
    71  	}
    72  
    73  	userName := fmt.Sprintf("%s-admin", clusterName)
    74  	contextName := fmt.Sprintf("%s@%s", userName, clusterName)
    75  
    76  	return &api.Config{
    77  		Clusters: map[string]*api.Cluster{
    78  			clusterName: {
    79  				Server:                   endpoint,
    80  				CertificateAuthorityData: certs.EncodeCertPEM(caCert),
    81  			},
    82  		},
    83  		Contexts: map[string]*api.Context{
    84  			contextName: {
    85  				Cluster:  clusterName,
    86  				AuthInfo: userName,
    87  			},
    88  		},
    89  		AuthInfos: map[string]*api.AuthInfo{
    90  			userName: {
    91  				ClientKeyData:         certs.EncodePrivateKeyPEM(clientKey),
    92  				ClientCertificateData: certs.EncodeCertPEM(clientCert),
    93  			},
    94  		},
    95  		CurrentContext: contextName,
    96  	}, nil
    97  }
    98  
    99  // CreateSecret creates the Kubeconfig secret for the given cluster.
   100  func CreateSecret(ctx context.Context, c client.Client, cluster *clusterv1.Cluster) error {
   101  	name := util.ObjectKey(cluster)
   102  	return CreateSecretWithOwner(ctx, c, name, cluster.Spec.ControlPlaneEndpoint.String(), metav1.OwnerReference{
   103  		APIVersion: clusterv1.GroupVersion.String(),
   104  		Kind:       "Cluster",
   105  		Name:       cluster.Name,
   106  		UID:        cluster.UID,
   107  	})
   108  }
   109  
   110  // CreateSecretWithOwner creates the Kubeconfig secret for the given cluster name, namespace, endpoint, and owner reference.
   111  func CreateSecretWithOwner(ctx context.Context, c client.Client, clusterName client.ObjectKey, endpoint string, owner metav1.OwnerReference) error {
   112  	server := fmt.Sprintf("https://%s", endpoint)
   113  	out, err := generateKubeconfig(ctx, c, clusterName, server)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	return c.Create(ctx, GenerateSecretWithOwner(clusterName, out, owner))
   119  }
   120  
   121  // GenerateSecret returns a Kubernetes secret for the given Cluster and kubeconfig data.
   122  func GenerateSecret(cluster *clusterv1.Cluster, data []byte) *corev1.Secret {
   123  	name := util.ObjectKey(cluster)
   124  	return GenerateSecretWithOwner(name, data, metav1.OwnerReference{
   125  		APIVersion: clusterv1.GroupVersion.String(),
   126  		Kind:       "Cluster",
   127  		Name:       cluster.Name,
   128  		UID:        cluster.UID,
   129  	})
   130  }
   131  
   132  // GenerateSecretWithOwner returns a Kubernetes secret for the given Cluster name, namespace, kubeconfig data, and ownerReference.
   133  func GenerateSecretWithOwner(clusterName client.ObjectKey, data []byte, owner metav1.OwnerReference) *corev1.Secret {
   134  	return &corev1.Secret{
   135  		ObjectMeta: metav1.ObjectMeta{
   136  			Name:      secret.Name(clusterName.Name, secret.Kubeconfig),
   137  			Namespace: clusterName.Namespace,
   138  			Labels: map[string]string{
   139  				clusterv1.ClusterNameLabel: clusterName.Name,
   140  			},
   141  			OwnerReferences: []metav1.OwnerReference{
   142  				owner,
   143  			},
   144  		},
   145  		Data: map[string][]byte{
   146  			secret.KubeconfigDataName: data,
   147  		},
   148  		Type: clusterv1.ClusterSecretType,
   149  	}
   150  }
   151  
   152  // NeedsClientCertRotation returns whether any of the Kubeconfig secret's client certificates will expire before the given threshold.
   153  func NeedsClientCertRotation(configSecret *corev1.Secret, threshold time.Duration) (bool, error) {
   154  	now := time.Now()
   155  
   156  	data, err := toKubeconfigBytes(configSecret)
   157  	if err != nil {
   158  		return false, err
   159  	}
   160  
   161  	config, err := clientcmd.Load(data)
   162  	if err != nil {
   163  		return false, errors.Wrap(err, "failed to convert kubeconfig Secret into a clientcmdapi.Config")
   164  	}
   165  
   166  	for _, authInfo := range config.AuthInfos {
   167  		cert, err := certs.DecodeCertPEM(authInfo.ClientCertificateData)
   168  		if err != nil {
   169  			return false, errors.Wrap(err, "failed to decode kubeconfig client certificate")
   170  		}
   171  		if cert.NotAfter.Sub(now) < threshold {
   172  			return true, nil
   173  		}
   174  	}
   175  
   176  	return false, nil
   177  }
   178  
   179  // RegenerateSecret creates and stores a new Kubeconfig in the given secret.
   180  func RegenerateSecret(ctx context.Context, c client.Client, configSecret *corev1.Secret) error {
   181  	clusterName, _, err := secret.ParseSecretName(configSecret.Name)
   182  	if err != nil {
   183  		return errors.Wrap(err, "failed to parse secret name")
   184  	}
   185  	data, err := toKubeconfigBytes(configSecret)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	config, err := clientcmd.Load(data)
   191  	if err != nil {
   192  		return errors.Wrap(err, "failed to convert kubeconfig Secret into a clientcmdapi.Config")
   193  	}
   194  	endpoint := config.Clusters[clusterName].Server
   195  	key := client.ObjectKey{Name: clusterName, Namespace: configSecret.Namespace}
   196  	out, err := generateKubeconfig(ctx, c, key, endpoint)
   197  	if err != nil {
   198  		return err
   199  	}
   200  	configSecret.Data[secret.KubeconfigDataName] = out
   201  	return c.Update(ctx, configSecret)
   202  }
   203  
   204  func generateKubeconfig(ctx context.Context, c client.Client, clusterName client.ObjectKey, endpoint string) ([]byte, error) {
   205  	clusterCA, err := secret.GetFromNamespacedName(ctx, c, clusterName, secret.ClusterCA)
   206  	if err != nil {
   207  		if apierrors.IsNotFound(err) {
   208  			return nil, ErrDependentCertificateNotFound
   209  		}
   210  		return nil, err
   211  	}
   212  
   213  	cert, err := certs.DecodeCertPEM(clusterCA.Data[secret.TLSCrtDataName])
   214  	if err != nil {
   215  		return nil, errors.Wrap(err, "failed to decode CA Cert")
   216  	} else if cert == nil {
   217  		return nil, errors.New("certificate not found in config")
   218  	}
   219  
   220  	key, err := certs.DecodePrivateKeyPEM(clusterCA.Data[secret.TLSKeyDataName])
   221  	if err != nil {
   222  		return nil, errors.Wrap(err, "failed to decode private key")
   223  	} else if key == nil {
   224  		return nil, errors.New("CA private key not found")
   225  	}
   226  
   227  	cfg, err := New(clusterName.Name, endpoint, cert, key)
   228  	if err != nil {
   229  		return nil, errors.Wrap(err, "failed to generate a kubeconfig")
   230  	}
   231  
   232  	out, err := clientcmd.Write(*cfg)
   233  	if err != nil {
   234  		return nil, errors.Wrap(err, "failed to serialize config to yaml")
   235  	}
   236  	return out, nil
   237  }
   238  
   239  func toKubeconfigBytes(out *corev1.Secret) ([]byte, error) {
   240  	data, ok := out.Data[secret.KubeconfigDataName]
   241  	if !ok {
   242  		return nil, errors.Errorf("missing key %q in secret data", secret.KubeconfigDataName)
   243  	}
   244  	return data, nil
   245  }