From e4d8dff835d21345dde6cfb34f00c0834d998ce5 Mon Sep 17 00:00:00 2001 From: justinsb Date: Fri, 13 May 2022 12:34:28 -0400 Subject: [PATCH] kube-scheduler: MVP configuration validation We check that users haven't specified the kubeconfig file path, as this file is created / managed by kOps. We don't try to reuse the upstream configuration validation, as this allows the user to specify a partial configuration, and this means that we don't have to pull in the upstream libraries. We could in future accept the "correct" value or just treat providing a value as a signal that kOps should not manage the file; for now we are starting with the most restrictive configuration, as we can then relax it in future if needed. --- pkg/apis/kops/validation/additionalobjects.go | 58 +++++++++++++++++++ pkg/client/simple/vfsclientset/addons.go | 20 +++++++ 2 files changed, 78 insertions(+) create mode 100644 pkg/apis/kops/validation/additionalobjects.go diff --git a/pkg/apis/kops/validation/additionalobjects.go b/pkg/apis/kops/validation/additionalobjects.go new file mode 100644 index 0000000000..b2211baa34 --- /dev/null +++ b/pkg/apis/kops/validation/additionalobjects.go @@ -0,0 +1,58 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func ValidateAdditionalObject(ctx context.Context, fieldPath *field.Path, u *unstructured.Unstructured) field.ErrorList { + var errors field.ErrorList + + gvk := u.GroupVersionKind() + + // Note: we use unstructured because: + // 1) it means we don't have to depend on types / validation code from multiple projects + // 2) we can more easily differentiate whether a field is set. + // 3) we can support partial configuration specification - just the fields we care about. + // + // It would be nice to be able to consume validation code e.g. via a container, + // so we could be more extensible. + errors = append(errors, validateAdditionalObjectKubescheduler(ctx, fieldPath, gvk, u)...) + return errors +} + +func validateAdditionalObjectKubescheduler(ctx context.Context, fieldPath *field.Path, gvk schema.GroupVersionKind, u *unstructured.Unstructured) field.ErrorList { + var errors field.ErrorList + + if gvk.Kind == "KubeSchedulerConfiguration" && gvk.Group == "kubescheduler.config.k8s.io" { + kubeconfig, found, err := unstructured.NestedString(u.Object, "clientConnection", "kubeconfig") + if err != nil { + errors = append(errors, field.Invalid(fieldPath.Child("clientConnection", "kubeconfig"), u, fmt.Sprintf("error reading field: %v", err))) + } + if found && kubeconfig != "" { + errors = append(errors, field.Invalid(fieldPath.Child("clientConnection", "kubeconfig"), kubeconfig, "value is controlled by kOps and should not be set")) + } + } + + return errors +} diff --git a/pkg/client/simple/vfsclientset/addons.go b/pkg/client/simple/vfsclientset/addons.go index c9e6e19d0f..d3597bb10c 100644 --- a/pkg/client/simple/vfsclientset/addons.go +++ b/pkg/client/simple/vfsclientset/addons.go @@ -18,12 +18,15 @@ package vfsclientset import ( "bytes" + "context" "fmt" "os" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/klog/v2" "k8s.io/kops/pkg/acls" "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/pkg/apis/kops/validation" "k8s.io/kops/pkg/client/simple" "k8s.io/kops/pkg/kubemanifest" "k8s.io/kops/util/pkg/vfs" @@ -56,6 +59,23 @@ func newAddonsVFS(c *VFSClientset, cluster *kops.Cluster) *vfsAddonsClient { // TODO: Offer partial replacement? func (c *vfsAddonsClient) Replace(addons kubemanifest.ObjectList) error { + ctx := context.TODO() + + for _, addon := range addons { + fieldPath := field.NewPath("addons") + if kind := addon.Kind(); kind != "" { + fieldPath = fieldPath.Child("kind=" + kind) + } + if name := addon.GetName(); name != "" { + fieldPath = fieldPath.Child("name=" + name) + } + + errors := validation.ValidateAdditionalObject(ctx, fieldPath, addon.ToUnstructured()) + if len(errors) != 0 { + return errors.ToAggregate() + } + } + b, err := addons.ToYAML() if err != nil { return err