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 }