github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/transformer_rbac.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package apps
    21  
    22  import (
    23  	"fmt"
    24  	"time"
    25  
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	corev1 "k8s.io/api/core/v1"
    28  	rbacv1 "k8s.io/api/rbac/v1"
    29  	"k8s.io/apimachinery/pkg/api/errors"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  	"sigs.k8s.io/controller-runtime/pkg/log"
    33  
    34  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    35  	"github.com/1aal/kubeblocks/pkg/constant"
    36  	"github.com/1aal/kubeblocks/pkg/controller/component"
    37  	"github.com/1aal/kubeblocks/pkg/controller/factory"
    38  	"github.com/1aal/kubeblocks/pkg/controller/graph"
    39  	"github.com/1aal/kubeblocks/pkg/controller/model"
    40  	ictrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    41  	dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types"
    42  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    43  )
    44  
    45  // RBACTransformer puts the rbac at the beginning of the DAG
    46  type RBACTransformer struct{}
    47  
    48  var _ graph.Transformer = &RBACTransformer{}
    49  
    50  func (c *RBACTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
    51  	transCtx, _ := ctx.(*clusterTransformContext)
    52  	cluster := transCtx.Cluster
    53  	graphCli, _ := transCtx.Client.(model.GraphClient)
    54  
    55  	componentSpecs, err := getComponentSpecs(transCtx)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	serviceAccounts, serviceAccountsNeedCrb, err := buildServiceAccounts(transCtx, componentSpecs)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	if !viper.GetBool(constant.EnableRBACManager) {
    66  		transCtx.Logger.V(1).Info("rbac manager is disabled")
    67  		saNotExist := false
    68  		for saName := range serviceAccounts {
    69  			if !isServiceAccountExist(transCtx, saName) {
    70  				transCtx.EventRecorder.Event(transCtx.Cluster, corev1.EventTypeWarning,
    71  					string(ictrlutil.ErrorTypeNotFound), saName+" ServiceAccount is not exist")
    72  				saNotExist = true
    73  			}
    74  		}
    75  		if saNotExist {
    76  			return ictrlutil.NewRequeueError(time.Second, "RBAC manager is disabed, but service account is not exist")
    77  		}
    78  		return nil
    79  	}
    80  
    81  	var parent client.Object
    82  	rb := buildRoleBinding(cluster, serviceAccounts)
    83  	graphCli.Create(dag, rb)
    84  	parent = rb
    85  	if len(serviceAccountsNeedCrb) > 0 {
    86  		crb := buildClusterRoleBinding(cluster, serviceAccountsNeedCrb)
    87  		graphCli.Create(dag, crb)
    88  		graphCli.DependOn(dag, parent, crb)
    89  		parent = crb
    90  	}
    91  
    92  	sas := createServiceAccounts(serviceAccounts, graphCli, dag, parent)
    93  	stsList := graphCli.FindAll(dag, &appsv1.StatefulSet{})
    94  	for _, sts := range stsList {
    95  		// serviceaccount must be created before statefulset
    96  		graphCli.DependOn(dag, sts, sas...)
    97  	}
    98  
    99  	deployList := graphCli.FindAll(dag, &appsv1.Deployment{})
   100  	for _, deploy := range deployList {
   101  		// serviceaccount must be created before deployment
   102  		graphCli.DependOn(dag, deploy, sas...)
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  func isProbesEnabled(clusterDef *appsv1alpha1.ClusterDefinition, compSpec *appsv1alpha1.ClusterComponentSpec) bool {
   109  	for _, compDef := range clusterDef.Spec.ComponentDefs {
   110  		if compDef.Name == compSpec.ComponentDefRef && compDef.Probes != nil {
   111  			return true
   112  		}
   113  	}
   114  	return false
   115  }
   116  
   117  func isDataProtectionEnabled(backupTpl *appsv1alpha1.BackupPolicyTemplate, compSpec *appsv1alpha1.ClusterComponentSpec) bool {
   118  	if backupTpl == nil {
   119  		return false
   120  	}
   121  	for _, policy := range backupTpl.Spec.BackupPolicies {
   122  		if policy.ComponentDefRef == compSpec.ComponentDefRef {
   123  			return true
   124  		}
   125  	}
   126  	return false
   127  }
   128  
   129  func isVolumeProtectionEnabled(clusterDef *appsv1alpha1.ClusterDefinition, compSpec *appsv1alpha1.ClusterComponentSpec) bool {
   130  	for _, compDef := range clusterDef.Spec.ComponentDefs {
   131  		if compDef.Name == compSpec.ComponentDefRef && compDef.VolumeProtectionSpec != nil {
   132  			return true
   133  		}
   134  	}
   135  	return false
   136  }
   137  
   138  func isServiceAccountExist(transCtx *clusterTransformContext, serviceAccountName string) bool {
   139  	cluster := transCtx.Cluster
   140  	namespaceName := types.NamespacedName{
   141  		Namespace: cluster.Namespace,
   142  		Name:      serviceAccountName,
   143  	}
   144  	sa := &corev1.ServiceAccount{}
   145  	if err := transCtx.Client.Get(transCtx.Context, namespaceName, sa); err != nil {
   146  		// KubeBlocks will create a rolebinding only if it has RBAC access priority and
   147  		// the rolebinding is not already present.
   148  		if errors.IsNotFound(err) {
   149  			transCtx.Logger.V(1).Info("ServiceAccount not exists", "namespaceName", namespaceName)
   150  			return false
   151  		}
   152  		transCtx.Logger.Error(err, "get ServiceAccount failed")
   153  		return false
   154  	}
   155  	return true
   156  }
   157  
   158  func isClusterRoleBindingExist(transCtx *clusterTransformContext, serviceAccountName string) bool {
   159  	cluster := transCtx.Cluster
   160  	namespaceName := types.NamespacedName{
   161  		Namespace: cluster.Namespace,
   162  		Name:      "kb-" + cluster.Name,
   163  	}
   164  	crb := &rbacv1.ClusterRoleBinding{}
   165  	if err := transCtx.Client.Get(transCtx.Context, namespaceName, crb); err != nil {
   166  		// KubeBlocks will create a cluster role binding only if it has RBAC access priority and
   167  		// the cluster role binding is not already present.
   168  		if errors.IsNotFound(err) {
   169  			transCtx.Logger.V(1).Info("ClusterRoleBinding not exists", "namespaceName", namespaceName)
   170  			return false
   171  		}
   172  		transCtx.Logger.Error(err, fmt.Sprintf("get cluster role binding failed: %s", namespaceName))
   173  		return false
   174  	}
   175  
   176  	if crb.RoleRef.Name != constant.RBACClusterRoleName {
   177  		transCtx.Logger.V(1).Info("rbac manager: ClusterRole not match", "ClusterRole",
   178  			constant.RBACClusterRoleName, "clusterrolebinding.RoleRef", crb.RoleRef.Name)
   179  	}
   180  
   181  	isServiceAccountMatch := false
   182  	for _, sub := range crb.Subjects {
   183  		if sub.Kind == rbacv1.ServiceAccountKind && sub.Name == serviceAccountName {
   184  			isServiceAccountMatch = true
   185  			break
   186  		}
   187  	}
   188  
   189  	if !isServiceAccountMatch {
   190  		transCtx.Logger.V(1).Info("rbac manager: ServiceAccount not match", "ServiceAccount",
   191  			serviceAccountName, "clusterrolebinding.Subjects", crb.Subjects)
   192  	}
   193  	return true
   194  }
   195  
   196  func isRoleBindingExist(transCtx *clusterTransformContext, serviceAccountName string) bool {
   197  	cluster := transCtx.Cluster
   198  	namespaceName := types.NamespacedName{
   199  		Namespace: cluster.Namespace,
   200  		Name:      "kb-" + cluster.Name,
   201  	}
   202  	rb := &rbacv1.RoleBinding{}
   203  	if err := transCtx.Client.Get(transCtx.Context, namespaceName, rb); err != nil {
   204  		// KubeBlocks will create a role binding only if it has RBAC access priority and
   205  		// the role binding is not already present.
   206  		if errors.IsNotFound(err) {
   207  			transCtx.Logger.V(1).Info("RoleBinding not exists", "namespaceName", namespaceName)
   208  			return false
   209  		}
   210  		transCtx.Logger.Error(err, fmt.Sprintf("get role binding failed: %s", namespaceName))
   211  		return false
   212  	}
   213  
   214  	if rb.RoleRef.Name != constant.RBACClusterRoleName {
   215  		transCtx.Logger.V(1).Info("rbac manager: ClusterRole not match", "ClusterRole",
   216  			constant.RBACRoleName, "rolebinding.RoleRef", rb.RoleRef.Name)
   217  	}
   218  
   219  	isServiceAccountMatch := false
   220  	for _, sub := range rb.Subjects {
   221  		if sub.Kind == rbacv1.ServiceAccountKind && sub.Name == serviceAccountName {
   222  			isServiceAccountMatch = true
   223  			break
   224  		}
   225  	}
   226  
   227  	if !isServiceAccountMatch {
   228  		transCtx.Logger.V(1).Info("rbac manager: ServiceAccount not match", "ServiceAccount",
   229  			serviceAccountName, "rolebinding.Subjects", rb.Subjects)
   230  	}
   231  	return true
   232  }
   233  
   234  func getComponentSpecs(transCtx *clusterTransformContext) ([]appsv1alpha1.ClusterComponentSpec, error) {
   235  	cluster := transCtx.Cluster
   236  	clusterDef := transCtx.ClusterDef
   237  	componentSpecs := make([]appsv1alpha1.ClusterComponentSpec, 0, 1)
   238  	compSpecMap := cluster.Spec.GetDefNameMappingComponents()
   239  	for _, compDef := range clusterDef.Spec.ComponentDefs {
   240  		comps := compSpecMap[compDef.Name]
   241  		if len(comps) == 0 {
   242  			// if componentSpecs is empty, it may be generated from the cluster template and cluster.
   243  			reqCtx := ictrlutil.RequestCtx{
   244  				Ctx: transCtx.Context,
   245  				Log: log.Log.WithName("rbac"),
   246  			}
   247  			synthesizedComponent, err := component.BuildComponent(reqCtx, nil, cluster, transCtx.ClusterDef, &compDef, nil, nil)
   248  			if err != nil {
   249  				return nil, err
   250  			}
   251  			if synthesizedComponent == nil {
   252  				continue
   253  			}
   254  			comps = []appsv1alpha1.ClusterComponentSpec{{
   255  				ServiceAccountName: synthesizedComponent.ServiceAccountName,
   256  				ComponentDefRef:    compDef.Name,
   257  			}}
   258  		}
   259  		componentSpecs = append(componentSpecs, comps...)
   260  	}
   261  	return componentSpecs, nil
   262  }
   263  
   264  func getDefaultBackupPolicyTemplate(transCtx *clusterTransformContext, clusterDefName string) (*appsv1alpha1.BackupPolicyTemplate, error) {
   265  	backupPolicyTPLs := &appsv1alpha1.BackupPolicyTemplateList{}
   266  	if err := transCtx.Client.List(transCtx.Context, backupPolicyTPLs, client.MatchingLabels{constant.ClusterDefLabelKey: clusterDefName}); err != nil {
   267  		return nil, err
   268  	}
   269  	if len(backupPolicyTPLs.Items) == 0 {
   270  		return nil, nil
   271  	}
   272  	for _, item := range backupPolicyTPLs.Items {
   273  		if item.Annotations[dptypes.DefaultBackupPolicyTemplateAnnotationKey] == trueVal {
   274  			return &item, nil
   275  		}
   276  	}
   277  	return &backupPolicyTPLs.Items[0], nil
   278  }
   279  
   280  func buildServiceAccounts(transCtx *clusterTransformContext, componentSpecs []appsv1alpha1.ClusterComponentSpec) (map[string]*corev1.ServiceAccount, map[string]*corev1.ServiceAccount, error) {
   281  	serviceAccounts := map[string]*corev1.ServiceAccount{}
   282  	serviceAccountsNeedCrb := map[string]*corev1.ServiceAccount{}
   283  	clusterDef := transCtx.ClusterDef
   284  	cluster := transCtx.Cluster
   285  	backupPolicyTPL, err := getDefaultBackupPolicyTemplate(transCtx, clusterDef.Name)
   286  	if err != nil {
   287  		return serviceAccounts, serviceAccountsNeedCrb, err
   288  	}
   289  	for _, compSpec := range componentSpecs {
   290  		serviceAccountName := compSpec.ServiceAccountName
   291  		if serviceAccountName == "" {
   292  			if !isProbesEnabled(clusterDef, &compSpec) && !isVolumeProtectionEnabled(clusterDef, &compSpec) && !isDataProtectionEnabled(backupPolicyTPL, &compSpec) {
   293  				continue
   294  			}
   295  			serviceAccountName = "kb-" + cluster.Name
   296  		}
   297  
   298  		if isRoleBindingExist(transCtx, serviceAccountName) && isServiceAccountExist(transCtx, serviceAccountName) {
   299  			if !isVolumeProtectionEnabled(clusterDef, &compSpec) || isClusterRoleBindingExist(transCtx, serviceAccountName) {
   300  				continue
   301  			}
   302  		}
   303  
   304  		if _, ok := serviceAccounts[serviceAccountName]; ok {
   305  			continue
   306  		}
   307  		serviceAccount := factory.BuildServiceAccount(cluster)
   308  		serviceAccount.Name = serviceAccountName
   309  		serviceAccounts[serviceAccountName] = serviceAccount
   310  
   311  		if isVolumeProtectionEnabled(clusterDef, &compSpec) {
   312  			serviceAccountsNeedCrb[serviceAccountName] = serviceAccount
   313  		}
   314  	}
   315  	return serviceAccounts, serviceAccountsNeedCrb, nil
   316  }
   317  
   318  func buildRoleBinding(cluster *appsv1alpha1.Cluster, serviceAccounts map[string]*corev1.ServiceAccount) *rbacv1.RoleBinding {
   319  	roleBinding := factory.BuildRoleBinding(cluster)
   320  	roleBinding.Subjects = []rbacv1.Subject{}
   321  	for saName := range serviceAccounts {
   322  		subject := rbacv1.Subject{
   323  			Name:      saName,
   324  			Namespace: cluster.Namespace,
   325  			Kind:      rbacv1.ServiceAccountKind,
   326  		}
   327  		roleBinding.Subjects = append(roleBinding.Subjects, subject)
   328  	}
   329  	return roleBinding
   330  }
   331  
   332  func buildClusterRoleBinding(cluster *appsv1alpha1.Cluster, serviceAccounts map[string]*corev1.ServiceAccount) *rbacv1.ClusterRoleBinding {
   333  	clusterRoleBinding := factory.BuildClusterRoleBinding(cluster)
   334  	clusterRoleBinding.Subjects = []rbacv1.Subject{}
   335  	for saName := range serviceAccounts {
   336  		subject := rbacv1.Subject{
   337  			Name:      saName,
   338  			Namespace: cluster.Namespace,
   339  			Kind:      rbacv1.ServiceAccountKind,
   340  		}
   341  		clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, subject)
   342  	}
   343  	return clusterRoleBinding
   344  }
   345  
   346  func createServiceAccounts(serviceAccounts map[string]*corev1.ServiceAccount, graphCli model.GraphClient, dag *graph.DAG, parent client.Object) []client.Object {
   347  	var sas []client.Object
   348  	for _, sa := range serviceAccounts {
   349  		// serviceaccount must be created before rolebinding and clusterrolebinding
   350  		graphCli.Create(dag, sa)
   351  		graphCli.DependOn(dag, parent, sa)
   352  		sas = append(sas, sa)
   353  	}
   354  	return sas
   355  }