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  }