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