246 lines
7.9 KiB
Go
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{}
|
|
}
|