sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/cluster.go (about)

     1  /*
     2  Copyright 2020 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 internal
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"fmt"
    24  	"time"
    25  
    26  	"github.com/pkg/errors"
    27  	corev1 "k8s.io/api/core/v1"
    28  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    29  	"k8s.io/client-go/rest"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    33  	"sigs.k8s.io/cluster-api/controllers/remote"
    34  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    35  	"sigs.k8s.io/cluster-api/util/collections"
    36  	"sigs.k8s.io/cluster-api/util/secret"
    37  )
    38  
    39  const (
    40  	// KubeadmControlPlaneControllerName defines the controller used when creating clients.
    41  	KubeadmControlPlaneControllerName = "kubeadm-controlplane-controller"
    42  )
    43  
    44  // ManagementCluster defines all behaviors necessary for something to function as a management cluster.
    45  type ManagementCluster interface {
    46  	client.Reader
    47  
    48  	GetMachinesForCluster(ctx context.Context, cluster *clusterv1.Cluster, filters ...collections.Func) (collections.Machines, error)
    49  	GetMachinePoolsForCluster(ctx context.Context, cluster *clusterv1.Cluster) (*expv1.MachinePoolList, error)
    50  	GetWorkloadCluster(ctx context.Context, clusterKey client.ObjectKey) (WorkloadCluster, error)
    51  }
    52  
    53  // Management holds operations on the management cluster.
    54  type Management struct {
    55  	Client              client.Reader
    56  	SecretCachingClient client.Reader
    57  	Tracker             *remote.ClusterCacheTracker
    58  	EtcdDialTimeout     time.Duration
    59  	EtcdCallTimeout     time.Duration
    60  }
    61  
    62  // RemoteClusterConnectionError represents a failure to connect to a remote cluster.
    63  type RemoteClusterConnectionError struct {
    64  	Name string
    65  	Err  error
    66  }
    67  
    68  // Error satisfies the error interface.
    69  func (e *RemoteClusterConnectionError) Error() string { return e.Name + ": " + e.Err.Error() }
    70  
    71  // Unwrap satisfies the unwrap error inteface.
    72  func (e *RemoteClusterConnectionError) Unwrap() error { return e.Err }
    73  
    74  // Get implements client.Reader.
    75  func (m *Management) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
    76  	return m.Client.Get(ctx, key, obj, opts...)
    77  }
    78  
    79  // List implements client.Reader.
    80  func (m *Management) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
    81  	return m.Client.List(ctx, list, opts...)
    82  }
    83  
    84  // GetMachinesForCluster returns a list of machines that can be filtered or not.
    85  // If no filter is supplied then all machines associated with the target cluster are returned.
    86  func (m *Management) GetMachinesForCluster(ctx context.Context, cluster *clusterv1.Cluster, filters ...collections.Func) (collections.Machines, error) {
    87  	return collections.GetFilteredMachinesForCluster(ctx, m.Client, cluster, filters...)
    88  }
    89  
    90  // GetMachinePoolsForCluster returns a list of machine pools owned by the cluster.
    91  func (m *Management) GetMachinePoolsForCluster(ctx context.Context, cluster *clusterv1.Cluster) (*expv1.MachinePoolList, error) {
    92  	selectors := []client.ListOption{
    93  		client.InNamespace(cluster.GetNamespace()),
    94  		client.MatchingLabels{
    95  			clusterv1.ClusterNameLabel: cluster.GetName(),
    96  		},
    97  	}
    98  	machinePoolList := &expv1.MachinePoolList{}
    99  	err := m.Client.List(ctx, machinePoolList, selectors...)
   100  	return machinePoolList, err
   101  }
   102  
   103  // GetWorkloadCluster builds a cluster object.
   104  // The cluster comes with an etcd client generator to connect to any etcd pod living on a managed machine.
   105  func (m *Management) GetWorkloadCluster(ctx context.Context, clusterKey client.ObjectKey) (WorkloadCluster, error) {
   106  	// TODO(chuckha): Inject this dependency.
   107  	// TODO(chuckha): memoize this function. The workload client only exists as long as a reconciliation loop.
   108  	restConfig, err := m.Tracker.GetRESTConfig(ctx, clusterKey)
   109  	if err != nil {
   110  		return nil, &RemoteClusterConnectionError{Name: clusterKey.String(), Err: err}
   111  	}
   112  	restConfig = rest.CopyConfig(restConfig)
   113  	restConfig.Timeout = 30 * time.Second
   114  
   115  	c, err := m.Tracker.GetClient(ctx, clusterKey)
   116  	if err != nil {
   117  		return nil, &RemoteClusterConnectionError{Name: clusterKey.String(), Err: err}
   118  	}
   119  
   120  	// Retrieves the etcd CA key Pair
   121  	crtData, keyData, err := m.getEtcdCAKeyPair(ctx, clusterKey)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	// If the CA key is defined, the cluster is using a managed etcd, and so we can generate a new
   127  	// etcd client certificate for the controllers.
   128  	// Otherwise the cluster is using an external etcd; in this case the only option to connect to etcd is to re-use
   129  	// the apiserver-etcd-client certificate.
   130  	// TODO: consider if we can detect if we are using external etcd in a more explicit way (e.g. looking at the config instead of deriving from the existing certificates)
   131  	var clientCert tls.Certificate
   132  	if keyData != nil {
   133  		clientKey, err := m.Tracker.GetEtcdClientCertificateKey(ctx, clusterKey)
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  
   138  		clientCert, err = generateClientCert(crtData, keyData, clientKey)
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  	} else {
   143  		clientCert, err = m.getAPIServerEtcdClientCert(ctx, clusterKey)
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  	}
   148  
   149  	caPool := x509.NewCertPool()
   150  	caPool.AppendCertsFromPEM(crtData)
   151  	tlsConfig := &tls.Config{
   152  		RootCAs:      caPool,
   153  		Certificates: []tls.Certificate{clientCert},
   154  		MinVersion:   tls.VersionTLS12,
   155  	}
   156  	tlsConfig.InsecureSkipVerify = true
   157  	return &Workload{
   158  		restConfig:          restConfig,
   159  		Client:              c,
   160  		CoreDNSMigrator:     &CoreDNSMigrator{},
   161  		etcdClientGenerator: NewEtcdClientGenerator(restConfig, tlsConfig, m.EtcdDialTimeout, m.EtcdCallTimeout),
   162  	}, nil
   163  }
   164  
   165  func (m *Management) getEtcdCAKeyPair(ctx context.Context, clusterKey client.ObjectKey) ([]byte, []byte, error) {
   166  	etcdCASecret := &corev1.Secret{}
   167  	etcdCAObjectKey := client.ObjectKey{
   168  		Namespace: clusterKey.Namespace,
   169  		Name:      fmt.Sprintf("%s-etcd", clusterKey.Name),
   170  	}
   171  
   172  	// Try to get the certificate via the cached client.
   173  	err := m.SecretCachingClient.Get(ctx, etcdCAObjectKey, etcdCASecret)
   174  	if err != nil {
   175  		if !apierrors.IsNotFound(err) {
   176  			// Return error if we got an errors which is not a NotFound error.
   177  			return nil, nil, errors.Wrapf(err, "failed to get secret; etcd CA bundle %s/%s", etcdCAObjectKey.Namespace, etcdCAObjectKey.Name)
   178  		}
   179  
   180  		// Try to get the certificate via the uncached client.
   181  		if err := m.Client.Get(ctx, etcdCAObjectKey, etcdCASecret); err != nil {
   182  			return nil, nil, errors.Wrapf(err, "failed to get secret; etcd CA bundle %s/%s", etcdCAObjectKey.Namespace, etcdCAObjectKey.Name)
   183  		}
   184  	}
   185  
   186  	crtData, ok := etcdCASecret.Data[secret.TLSCrtDataName]
   187  	if !ok {
   188  		return nil, nil, errors.Errorf("etcd tls crt does not exist for cluster %s/%s", clusterKey.Namespace, clusterKey.Name)
   189  	}
   190  	keyData := etcdCASecret.Data[secret.TLSKeyDataName]
   191  	return crtData, keyData, nil
   192  }
   193  
   194  func (m *Management) getAPIServerEtcdClientCert(ctx context.Context, clusterKey client.ObjectKey) (tls.Certificate, error) {
   195  	apiServerEtcdClientCertificateSecret := &corev1.Secret{}
   196  	apiServerEtcdClientCertificateObjectKey := client.ObjectKey{
   197  		Namespace: clusterKey.Namespace,
   198  		Name:      fmt.Sprintf("%s-apiserver-etcd-client", clusterKey.Name),
   199  	}
   200  	if err := m.Client.Get(ctx, apiServerEtcdClientCertificateObjectKey, apiServerEtcdClientCertificateSecret); err != nil {
   201  		return tls.Certificate{}, errors.Wrapf(err, "failed to get secret; etcd apiserver-etcd-client %s/%s", apiServerEtcdClientCertificateObjectKey.Namespace, apiServerEtcdClientCertificateObjectKey.Name)
   202  	}
   203  	crtData, ok := apiServerEtcdClientCertificateSecret.Data[secret.TLSCrtDataName]
   204  	if !ok {
   205  		return tls.Certificate{}, errors.Errorf("etcd tls crt does not exist for cluster %s/%s", clusterKey.Namespace, clusterKey.Name)
   206  	}
   207  	keyData, ok := apiServerEtcdClientCertificateSecret.Data[secret.TLSKeyDataName]
   208  	if !ok {
   209  		return tls.Certificate{}, errors.Errorf("etcd tls key does not exist for cluster %s/%s", clusterKey.Namespace, clusterKey.Name)
   210  	}
   211  	return tls.X509KeyPair(crtData, keyData)
   212  }