diff --git a/apis/apps/v1/cluster_types.go b/apis/apps/v1/cluster_types.go
index fe8b5eb5236..abf2778adb1 100644
--- a/apis/apps/v1/cluster_types.go
+++ b/apis/apps/v1/cluster_types.go
@@ -405,17 +405,13 @@ type ClusterComponentSpec struct {
// This ServiceAccount is used to grant necessary permissions for the Component's Pods to interact
// with other Kubernetes resources, such as modifying Pod labels or sending events.
//
- // Defaults:
- // To perform certain operational tasks, agent sidecars running in Pods require specific RBAC permissions.
- // The service account will be bound to a default role named "kubeblocks-cluster-pod-role" which is installed together with KubeBlocks.
- // If not specified, KubeBlocks automatically assigns a default ServiceAccount named "kb-{cluster.name}"
+ // If not specified, KubeBlocks automatically creates a default ServiceAccount named
+ // "kb-{cluster.name}-{component.name}", bound to a role with rules defined in ComponentDefinition's
+ // `policyRules` field. It will also be bound to a default role named "kubeblocks-cluster-pod-role"
+ // which is installed together with KubeBlocks.
//
- // Future Changes:
- // Future versions might change the default ServiceAccount creation strategy to one per Component,
- // potentially revising the naming to "kb-{cluster.name}-{component.name}".
- //
- // Users can override the automatic ServiceAccount assignment by explicitly setting the name of
- // an existed ServiceAccount in this field.
+ // If the field is not empty, the specified ServiceAccount will be used. And KubeBlocks will not
+ // create a ServiceAccount, nor create RoleBinding accordingly.
//
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
diff --git a/apis/apps/v1/component_types.go b/apis/apps/v1/component_types.go
index d0dcc15ebd5..4096e3de81c 100644
--- a/apis/apps/v1/component_types.go
+++ b/apis/apps/v1/component_types.go
@@ -175,16 +175,13 @@ type ComponentSpec struct {
// This ServiceAccount is used to grant necessary permissions for the Component's Pods to interact
// with other Kubernetes resources, such as modifying Pod labels or sending events.
//
- // Defaults:
- // If not specified, KubeBlocks automatically assigns a default ServiceAccount named "kb-{cluster.name}",
- // bound to a default role defined during KubeBlocks installation.
+ // If not specified, KubeBlocks automatically creates a default ServiceAccount named
+ // "kb-{cluster.name}-{component.name}", bound to a role with rules defined in ComponentDefinition's
+ // `policyRules` field. It will also be bound to a default role named "kubeblocks-cluster-pod-role"
+ // which is installed together with KubeBlocks.
//
- // Future Changes:
- // Future versions might change the default ServiceAccount creation strategy to one per Component,
- // potentially revising the naming to "kb-{cluster.name}-{component.name}".
- //
- // Users can override the automatic ServiceAccount assignment by explicitly setting the name of
- // an existed ServiceAccount in this field.
+ // If the field is not empty, the specified ServiceAccount will be used. And KubeBlocks will not
+ // create a ServiceAccount, nor create RoleBinding accordingly.
//
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
diff --git a/apis/apps/v1/componentdefinition_types.go b/apis/apps/v1/componentdefinition_types.go
index f9bb51652d0..8b60d37fbb9 100644
--- a/apis/apps/v1/componentdefinition_types.go
+++ b/apis/apps/v1/componentdefinition_types.go
@@ -469,8 +469,6 @@ type ComponentDefinitionSpec struct {
// for the Component based on the specified policy rules.
// This ensures that the Pods in the Component has appropriate permissions to function.
//
- // Note: This field is currently non-functional and is reserved for future implementation.
- //
// This field is immutable.
//
// +optional
diff --git a/config/crd/bases/apps.kubeblocks.io_clusters.yaml b/config/crd/bases/apps.kubeblocks.io_clusters.yaml
index f6ff2897e8e..e742c2e68e2 100644
--- a/config/crd/bases/apps.kubeblocks.io_clusters.yaml
+++ b/config/crd/bases/apps.kubeblocks.io_clusters.yaml
@@ -5032,19 +5032,14 @@ spec:
with other Kubernetes resources, such as modifying Pod labels or sending events.
- Defaults:
- To perform certain operational tasks, agent sidecars running in Pods require specific RBAC permissions.
- The service account will be bound to a default role named "kubeblocks-cluster-pod-role" which is installed together with KubeBlocks.
- If not specified, KubeBlocks automatically assigns a default ServiceAccount named "kb-{cluster.name}"
+ If not specified, KubeBlocks automatically creates a default ServiceAccount named
+ "kb-{cluster.name}-{component.name}", bound to a role with rules defined in ComponentDefinition's
+ `policyRules` field. It will also be bound to a default role named "kubeblocks-cluster-pod-role"
+ which is installed together with KubeBlocks.
- Future Changes:
- Future versions might change the default ServiceAccount creation strategy to one per Component,
- potentially revising the naming to "kb-{cluster.name}-{component.name}".
-
-
- Users can override the automatic ServiceAccount assignment by explicitly setting the name of
- an existed ServiceAccount in this field.
+ If the field is not empty, the specified ServiceAccount will be used. And KubeBlocks will not
+ create a ServiceAccount, nor create RoleBinding accordingly.
type: string
serviceRefs:
description: |-
@@ -13734,19 +13729,14 @@ spec:
with other Kubernetes resources, such as modifying Pod labels or sending events.
- Defaults:
- To perform certain operational tasks, agent sidecars running in Pods require specific RBAC permissions.
- The service account will be bound to a default role named "kubeblocks-cluster-pod-role" which is installed together with KubeBlocks.
- If not specified, KubeBlocks automatically assigns a default ServiceAccount named "kb-{cluster.name}"
+ If not specified, KubeBlocks automatically creates a default ServiceAccount named
+ "kb-{cluster.name}-{component.name}", bound to a role with rules defined in ComponentDefinition's
+ `policyRules` field. It will also be bound to a default role named "kubeblocks-cluster-pod-role"
+ which is installed together with KubeBlocks.
- Future Changes:
- Future versions might change the default ServiceAccount creation strategy to one per Component,
- potentially revising the naming to "kb-{cluster.name}-{component.name}".
-
-
- Users can override the automatic ServiceAccount assignment by explicitly setting the name of
- an existed ServiceAccount in this field.
+ If the field is not empty, the specified ServiceAccount will be used. And KubeBlocks will not
+ create a ServiceAccount, nor create RoleBinding accordingly.
type: string
serviceRefs:
description: |-
diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
index 54164157d4a..3a7325a9b99 100644
--- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
+++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
@@ -3949,9 +3949,6 @@ spec:
This ensures that the Pods in the Component has appropriate permissions to function.
- Note: This field is currently non-functional and is reserved for future implementation.
-
-
This field is immutable.
items:
description: |-
diff --git a/config/crd/bases/apps.kubeblocks.io_components.yaml b/config/crd/bases/apps.kubeblocks.io_components.yaml
index d2bc9d66174..eceb1e27938 100644
--- a/config/crd/bases/apps.kubeblocks.io_components.yaml
+++ b/config/crd/bases/apps.kubeblocks.io_components.yaml
@@ -4800,18 +4800,14 @@ spec:
with other Kubernetes resources, such as modifying Pod labels or sending events.
- Defaults:
- If not specified, KubeBlocks automatically assigns a default ServiceAccount named "kb-{cluster.name}",
- bound to a default role defined during KubeBlocks installation.
-
-
- Future Changes:
- Future versions might change the default ServiceAccount creation strategy to one per Component,
- potentially revising the naming to "kb-{cluster.name}-{component.name}".
+ If not specified, KubeBlocks automatically creates a default ServiceAccount named
+ "kb-{cluster.name}-{component.name}", bound to a role with rules defined in ComponentDefinition's
+ `policyRules` field. It will also be bound to a default role named "kubeblocks-cluster-pod-role"
+ which is installed together with KubeBlocks.
- Users can override the automatic ServiceAccount assignment by explicitly setting the name of
- an existed ServiceAccount in this field.
+ If the field is not empty, the specified ServiceAccount will be used. And KubeBlocks will not
+ create a ServiceAccount, nor create RoleBinding accordingly.
type: string
serviceRefs:
description: |-
diff --git a/controllers/apps/component_controller.go b/controllers/apps/component_controller.go
index 62d957195ca..66cd78553b1 100644
--- a/controllers/apps/component_controller.go
+++ b/controllers/apps/component_controller.go
@@ -214,9 +214,6 @@ func (r *ComponentReconciler) setupWithManager(mgr ctrl.Manager) error {
if viper.GetBool(constant.EnableRBACManager) {
b.Owns(&rbacv1.RoleBinding{}).
Owns(&corev1.ServiceAccount{})
- } else {
- b.Watches(&rbacv1.RoleBinding{}, handler.EnqueueRequestsFromMapFunc(r.filterComponentResources)).
- Watches(&corev1.ServiceAccount{}, handler.EnqueueRequestsFromMapFunc(r.filterComponentResources))
}
return b.Complete(r)
diff --git a/controllers/apps/component_controller_test.go b/controllers/apps/component_controller_test.go
index 355e9a5e8f4..f681341ae79 100644
--- a/controllers/apps/component_controller_test.go
+++ b/controllers/apps/component_controller_test.go
@@ -50,6 +50,7 @@ import (
dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
workloads "github.com/apecloud/kubeblocks/apis/workloads/v1"
"github.com/apecloud/kubeblocks/pkg/constant"
+ "github.com/apecloud/kubeblocks/pkg/controller/builder"
"github.com/apecloud/kubeblocks/pkg/controller/component"
"github.com/apecloud/kubeblocks/pkg/controller/plan"
intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
@@ -1408,33 +1409,29 @@ var _ = Describe("Component Controller", func() {
testCompConfiguration := func(compName, compDefName string) {
}
- checkRBACResourcesExistence := func(saName string, expectExisted bool) {
+ checkRBACResourcesExistence := func(saName, rbName string, expectExisted bool) {
saKey := types.NamespacedName{
Namespace: compObj.Namespace,
Name: saName,
}
rbKey := types.NamespacedName{
Namespace: compObj.Namespace,
- Name: saName,
+ Name: rbName,
}
Eventually(testapps.CheckObjExists(&testCtx, saKey, &corev1.ServiceAccount{}, expectExisted)).Should(Succeed())
Eventually(testapps.CheckObjExists(&testCtx, rbKey, &rbacv1.RoleBinding{}, expectExisted)).Should(Succeed())
}
- testCompRBAC := func(compName, compDefName, saName string) {
- By("update comp definition to enable lifecycle actions")
- Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(compDefObj), func(compDef *kbappsv1.ComponentDefinition) {
- compDef.Spec.LifecycleActions.Readonly = testapps.NewLifecycleAction("readonly")
- compDef.Spec.LifecycleActions.Readwrite = testapps.NewLifecycleAction("readwrite")
- })()).Should(Succeed())
-
+ testCompRBAC := func(compName, compDefName, saName string, rbacResourceCreated bool) {
By("creating a component with target service account name")
if len(saName) == 0 {
- saName = "test-sa-" + randomStr()
+ createClusterObj(compName, compDefName, nil)
+ saName = constant.GenerateDefaultServiceAccountName(clusterObj.Name, compName)
+ } else {
+ createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) {
+ f.SetServiceAccountName(saName)
+ })
}
- createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) {
- f.SetServiceAccountName(saName)
- })
By("check the service account used in Pod")
itsKey := types.NamespacedName{
@@ -1445,52 +1442,53 @@ var _ = Describe("Component Controller", func() {
g.Expect(its.Spec.Template.Spec.ServiceAccountName).To(Equal(saName))
})).Should(Succeed())
- By("check the RBAC resources created")
- checkRBACResourcesExistence(saName, true)
+ By("check the RBAC resources status")
+ checkRBACResourcesExistence(saName, fmt.Sprintf("%v-pod", saName), rbacResourceCreated)
}
testRecreateCompWithRBACCreateByKubeBlocks := func(compName, compDefName string) {
- saName := "test-sa-" + randomStr()
- testCompRBAC(compName, compDefName, saName)
+ testCompRBAC(compName, compDefName, "", true)
By("delete the cluster(component)")
testapps.DeleteObject(&testCtx, clusterKey, &kbappsv1.Cluster{})
Eventually(testapps.CheckObjExists(&testCtx, clusterKey, &kbappsv1.Cluster{}, false)).Should(Succeed())
By("check the RBAC resources deleted")
- checkRBACResourcesExistence(saName, false)
+ saName := constant.GenerateDefaultServiceAccountName(clusterObj.Name, compName)
+ checkRBACResourcesExistence(saName, fmt.Sprintf("%v-pod", saName), false)
By("re-create cluster(component) with same name")
- testCompRBAC(compName, compDefName, saName)
+ testCompRBAC(compName, compDefName, "", true)
}
tesCreateCompWithRBACCreateByUser := func(compName, compDefName string) {
saName := "test-sa-exist" + randomStr()
- testCompRBAC(compName, compDefName, saName)
+ testCompRBAC(compName, compDefName, saName, false)
- saKey := types.NamespacedName{
- Namespace: compObj.Namespace,
- Name: saName,
- }
- By("mock the ServiceAccount and RoleBinding created by user by setting annotations to nil")
- Eventually(testapps.GetAndChangeObj(&testCtx, saKey, func(sa *corev1.ServiceAccount) {
- sa.Annotations = nil
- })).Should(Succeed())
- rbKey := types.NamespacedName{
- Namespace: compObj.Namespace,
- Name: saName,
- }
- Eventually(testapps.GetAndChangeObj(&testCtx, rbKey, func(rb *rbacv1.RoleBinding) {
- rb.Annotations = nil
- })).Should(Succeed())
+ By("user manually creates ServiceAccount and RoleBinding")
+ sa := builder.NewServiceAccountBuilder(compObj.Namespace, saName).GetObject()
+ testapps.CheckedCreateK8sResource(&testCtx, sa)
+ rb := builder.NewRoleBindingBuilder(compObj.Namespace, saName).
+ SetRoleRef(rbacv1.RoleRef{
+ APIGroup: rbacv1.GroupName,
+ Kind: "Role",
+ Name: constant.RBACRoleName,
+ }).
+ AddSubjects(rbacv1.Subject{
+ Kind: rbacv1.ServiceAccountKind,
+ Namespace: compObj.Namespace,
+ Name: saName,
+ }).
+ GetObject()
+ testapps.CheckedCreateK8sResource(&testCtx, rb)
By("delete the cluster(component)")
testapps.DeleteObject(&testCtx, clusterKey, &kbappsv1.Cluster{})
Eventually(testapps.CheckObjExists(&testCtx, clusterKey, &kbappsv1.Cluster{}, true)).Should(Succeed())
- By("check the RBAC resources deleted")
- checkRBACResourcesExistence(saName, true)
+ By("check the RBAC resources not deleted")
+ checkRBACResourcesExistence(saName, saName, true)
}
testThreeReplicas := func(compName, compDefName string) {
@@ -1793,7 +1791,7 @@ var _ = Describe("Component Controller", func() {
})
It("with component RBAC set", func() {
- testCompRBAC(defaultCompName, compDefName, "")
+ testCompRBAC(defaultCompName, compDefName, "", true)
})
It("re-create component with custom RBAC which is not exist and auto created by KubeBlocks", func() {
diff --git a/controllers/apps/transformer_cluster_deletion.go b/controllers/apps/transformer_cluster_deletion.go
index f0790b312bb..b5f827eccb1 100644
--- a/controllers/apps/transformer_cluster_deletion.go
+++ b/controllers/apps/transformer_cluster_deletion.go
@@ -21,13 +21,11 @@ package apps
import (
"fmt"
- "reflect"
"strings"
"time"
"golang.org/x/exp/maps"
corev1 "k8s.io/api/core/v1"
- rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -166,33 +164,9 @@ func kindsForWipeOut() ([]client.ObjectList, []client.ObjectList) {
// shouldSkipObjOwnedByComp is used to judge whether the object owned by component should be skipped when deleting the cluster
func shouldSkipObjOwnedByComp(obj client.Object, cluster kbappsv1.Cluster) bool {
+ // if the object is not owned by component, it should not be skipped
ownByComp := isOwnedByComp(obj)
- if !ownByComp {
- // if the object is not owned by component, it should not be skipped
- return false
- }
-
- // Due to compatibility reasons, the component controller creates cluster-scoped RoleBinding and ServiceAccount objects in the following two scenarios:
- // 1. When the user does not specify a ServiceAccount, KubeBlocks automatically creates a ServiceAccount and a RoleBinding with named pattern kb-{cluster.Name}.
- // 2. When the user specifies a ServiceAccount that does not exist, KubeBlocks will automatically create a ServiceAccount and a RoleBinding with the same name.
- // In both cases, the lifecycle of the RoleBinding and ServiceAccount should not be tied to the component. They should be deleted when the cluster is deleted.
- doNotSkipTypes := []interface{}{
- &rbacv1.RoleBinding{},
- &corev1.ServiceAccount{},
- }
- for _, t := range doNotSkipTypes {
- if objType, ok := obj.(interface{ GetName() string }); ok && reflect.TypeOf(obj) == reflect.TypeOf(t) {
- if strings.EqualFold(objType.GetName(), constant.GenerateDefaultServiceAccountName(cluster.GetName())) {
- return false
- }
- labels := obj.GetLabels()
- value, ok := labels[constant.AppManagedByLabelKey]
- if ok && value == constant.AppName {
- return false
- }
- }
- }
- return true
+ return ownByComp
}
func deleteCompNShardingInOrder4Terminate(transCtx *clusterTransformContext, dag *graph.DAG) (sets.Set[string], error) {
diff --git a/controllers/apps/transformer_component_rbac.go b/controllers/apps/transformer_component_rbac.go
index 46e9e4ed74a..aef556c0961 100644
--- a/controllers/apps/transformer_component_rbac.go
+++ b/controllers/apps/transformer_component_rbac.go
@@ -21,23 +21,21 @@ package apps
import (
"fmt"
- "time"
+ "reflect"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
- "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
workloads "github.com/apecloud/kubeblocks/apis/workloads/v1"
"github.com/apecloud/kubeblocks/pkg/common"
"github.com/apecloud/kubeblocks/pkg/constant"
- "github.com/apecloud/kubeblocks/pkg/controller/component"
"github.com/apecloud/kubeblocks/pkg/controller/factory"
"github.com/apecloud/kubeblocks/pkg/controller/graph"
"github.com/apecloud/kubeblocks/pkg/controller/model"
- ictrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
+ "github.com/apecloud/kubeblocks/pkg/generics"
viper "github.com/apecloud/kubeblocks/pkg/viperx"
)
@@ -46,6 +44,8 @@ type componentRBACTransformer struct{}
var _ graph.Transformer = &componentRBACTransformer{}
+const EventReasonRBACManager = "RBACManager"
+
func (t *componentRBACTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
transCtx, _ := ctx.(*componentTransformContext)
if model.IsObjectDeleting(transCtx.ComponentOrig) {
@@ -57,39 +57,44 @@ func (t *componentRBACTransformer) Transform(ctx graph.TransformContext, dag *gr
return nil
}
- graphCli, _ := transCtx.Client.(model.GraphClient)
-
- serviceAccount, err := buildServiceAccount(transCtx)
- if err != nil {
- return err
+ // If the user has disabled rbac manager or specified comp.Spec.ServiceAccountName, it is now the user's responsibility to
+ // provide appropriate serviceaccount, roles and rolebindings.
+ if transCtx.Component.Spec.ServiceAccountName != "" {
+ return nil
}
- if serviceAccount == nil {
- transCtx.Logger.V(1).Info("buildServiceAccounts returns serviceAccount nil")
+ if !viper.GetBool(constant.EnableRBACManager) {
+ transCtx.EventRecorder.Event(transCtx.Cluster, corev1.EventTypeWarning, EventReasonRBACManager, "RBAC manager is disabled")
return nil
}
- if isServiceAccountExist(transCtx, serviceAccount.Name) {
- return nil
+ graphCli, _ := transCtx.Client.(model.GraphClient)
+ synthesizedComp := transCtx.SynthesizeComponent
+ serviceAccountName := constant.GenerateDefaultServiceAccountName(synthesizedComp.ClusterName, synthesizedComp.Name)
+
+ role, err := createOrUpdateRole(transCtx, serviceAccountName, graphCli, dag)
+ if err != nil {
+ return err
}
- if !viper.GetBool(constant.EnableRBACManager) {
- transCtx.Logger.V(1).Info("rbac manager is disabled")
- transCtx.EventRecorder.Event(transCtx.Cluster, corev1.EventTypeWarning,
- string(ictrlutil.ErrorTypeNotFound), fmt.Sprintf("ServiceAccount %s is not exist", serviceAccount.Name))
- return ictrlutil.NewRequeueError(time.Second, "RBAC manager is disabled, but service account is not exist")
+ rbs, err := createOrUpdateRoleBinding(transCtx, role, serviceAccountName, graphCli, dag)
+ if err != nil {
+ return err
}
- rb, err := buildRoleBinding(transCtx.SynthesizeComponent, transCtx.Component, serviceAccount.Name)
+ // if no rolebinding is needed, sa will be created anyway, because other modules may reference it.
+ sa, err := createOrUpdateServiceAccount(transCtx, serviceAccountName, graphCli, dag)
if err != nil {
return err
}
- graphCli.Create(dag, rb, inDataContext4G())
- createServiceAccount(serviceAccount, graphCli, dag, rb)
+ // serviceAccount should be created before roleBinding and role
+ for _, rb := range rbs {
+ graphCli.DependOn(dag, rb, sa, role)
+ }
+ // serviceAccount should be created before workload
itsList := graphCli.FindAll(dag, &workloads.InstanceSet{})
for _, its := range itsList {
- // serviceAccount must be created before workload
- graphCli.DependOn(dag, its, serviceAccount)
+ graphCli.DependOn(dag, its, sa)
}
return nil
@@ -99,103 +104,91 @@ func isLifecycleActionsEnabled(compDef *appsv1.ComponentDefinition) bool {
return compDef.Spec.LifecycleActions != nil
}
-func isServiceAccountExist(transCtx *componentTransformContext, serviceAccountName string) bool {
- synthesizedComp := transCtx.SynthesizeComponent
- namespaceName := types.NamespacedName{
- Namespace: synthesizedComp.Namespace,
- Name: serviceAccountName,
- }
- sa := &corev1.ServiceAccount{}
- if err := transCtx.Client.Get(transCtx.Context, namespaceName, sa, inDataContext4C()); err != nil {
- // KubeBlocks will create a rolebinding only if it has RBAC access priority and
- // the rolebinding is not already present.
+func createOrUpdate[T any, PT generics.PObject[T]](
+ transCtx *componentTransformContext, obj PT, graphCli model.GraphClient, dag *graph.DAG, cmpFn func(oldObj, newObj PT) bool,
+) (PT, error) {
+ oldObj := PT(new(T))
+ if err := transCtx.Client.Get(transCtx.Context, client.ObjectKeyFromObject(obj), oldObj); err != nil {
if errors.IsNotFound(err) {
- transCtx.Logger.V(1).Info("ServiceAccount not exists", "namespaceName", namespaceName)
- return false
+ graphCli.Create(dag, obj, inDataContext4G())
+ return obj, nil
}
- transCtx.Logger.Error(err, "get ServiceAccount failed")
- return false
+ return nil, err
+ }
+ if !cmpFn(oldObj, obj) {
+ graphCli.Update(dag, oldObj, obj, inDataContext4G())
}
- return true
+ return obj, nil
}
-func isRoleBindingExist(transCtx *componentTransformContext, serviceAccountName string) bool {
+func createOrUpdateServiceAccount(transCtx *componentTransformContext, serviceAccountName string, graphCli model.GraphClient, dag *graph.DAG) (*corev1.ServiceAccount, error) {
synthesizedComp := transCtx.SynthesizeComponent
- namespaceName := types.NamespacedName{
- Namespace: synthesizedComp.Namespace,
- Name: constant.GenerateDefaultServiceAccountName(synthesizedComp.ClusterName),
- }
- rb := &rbacv1.RoleBinding{}
- if err := transCtx.Client.Get(transCtx.Context, namespaceName, rb, inDataContext4C()); err != nil {
- // KubeBlocks will create a role binding only if it has RBAC access priority and
- // the role binding is not already present.
- if errors.IsNotFound(err) {
- transCtx.Logger.V(1).Info("RoleBinding not exists", "namespaceName", namespaceName)
- return false
- }
- transCtx.Logger.Error(err, fmt.Sprintf("get role binding failed: %s", namespaceName))
- return false
- }
- if rb.RoleRef.Name != constant.RBACRoleName {
- transCtx.Logger.V(1).Info("rbac manager: ClusterRole not match", "ClusterRole",
- constant.RBACRoleName, "rolebinding.RoleRef", rb.RoleRef.Name)
- }
-
- isServiceAccountMatch := false
- for _, sub := range rb.Subjects {
- if sub.Kind == rbacv1.ServiceAccountKind && sub.Name == serviceAccountName {
- isServiceAccountMatch = true
- break
- }
+ sa := factory.BuildServiceAccount(synthesizedComp, serviceAccountName)
+ if err := setCompOwnershipNFinalizer(transCtx.Component, sa); err != nil {
+ return nil, err
}
- if !isServiceAccountMatch {
- transCtx.Logger.V(1).Info("rbac manager: ServiceAccount not match", "ServiceAccount",
- serviceAccountName, "rolebinding.Subjects", rb.Subjects)
- }
- return true
+ return createOrUpdate(transCtx, sa, graphCli, dag, func(old, new *corev1.ServiceAccount) bool {
+ return reflect.DeepEqual(old.ImagePullSecrets, new.ImagePullSecrets) &&
+ reflect.DeepEqual(old.Secrets, new.Secrets) &&
+ *old.AutomountServiceAccountToken == *new.AutomountServiceAccountToken
+ })
}
-// buildServiceAccount builds the service account for the component.
-func buildServiceAccount(transCtx *componentTransformContext) (*corev1.ServiceAccount, error) {
- var (
- cluster = transCtx.Cluster
- comp = transCtx.Component
- compDef = transCtx.CompDef
- synthesizedComp = transCtx.SynthesizeComponent
- )
- serviceAccountName := comp.Spec.ServiceAccountName
- if serviceAccountName == "" {
- // If lifecycle actions are disabled, then do not create a service account.
- if !isLifecycleActionsEnabled(compDef) {
- return nil, nil
- }
- // use cluster.name to keep compatible with existed clusters
- serviceAccountName = constant.GenerateDefaultServiceAccountName(cluster.Name)
- }
-
- if isRoleBindingExist(transCtx, serviceAccountName) && isServiceAccountExist(transCtx, serviceAccountName) {
+func createOrUpdateRole(
+ transCtx *componentTransformContext, serviceAccountName string, graphCli model.GraphClient, dag *graph.DAG,
+) (*rbacv1.Role, error) {
+ role := factory.BuildComponentRole(transCtx.SynthesizeComponent, transCtx.CompDef, serviceAccountName)
+ if role == nil {
return nil, nil
}
-
- saObj := factory.BuildServiceAccount(synthesizedComp, serviceAccountName)
- if err := setCompOwnershipNFinalizer(comp, saObj); err != nil {
+ if err := setCompOwnershipNFinalizer(transCtx.Component, role); err != nil {
return nil, err
}
- return saObj, nil
+ return createOrUpdate(transCtx, role, graphCli, dag, func(old, new *rbacv1.Role) bool {
+ return reflect.DeepEqual(old.Rules, new.Rules)
+ })
}
-func buildRoleBinding(synthesizedComp *component.SynthesizedComponent, comp *appsv1.Component, serviceAccountName string) (*rbacv1.RoleBinding, error) {
- roleBinding := factory.BuildRoleBinding(synthesizedComp, serviceAccountName)
- if err := setCompOwnershipNFinalizer(comp, roleBinding); err != nil {
- return nil, err
+func createOrUpdateRoleBinding(
+ transCtx *componentTransformContext, cmpdRole *rbacv1.Role, serviceAccountName string, graphCli model.GraphClient, dag *graph.DAG,
+) ([]*rbacv1.RoleBinding, error) {
+ cmpRoleBinding := func(old, new *rbacv1.RoleBinding) bool {
+ return reflect.DeepEqual(old.Subjects, new.Subjects) && reflect.DeepEqual(old.RoleRef, new.RoleRef)
+ }
+ res := make([]*rbacv1.RoleBinding, 0)
+
+ if cmpdRole != nil {
+ cmpdRoleBinding := factory.BuildRoleBinding(transCtx.SynthesizeComponent, serviceAccountName, &rbacv1.RoleRef{
+ APIGroup: rbacv1.GroupName,
+ Kind: "Role",
+ Name: cmpdRole.Name,
+ }, serviceAccountName)
+ rb, err := createOrUpdate(transCtx, cmpdRoleBinding, graphCli, dag, cmpRoleBinding)
+ if err != nil {
+ return nil, err
+ }
+ res = append(res, rb)
+ }
+
+ if isLifecycleActionsEnabled(transCtx.CompDef) {
+ clusterPodRoleBinding := factory.BuildRoleBinding(
+ transCtx.SynthesizeComponent,
+ fmt.Sprintf("%v-pod", serviceAccountName),
+ &rbacv1.RoleRef{
+ APIGroup: rbacv1.GroupName,
+ Kind: "ClusterRole",
+ Name: constant.RBACRoleName,
+ },
+ serviceAccountName,
+ )
+ rb, err := createOrUpdate(transCtx, clusterPodRoleBinding, graphCli, dag, cmpRoleBinding)
+ if err != nil {
+ return nil, err
+ }
+ res = append(res, rb)
}
- return roleBinding, nil
-}
-func createServiceAccount(serviceAccount *corev1.ServiceAccount, graphCli model.GraphClient, dag *graph.DAG, parent client.Object) {
- // serviceAccount must be created before roleBinding
- graphCli.Create(dag, serviceAccount, inDataContext4G())
- graphCli.DependOn(dag, parent, serviceAccount)
+ return res, nil
}
diff --git a/controllers/apps/transformer_component_rbac_test.go b/controllers/apps/transformer_component_rbac_test.go
index 34fbc4afdc4..120713aa6b0 100644
--- a/controllers/apps/transformer_component_rbac_test.go
+++ b/controllers/apps/transformer_component_rbac_test.go
@@ -20,10 +20,14 @@ along with this program. If not, see
Specifies the name of the ServiceAccount required by the running Component. This ServiceAccount is used to grant necessary permissions for the Component’s Pods to interact with other Kubernetes resources, such as modifying Pod labels or sending events.
-Defaults: -If not specified, KubeBlocks automatically assigns a default ServiceAccount named “kb-{cluster.name}”, -bound to a default role defined during KubeBlocks installation.
-Future Changes: -Future versions might change the default ServiceAccount creation strategy to one per Component, -potentially revising the naming to “kb-{cluster.name}-{component.name}”.
-Users can override the automatic ServiceAccount assignment by explicitly setting the name of -an existed ServiceAccount in this field.
+If not specified, KubeBlocks automatically creates a default ServiceAccount named
+“kb-{cluster.name}-{component.name}”, bound to a role with rules defined in ComponentDefinition’s
+policyRules
field. It will also be bound to a default role named “kubeblocks-cluster-pod-role”
+which is installed together with KubeBlocks.
If the field is not empty, the specified ServiceAccount will be used. And KubeBlocks will not +create a ServiceAccount, nor create RoleBinding accordingly.
The purpose of this field is to automatically generate the necessary RBAC roles for the Component based on the specified policy rules. This ensures that the Pods in the Component has appropriate permissions to function.
-Note: This field is currently non-functional and is reserved for future implementation.
This field is immutable.
Specifies the name of the ServiceAccount required by the running Component. This ServiceAccount is used to grant necessary permissions for the Component’s Pods to interact with other Kubernetes resources, such as modifying Pod labels or sending events.
-Defaults: -To perform certain operational tasks, agent sidecars running in Pods require specific RBAC permissions. -The service account will be bound to a default role named “kubeblocks-cluster-pod-role” which is installed together with KubeBlocks. -If not specified, KubeBlocks automatically assigns a default ServiceAccount named “kb-{cluster.name}”
-Future Changes: -Future versions might change the default ServiceAccount creation strategy to one per Component, -potentially revising the naming to “kb-{cluster.name}-{component.name}”.
-Users can override the automatic ServiceAccount assignment by explicitly setting the name of -an existed ServiceAccount in this field.
+If not specified, KubeBlocks automatically creates a default ServiceAccount named
+“kb-{cluster.name}-{component.name}”, bound to a role with rules defined in ComponentDefinition’s
+policyRules
field. It will also be bound to a default role named “kubeblocks-cluster-pod-role”
+which is installed together with KubeBlocks.
If the field is not empty, the specified ServiceAccount will be used. And KubeBlocks will not +create a ServiceAccount, nor create RoleBinding accordingly.
The purpose of this field is to automatically generate the necessary RBAC roles for the Component based on the specified policy rules. This ensures that the Pods in the Component has appropriate permissions to function.
-Note: This field is currently non-functional and is reserved for future implementation.
This field is immutable.
Specifies the name of the ServiceAccount required by the running Component. This ServiceAccount is used to grant necessary permissions for the Component’s Pods to interact with other Kubernetes resources, such as modifying Pod labels or sending events.
-Defaults: -If not specified, KubeBlocks automatically assigns a default ServiceAccount named “kb-{cluster.name}”, -bound to a default role defined during KubeBlocks installation.
-Future Changes: -Future versions might change the default ServiceAccount creation strategy to one per Component, -potentially revising the naming to “kb-{cluster.name}-{component.name}”.
-Users can override the automatic ServiceAccount assignment by explicitly setting the name of -an existed ServiceAccount in this field.
+If not specified, KubeBlocks automatically creates a default ServiceAccount named
+“kb-{cluster.name}-{component.name}”, bound to a role with rules defined in ComponentDefinition’s
+policyRules
field. It will also be bound to a default role named “kubeblocks-cluster-pod-role”
+which is installed together with KubeBlocks.
If the field is not empty, the specified ServiceAccount will be used. And KubeBlocks will not +create a ServiceAccount, nor create RoleBinding accordingly.