github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/webhooks/project_authorization_policy.go (about)

     1  // Copyright (c) 2021, 2023, 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 webhooks
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  
    11  	cluv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    12  	"github.com/verrazzano/verrazzano/application-operator/constants"
    13  	vzstring "github.com/verrazzano/verrazzano/pkg/string"
    14  	"go.uber.org/zap"
    15  	"istio.io/client-go/pkg/apis/security/v1beta1"
    16  	istioversionedclient "istio.io/client-go/pkg/clientset/versioned"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/client-go/kubernetes"
    19  	"sigs.k8s.io/controller-runtime/pkg/client"
    20  )
    21  
    22  const verrazzanoIstioLabel = "verrazzano.io/istio"
    23  
    24  // AuthorizationPolicy type for fixing up authorization policies for projects
    25  type AuthorizationPolicy struct {
    26  	client.Client
    27  	KubeClient  kubernetes.Interface
    28  	IstioClient istioversionedclient.Interface
    29  }
    30  
    31  // cleanupAuthorizationPoliciesForProjects updates authorization policies so that all applications within a project
    32  // are allowed to talk to each other after delete of an appconfig.  This function is called from the appconfig-defaulter
    33  // webhook when an appconfig resource is deleted. This function will fixup the remaining authorization policies to not
    34  // reference the deleted appconfig.
    35  func (ap *AuthorizationPolicy) cleanupAuthorizationPoliciesForProjects(namespace string, appConfigName string, log *zap.SugaredLogger) error {
    36  	// Get the list of defined projects
    37  	projectsList := &cluv1alpha1.VerrazzanoProjectList{}
    38  	listOptions := &client.ListOptions{Namespace: constants.VerrazzanoMultiClusterNamespace}
    39  	err := ap.Client.List(context.TODO(), projectsList, listOptions)
    40  	if err != nil {
    41  		return err
    42  	}
    43  
    44  	// Walk the list of projects looking for a project namespace that matches the given namespace
    45  	for _, project := range projectsList.Items {
    46  		namespaceFound := false
    47  		for _, ns := range project.Spec.Template.Namespaces {
    48  			if ns.Metadata.Name == namespace {
    49  				namespaceFound = true
    50  				break
    51  			}
    52  		}
    53  
    54  		// Project has a namespace that matches the given namespace
    55  		if namespaceFound {
    56  			// Get the authorization policies for all the namespaces in a project.
    57  			tempAuthzPolicyList, err := ap.getAuthorizationPoliciesForProject(project.Spec.Template.Namespaces)
    58  			if err != nil {
    59  				return err
    60  			}
    61  
    62  			// Filter the authorization policies we retrieved.  Do not include authorization policies for the
    63  			// appconfig being deleted.
    64  			authzPolicyList := []*v1beta1.AuthorizationPolicy{}
    65  			for _, policy := range tempAuthzPolicyList {
    66  				if value, ok := policy.Spec.Selector.MatchLabels[verrazzanoIstioLabel]; ok {
    67  					if value != appConfigName {
    68  						authzPolicyList = append(authzPolicyList, policy)
    69  					}
    70  				}
    71  			}
    72  
    73  			// After filtering there are no authorization policies so nothing to do - we can return now.
    74  			if len(authzPolicyList) == 0 {
    75  				return nil
    76  			}
    77  
    78  			// Get a list of pods for the given namespace
    79  			podList, err := ap.KubeClient.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
    80  			if err != nil {
    81  				return err
    82  			}
    83  
    84  			// Get the list of service accounts remaining in the namespace of where an appconfig delete is taking place.
    85  			// This service account list does not include the service account used by the appconfig being deleted.
    86  			saList := []string{}
    87  			for _, pod := range podList.Items {
    88  				if value, ok := pod.Labels[verrazzanoIstioLabel]; ok {
    89  					if value != appConfigName {
    90  						saList = append(saList, pod.Spec.ServiceAccountName)
    91  					}
    92  				}
    93  			}
    94  
    95  			// Create list of unique principals for all authorization policies in a project.
    96  			// This list does not include principals used by the appconfig being deleted.
    97  			uniquePrincipals := make(map[string]bool)
    98  			for _, authzPolicy := range authzPolicyList {
    99  				for _, principal := range authzPolicy.Spec.Rules[0].From[0].Source.Principals {
   100  					// For the namespace passed, only include the service accounts remaining for the namespace
   101  					split := strings.Split(principal, "/")
   102  					if len(split) != 5 {
   103  						return fmt.Errorf("expected format of Istio authorization policy is cluster.local/ns/<namespace>/sa/<service-account>")
   104  					}
   105  					if split[2] == namespace {
   106  						for _, sa := range saList {
   107  							if split[4] == sa {
   108  								uniquePrincipals[principal] = true
   109  							}
   110  						}
   111  						continue
   112  					}
   113  					uniquePrincipals[principal] = true
   114  				}
   115  			}
   116  
   117  			// Update all authorization policies in a project.
   118  			err = ap.updateAuthorizationPoliciesForProject(authzPolicyList, uniquePrincipals, log)
   119  			if err != nil {
   120  				return err
   121  			}
   122  
   123  			break
   124  		}
   125  	}
   126  	return nil
   127  }
   128  
   129  // fixupAuthorizationPoliciesForProjects updates authorization policies so that all applications within a project
   130  // are allowed to talk to each other. This function is called by the istio-defaulter webhook when authorization
   131  // policies are created.
   132  func (ap *AuthorizationPolicy) fixupAuthorizationPoliciesForProjects(namespace string, log *zap.SugaredLogger) error {
   133  	// Get the list of defined projects
   134  	projectsList := &cluv1alpha1.VerrazzanoProjectList{}
   135  	listOptions := &client.ListOptions{Namespace: constants.VerrazzanoMultiClusterNamespace}
   136  	err := ap.Client.List(context.TODO(), projectsList, listOptions)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	// Walk the list of projects looking for a project namespace that matches the given namespace
   142  	for _, project := range projectsList.Items {
   143  		namespaceFound := false
   144  		for _, ns := range project.Spec.Template.Namespaces {
   145  			if ns.Metadata.Name == namespace {
   146  				namespaceFound = true
   147  				break
   148  			}
   149  		}
   150  
   151  		// Project has a namespace that matches the given namespace
   152  		if namespaceFound {
   153  			// Get the authorization policies for all the namespaces in a project.
   154  			tempAuthzPolicyList, err := ap.getAuthorizationPoliciesForProject(project.Spec.Template.Namespaces)
   155  			if err != nil {
   156  				return err
   157  			}
   158  
   159  			// Filter the authorization policies we retrieved
   160  			authzPolicyList := []*v1beta1.AuthorizationPolicy{}
   161  			for _, policy := range tempAuthzPolicyList {
   162  				if _, ok := policy.Spec.Selector.MatchLabels[verrazzanoIstioLabel]; ok {
   163  					authzPolicyList = append(authzPolicyList, policy)
   164  				}
   165  			}
   166  
   167  			// Create list of unique principals for all authorization policies in a project.
   168  			uniquePrincipals := make(map[string]bool)
   169  			for _, authzPolicy := range authzPolicyList {
   170  				for _, principal := range authzPolicy.Spec.Rules[0].From[0].Source.Principals {
   171  					uniquePrincipals[principal] = true
   172  				}
   173  			}
   174  
   175  			// Update all authorization policies in a project.
   176  			err = ap.updateAuthorizationPoliciesForProject(authzPolicyList, uniquePrincipals, log)
   177  			if err != nil {
   178  				return err
   179  			}
   180  
   181  			break
   182  		}
   183  	}
   184  	return nil
   185  }
   186  
   187  // getAuthorizationPoliciesForProject returns a list of Istio authorization policies for a given list of namespaces.
   188  // The returned authorization policies must a have an owner reference to an applicationConfiguration resource.
   189  func (ap *AuthorizationPolicy) getAuthorizationPoliciesForProject(namespaceList []cluv1alpha1.NamespaceTemplate) ([]*v1beta1.AuthorizationPolicy, error) {
   190  	var authzPolicyList = []*v1beta1.AuthorizationPolicy{}
   191  	for _, namespace := range namespaceList {
   192  		// Get the list of authorization policy resources in the namespace
   193  		list, err := ap.IstioClient.SecurityV1beta1().AuthorizationPolicies(namespace.Metadata.Name).List(context.TODO(), metav1.ListOptions{})
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  		for _, authzPolicy := range list.Items {
   198  			// If the owner reference is an appconfig resource then
   199  			// we add the authorization policy to our list of authorization policies
   200  			if authzPolicy.OwnerReferences[0].Kind == "ApplicationConfiguration" {
   201  				authzPolicyList = append(authzPolicyList, authzPolicy)
   202  			}
   203  		}
   204  	}
   205  
   206  	return authzPolicyList, nil
   207  }
   208  
   209  // updateAuthorizationPoliciesForProject updates Istio authorization policies for a project, if needed.
   210  func (ap *AuthorizationPolicy) updateAuthorizationPoliciesForProject(authzPolicyList []*v1beta1.AuthorizationPolicy, uniquePrincipals map[string]bool, log *zap.SugaredLogger) error {
   211  	for i, authzPolicy := range authzPolicyList {
   212  		// If the principals specified for the authorization policy do not have the expected principals then
   213  		// we need to update them.
   214  		if !vzstring.UnorderedEqual(uniquePrincipals, authzPolicy.Spec.Rules[0].From[0].Source.Principals) {
   215  			var principals = []string{}
   216  			for principal := range uniquePrincipals {
   217  				principals = append(principals, principal)
   218  			}
   219  			authzPolicy.Spec.Rules[0].From[0].Source.Principals = principals
   220  			log.Debugf("Updating project Istio authorization policy: %s:%s", authzPolicy.Namespace, authzPolicy.Name)
   221  			_, err := ap.IstioClient.SecurityV1beta1().AuthorizationPolicies(authzPolicy.Namespace).Update(context.TODO(), authzPolicyList[i], metav1.UpdateOptions{})
   222  			if err != nil {
   223  				return err
   224  			}
   225  		}
   226  	}
   227  
   228  	return nil
   229  }