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 }