From 5b0e9e5c8629a5df68beacdc244cd6e9ff55e039 Mon Sep 17 00:00:00 2001 From: "lukas.luedke@telekom.de" Date: Mon, 17 Jul 2023 16:16:18 +0200 Subject: [PATCH] setting AdditionalProperties to false in the scheme, if strict mode is enabled --- cmd/kubeconform/main.go | 1 + pkg/config/config.go | 5 ++++- pkg/validator/validator.go | 44 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/cmd/kubeconform/main.go b/cmd/kubeconform/main.go index e6ca3c0..f4905ed 100644 --- a/cmd/kubeconform/main.go +++ b/cmd/kubeconform/main.go @@ -86,6 +86,7 @@ func kubeconform(cfg config.Config) int { RejectKinds: cfg.RejectKinds, KubernetesVersion: cfg.KubernetesVersion, Strict: cfg.Strict, + StrictExceptions: cfg.StrictExceptions, IgnoreMissingSchemas: cfg.IgnoreMissingSchemas, }) if err != nil { diff --git a/pkg/config/config.go b/pkg/config/config.go index 093454e..97fef13 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -23,6 +23,7 @@ type Config struct { SkipKinds map[string]struct{} `yaml:"skip" json:"skip"` SkipTLS bool `yaml:"insecureSkipTLSVerify" json:"insecureSkipTLSVerify"` Strict bool `yaml:"strict" json:"strict"` + StrictExceptions map[string]struct{} `yaml:"strictExceptions" json:"strictExceptions"` Summary bool `yaml:"summary" json:"summary"` Verbose bool `yaml:"verbose" json:"verbose"` Version bool `yaml:"version" json:"version"` @@ -55,7 +56,7 @@ func splitCSV(csvStr string) map[string]struct{} { // FromFlags retrieves kubeconform's runtime configuration from the command-line parameters func FromFlags(progName string, args []string) (Config, string, error) { var schemaLocationsParam, ignoreFilenamePatterns arrayParam - var skipKindsCSV, rejectKindsCSV string + var skipKindsCSV, rejectKindsCSV, strictExceptionsCSV string flags := flag.NewFlagSet(progName, flag.ContinueOnError) var buf bytes.Buffer flags.SetOutput(&buf) @@ -74,6 +75,7 @@ func FromFlags(progName string, args []string) (Config, string, error) { flags.BoolVar(&c.Summary, "summary", false, "print a summary at the end (ignored for junit output)") flags.IntVar(&c.NumberOfWorkers, "n", 4, "number of goroutines to run concurrently") flags.BoolVar(&c.Strict, "strict", false, "disallow additional properties not in schema or duplicated keys") + flags.StringVar(&strictExceptionsCSV, "strict-exception", "", "comma-separated list of yaml paths to ignore when strict is enabled") flags.StringVar(&c.OutputFormat, "output", "text", "output format - json, junit, pretty, tap, text") flags.BoolVar(&c.Verbose, "verbose", false, "print results for all resources (ignored for tap and junit output)") flags.BoolVar(&c.SkipTLS, "insecure-skip-tls-verify", false, "disable verification of the server's SSL certificate. This will make your HTTPS connections insecure") @@ -89,6 +91,7 @@ func FromFlags(progName string, args []string) (Config, string, error) { c.SkipKinds = splitCSV(skipKindsCSV) c.RejectKinds = splitCSV(rejectKindsCSV) + c.StrictExceptions = splitCSV(strictExceptionsCSV) c.IgnoreFilenamePatterns = ignoreFilenamePatterns c.SchemaLocations = schemaLocationsParam c.Files = flags.Args() diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go index 82056f6..0af7840 100644 --- a/pkg/validator/validator.go +++ b/pkg/validator/validator.go @@ -61,6 +61,7 @@ type Opts struct { RejectKinds map[string]struct{} // List of resource Kinds to reject KubernetesVersion string // Kubernetes Version - has to match one in https://github.com/instrumenta/kubernetes-json-schema Strict bool // thros an error if resources contain undocumented fields + StrictExceptions map[string]struct{} // list of field paths (e.g. spec.template.metadata) to ignore when strict is enabled IgnoreMissingSchemas bool // skip a resource if no schema for that resource can be found } @@ -91,6 +92,12 @@ func New(schemaLocations []string, opts Opts) (Validator, error) { if opts.RejectKinds == nil { opts.RejectKinds = map[string]struct{}{} } + if opts.StrictExceptions == nil { + opts.StrictExceptions = map[string]struct{}{} + } + if len(opts.StrictExceptions) == 0 { + opts.StrictExceptions["metadata"] = struct{}{} + } return &v{ opts: opts, @@ -100,6 +107,38 @@ func New(schemaLocations []string, opts Opts) (Validator, error) { }, nil } +func (val *v) setStrictSchema(schema *jsonschema.Schema, prefixPath string) { + if _, ok := val.opts.StrictExceptions[prefixPath]; ok { + return + } + if schema.AdditionalProperties == nil { + schema.AdditionalProperties = false + } + if schema.Items != nil { + casted, ok := schema.Items.(*jsonschema.Schema) + if ok { + fullPath := prefixPath + ".0" + val.setStrictSchema(casted, fullPath) + } else { + casted, ok := schema.Items.([]*jsonschema.Schema) + if ok { + for i, s := range casted { + fullPath := prefixPath + "." + fmt.Sprintf("%d", i) + val.setStrictSchema(s, fullPath) + } + } + } + } + + for name, s := range schema.Properties { + fullPath := prefixPath + "." + name + if fullPath[0] == '.' { + fullPath = fullPath[1:] + } + val.setStrictSchema(s, fullPath) + } +} + type v struct { opts Opts schemaCache cache.Cache @@ -191,6 +230,11 @@ func (val *v) ValidateResource(res resource.Resource) Result { return Result{Resource: res, Err: fmt.Errorf("could not find schema for %s", sig.Kind), Status: Error} } + // If strict mode is enabled, we set additionalProperties to false + // if not explicitly set in the schema + if val.opts.Strict { + val.setStrictSchema(schema, "") + } err = schema.Validate(r) if err != nil { validationErrors := []ValidationError{}