From e2d42e77281331f5c6205d80556451a10989b069 Mon Sep 17 00:00:00 2001 From: Aniket Bhat Date: Thu, 29 Oct 2020 13:56:01 +0100 Subject: [PATCH] UPSTREAM: : openshift-kube-apiserver: Add custom resource validation for network spec --- .../cr_validation_registration.go | 5 +- .../customresourcevalidation/network/BUILD | 32 +++++ .../network/validate_network_config.go | 127 ++++++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 openshift-kube-apiserver/admission/customresourcevalidation/network/BUILD create mode 100644 openshift-kube-apiserver/admission/customresourcevalidation/network/validate_network_config.go diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/customresourcevalidationregistration/cr_validation_registration.go b/openshift-kube-apiserver/admission/customresourcevalidation/customresourcevalidationregistration/cr_validation_registration.go index bdc4681be54fa..2495d4eb67b68 100644 --- a/openshift-kube-apiserver/admission/customresourcevalidation/customresourcevalidationregistration/cr_validation_registration.go +++ b/openshift-kube-apiserver/admission/customresourcevalidation/customresourcevalidationregistration/cr_validation_registration.go @@ -10,6 +10,7 @@ import ( "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/console" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/features" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/image" + "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/network" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/oauth" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/project" "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/rolebindingrestriction" @@ -31,6 +32,7 @@ var AllCustomResourceValidators = []string{ clusterresourcequota.PluginName, securitycontextconstraints.PluginName, rolebindingrestriction.PluginName, + network.PluginName, // this one is special because we don't work without it. securitycontextconstraints.DefaultingPluginName, @@ -54,7 +56,8 @@ func RegisterCustomResourceValidation(plugins *admission.Plugins) { securitycontextconstraints.Register(plugins) // This plugin validates the authorization.openshift.io/v1 RoleBindingRestriction resources. rolebindingrestriction.Register(plugins) - + // This plugin validates the network.config.openshift.io object for service node port range changes + network.Register(plugins) // this one is special because we don't work without it. securitycontextconstraints.RegisterDefaulting(plugins) } diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/network/BUILD b/openshift-kube-apiserver/admission/customresourcevalidation/network/BUILD new file mode 100644 index 0000000000000..e1819a586cadb --- /dev/null +++ b/openshift-kube-apiserver/admission/customresourcevalidation/network/BUILD @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["validate_network_config.go"], + importpath = "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation/network", + visibility = ["//visibility:public"], + deps = [ + "//openshift-kube-apiserver/admission/customresourcevalidation:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/github.com/openshift/api/config/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/network/validate_network_config.go b/openshift-kube-apiserver/admission/customresourcevalidation/network/validate_network_config.go new file mode 100644 index 0000000000000..6362db49efaa9 --- /dev/null +++ b/openshift-kube-apiserver/admission/customresourcevalidation/network/validate_network_config.go @@ -0,0 +1,127 @@ +package network + +import ( + "fmt" + "io" + + "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/admission" + kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" + + configv1 "github.com/openshift/api/config/v1" + "k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation" +) + +const PluginName = "config.openshift.io/ValidateNetwork" + +// Register registers a plugin +func Register(plugins *admission.Plugins) { + plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { + return customresourcevalidation.NewValidator( + map[schema.GroupResource]bool{ + configv1.Resource("networks"): true, + }, + map[schema.GroupVersionKind]customresourcevalidation.ObjectValidator{ + configv1.GroupVersion.WithKind("Network"): networkV1{}, + }) + }) +} + +func toNetworkV1(uncastObj runtime.Object) (*configv1.Network, field.ErrorList) { + if uncastObj == nil { + return nil, nil + } + + allErrs := field.ErrorList{} + + obj, ok := uncastObj.(*configv1.Network) + if !ok { + return nil, append(allErrs, + field.NotSupported(field.NewPath("kind"), fmt.Sprintf("%T", uncastObj), []string{"Network"}), + field.NotSupported(field.NewPath("apiVersion"), fmt.Sprintf("%T", uncastObj), []string{"config.openshift.io/v1"})) + } + + return obj, nil +} + +type networkV1 struct { +} + +func validateNetworkServiceNodePortRangeUpdate(obj, oldObj *configv1.Network) *field.Error { + var err error + defaultRange := kubeoptions.DefaultServiceNodePortRange + oldRange := &defaultRange + newRange := &defaultRange + + oldRangeStr := oldObj.Spec.ServiceNodePortRange + if oldRangeStr != "" { + if oldRange, err = utilnet.ParsePortRange(oldRangeStr); err != nil { + return field.Invalid(field.NewPath("spec", "serviceNodePortRange"), + oldRangeStr, + fmt.Sprintf("failed to parse the old port range: %v", err)) + } + } + newRangeStr := obj.Spec.ServiceNodePortRange + if newRangeStr != "" { + if newRange, err = utilnet.ParsePortRange(newRangeStr); err != nil { + return field.Invalid(field.NewPath("spec", "serviceNodePortRange"), + newRangeStr, + fmt.Sprintf("failed to parse the new port range: %v", err)) + } + } + if !newRange.Contains(oldRange.Base) || !newRange.Contains(oldRange.Base+oldRange.Size-1) { + return field.Invalid(field.NewPath("spec", "serviceNodePortRange"), + newRangeStr, + fmt.Sprintf("new service node port range %s does not completely cover the previous range %s", newRange, oldRange)) + } + return nil +} + +func (networkV1) ValidateCreate(uncastObj runtime.Object) field.ErrorList { + obj, allErrs := toNetworkV1(uncastObj) + if len(allErrs) > 0 { + return allErrs + } + + allErrs = append(allErrs, validation.ValidateObjectMeta(&obj.ObjectMeta, false, customresourcevalidation.RequireNameCluster, field.NewPath("metadata"))...) + + return allErrs +} + +func (networkV1) ValidateUpdate(uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList { + obj, allErrs := toNetworkV1(uncastObj) + if len(allErrs) > 0 { + return allErrs + } + oldObj, allErrs := toNetworkV1(uncastOldObj) + if len(allErrs) > 0 { + return allErrs + } + + allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))...) + if err := validateNetworkServiceNodePortRangeUpdate(obj, oldObj); err != nil { + allErrs = append(allErrs, err) + } + + return allErrs +} + +func (networkV1) ValidateStatusUpdate(uncastObj runtime.Object, uncastOldObj runtime.Object) field.ErrorList { + obj, errs := toNetworkV1(uncastObj) + if len(errs) > 0 { + return errs + } + oldObj, errs := toNetworkV1(uncastOldObj) + if len(errs) > 0 { + return errs + } + + // TODO validate the obj. remember that status validation should *never* fail on spec validation errors. + errs = append(errs, validation.ValidateObjectMetaUpdate(&obj.ObjectMeta, &oldObj.ObjectMeta, field.NewPath("metadata"))...) + + return errs +}