diff --git a/controller/deploy/operator/internal/controller/jumpstarter/jumpstarter_controller.go b/controller/deploy/operator/internal/controller/jumpstarter/jumpstarter_controller.go index dcf512b7e..c5c2f57f6 100644 --- a/controller/deploy/operator/internal/controller/jumpstarter/jumpstarter_controller.go +++ b/controller/deploy/operator/internal/controller/jumpstarter/jumpstarter_controller.go @@ -1017,7 +1017,7 @@ func (r *JumpstarterReconciler) createRouterDeployment(jumpstarter *operatorv1al Type: corev1.SeccompProfileTypeRuntimeDefault, }, }, - ServiceAccountName: fmt.Sprintf("%s-controller-manager", jumpstarter.Name), + ServiceAccountName: fmt.Sprintf("%s-router-sa", jumpstarter.Name), TopologySpreadConstraints: jumpstarter.Spec.Routers.TopologySpreadConstraints, }, }, diff --git a/controller/deploy/operator/internal/controller/jumpstarter/rbac.go b/controller/deploy/operator/internal/controller/jumpstarter/rbac.go index e03336181..d31b40d44 100644 --- a/controller/deploy/operator/internal/controller/jumpstarter/rbac.go +++ b/controller/deploy/operator/internal/controller/jumpstarter/rbac.go @@ -61,6 +61,44 @@ func (r *JumpstarterReconciler) reconcileRBAC(ctx context.Context, jumpstarter * "namespace", existingSA.Namespace, "operation", op) + // Router ServiceAccount (zero RBAC, no token automount) + desiredRouterSA := r.createRouterServiceAccount(jumpstarter) + + existingRouterSA := &corev1.ServiceAccount{} + existingRouterSA.Name = desiredRouterSA.Name + existingRouterSA.Namespace = desiredRouterSA.Namespace + + op, err = controllerutil.CreateOrUpdate(ctx, r.Client, existingRouterSA, func() error { + if existingRouterSA.CreationTimestamp.IsZero() { + existingRouterSA.Labels = desiredRouterSA.Labels + existingRouterSA.Annotations = desiredRouterSA.Annotations + return nil + } + + if !serviceAccountNeedsUpdate(existingRouterSA, desiredRouterSA) { + log.V(1).Info("Router ServiceAccount is up to date, skipping update", + "name", existingRouterSA.Name, + "namespace", existingRouterSA.Namespace) + return nil + } + + existingRouterSA.Labels = desiredRouterSA.Labels + existingRouterSA.Annotations = desiredRouterSA.Annotations + return nil + }) + + if err != nil { + log.Error(err, "Failed to reconcile Router ServiceAccount", + "name", desiredRouterSA.Name, + "namespace", desiredRouterSA.Namespace) + return err + } + + log.Info("Router ServiceAccount reconciled", + "name", existingRouterSA.Name, + "namespace", existingRouterSA.Namespace, + "operation", op) + // Role desiredRole := r.createRole(jumpstarter) @@ -151,6 +189,88 @@ func (r *JumpstarterReconciler) reconcileRBAC(ctx context.Context, jumpstarter * "namespace", existingRoleBinding.Namespace, "operation", op) + // Router Role (minimal permissions: read configmaps) + desiredRouterRole := r.createRouterRole(jumpstarter) + + existingRouterRole := &rbacv1.Role{} + existingRouterRole.Name = desiredRouterRole.Name + existingRouterRole.Namespace = desiredRouterRole.Namespace + + op, err = controllerutil.CreateOrUpdate(ctx, r.Client, existingRouterRole, func() error { + if existingRouterRole.CreationTimestamp.IsZero() { + existingRouterRole.Labels = desiredRouterRole.Labels + existingRouterRole.Annotations = desiredRouterRole.Annotations + existingRouterRole.Rules = desiredRouterRole.Rules + return controllerutil.SetControllerReference(jumpstarter, existingRouterRole, r.Scheme) + } + + if !roleNeedsUpdate(existingRouterRole, desiredRouterRole) { + log.V(1).Info("Router Role is up to date, skipping update", + "name", existingRouterRole.Name, + "namespace", existingRouterRole.Namespace) + return nil + } + + existingRouterRole.Labels = desiredRouterRole.Labels + existingRouterRole.Annotations = desiredRouterRole.Annotations + existingRouterRole.Rules = desiredRouterRole.Rules + return controllerutil.SetControllerReference(jumpstarter, existingRouterRole, r.Scheme) + }) + + if err != nil { + log.Error(err, "Failed to reconcile Router Role", + "name", desiredRouterRole.Name, + "namespace", desiredRouterRole.Namespace) + return err + } + + log.Info("Router Role reconciled", + "name", existingRouterRole.Name, + "namespace", existingRouterRole.Namespace, + "operation", op) + + // Router RoleBinding + desiredRouterRoleBinding := r.createRouterRoleBinding(jumpstarter) + + existingRouterRoleBinding := &rbacv1.RoleBinding{} + existingRouterRoleBinding.Name = desiredRouterRoleBinding.Name + existingRouterRoleBinding.Namespace = desiredRouterRoleBinding.Namespace + + op, err = controllerutil.CreateOrUpdate(ctx, r.Client, existingRouterRoleBinding, func() error { + if existingRouterRoleBinding.CreationTimestamp.IsZero() { + existingRouterRoleBinding.Labels = desiredRouterRoleBinding.Labels + existingRouterRoleBinding.Annotations = desiredRouterRoleBinding.Annotations + existingRouterRoleBinding.Subjects = desiredRouterRoleBinding.Subjects + existingRouterRoleBinding.RoleRef = desiredRouterRoleBinding.RoleRef + return controllerutil.SetControllerReference(jumpstarter, existingRouterRoleBinding, r.Scheme) + } + + if !roleBindingNeedsUpdate(existingRouterRoleBinding, desiredRouterRoleBinding) { + log.V(1).Info("Router RoleBinding is up to date, skipping update", + "name", existingRouterRoleBinding.Name, + "namespace", existingRouterRoleBinding.Namespace) + return nil + } + + existingRouterRoleBinding.Labels = desiredRouterRoleBinding.Labels + existingRouterRoleBinding.Annotations = desiredRouterRoleBinding.Annotations + existingRouterRoleBinding.Subjects = desiredRouterRoleBinding.Subjects + existingRouterRoleBinding.RoleRef = desiredRouterRoleBinding.RoleRef + return controllerutil.SetControllerReference(jumpstarter, existingRouterRoleBinding, r.Scheme) + }) + + if err != nil { + log.Error(err, "Failed to reconcile Router RoleBinding", + "name", desiredRouterRoleBinding.Name, + "namespace", desiredRouterRoleBinding.Namespace) + return err + } + + log.Info("Router RoleBinding reconciled", + "name", existingRouterRoleBinding.Name, + "namespace", existingRouterRoleBinding.Namespace, + "operation", op) + return nil } @@ -169,6 +289,21 @@ func (r *JumpstarterReconciler) createServiceAccount(jumpstarter *operatorv1alph } } +// createRouterServiceAccount creates a service account for the router with minimal RBAC permissions +func (r *JumpstarterReconciler) createRouterServiceAccount(jumpstarter *operatorv1alpha1.Jumpstarter) *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-router-sa", jumpstarter.Name), + Namespace: jumpstarter.Namespace, + Labels: map[string]string{ + "app": "jumpstarter-router", + "app.kubernetes.io/name": "jumpstarter-router", + "app.kubernetes.io/managed-by": "jumpstarter-operator", + }, + }, + } +} + // createRole creates a role with necessary permissions for the controller func (r *JumpstarterReconciler) createRole(jumpstarter *operatorv1alpha1.Jumpstarter) *rbacv1.Role { return &rbacv1.Role{ @@ -221,6 +356,55 @@ func (r *JumpstarterReconciler) createRole(jumpstarter *operatorv1alpha1.Jumpsta } } +// createRouterRole creates a role with minimal permissions for the router (read configmaps) +func (r *JumpstarterReconciler) createRouterRole(jumpstarter *operatorv1alpha1.Jumpstarter) *rbacv1.Role { + return &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-router-role", jumpstarter.Name), + Namespace: jumpstarter.Namespace, + Labels: map[string]string{ + "app": "jumpstarter-router", + "app.kubernetes.io/name": "jumpstarter-router", + "app.kubernetes.io/managed-by": "jumpstarter-operator", + }, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"configmaps"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + } +} + +// createRouterRoleBinding creates a role binding for the router service account +func (r *JumpstarterReconciler) createRouterRoleBinding(jumpstarter *operatorv1alpha1.Jumpstarter) *rbacv1.RoleBinding { + return &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-router-rolebinding", jumpstarter.Name), + Namespace: jumpstarter.Namespace, + Labels: map[string]string{ + "app": "jumpstarter-router", + "app.kubernetes.io/name": "jumpstarter-router", + "app.kubernetes.io/managed-by": "jumpstarter-operator", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: fmt.Sprintf("%s-router-role", jumpstarter.Name), + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: fmt.Sprintf("%s-router-sa", jumpstarter.Name), + Namespace: jumpstarter.Namespace, + }, + }, + } +} + // createRoleBinding creates a role binding for the controller func (r *JumpstarterReconciler) createRoleBinding(jumpstarter *operatorv1alpha1.Jumpstarter) *rbacv1.RoleBinding { return &rbacv1.RoleBinding{