1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package webhook
16
17 import (
18 "context"
19 "fmt"
20 "net/http"
21
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/iam/v1beta1"
24 kcciamclient "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/iamclient"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/extension"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/schema/dclschemaloader"
28 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gvks/externalonlygvks"
29 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
30 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
31
32 "github.com/nasa9084/go-openapi"
33 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
34 "k8s.io/apimachinery/pkg/runtime/schema"
35 "k8s.io/klog/v2"
36 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
37 )
38
39 type iamValidatorHandler struct {
40 smLoader *servicemappingloader.ServiceMappingLoader
41 serviceMetadataLoader metadata.ServiceMetadataLoader
42 schemaLoader dclschemaloader.DCLSchemaLoader
43 }
44
45 func NewIAMValidatorHandler(smLoader *servicemappingloader.ServiceMappingLoader,
46 serviceMetadataLoader metadata.ServiceMetadataLoader,
47 schemaLoader dclschemaloader.DCLSchemaLoader) *iamValidatorHandler {
48 return &iamValidatorHandler{
49 smLoader: smLoader,
50 serviceMetadataLoader: serviceMetadataLoader,
51 schemaLoader: schemaLoader,
52 }
53 }
54
55 func (a *iamValidatorHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
56 deserializer := codecs.UniversalDeserializer()
57 obj := &unstructured.Unstructured{}
58 if _, _, err := deserializer.Decode(req.AdmissionRequest.Object.Raw, nil, obj); err != nil {
59 klog.Error(err)
60 return admission.Errored(http.StatusBadRequest,
61 fmt.Errorf("error decoding object: %v", err))
62 }
63 switch {
64 case isIAMPolicy(obj):
65 policy, err := toIAMPolicy(obj)
66 if err != nil {
67 return admission.Errored(http.StatusInternalServerError, err)
68 }
69 refResourceGVK := policy.Spec.ResourceReference.GroupVersionKind()
70 isDCLResource := metadata.IsDCLBasedResourceKind(refResourceGVK, a.serviceMetadataLoader)
71 return a.validateIAMPolicy(policy, isDCLResource)
72
73 case isIAMPartialPolicy(obj):
74 partialPolicy, err := toIAMPartialPolicy(obj)
75 if err != nil {
76 return admission.Errored(http.StatusInternalServerError, err)
77 }
78 refResourceGVK := partialPolicy.Spec.ResourceReference.GroupVersionKind()
79 isDCLResource := metadata.IsDCLBasedResourceKind(refResourceGVK, a.serviceMetadataLoader)
80 return a.validateIAMPartialPolicy(partialPolicy, isDCLResource)
81
82 case isIAMPolicyMember(obj):
83 policyMember, err := toIAMPolicyMember(obj)
84 if err != nil {
85 return admission.Errored(http.StatusInternalServerError, err)
86 }
87 refResourceGVK := policyMember.Spec.ResourceReference.GroupVersionKind()
88 isDCLResource := metadata.IsDCLBasedResourceKind(refResourceGVK, a.serviceMetadataLoader)
89 return a.validateIAMPolicyMember(policyMember, isDCLResource)
90 case isIAMAuditConfig(obj):
91 auditConfig, err := toIAMAuditConfig(obj)
92 if err != nil {
93 return admission.Errored(http.StatusInternalServerError, err)
94 }
95 refResourceGVK := auditConfig.Spec.ResourceReference.GroupVersionKind()
96 isDCLResource := metadata.IsDCLBasedResourceKind(refResourceGVK, a.serviceMetadataLoader)
97 if isDCLResource {
98 return admission.Errored(http.StatusForbidden,
99 fmt.Errorf("object of GroupVersionKind %v does not have IAM Audit Config support", obj.GroupVersionKind()))
100 }
101 rcs, err := getResourceConfigs(a.smLoader, refResourceGVK)
102 if err != nil {
103 return admission.Errored(http.StatusBadRequest, err)
104 }
105 return validateIAMAuditConfig(auditConfig, rcs)
106 default:
107 return admission.Errored(http.StatusInternalServerError,
108 fmt.Errorf("object of GroupVersionKind %v is not a supported IAM resource", obj.GroupVersionKind()))
109 }
110 }
111
112 func toIAMPolicy(obj *unstructured.Unstructured) (*v1beta1.IAMPolicy, error) {
113 policy := &v1beta1.IAMPolicy{}
114 if err := util.Marshal(obj, policy); err != nil {
115 return nil, fmt.Errorf("error parsing %v into IAM Policy object: %v", obj.GetName(), err)
116 }
117 return policy, nil
118 }
119
120 func toIAMPartialPolicy(obj *unstructured.Unstructured) (*v1beta1.IAMPartialPolicy, error) {
121 partialPolicy := &v1beta1.IAMPartialPolicy{}
122 if err := util.Marshal(obj, partialPolicy); err != nil {
123 return nil, fmt.Errorf("error parsing %v into IAMPartialPolicy object: %v", obj.GetName(), err)
124 }
125 return partialPolicy, nil
126 }
127
128 func toIAMPolicyMember(obj *unstructured.Unstructured) (*v1beta1.IAMPolicyMember, error) {
129 policyMember := &v1beta1.IAMPolicyMember{}
130 if err := util.Marshal(obj, policyMember); err != nil {
131 return nil, fmt.Errorf("error parsing %v into IAM Policy Member object: %v", obj.GetName(), err)
132 }
133 return policyMember, nil
134 }
135
136 func toIAMAuditConfig(obj *unstructured.Unstructured) (*v1beta1.IAMAuditConfig, error) {
137 auditConfig := &v1beta1.IAMAuditConfig{}
138 if err := util.Marshal(obj, auditConfig); err != nil {
139 return nil, fmt.Errorf("error parsing %v into IAMAuditConfig object: %v", obj.GetName(), err)
140 }
141 return auditConfig, nil
142 }
143
144 func getDCLSchema(gvk schema.GroupVersionKind, serviceMetadataLoader metadata.ServiceMetadataLoader, schemaLoader dclschemaloader.DCLSchemaLoader) (*openapi.Schema, admission.Response) {
145 dclSchema, err := dclschemaloader.GetDCLSchemaForGVK(gvk, serviceMetadataLoader, schemaLoader)
146 if err != nil {
147 return nil, admission.Errored(http.StatusBadRequest, err)
148 }
149 return dclSchema, allowedResponse
150 }
151
152 func getResourceConfigs(smLoader *servicemappingloader.ServiceMappingLoader, gvk schema.GroupVersionKind) ([]*v1alpha1.ResourceConfig, error) {
153
154
155 if gvk.Group == "" {
156 if gvk.Kind == kcciamclient.ProjectKind {
157 gvk = kcciamclient.ProjectGVK
158 } else {
159 return []*v1alpha1.ResourceConfig{}, fmt.Errorf("resource reference for kind '%v' must include API group", gvk.Kind)
160 }
161 }
162 if externalonlygvks.IsExternalOnlyGVK(gvk) {
163 rc, err := kcciamclient.GetResourceConfigForExternalOnlyGVK(gvk)
164 if err != nil {
165 return []*v1alpha1.ResourceConfig{}, fmt.Errorf("error getting ResourceConfig for GroupVersionKind %v: %v", gvk, err)
166 }
167 return []*v1alpha1.ResourceConfig{rc}, nil
168 }
169 rcs, err := smLoader.GetResourceConfigs(gvk)
170 if err != nil {
171 return []*v1alpha1.ResourceConfig{}, fmt.Errorf("error getting ResourceConfig for GroupVersionKind %v: %v", gvk, err)
172 }
173 if len(rcs) == 0 {
174 return []*v1alpha1.ResourceConfig{}, fmt.Errorf("couldn't find any ResourceConfig defined for GroupVersionKind %v", gvk)
175 }
176 return rcs, nil
177 }
178
179 func (a *iamValidatorHandler) validateIAMPolicy(policy *v1beta1.IAMPolicy, isDCLResource bool) admission.Response {
180 resourceRef := policy.Spec.ResourceReference
181 if isDCLResource {
182 return a.dclValidateIAMPolicy(policy)
183 }
184
185
186 rcs, err := getResourceConfigs(a.smLoader, resourceRef.GroupVersionKind())
187 if err != nil {
188 return admission.Errored(http.StatusBadRequest, err)
189 }
190 return a.tfValidateIAMPolicy(policy, rcs)
191 }
192
193 func (a *iamValidatorHandler) validateIAMPartialPolicy(partialPolicy *v1beta1.IAMPartialPolicy, isDCLResource bool) admission.Response {
194 resourceRef := partialPolicy.Spec.ResourceReference
195 if isDCLResource {
196 return a.dclValidateIAMPartialPolicy(partialPolicy)
197 }
198
199 rcs, err := getResourceConfigs(a.smLoader, resourceRef.GroupVersionKind())
200 if err != nil {
201 return admission.Errored(http.StatusBadRequest, err)
202 }
203 return a.tfValidateIAMPartialPolicy(partialPolicy, rcs)
204 }
205
206 func (a *iamValidatorHandler) validateIAMPolicyMember(policyMember *v1beta1.IAMPolicyMember, isDCLResource bool) admission.Response {
207 resourceRef := policyMember.Spec.ResourceReference
208 if isDCLResource {
209 return a.dclValidateIAMPolicyMember(policyMember)
210 }
211
212 rcs, err := getResourceConfigs(a.smLoader, resourceRef.GroupVersionKind())
213 if err != nil {
214 return admission.Errored(http.StatusBadRequest, err)
215 }
216 return a.tfValidateIAMPolicyMember(policyMember, rcs)
217 }
218
219 func validateIAMAuditConfig(auditConfig *v1beta1.IAMAuditConfig, refResourceRCs []*v1alpha1.ResourceConfig) admission.Response {
220 resourceRef := auditConfig.Spec.ResourceReference
221 if !doesTFResourceSupportAuditConfigs(refResourceRCs) {
222 return admission.Errored(http.StatusForbidden,
223 fmt.Errorf("GroupVersionKind %v does not support IAM Audit Configs", resourceRef.GroupVersionKind()))
224 }
225 return allowedResponse
226 }
227
228 func (a *iamValidatorHandler) dclValidateIAMPolicy(policy *v1beta1.IAMPolicy) admission.Response {
229 resourceRef := policy.Spec.ResourceReference
230
231 dclSchema, resp := getDCLSchema(resourceRef.GroupVersionKind(), a.serviceMetadataLoader, a.schemaLoader)
232 if !resp.Allowed {
233 return resp
234 }
235 supportsIAM, err := extension.HasIam(dclSchema)
236 if err != nil {
237 return admission.Errored(http.StatusInternalServerError, err)
238 }
239 if !supportsIAM {
240 return admission.Errored(http.StatusForbidden, fmt.Errorf("GroupVersionKind %v does not support IAM Policy", resourceRef.GroupVersionKind()))
241 }
242
243
244
245 if len(policy.Spec.AuditConfigs) > 0 {
246 return admission.Errored(http.StatusForbidden, fmt.Errorf("GroupVersionKind %v does not support IAM Audit Configs", resourceRef.GroupVersionKind()))
247 }
248 return allowedResponse
249 }
250
251 func (a *iamValidatorHandler) tfValidateIAMPolicy(policy *v1beta1.IAMPolicy, rcs []*v1alpha1.ResourceConfig) admission.Response {
252 resourceRef := policy.Spec.ResourceReference
253 if doesIAMPolicyHaveConditions(policy) && !doesTFResourceSupportConditions(rcs) {
254 return admission.Errored(http.StatusForbidden,
255 fmt.Errorf("GroupVersionKind %v does not support IAM Conditions", resourceRef.GroupVersionKind()))
256 }
257 if len(policy.Spec.AuditConfigs) > 0 && !doesTFResourceSupportAuditConfigs(rcs) {
258 return admission.Errored(http.StatusForbidden,
259 fmt.Errorf("GroupVersionKind %v does not support IAM Audit Configs", resourceRef.GroupVersionKind()))
260 }
261 return allowedResponse
262 }
263
264 func (a *iamValidatorHandler) dclValidateIAMPartialPolicy(partialPolicy *v1beta1.IAMPartialPolicy) admission.Response {
265 resourceRef := partialPolicy.Spec.ResourceReference
266
267 dclSchema, resp := getDCLSchema(resourceRef.GroupVersionKind(), a.serviceMetadataLoader, a.schemaLoader)
268 if !resp.Allowed {
269 return resp
270 }
271 supportsIAM, err := extension.HasIam(dclSchema)
272 if err != nil {
273 return admission.Errored(http.StatusInternalServerError, err)
274 }
275 if !supportsIAM {
276 return admission.Errored(http.StatusForbidden, fmt.Errorf("GroupVersionKind %v does not support IAM Partial Policy", resourceRef.GroupVersionKind()))
277 }
278 return allowedResponse
279 }
280
281 func (a *iamValidatorHandler) tfValidateIAMPartialPolicy(partialPolicy *v1beta1.IAMPartialPolicy, rcs []*v1alpha1.ResourceConfig) admission.Response {
282 resourceRef := partialPolicy.Spec.ResourceReference
283 if doesIAMPartialPolicyHaveConditions(partialPolicy) && !doesTFResourceSupportConditions(rcs) {
284 return admission.Errored(http.StatusForbidden,
285 fmt.Errorf("GroupVersionKind %v does not support IAM Conditions", resourceRef.GroupVersionKind()))
286 }
287 return allowedResponse
288 }
289
290 func (a *iamValidatorHandler) dclValidateIAMPolicyMember(policyMember *v1beta1.IAMPolicyMember) admission.Response {
291 resourceRef := policyMember.Spec.ResourceReference
292
293 dclSchema, resp := getDCLSchema(resourceRef.GroupVersionKind(), a.serviceMetadataLoader, a.schemaLoader)
294 if !resp.Allowed {
295 return resp
296 }
297 supportsIAM, err := extension.HasIam(dclSchema)
298 if err != nil {
299 return admission.Errored(http.StatusInternalServerError, err)
300 }
301 if !supportsIAM {
302 return admission.Errored(http.StatusForbidden, fmt.Errorf("GroupVersionKind %v does not support IAM Policy Member", resourceRef.GroupVersionKind()))
303 }
304
305 if doesIAMPolicyMemberHaveCondition(policyMember) {
306 return admission.Errored(http.StatusForbidden,
307 fmt.Errorf("GroupVersionKind %v does not support IAM Conditions in IAM Policy Member", resourceRef.GroupVersionKind()))
308 }
309 return allowedResponse
310 }
311
312 func (a *iamValidatorHandler) tfValidateIAMPolicyMember(policyMember *v1beta1.IAMPolicyMember, rcs []*v1alpha1.ResourceConfig) admission.Response {
313 resourceRef := policyMember.Spec.ResourceReference
314 if doesIAMPolicyMemberHaveCondition(policyMember) && !doesTFResourceSupportConditions(rcs) {
315 return admission.Errored(http.StatusForbidden,
316 fmt.Errorf("GroupVersionKind %v does not support IAM Conditions", resourceRef.GroupVersionKind()))
317 }
318 return allowedResponse
319 }
320
321 func doesIAMPolicyHaveConditions(policy *v1beta1.IAMPolicy) bool {
322 for _, binding := range policy.Spec.Bindings {
323 if binding.Condition != nil {
324 return true
325 }
326 }
327 return false
328 }
329
330 func doesIAMPartialPolicyHaveConditions(partialPolicy *v1beta1.IAMPartialPolicy) bool {
331 for _, binding := range partialPolicy.Spec.Bindings {
332 if binding.Condition != nil {
333 return true
334 }
335 }
336 return false
337 }
338
339 func doesIAMPolicyMemberHaveCondition(policyMember *v1beta1.IAMPolicyMember) bool {
340 return policyMember.Spec.Condition != nil
341 }
342
343 func doesTFResourceSupportConditions(rcs []*v1alpha1.ResourceConfig) bool {
344
345 return rcs[0].IAMConfig.SupportsConditions
346 }
347
348 func doesTFResourceSupportAuditConfigs(rcs []*v1alpha1.ResourceConfig) bool {
349
350 return rcs[0].IAMConfig.AuditConfigName != ""
351 }
352
View as plain text