github.com/verrazzano/verrazzano@v1.7.1/application-operator/controllers/clusters/verrazzanoproject/verrazzanoproject_controller.go (about) 1 // Copyright (c) 2021, 2022, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package verrazzanoproject 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 11 clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1" 12 "github.com/verrazzano/verrazzano/application-operator/constants" 13 "github.com/verrazzano/verrazzano/application-operator/controllers/clusters" 14 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1" 15 vzconst "github.com/verrazzano/verrazzano/pkg/constants" 16 log2 "github.com/verrazzano/verrazzano/pkg/log" 17 vzlog2 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 18 vzstring "github.com/verrazzano/verrazzano/pkg/string" 19 "go.uber.org/zap" 20 corev1 "k8s.io/api/core/v1" 21 netv1 "k8s.io/api/networking/v1" 22 rbacv1 "k8s.io/api/rbac/v1" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/runtime" 25 "k8s.io/apimachinery/pkg/types" 26 ctrl "sigs.k8s.io/controller-runtime" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 29 "sigs.k8s.io/controller-runtime/pkg/reconcile" 30 ) 31 32 const ( 33 projectAdminRole = "verrazzano-project-admin" 34 projectAdminK8sRole = "admin" 35 projectAdminGroupTemplate = "verrazzano-project-%s-admins" 36 projectMonitorRole = "verrazzano-project-monitor" 37 projectMonitorK8sRole = "view" 38 projectMonitorGroupTemplate = "verrazzano-project-%s-monitors" 39 finalizerName = "project.verrazzano.io" 40 managedClusterRole = "verrazzano-managed-cluster" 41 controllerName = "verrazzanoproject" 42 ) 43 44 // Reconciler reconciles a VerrazzanoProject object 45 type Reconciler struct { 46 client.Client 47 Log *zap.SugaredLogger 48 Scheme *runtime.Scheme 49 AgentChannel chan clusters.StatusUpdateMessage 50 } 51 52 // SetupWithManager registers our controller with the manager 53 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { 54 return ctrl.NewControllerManagedBy(mgr). 55 For(&clustersv1alpha1.VerrazzanoProject{}). 56 Complete(r) 57 } 58 59 // Reconcile reconciles a VerrazzanoProject resource. 60 // It fetches its namespaces if the VerrazzanoProject is in the verrazzano-mc namespace 61 // and create namespaces in the local cluster. 62 func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 63 if ctx == nil { 64 return ctrl.Result{}, errors.New("context cannot be nil") 65 } 66 67 // We do not want any resource to get reconciled if it is in namespace kube-system 68 // This is due to a bug found in OKE, it should not affect functionality of any vz operators 69 // If this is the case then return success 70 if req.Namespace == vzconst.KubeSystem { 71 log := zap.S().With(log2.FieldResourceNamespace, req.Namespace, log2.FieldResourceName, req.Name, log2.FieldController, controllerName) 72 log.Infof("Verrazzano project resource %v should not be reconciled in kube-system namespace, ignoring", req.NamespacedName) 73 return reconcile.Result{}, nil 74 } 75 76 var vp clustersv1alpha1.VerrazzanoProject 77 err := r.Get(ctx, req.NamespacedName, &vp) 78 if err != nil { 79 // If the resource is not found, that means all of the finalizers have been removed, 80 // and the Verrazzano resource has been deleted, so there is nothing left to do. 81 return clusters.IgnoreNotFoundWithLog(err, zap.S()) 82 } 83 log, err := clusters.GetResourceLogger("mcconfigmap", req.NamespacedName, &vp) 84 if err != nil { 85 zap.S().Errorf("Failed to create controller logger for Verrazzano project resource: %v", err) 86 return clusters.NewRequeueWithDelay(), nil 87 } 88 log.Oncef("Reconciling Verrazzano project resource %v, generation %v", req.NamespacedName, vp.Generation) 89 90 res, err := r.doReconcile(ctx, vp, log) 91 if clusters.ShouldRequeue(res) { 92 return res, nil 93 } 94 // Never return an error since it has already been logged and we don't want the 95 // controller runtime to log again (with stack trace). Just re-queue if there is an error. 96 if err != nil { 97 return clusters.NewRequeueWithDelay(), nil 98 } 99 100 log.Oncef("Finished reconciling Verrazzano project %v", req.NamespacedName) 101 102 return ctrl.Result{}, nil 103 } 104 105 // doReconcile performs the reconciliation operations for the VZ project 106 func (r *Reconciler) doReconcile(ctx context.Context, vp clustersv1alpha1.VerrazzanoProject, log vzlog2.VerrazzanoLogger) (ctrl.Result, error) { 107 // Check if the project is being deleted 108 if !vp.ObjectMeta.DeletionTimestamp.IsZero() { 109 // If finalizer is present, delete the network policies in the project namespaces 110 if vzstring.SliceContainsString(vp.ObjectMeta.Finalizers, finalizerName) { 111 log.Debug("Deleting all network policies for project") 112 if err := r.deleteNetworkPolicies(ctx, &vp, nil, log); err != nil { 113 return reconcile.Result{}, err 114 } 115 if err := r.deleteRoleBindings(ctx, &vp, log); err != nil { 116 return reconcile.Result{}, err 117 } 118 // Remove the finalizer and update the Verrazzano resource if the deletion has finished. 119 vp.ObjectMeta.Finalizers = vzstring.RemoveStringFromSlice(vp.ObjectMeta.Finalizers, finalizerName) 120 err := r.Update(ctx, &vp) 121 if err != nil { 122 return reconcile.Result{}, err 123 } 124 } 125 return reconcile.Result{}, nil 126 } 127 128 // Add finalizer if not already added 129 if !vzstring.SliceContainsString(vp.ObjectMeta.Finalizers, finalizerName) { 130 vp.ObjectMeta.Finalizers = append(vp.ObjectMeta.Finalizers, finalizerName) 131 if err := r.Update(ctx, &vp); err != nil { 132 return ctrl.Result{}, err 133 } 134 } 135 136 // Use OperationResultCreated by default since we don't really know what happened to individual resources 137 opResult := controllerutil.OperationResultCreated 138 err := r.syncAll(ctx, vp, log) 139 if err != nil { 140 opResult = controllerutil.OperationResultNone 141 } 142 143 // Update the cluster status 144 _, statusErr := r.updateStatus(ctx, &vp, opResult, err) 145 if statusErr != nil { 146 return ctrl.Result{}, statusErr 147 } 148 149 // Update the VerrazzanoProject state 150 oldState := clusters.SetEffectiveStateIfChanged(vp.Spec.Placement, &vp.Status) 151 if oldState != vp.Status.State { 152 stateErr := r.Status().Update(ctx, &vp) 153 if stateErr != nil { 154 return ctrl.Result{}, stateErr 155 } 156 } 157 158 // if an error occurred in createOrUpdate, return that error with a requeue 159 // even if update status succeeded 160 if err != nil { 161 return ctrl.Result{Requeue: true, RequeueAfter: clusters.GetRandomRequeueDelay()}, err 162 } 163 return ctrl.Result{}, nil 164 } 165 166 // Sync all the project resources, return immediately with error if failure 167 func (r *Reconciler) syncAll(ctx context.Context, vp clustersv1alpha1.VerrazzanoProject, log vzlog2.VerrazzanoLogger) error { 168 err := r.createOrUpdateNamespaces(ctx, vp, log) 169 if err != nil { 170 return err 171 } 172 173 // Sync the network policies 174 err = r.syncNetworkPolicies(ctx, &vp, log) 175 if err != nil { 176 return err 177 } 178 return nil 179 } 180 181 func (r *Reconciler) createOrUpdateNamespaces(ctx context.Context, vp clustersv1alpha1.VerrazzanoProject, log vzlog2.VerrazzanoLogger) error { 182 if vp.Namespace == constants.VerrazzanoMultiClusterNamespace { 183 for _, nsTemplate := range vp.Spec.Template.Namespaces { 184 log.Debug("create or update with underlying namespace %s", nsTemplate.Metadata.Name) 185 var namespace corev1.Namespace 186 namespace.Name = nsTemplate.Metadata.Name 187 188 // ascertain whether istio injection is enabled 189 istioInjection := "enabled" 190 vzns := corev1.Namespace{} 191 if err := r.Client.Get(context.TODO(), client.ObjectKey{Namespace: "", Name: constants.VerrazzanoSystemNamespace}, &vzns); err != nil { 192 return err 193 } 194 if val, ok := vzns.Labels[constants.LabelIstioInjection]; ok { 195 istioInjection = val 196 } 197 198 opResult, err := controllerutil.CreateOrUpdate(ctx, r.Client, &namespace, func() error { 199 r.mutateNamespace(nsTemplate, istioInjection, &namespace) 200 return nil 201 }) 202 if err != nil { 203 return log2.ConflictWithLog(fmt.Sprintf("Failed to create or update namespace %s. result: %v", nsTemplate.Metadata.Name, opResult), err, zap.S()) 204 } 205 206 if err = r.createOrUpdateRoleBindings(ctx, nsTemplate.Metadata.Name, vp, log); err != nil { 207 return err 208 } 209 210 if err = r.deleteRoleBindings(ctx, nil, log); err != nil { 211 return err 212 } 213 } 214 } 215 return nil 216 } 217 218 func (r *Reconciler) mutateNamespace(nsTemplate clustersv1alpha1.NamespaceTemplate, istioInjection string, namespace *corev1.Namespace) { 219 namespace.Annotations = nsTemplate.Metadata.Annotations 220 namespace.Spec = nsTemplate.Spec 221 222 // Add Verrazzano generated labels if not already present 223 if namespace.Labels == nil { 224 namespace.Labels = map[string]string{} 225 } 226 227 // Apply the standard Verrazzano labels 228 namespace.Labels[vzconst.VerrazzanoManagedLabelKey] = constants.LabelVerrazzanoManagedDefault 229 namespace.Labels[constants.LabelIstioInjection] = istioInjection 230 231 // Apply user specified labels, which may override standard Verrazzano labels 232 for label, value := range nsTemplate.Metadata.Labels { 233 namespace.Labels[label] = value 234 } 235 } 236 237 // createOrUpdateRoleBindings creates project role bindings if there are security subjects specified in 238 // the project spec 239 func (r *Reconciler) createOrUpdateRoleBindings(ctx context.Context, namespace string, vp clustersv1alpha1.VerrazzanoProject, log vzlog2.VerrazzanoLogger) error { 240 log.Oncef("Create or update role bindings for namespace %s", namespace) 241 242 // get the default binding subjects 243 adminSubjects, monitorSubjects := r.getDefaultRoleBindingSubjects(vp) 244 245 // override defaults if specified in the project 246 if len(vp.Spec.Template.Security.ProjectAdminSubjects) > 0 { 247 adminSubjects = vp.Spec.Template.Security.ProjectAdminSubjects 248 } 249 if len(vp.Spec.Template.Security.ProjectMonitorSubjects) > 0 { 250 monitorSubjects = vp.Spec.Template.Security.ProjectMonitorSubjects 251 } 252 253 // create two role bindings, one for the project admin role and one for the k8s admin role 254 if len(adminSubjects) > 0 { 255 rb := newRoleBinding(namespace, projectAdminRole, adminSubjects) 256 if err := r.createOrUpdateRoleBinding(ctx, rb, log); err != nil { 257 return err 258 } 259 rb = newRoleBinding(namespace, projectAdminK8sRole, adminSubjects) 260 if err := r.createOrUpdateRoleBinding(ctx, rb, log); err != nil { 261 return err 262 } 263 } 264 265 // create two role bindings, one for the project monitor role and one for the k8s monitor role 266 if len(monitorSubjects) > 0 { 267 rb := newRoleBinding(namespace, projectMonitorRole, monitorSubjects) 268 if err := r.createOrUpdateRoleBinding(ctx, rb, log); err != nil { 269 return err 270 } 271 rb = newRoleBinding(namespace, projectMonitorK8sRole, monitorSubjects) 272 if err := r.createOrUpdateRoleBinding(ctx, rb, log); err != nil { 273 return err 274 } 275 } 276 277 // create role binding for each managed cluster to limit resource access to admin cluster 278 for _, cluster := range vp.Spec.Placement.Clusters { 279 if cluster.Name != constants.DefaultClusterName { 280 rb := newRoleBindingManagedCluster(namespace, cluster.Name) 281 if err := r.createOrUpdateRoleBinding(ctx, rb, log); err != nil { 282 return err 283 } 284 } 285 } 286 return nil 287 } 288 289 // createOrUpdateRoleBinding creates or updates a role binding 290 func (r *Reconciler) createOrUpdateRoleBinding(ctx context.Context, roleBinding *rbacv1.RoleBinding, log vzlog2.VerrazzanoLogger) error { 291 log.Oncef("Create or update role binding for roleName %s", roleBinding.ObjectMeta.Name) 292 293 // deep copy the rolebinding so we can use the data in the mutate function 294 rbCopy := roleBinding.DeepCopy() 295 296 _, err := controllerutil.CreateOrUpdate(ctx, r.Client, roleBinding, func() error { 297 // overwrite the roleref and subjects in case they changed out of band 298 roleBinding.RoleRef = rbCopy.RoleRef 299 roleBinding.Subjects = rbCopy.Subjects 300 return nil 301 }) 302 if err != nil { 303 log.Errorf("Failed to create or update rolebinding %s: %v", roleBinding.ObjectMeta.Name, err) 304 return err 305 } 306 return err 307 } 308 309 // updateStatus updates the status of a VerrazzanoProject 310 func (r *Reconciler) updateStatus(ctx context.Context, vp *clustersv1alpha1.VerrazzanoProject, opResult controllerutil.OperationResult, err error) (ctrl.Result, error) { 311 clusterName := clusters.GetClusterName(ctx, r.Client) 312 newCondition := clusters.GetConditionFromResult(err, opResult, "VerrazzanoProject") 313 updateFunc := func() error { return r.Status().Update(ctx, vp) } 314 return clusters.UpdateStatus(vp, &vp.Status, vp.Spec.Placement, newCondition, clusterName, 315 r.AgentChannel, updateFunc) 316 } 317 318 // newRoleBinding returns a populated RoleBinding struct 319 func newRoleBinding(namespace string, roleName string, subjects []rbacv1.Subject) *rbacv1.RoleBinding { 320 return &rbacv1.RoleBinding{ 321 ObjectMeta: metav1.ObjectMeta{ 322 Namespace: namespace, 323 Name: roleName, 324 }, 325 RoleRef: rbacv1.RoleRef{ 326 APIGroup: rbacv1.GroupName, 327 Kind: "ClusterRole", 328 Name: roleName, 329 }, 330 Subjects: subjects, 331 } 332 } 333 334 // newRoleBinding returns a populated RoleBinding struct for a given managed cluster 335 func newRoleBindingManagedCluster(namespace string, name string) *rbacv1.RoleBinding { 336 clusterNameRef := generateRoleBindingManagedClusterRef(name) 337 return &rbacv1.RoleBinding{ 338 ObjectMeta: metav1.ObjectMeta{ 339 Namespace: namespace, 340 Name: clusterNameRef, 341 }, 342 RoleRef: rbacv1.RoleRef{ 343 APIGroup: rbacv1.GroupName, 344 Kind: "ClusterRole", 345 Name: managedClusterRole, 346 }, 347 Subjects: []rbacv1.Subject{{ 348 Kind: "ServiceAccount", 349 Name: clusterNameRef, 350 Namespace: constants.VerrazzanoMultiClusterNamespace, 351 }, 352 }, 353 } 354 } 355 func generateRoleBindingManagedClusterRef(name string) string { 356 return fmt.Sprintf("verrazzano-cluster-%s", name) 357 } 358 359 // getDefaultRoleBindingSubjects returns the default binding subjects for project admin/monitor roles 360 func (r *Reconciler) getDefaultRoleBindingSubjects(vp clustersv1alpha1.VerrazzanoProject) ([]rbacv1.Subject, []rbacv1.Subject) { 361 adminSubjects := []rbacv1.Subject{{ 362 Kind: "Group", 363 Name: fmt.Sprintf(projectAdminGroupTemplate, vp.Name), 364 }} 365 monitorSubjects := []rbacv1.Subject{{ 366 Kind: "Group", 367 Name: fmt.Sprintf(projectMonitorGroupTemplate, vp.Name), 368 }} 369 return adminSubjects, monitorSubjects 370 } 371 372 // syncNetworkPolicies syncs the NetworkPolicies specified in the project 373 func (r *Reconciler) syncNetworkPolicies(ctx context.Context, project *clustersv1alpha1.VerrazzanoProject, log vzlog2.VerrazzanoLogger) error { 374 // Create or update policies that are in the project spec 375 // The project webhook validates that the network policies use project namespaces 376 desiredPolicySet := make(map[string]bool) 377 for i, policyTemplate := range project.Spec.Template.NetworkPolicies { 378 desiredPolicySet[policyTemplate.Metadata.Namespace+policyTemplate.Metadata.Name] = true 379 _, err := r.createOrUpdateNetworkPolicy(ctx, &project.Spec.Template.NetworkPolicies[i]) 380 if err != nil { 381 return err 382 } 383 } 384 // Delete policies in this namespace that should not exist 385 return r.deleteNetworkPolicies(ctx, project, desiredPolicySet, log) 386 } 387 388 // createOrUpdateNetworkPolicy creates or updates the network polices in the project 389 func (r *Reconciler) createOrUpdateNetworkPolicy(ctx context.Context, desiredPolicy *clustersv1alpha1.NetworkPolicyTemplate) (controllerutil.OperationResult, error) { 390 var policy netv1.NetworkPolicy 391 policy.Namespace = desiredPolicy.Metadata.Namespace 392 policy.Name = desiredPolicy.Metadata.Name 393 394 return controllerutil.CreateOrUpdate(ctx, r.Client, &policy, func() error { 395 desiredPolicy.Metadata.DeepCopyInto(&policy.ObjectMeta) 396 desiredPolicy.Spec.DeepCopyInto(&policy.Spec) 397 return nil 398 }) 399 } 400 401 func (r *Reconciler) deleteRoleBindings(ctx context.Context, project *clustersv1alpha1.VerrazzanoProject, log vzlog2.VerrazzanoLogger) error { 402 // Get the list of VerrazzanoProject resources 403 vpList := clustersv1alpha1.VerrazzanoProjectList{} 404 if err := r.List(ctx, &vpList, client.InNamespace(constants.VerrazzanoMultiClusterNamespace)); err != nil { 405 return err 406 } 407 408 // Create map of expected namespace/cluster pairs for rolebindings 409 expectedPairs := make(map[string]bool) 410 for _, vp := range vpList.Items { 411 if project != nil && project.Name == vp.Name { 412 continue 413 } 414 for _, ns := range vp.Spec.Template.Namespaces { 415 for _, cluster := range vp.Spec.Placement.Clusters { 416 expectedPairs[ns.Metadata.Name+cluster.Name] = true 417 } 418 } 419 } 420 421 // Get the list of VerrazzanoManagedCluster resources 422 vmcList := v1alpha1.VerrazzanoManagedClusterList{} 423 err := r.List(ctx, &vmcList, client.InNamespace(constants.VerrazzanoMultiClusterNamespace)) 424 if err != nil { 425 return err 426 } 427 428 for _, vmc := range vmcList.Items { 429 for _, vp := range vpList.Items { 430 for _, ns := range vp.Spec.Template.Namespaces { 431 // rolebinding is expected for this namespace/cluster pairing 432 // so nothing to delete 433 if _, ok := expectedPairs[ns.Metadata.Name+vmc.Name]; ok { 434 continue 435 } 436 // rolebinding is not expected for this namespace/cluster pairing 437 objectKey := types.NamespacedName{ 438 Namespace: ns.Metadata.Name, 439 Name: generateRoleBindingManagedClusterRef(vmc.Name), 440 } 441 rb := rbacv1.RoleBinding{} 442 if err := r.Get(ctx, objectKey, &rb); err != nil { 443 continue 444 } 445 // This is an orphaned rolebinding so we delete it 446 log.Debugf("Deleting rolebinding %s in namespace %s from project", "namespace", rb.ObjectMeta.Name, rb.ObjectMeta.Namespace) 447 if err := r.Delete(ctx, &rb); err != nil { 448 return err 449 } 450 } 451 } 452 } 453 454 return nil 455 } 456 457 // deleteNetworkPolicies deletes policies that exist in the project namespaces, but are not defined in the project spec 458 func (r *Reconciler) deleteNetworkPolicies(ctx context.Context, project *clustersv1alpha1.VerrazzanoProject, desiredPolicySet map[string]bool, log vzlog2.VerrazzanoLogger) error { 459 for _, ns := range project.Spec.Template.Namespaces { 460 // Get the list of policies in the namespace 461 policies := netv1.NetworkPolicyList{} 462 if err := r.List(ctx, &policies, client.InNamespace(ns.Metadata.Name)); err != nil { 463 return err 464 } 465 // Loop through the policies found in the namespace 466 for pi, policy := range policies.Items { 467 if desiredPolicySet != nil { 468 // Don't delete policy if it should be in the namespace 469 if _, ok := desiredPolicySet[policy.Namespace+policy.Name]; ok { 470 continue 471 } 472 } 473 474 // Found a policy in the namespace that is not specified in the project. Delete it 475 if err := r.Delete(ctx, &policies.Items[pi], &client.DeleteOptions{}); err != nil { 476 log.Errorf("Failed to delete NetworkPolicy %s from namespace %s during cleanup of project: %v", policy.Name, 477 policy.Namespace, err) 478 } 479 } 480 } 481 return nil 482 }