diff --git a/controllers/hetznercluster_controller.go b/controllers/hetznercluster_controller.go index 232cb232f..c189028dd 100644 --- a/controllers/hetznercluster_controller.go +++ b/controllers/hetznercluster_controller.go @@ -79,6 +79,7 @@ type HetznerClusterReconciler struct { targetClusterManagersLock sync.Mutex TargetClusterManagersWaitGroup *sync.WaitGroup WatchFilterValue string + DisableCSRApproval bool } //+kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch @@ -708,19 +709,21 @@ func (r *HetznerClusterReconciler) newTargetClusterManager(ctx context.Context, return nil, fmt.Errorf("failed to setup guest cluster manager: %w", err) } - gr := &GuestCSRReconciler{ - Client: clusterMgr.GetClient(), - mCluster: &managementCluster{ - Client: r.Client, - hetznerCluster: hetznerCluster, - }, - WatchFilterValue: r.WatchFilterValue, - clientSet: clientSet, - clusterName: clusterScope.Cluster.Name, - } + if !r.DisableCSRApproval { + gr := &GuestCSRReconciler{ + Client: clusterMgr.GetClient(), + mCluster: &managementCluster{ + Client: r.Client, + hetznerCluster: hetznerCluster, + }, + WatchFilterValue: r.WatchFilterValue, + clientSet: clientSet, + clusterName: clusterScope.Cluster.Name, + } - if err := gr.SetupWithManager(ctx, clusterMgr, controller.Options{}); err != nil { - return nil, fmt.Errorf("failed to setup CSR controller: %w", err) + if err := gr.SetupWithManager(ctx, clusterMgr, controller.Options{}); err != nil { + return nil, fmt.Errorf("failed to setup CSR controller: %w", err) + } } return clusterMgr, nil diff --git a/docs/caph/02-topics/06-advanced/01-csr-controller.md b/docs/caph/02-topics/06-advanced/01-csr-controller.md index e226ca5da..fa89c7a94 100644 --- a/docs/caph/02-topics/06-advanced/01-csr-controller.md +++ b/docs/caph/02-topics/06-advanced/01-csr-controller.md @@ -4,7 +4,11 @@ title: CSR controller For the secure operation of Kubernetes, it is necessary to sign the kubelet serving certificates. By default, these are self-signed by kubeadm. By using the kubelet flag `rotate-server-certificates: "true"`, which can be found in initConfiguration/joinConfiguration.nodeRegistration.kubeletExtraArgs, the kubelet will do a certificate signing request (CSR) to the certificates API of Kubernetes. -These CSRs are not approved by default for security reasons. As described in the docs, this should be done manually by the cloud provider or with a custom approval controller. Since the provider integration is the responsible cloud provider in a way, it makes sense to implement such a controller directly here. The CSR controller that we implemented checks the DNS name and the IP address and thus ensures that only those nodes receive the signed certificate that are supposed to. +These CSRs are not approved by default for security reasons. As described in the docs, this should be done manually by the cloud provider or with a custom approval controller. + +## Default CSR controller + +Since the provider integration is the responsible cloud provider in a way, it makes sense to implement such a controller directly here. The CSR controller that we implemented checks the DNS name and the IP address and thus ensures that only those nodes receive the signed certificate that are supposed to. For error-free operation, the following kubelet flags should not be set: @@ -17,3 +21,16 @@ For more information, see: - [https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-certs/](https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-certs/) - [https://kubernetes.io/docs/reference/access-authn-authz/kubelet-tls-bootstrapping/#client-and-serving-certificates](https://kubernetes.io/docs/reference/access-authn-authz/kubelet-tls-bootstrapping/#client-and-serving-certificates) + +## Custom CSR controller + +It is possible to disable the CSR controller using the flag `--disable-csr-approval`. However, this flag disables this feature globally, for all `HetznerCluster` objects in the management cluster. There is currently no way to toggle this on or off for just a single cluster. + +This is useful for cases where the validation or approval logic of the default CSR controller is insufficient for your use cases. + +If you disable the default CSR controller, you'll need to deploy an equivalent controller that can validate and approve CSRs securely. This is a security critical process and needs to be handled with care. + +For more information, see: + +- [https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) +- [https://kubernetes.io/docs/reference/access-authn-authz/kubelet-tls-bootstrapping/#certificate-rotation](https://kubernetes.io/docs/reference/access-authn-authz/kubelet-tls-bootstrapping/#certificate-rotation) \ No newline at end of file diff --git a/main.go b/main.go index 4e8269abf..789cf0d22 100644 --- a/main.go +++ b/main.go @@ -65,6 +65,7 @@ func init() { var ( metricsAddr string enableLeaderElection bool + disableCSRApproval bool leaderElectionNamespace string probeAddr string watchFilterValue string @@ -83,6 +84,7 @@ func main() { fs.StringVar(&metricsAddr, "metrics-bind-address", "localhost:8080", "The address the metric endpoint binds to.") fs.StringVar(&probeAddr, "health-probe-bind-address", ":9440", "The address the probe endpoint binds to.") fs.BoolVar(&enableLeaderElection, "leader-elect", true, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") + fs.BoolVar(&disableCSRApproval, "disable-csr-approval", false, "Disables builtin workload cluster CSR validation and approval.") fs.StringVar(&leaderElectionNamespace, "leader-elect-namespace", "", "Namespace that the controller performs leader election in. If unspecified, the controller will discover which namespace it is running in.") fs.StringVar(&watchFilterValue, "watch-filter", "", fmt.Sprintf("Label value that the controller watches to reconcile cluster-api objects. Label key is always %s. If unspecified, the controller watches for all cluster-api objects.", clusterv1.WatchLabel)) fs.StringVar(&watchNamespace, "namespace", "", "Namespace that the controller watches to reconcile cluster-api objects. If unspecified, the controller watches for cluster-api objects across all namespaces.") @@ -147,6 +149,7 @@ func main() { RateLimitWaitTime: rateLimitWaitTime, HCloudClientFactory: hcloudClientFactory, WatchFilterValue: watchFilterValue, + DisableCSRApproval: disableCSRApproval, TargetClusterManagersWaitGroup: &wg, }).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: hetznerClusterConcurrency}); err != nil { setupLog.Error(err, "unable to create controller", "controller", "HetznerCluster")