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 }