...
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 "strings"
22
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
26
27 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29 apitypes "k8s.io/apimachinery/pkg/types"
30 "k8s.io/klog/v2"
31 "sigs.k8s.io/controller-runtime/pkg/client"
32 "sigs.k8s.io/controller-runtime/pkg/runtime/inject"
33 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
34 )
35
36 type noUnknownFieldsValidatorHandler struct {
37 client client.Client
38 smLoader *servicemappingloader.ServiceMappingLoader
39 }
40
41
42 var _ inject.Client = &noUnknownFieldsValidatorHandler{}
43
44 func NewNoUnknownFieldsValidatorHandler(smLoader *servicemappingloader.ServiceMappingLoader) *noUnknownFieldsValidatorHandler {
45 return &noUnknownFieldsValidatorHandler{
46 smLoader: smLoader,
47 }
48 }
49
50
51 func (a *noUnknownFieldsValidatorHandler) InjectClient(c client.Client) error {
52 a.client = c
53 return nil
54 }
55
56 func (a *noUnknownFieldsValidatorHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
57 deserializer := codecs.UniversalDeserializer()
58 obj := &unstructured.Unstructured{}
59 if _, _, err := deserializer.Decode(req.AdmissionRequest.Object.Raw, nil, obj); err != nil {
60 klog.Error(err)
61 return admission.Errored(http.StatusBadRequest, err)
62 }
63 crd := &apiextensions.CustomResourceDefinition{}
64 nn := apitypes.NamespacedName{
65 Name: text.Pluralize(strings.ToLower(obj.GetKind())) + "." + obj.GroupVersionKind().Group,
66 }
67 if err := a.client.Get(context.Background(), nn, crd); err != nil {
68 return admission.Errored(http.StatusInternalServerError, err)
69 }
70 schema := k8s.GetOpenAPIV3SchemaFromCRD(crd)
71 if err := validateNoUnknownFields(schema, obj.Object, ""); err != nil {
72 return admission.Errored(http.StatusForbidden, err)
73 }
74 return admission.ValidationResponse(true, "admission controller passed")
75 }
76
77 func validateNoUnknownFields(schema *apiextensions.JSONSchemaProps, field interface{}, key string) error {
78 switch schema.Type {
79 case "object":
80 m, ok := field.(map[string]interface{})
81 if !ok {
82 return fmt.Errorf("unrecognized type for field %v; expected object", key)
83 }
84 if len(schema.Properties) == 0 {
85
86
87 return nil
88 }
89 for k, v := range m {
90 subfieldKey := key + "." + k
91 if key == "" {
92 subfieldKey = k
93 }
94 subfieldSchema, ok := schema.Properties[k]
95 if !ok {
96 return fmt.Errorf("unknown field \"%v\"", subfieldKey)
97 }
98 if err := validateNoUnknownFields(&subfieldSchema, v, subfieldKey); err != nil {
99 return err
100 }
101 }
102 case "array":
103 a, ok := field.([]interface{})
104 if !ok {
105 return fmt.Errorf("unrecognized type for field %v; expected array", key)
106 }
107 for i, v := range a {
108 itemKey := fmt.Sprintf("%v[%v]", key, i)
109 if err := validateNoUnknownFields(schema.Items.Schema, v, itemKey); err != nil {
110 return err
111 }
112 }
113 case "string", "number", "integer", "boolean", "null":
114 return nil
115 default:
116 return fmt.Errorf("unrecognized schema type: %v", schema.Type)
117 }
118 return nil
119 }
120
View as plain text