Files
kubesphere/pkg/controller/roletemplate/roletemplate_controller.go
2025-04-30 15:53:51 +08:00

246 lines
7.9 KiB
Go

/*
* Copyright 2024 the KubeSphere Authors.
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package roletemplate
import (
"context"
kscontroller "kubesphere.io/kubesphere/pkg/controller"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/klog/v2"
"k8s.io/client-go/tools/record"
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
rbachelper "kubesphere.io/kubesphere/pkg/componenthelper/auth/rbac"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
)
const (
autoAggregateIndexKey = ".metadata.annotations[iam.kubesphere.io/auto-aggregate]"
autoAggregationLabel = "iam.kubesphere.io/auto-aggregate"
controllerName = "roletemplate"
reasonFailedSync = "FailedInjectRoleTemplate"
messageResourceSynced = "RoleTemplate injected successfully"
)
var _ kscontroller.Controller = &Reconciler{}
// Reconciler reconciles a RoleTemplate object
type Reconciler struct {
client.Client
recorder record.EventRecorder
logger klog.Logger
}
func (r *Reconciler) Name() string {
return controllerName
}
// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr *kscontroller.Manager) error {
r.recorder = mgr.GetEventRecorderFor(controllerName)
r.logger = mgr.GetLogger().WithName(controllerName)
r.Client = mgr.GetClient()
if err := mgr.GetCache().IndexField(context.Background(), &iamv1beta1.GlobalRole{}, autoAggregateIndexKey, globalRoleIndexByAnnotation); err != nil {
return err
}
if err := mgr.GetCache().IndexField(context.Background(), &iamv1beta1.WorkspaceRole{}, autoAggregateIndexKey, workspaceRoleIndexByAnnotation); err != nil {
return err
}
if err := mgr.GetCache().IndexField(context.Background(), &iamv1beta1.ClusterRole{}, autoAggregateIndexKey, clusterRoleIndexByAnnotation); err != nil {
return err
}
if err := mgr.GetCache().IndexField(context.Background(), &iamv1beta1.Role{}, autoAggregateIndexKey, roleIndexByAnnotation); err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
Named(controllerName).
For(&iamv1beta1.RoleTemplate{}).
Complete(r)
}
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
roleTemplate := &iamv1beta1.RoleTemplate{}
if err := r.Get(ctx, req.NamespacedName, roleTemplate); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if err := r.injectRoleTemplateToRuleOwner(ctx, roleTemplate); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *Reconciler) injectRoleTemplateToRuleOwner(ctx context.Context, roleTemplate *iamv1beta1.RoleTemplate) error {
if roleTemplate.Labels[iamv1beta1.ScopeLabel] == iamv1beta1.ScopeGlobal {
if err := r.aggregateGlobalRoles(ctx, roleTemplate); err != nil {
return err
}
}
if roleTemplate.Labels[iamv1beta1.ScopeLabel] == iamv1beta1.ScopeWorkspace {
if err := r.aggregateWorkspaceRoles(ctx, roleTemplate); err != nil {
return err
}
}
if roleTemplate.Labels[iamv1beta1.ScopeLabel] == iamv1beta1.ScopeCluster {
if err := r.aggregateClusterRoles(ctx, roleTemplate); err != nil {
return err
}
}
if roleTemplate.Labels[iamv1beta1.ScopeLabel] == iamv1beta1.ScopeNamespace {
if err := r.aggregateRoles(ctx, roleTemplate); err != nil {
return err
}
}
return nil
}
// aggregateGlobalRoles automatic inject the RoleTemplate`s rules to the role
// (all role types have the field AggregationRoleTemplates) matching the AggregationRoleTemplates filed.
// Note that autoAggregateRoles just aggregate the templates by field ".aggregationRoleTemplates.roleSelectors",
// and if the roleTemplate content is changed, the role including the roleTemplate should not be updated.
func (r *Reconciler) aggregateGlobalRoles(ctx context.Context, roleTemplate *iamv1beta1.RoleTemplate) error {
list := &iamv1beta1.GlobalRoleList{}
if err := r.List(ctx, list, client.MatchingFields(fields.Set{autoAggregateIndexKey: "true"})); err != nil {
return err
}
for _, role := range list.Items {
err := r.aggregate(ctx, rbachelper.GlobalRoleRuleOwner{GlobalRole: &role}, roleTemplate)
if err != nil {
return err
}
}
return nil
}
func (r *Reconciler) aggregateWorkspaceRoles(ctx context.Context, roleTemplate *iamv1beta1.RoleTemplate) error {
list := &iamv1beta1.WorkspaceRoleList{}
if err := r.List(ctx, list, client.MatchingFields(fields.Set{autoAggregateIndexKey: "true"})); err != nil {
return err
}
for _, role := range list.Items {
err := r.aggregate(ctx, rbachelper.WorkspaceRoleRuleOwner{WorkspaceRole: &role}, roleTemplate)
if err != nil {
return err
}
}
return nil
}
func (r *Reconciler) aggregateClusterRoles(ctx context.Context, roleTemplate *iamv1beta1.RoleTemplate) error {
list := &iamv1beta1.ClusterRoleList{}
if err := r.List(ctx, list, client.MatchingFields(fields.Set{autoAggregateIndexKey: "true"})); err != nil {
return err
}
for _, role := range list.Items {
err := r.aggregate(ctx, rbachelper.ClusterRoleRuleOwner{ClusterRole: &role}, roleTemplate)
if err != nil {
return err
}
}
return nil
}
func (r *Reconciler) aggregateRoles(ctx context.Context, roleTemplate *iamv1beta1.RoleTemplate) error {
list := &iamv1beta1.RoleList{}
if err := r.List(ctx, list, client.MatchingFields(fields.Set{autoAggregateIndexKey: "true"})); err != nil {
return err
}
for _, role := range list.Items {
err := r.aggregate(ctx, rbachelper.RoleRuleOwner{Role: &role}, roleTemplate)
if err != nil {
return err
}
}
return nil
}
// aggregate the role-template rules to the ruleOwner. If the role-template is updated but has already been aggregated by the ruleOwner,
// the ruleOwner cannot update the new role-template rule to the ruleOwner.
func (r *Reconciler) aggregate(ctx context.Context, ruleOwner rbachelper.RuleOwner, roleTemplate *iamv1beta1.RoleTemplate) error {
aggregation := ruleOwner.GetAggregationRule()
if aggregation == nil {
return nil
}
hasTemplateName := sliceutil.HasString(aggregation.TemplateNames, roleTemplate.Name)
if hasTemplateName {
return nil
}
selector, err := metav1.LabelSelectorAsSelector(aggregation.RoleSelector)
if err != nil {
r.logger.V(4).Error(err, "failed to pares role selector", "template", ruleOwner.GetName())
return nil
}
if !selector.Matches(labels.Set(roleTemplate.Labels)) {
return nil
}
cover, _ := rbachelper.Covers(ruleOwner.GetRules(), roleTemplate.Spec.Rules)
if cover && hasTemplateName {
return nil
}
if !cover {
ruleOwner.SetRules(append(ruleOwner.GetRules(), roleTemplate.Spec.Rules...))
}
aggregation.TemplateNames = append(aggregation.TemplateNames, roleTemplate.Name)
ruleOwner.SetAggregationRule(aggregation)
if err := r.Update(ctx, ruleOwner.GetObject().(client.Object)); err != nil {
r.recorder.Event(ruleOwner.GetObject(), corev1.EventTypeWarning, reasonFailedSync, err.Error())
return err
}
r.recorder.Event(ruleOwner.GetObject(), corev1.EventTypeNormal, "Synced", messageResourceSynced)
return nil
}
func globalRoleIndexByAnnotation(obj client.Object) []string {
role := obj.(*iamv1beta1.GlobalRole)
if val, ok := role.Annotations[autoAggregationLabel]; ok {
return []string{val}
}
return []string{}
}
func workspaceRoleIndexByAnnotation(obj client.Object) []string {
role := obj.(*iamv1beta1.WorkspaceRole)
if val, ok := role.Annotations[autoAggregationLabel]; ok {
return []string{val}
}
return []string{}
}
func clusterRoleIndexByAnnotation(obj client.Object) []string {
role := obj.(*iamv1beta1.ClusterRole)
if val, ok := role.Annotations[autoAggregationLabel]; ok {
return []string{val}
}
return []string{}
}
func roleIndexByAnnotation(obj client.Object) []string {
role := obj.(*iamv1beta1.Role)
if val, ok := role.Annotations[autoAggregationLabel]; ok {
return []string{val}
}
return []string{}
}