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 }