Skip to content

Commit

Permalink
✨Add flag to disable default CSR controller (#1480)
Browse files Browse the repository at this point in the history
  • Loading branch information
xaverkapeller authored Sep 11, 2024
1 parent c05f7aa commit 1136559
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 13 deletions.
27 changes: 15 additions & 12 deletions controllers/hetznercluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 18 additions & 1 deletion docs/caph/02-topics/06-advanced/01-csr-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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)
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func init() {
var (
metricsAddr string
enableLeaderElection bool
disableCSRApproval bool
leaderElectionNamespace string
probeAddr string
watchFilterValue string
Expand All @@ -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.")
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit 1136559

Please sign in to comment.