...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/no_unknown_fields_validator.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  // noUnknownFieldsValidatorHandler implements inject.Client.
    42  var _ inject.Client = &noUnknownFieldsValidatorHandler{}
    43  
    44  func NewNoUnknownFieldsValidatorHandler(smLoader *servicemappingloader.ServiceMappingLoader) *noUnknownFieldsValidatorHandler {
    45  	return &noUnknownFieldsValidatorHandler{
    46  		smLoader: smLoader,
    47  	}
    48  }
    49  
    50  // InjectClient injects the client into the noUnknownFieldsValidatorHandler
    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  			// If the schema is of type object but it has no properties, it should allow
    86  			// any keys.
    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