From 4d420cd929c4ecea4e887c882659da11f827e453 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Wed, 8 Oct 2025 18:08:55 +0200 Subject: [PATCH 01/13] Add fallback decoder for unknown resources to handle CRDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds a fallback when decoding unknown resources to be able to handle CRDs. The schema validation could be then performed with kubeconform check from https://github.com/stackrox/kube-linter/pull/1033 and CEL from https://github.com/stackrox/kube-linter/pull/1012. This should fix https://github.com/stackrox/kube-linter/issues/606 Changes: - Modified parseObjects to use unstructured decoder as fallback for unknown resource types - Added comprehensive test suite covering standard K8s resources and CRDs - Maintained backward compatibility for existing decode error handling - Added test cases for Tekton Task CRD and other custom resources The fallback allows kube-linter to parse CRDs like Tekton Pipelines without failing, while delegating proper schema validation to specialized templates like kubeconform and CEL expressions. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Signed-off-by: Tomasz Janiszewski --- pkg/lintcontext/parse_yaml.go | 14 +- pkg/lintcontext/parse_yaml_test.go | 259 +++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 pkg/lintcontext/parse_yaml_test.go diff --git a/pkg/lintcontext/parse_yaml.go b/pkg/lintcontext/parse_yaml.go index da8855c20..ddaf08a31 100644 --- a/pkg/lintcontext/parse_yaml.go +++ b/pkg/lintcontext/parse_yaml.go @@ -24,8 +24,10 @@ import ( "helm.sh/helm/v3/pkg/engine" autoscalingV2Beta1 "k8s.io/api/autoscaling/v2beta1" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" + runtimeYaml "k8s.io/apimachinery/pkg/runtime/serializer/yaml" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/kubernetes/scheme" y "sigs.k8s.io/yaml" @@ -58,7 +60,17 @@ func parseObjects(data []byte, d runtime.Decoder) ([]k8sutil.Object, error) { } obj, _, err := d.Decode(data, nil, nil) if err != nil { - return nil, fmt.Errorf("failed to decode: %w", err) + // this is for backward compatibility, should be replaced with kubeconform + if strings.Contains(err.Error(), "json: cannot unmarshal") { + return nil, fmt.Errorf("failed to decode: %w", err) + } + // fallback to unstructured as schema validation will be performed by kubeconform check + dec := runtimeYaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) + var unstructuredErr error + obj, _, unstructuredErr = dec.Decode(data, nil, obj) + if unstructuredErr != nil { + return nil, fmt.Errorf("failed to decode: %w: %w", err, unstructuredErr) + } } if list, ok := obj.(*v1.List); ok { objs := make([]k8sutil.Object, 0, len(list.Items)) diff --git a/pkg/lintcontext/parse_yaml_test.go b/pkg/lintcontext/parse_yaml_test.go new file mode 100644 index 000000000..38dc2eac2 --- /dev/null +++ b/pkg/lintcontext/parse_yaml_test.go @@ -0,0 +1,259 @@ +package lintcontext + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" +) + +func TestParseObjects(t *testing.T) { + tests := []struct { + name string + yamlData string + expectError bool + expectCount int + expectKind string + expectName string + }{ + { + name: "valid Pod", + yamlData: `apiVersion: v1 +kind: Pod +metadata: + name: test-pod + namespace: default +spec: + containers: + - name: nginx + image: nginx:latest + ports: + - containerPort: 80`, + expectError: false, + expectCount: 1, + expectKind: "Pod", + expectName: "test-pod", + }, + { + name: "valid Service", + yamlData: `apiVersion: v1 +kind: Service +metadata: + name: test-service + namespace: default +spec: + selector: + app: nginx + ports: + - port: 80 + targetPort: 80 + type: ClusterIP`, + expectError: false, + expectCount: 1, + expectKind: "Service", + expectName: "test-service", + }, + { + name: "Tekton Task CRD", + yamlData: `apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: hello-world-task + namespace: default +spec: + description: A simple hello world task + steps: + - name: hello + image: alpine:latest + command: + - echo + args: + - "Hello World!"`, + expectError: false, + expectCount: 1, + expectKind: "Task", + expectName: "hello-world-task", + }, + { + name: "List with multiple objects", + yamlData: `apiVersion: v1 +kind: List +metadata: {} +items: +- apiVersion: v1 + kind: Pod + metadata: + name: pod1 + spec: + containers: + - name: nginx + image: nginx:latest +- apiVersion: v1 + kind: Service + metadata: + name: service1 + spec: + selector: + app: nginx + ports: + - port: 80`, + expectError: false, + expectCount: 2, + expectKind: "Pod", // First object + expectName: "pod1", + }, + { + name: "invalid YAML", + yamlData: `apiVersion: v1 +kind: Pod +metadata: + name: test-pod +spec: + invalidField: this-should-not-be-here + containers: + - name: nginx + image: nginx:latest + invalidContainerField: also-invalid`, + expectError: false, // parseObjects doesn't validate schema, only structure + expectCount: 1, + expectKind: "Pod", + expectName: "test-pod", + }, + { + name: "malformed YAML", + yamlData: `apiVersion: v1 +kind: Pod +metadata: + name: test-pod +spec: + containers: + - name: nginx + image: nginx:latest + ports: + - containerPort: "invalid-port-type"`, // string instead of int + expectError: true, // Should fail due to type mismatch + expectCount: 0, + expectKind: "", + expectName: "", + }, + { + name: "unknown Kubernetes resource type", + yamlData: `apiVersion: example.com/v1 +kind: CustomResource +metadata: + name: test-custom + namespace: default +spec: + customField: value`, + expectError: false, + expectCount: 1, + expectKind: "CustomResource", + expectName: "test-custom", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + objects, err := parseObjects([]byte(tt.yamlData), nil) + + if tt.expectError { + assert.Error(t, err, "Expected parseObjects to return an error") + assert.Len(t, objects, tt.expectCount) + } else { + assert.NoError(t, err, "Expected parseObjects to succeed") + require.Len(t, objects, tt.expectCount, "Expected specific number of objects") + + if tt.expectCount > 0 { + // Check first object + firstObj := objects[0] + assert.Equal(t, tt.expectKind, firstObj.GetObjectKind().GroupVersionKind().Kind) + assert.Equal(t, tt.expectName, firstObj.GetName()) + + // Additional validation for Pod objects + if tt.expectKind == "Pod" { + pod, ok := firstObj.(*corev1.Pod) + require.True(t, ok, "Expected object to be a Pod") + assert.Equal(t, "v1", pod.APIVersion) + assert.Equal(t, "Pod", pod.Kind) + assert.NotEmpty(t, pod.Spec.Containers, "Expected Pod to have containers") + } + } + } + }) + } +} + +func TestParseObjectsWithCustomDecoder(t *testing.T) { + // Test that parseObjects respects the custom decoder parameter + tektonTaskYAML := `apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: hello-world-task +spec: + description: A simple hello world task + steps: + - name: hello + image: alpine:latest + command: + - echo + args: + - "Hello World!"` + + // Test with default decoder (should fail) + objects, err := parseObjects([]byte(tektonTaskYAML), nil) + assert.Error(t, err, "Expected Tekton Task to fail with default decoder") + assert.Empty(t, objects) + + // Test with explicit decoder (should also fail since we're using the same decoder) + objects, err = parseObjects([]byte(tektonTaskYAML), decoder) + assert.Error(t, err, "Expected Tekton Task to fail with current scheme") + assert.Empty(t, objects) +} + +func TestParseObjectsEmptyInput(t *testing.T) { + // Test empty input + objects, err := parseObjects([]byte(""), nil) + assert.Error(t, err, "Expected empty input to return an error") + assert.Empty(t, objects) + + // Test whitespace only + objects, err = parseObjects([]byte(" \n \t \n"), nil) + assert.Error(t, err, "Expected whitespace-only input to return an error") + assert.Empty(t, objects) +} + +func TestParseObjectsValidateObjectInterface(t *testing.T) { + // Test that parsed objects implement the k8sutil.Object interface correctly + podYAML := `apiVersion: v1 +kind: Pod +metadata: + name: test-pod + namespace: test-namespace + labels: + app: test + annotations: + test: annotation +spec: + containers: + - name: nginx + image: nginx:latest` + + objects, err := parseObjects([]byte(podYAML), nil) + require.NoError(t, err) + require.Len(t, objects, 1) + + pod := objects[0] + + // Test Object interface methods + assert.Equal(t, "test-pod", pod.GetName()) + assert.Equal(t, "test-namespace", pod.GetNamespace()) + assert.Equal(t, map[string]string{"app": "test"}, pod.GetLabels()) + assert.Equal(t, map[string]string{"test": "annotation"}, pod.GetAnnotations()) + + // Test GroupVersionKind + gvk := pod.GetObjectKind().GroupVersionKind() + assert.Equal(t, "", gvk.Group) + assert.Equal(t, "v1", gvk.Version) + assert.Equal(t, "Pod", gvk.Kind) +} From 0304239904bdd88ca5959381405a4301e6cfb36b Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 9 Oct 2025 10:58:04 +0200 Subject: [PATCH 02/13] add a check Signed-off-by: Tomasz Janiszewski --- e2etests/bats-tests.sh | 18 ++++++++++++++++++ pkg/command/lint/command.go | 1 + 2 files changed, 19 insertions(+) diff --git a/e2etests/bats-tests.sh b/e2etests/bats-tests.sh index 3b8c3447f..9290e220b 100755 --- a/e2etests/bats-tests.sh +++ b/e2etests/bats-tests.sh @@ -63,6 +63,24 @@ get_value_from() { [[ "${message2}" =~ "Pod: resource is not valid:" ]] } +@test "builtin-schema-validation" { + tmp="tests/checks/kubeconform.yml" + cmd="${KUBE_LINTER_BIN} lint --config e2etests/testdata/schema-validation-config.yaml --do-not-auto-add-defaults --format json ${tmp}" + run ${cmd} + + print_info "${status}" "${output}" "${cmd}" "${tmp}" + [ "$status" -eq 1 ] + + message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message') + message2=$(get_value_from "${lines[0]}" '.Reports[1].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[1].Diagnostic.Message') + count=$(get_value_from "${lines[0]}" '.Reports | length') + + # Should find 2 validation errors using builtin schema-validation check + [[ "${count}" == "2" ]] + [[ "${message1}" =~ "DaemonSet: resource is not valid:" ]] + [[ "${message2}" =~ "Pod: resource is not valid:" ]] +} + @test "template-check-installed-bash-version" { run "bash --version" [[ "${BASH_VERSION:0:1}" -ge '4' ]] || false diff --git a/pkg/command/lint/command.go b/pkg/command/lint/command.go index 3db4724dd..23c9ca0e2 100644 --- a/pkg/command/lint/command.go +++ b/pkg/command/lint/command.go @@ -156,6 +156,7 @@ func Command() *cobra.Command { c.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging") c.Flags().Var(format, "format", format.Usage()) c.Flags().BoolVarP(&errorOnInvalidResource, "fail-on-invalid-resource", "", false, "Error out when we have an invalid resource") + c.Flags().MarkDeprecated("fail-on-invalid-resource", "Use 'schema-validation' builtin check or kubeconform template for better schema validation.") config.AddFlags(c, v) return c From 3e0331541d2bcbcd664973d1a139e23c41c66a4c Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 9 Oct 2025 12:20:34 +0200 Subject: [PATCH 03/13] fix Signed-off-by: Tomasz Janiszewski --- pkg/command/lint/command.go | 2 +- pkg/lintcontext/parse_yaml_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/command/lint/command.go b/pkg/command/lint/command.go index 23c9ca0e2..17354deeb 100644 --- a/pkg/command/lint/command.go +++ b/pkg/command/lint/command.go @@ -156,7 +156,7 @@ func Command() *cobra.Command { c.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging") c.Flags().Var(format, "format", format.Usage()) c.Flags().BoolVarP(&errorOnInvalidResource, "fail-on-invalid-resource", "", false, "Error out when we have an invalid resource") - c.Flags().MarkDeprecated("fail-on-invalid-resource", "Use 'schema-validation' builtin check or kubeconform template for better schema validation.") + _ = c.Flags().MarkDeprecated("fail-on-invalid-resource", "Use 'schema-validation' builtin check or kubeconform template for better schema validation.") config.AddFlags(c, v) return c diff --git a/pkg/lintcontext/parse_yaml_test.go b/pkg/lintcontext/parse_yaml_test.go index 38dc2eac2..58ea27476 100644 --- a/pkg/lintcontext/parse_yaml_test.go +++ b/pkg/lintcontext/parse_yaml_test.go @@ -132,7 +132,7 @@ spec: image: nginx:latest ports: - containerPort: "invalid-port-type"`, // string instead of int - expectError: true, // Should fail due to type mismatch + expectError: true, // Should fail due to type mismatch expectCount: 0, expectKind: "", expectName: "", @@ -253,7 +253,7 @@ spec: // Test GroupVersionKind gvk := pod.GetObjectKind().GroupVersionKind() - assert.Equal(t, "", gvk.Group) + assert.Empty(t, gvk.Group) assert.Equal(t, "v1", gvk.Version) assert.Equal(t, "Pod", gvk.Kind) } From e4454312fccb7ef4fa8af139ae46f1bf5de1e3de Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 9 Oct 2025 13:07:18 +0200 Subject: [PATCH 04/13] fix Signed-off-by: Tomasz Janiszewski --- pkg/lintcontext/create_contexts_test.go | 6 ++++++ pkg/lintcontext/parse_yaml_test.go | 18 +++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pkg/lintcontext/create_contexts_test.go b/pkg/lintcontext/create_contexts_test.go index 23079fc6b..39cbe8319 100644 --- a/pkg/lintcontext/create_contexts_test.go +++ b/pkg/lintcontext/create_contexts_test.go @@ -31,6 +31,12 @@ func TestCreateContextsWithIgnorePaths(t *testing.T) { "../../.pre-commit-hooks*", "../../dist/**/*", "../../pkg/**/*", + "../../demo/**", + "../../stackrox-kube-linter-bug-example/**", + "../../tests/**/*", + "../../cmd/**/*", + "../../docs/**/*", + "../../internal/**/*", "/**/*/checks/**/*", "/**/*/test_helper/**/*", "/**/*/testdata/**/*", diff --git a/pkg/lintcontext/parse_yaml_test.go b/pkg/lintcontext/parse_yaml_test.go index 58ea27476..87a667291 100644 --- a/pkg/lintcontext/parse_yaml_test.go +++ b/pkg/lintcontext/parse_yaml_test.go @@ -185,7 +185,7 @@ spec: } func TestParseObjectsWithCustomDecoder(t *testing.T) { - // Test that parseObjects respects the custom decoder parameter + // Test that parseObjects can handle CRDs by falling back to unstructured parsing tektonTaskYAML := `apiVersion: tekton.dev/v1 kind: Task metadata: @@ -200,15 +200,19 @@ spec: args: - "Hello World!"` - // Test with default decoder (should fail) + // Test with default decoder (should succeed by falling back to unstructured) objects, err := parseObjects([]byte(tektonTaskYAML), nil) - assert.Error(t, err, "Expected Tekton Task to fail with default decoder") - assert.Empty(t, objects) + assert.NoError(t, err, "Expected Tekton Task to parse as unstructured with default decoder") + assert.Len(t, objects, 1) + assert.Equal(t, "Task", objects[0].GetObjectKind().GroupVersionKind().Kind) + assert.Equal(t, "hello-world-task", objects[0].GetName()) - // Test with explicit decoder (should also fail since we're using the same decoder) + // Test with explicit decoder (should also succeed) objects, err = parseObjects([]byte(tektonTaskYAML), decoder) - assert.Error(t, err, "Expected Tekton Task to fail with current scheme") - assert.Empty(t, objects) + assert.NoError(t, err, "Expected Tekton Task to parse as unstructured with explicit decoder") + assert.Len(t, objects, 1) + assert.Equal(t, "Task", objects[0].GetObjectKind().GroupVersionKind().Kind) + assert.Equal(t, "hello-world-task", objects[0].GetName()) } func TestParseObjectsEmptyInput(t *testing.T) { From 0c8fdb9b48e3f4e451b9ea8c245c0426f53b2ccd Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 9 Oct 2025 16:35:37 +0200 Subject: [PATCH 05/13] fix Signed-off-by: Tomasz Janiszewski --- .../lint/testdata/invalid-pod-resources.yaml | 21 ++----------------- .../lint/testdata/invalid-pvc-resources.yaml | 14 ++----------- pkg/templates/kubeconform/template.go | 8 +++++++ 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/pkg/command/lint/testdata/invalid-pod-resources.yaml b/pkg/command/lint/testdata/invalid-pod-resources.yaml index f476a747a..cbba61aab 100644 --- a/pkg/command/lint/testdata/invalid-pod-resources.yaml +++ b/pkg/command/lint/testdata/invalid-pod-resources.yaml @@ -1,24 +1,7 @@ apiVersion: v1 -kind: Pod +kind: InvalidKind metadata: - creationTimestamp: null name: foo-pod namespace: foo spec: - containers: - - image: busybox - name: invalid - command: - - "sleep" - args: - - "infinity" - resources: - limits: - cpu: 25m - memory: 1GB - requests: - cpu: 25m - memory: 1GB - dnsPolicy: ClusterFirst - restartPolicy: Always -status: {} \ No newline at end of file + invalidField: [this is invalid YAML that should fail to parse \ No newline at end of file diff --git a/pkg/command/lint/testdata/invalid-pvc-resources.yaml b/pkg/command/lint/testdata/invalid-pvc-resources.yaml index 95b0413be..aa8ede32e 100644 --- a/pkg/command/lint/testdata/invalid-pvc-resources.yaml +++ b/pkg/command/lint/testdata/invalid-pvc-resources.yaml @@ -1,12 +1,2 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: foo-pvc - namespace: foo -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 250GB - storageClassName: thin-disk \ No newline at end of file +this is malformed YAML that should fail to parse: { +invalid: unclosed bracket \ No newline at end of file diff --git a/pkg/templates/kubeconform/template.go b/pkg/templates/kubeconform/template.go index 4672b8d06..5533247d5 100644 --- a/pkg/templates/kubeconform/template.go +++ b/pkg/templates/kubeconform/template.go @@ -2,6 +2,7 @@ package kubeconform import ( "fmt" + "os" "github.com/yannh/kubeconform/pkg/resource" "github.com/yannh/kubeconform/pkg/validator" @@ -33,6 +34,13 @@ func init() { } func validate(p params.Params) (check.Func, error) { + // Create cache directory if it doesn't exist + if p.Cache != "" { + if err := os.MkdirAll(p.Cache, 0750); err != nil { + return nil, fmt.Errorf("creating cache directory %s: %w", p.Cache, err) + } + } + v, err := validator.New(p.SchemaLocations, validator.Opts{ Cache: p.Cache, SkipKinds: sliceToMap(p.SkipKinds), From 61e6c97353c91a16df0007ad018baa2640c22164 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Thu, 30 Oct 2025 15:51:16 +0100 Subject: [PATCH 06/13] Apply suggestion from @janisz --- pkg/templates/kubeconform/template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/templates/kubeconform/template.go b/pkg/templates/kubeconform/template.go index 5533247d5..4d624112f 100644 --- a/pkg/templates/kubeconform/template.go +++ b/pkg/templates/kubeconform/template.go @@ -36,7 +36,7 @@ func init() { func validate(p params.Params) (check.Func, error) { // Create cache directory if it doesn't exist if p.Cache != "" { - if err := os.MkdirAll(p.Cache, 0750); err != nil { + if err := os.MkdirAll(p.Cache, 0o750); err != nil { return nil, fmt.Errorf("creating cache directory %s: %w", p.Cache, err) } } From e06505f321008d4ae8ef38dd96a2fdda1a2ed91d Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 7 Nov 2025 14:35:43 +0100 Subject: [PATCH 07/13] Add missing schema-validation built-in check and config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the builtin-schema-validation test failure by creating: - Built-in check that wraps kubeconform template for schema validation - Test config file that enables the schema-validation check This completes the implementation started in commit 0304239 which added the test and deprecation message but missed these files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- e2etests/testdata/schema-validation-config.yaml | 4 ++++ pkg/builtinchecks/yamls/schema-validation.yaml | 10 ++++++++++ 2 files changed, 14 insertions(+) create mode 100644 e2etests/testdata/schema-validation-config.yaml create mode 100644 pkg/builtinchecks/yamls/schema-validation.yaml diff --git a/e2etests/testdata/schema-validation-config.yaml b/e2etests/testdata/schema-validation-config.yaml new file mode 100644 index 000000000..5651bc572 --- /dev/null +++ b/e2etests/testdata/schema-validation-config.yaml @@ -0,0 +1,4 @@ +checks: + addAllBuiltIn: false + include: + - "schema-validation" diff --git a/pkg/builtinchecks/yamls/schema-validation.yaml b/pkg/builtinchecks/yamls/schema-validation.yaml new file mode 100644 index 000000000..a7669be01 --- /dev/null +++ b/pkg/builtinchecks/yamls/schema-validation.yaml @@ -0,0 +1,10 @@ +name: "schema-validation" +description: "Validate Kubernetes resources against their schemas using kubeconform" +remediation: "Fix the resource to conform to the Kubernetes API schema" +scope: + objectKinds: + - Any +template: "kubeconform" +params: + strict: true + ignoreMissingSchemas: true From ab2c458ca046733832adb06fba6f91e53436bd73 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 14 Nov 2025 11:59:31 +0100 Subject: [PATCH 08/13] fix Signed-off-by: Tomasz Janiszewski --- docs/generated/checks.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/generated/checks.md b/docs/generated/checks.md index 7addc722e..b34ecaa36 100644 --- a/docs/generated/checks.md +++ b/docs/generated/checks.md @@ -590,6 +590,22 @@ key: owner ```yaml AllowPrivilegedContainer: true ``` +## schema-validation + +**Enabled by default**: No + +**Description**: Validate Kubernetes resources against their schemas using kubeconform + +**Remediation**: Fix the resource to conform to the Kubernetes API schema + +**Template**: [kubeconform](templates.md#kubeconform) + +**Parameters**: + +```yaml +ignoreMissingSchemas: true +strict: true +``` ## sensitive-host-mounts **Enabled by default**: Yes From 6f6267a9c05ed3fc4c8e389d0304b706dd0ff3a9 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 14 Nov 2025 12:58:50 +0100 Subject: [PATCH 09/13] fix Signed-off-by: Tomasz Janiszewski --- docs/generated/checks.md | 2 +- pkg/builtinchecks/yamls/schema-validation.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/generated/checks.md b/docs/generated/checks.md index b34ecaa36..f74eb6509 100644 --- a/docs/generated/checks.md +++ b/docs/generated/checks.md @@ -596,7 +596,7 @@ AllowPrivilegedContainer: true **Description**: Validate Kubernetes resources against their schemas using kubeconform -**Remediation**: Fix the resource to conform to the Kubernetes API schema +**Remediation**: Fix the resource to conform to the Kubernetes API schema. **Template**: [kubeconform](templates.md#kubeconform) diff --git a/pkg/builtinchecks/yamls/schema-validation.yaml b/pkg/builtinchecks/yamls/schema-validation.yaml index a7669be01..bc0c3084d 100644 --- a/pkg/builtinchecks/yamls/schema-validation.yaml +++ b/pkg/builtinchecks/yamls/schema-validation.yaml @@ -1,6 +1,6 @@ name: "schema-validation" description: "Validate Kubernetes resources against their schemas using kubeconform" -remediation: "Fix the resource to conform to the Kubernetes API schema" +remediation: "Fix the resource to conform to the Kubernetes API schema." scope: objectKinds: - Any From 481fb13749c1d0990a40f048f4f64aab7309243c Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 14 Nov 2025 13:08:37 +0100 Subject: [PATCH 10/13] fix Signed-off-by: Tomasz Janiszewski --- e2etests/bats-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2etests/bats-tests.sh b/e2etests/bats-tests.sh index 9290e220b..0386e74e4 100755 --- a/e2etests/bats-tests.sh +++ b/e2etests/bats-tests.sh @@ -63,7 +63,7 @@ get_value_from() { [[ "${message2}" =~ "Pod: resource is not valid:" ]] } -@test "builtin-schema-validation" { +@test "schema-validation" { tmp="tests/checks/kubeconform.yml" cmd="${KUBE_LINTER_BIN} lint --config e2etests/testdata/schema-validation-config.yaml --do-not-auto-add-defaults --format json ${tmp}" run ${cmd} From 41d721423d95fcae584ea9cecd76db1ebbd50c7e Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 14 Nov 2025 13:48:46 +0100 Subject: [PATCH 11/13] fix Signed-off-by: Tomasz Janiszewski --- e2etests/bats-tests.sh | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/e2etests/bats-tests.sh b/e2etests/bats-tests.sh index 0386e74e4..84fa46155 100755 --- a/e2etests/bats-tests.sh +++ b/e2etests/bats-tests.sh @@ -63,24 +63,6 @@ get_value_from() { [[ "${message2}" =~ "Pod: resource is not valid:" ]] } -@test "schema-validation" { - tmp="tests/checks/kubeconform.yml" - cmd="${KUBE_LINTER_BIN} lint --config e2etests/testdata/schema-validation-config.yaml --do-not-auto-add-defaults --format json ${tmp}" - run ${cmd} - - print_info "${status}" "${output}" "${cmd}" "${tmp}" - [ "$status" -eq 1 ] - - message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message') - message2=$(get_value_from "${lines[0]}" '.Reports[1].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[1].Diagnostic.Message') - count=$(get_value_from "${lines[0]}" '.Reports | length') - - # Should find 2 validation errors using builtin schema-validation check - [[ "${count}" == "2" ]] - [[ "${message1}" =~ "DaemonSet: resource is not valid:" ]] - [[ "${message2}" =~ "Pod: resource is not valid:" ]] -} - @test "template-check-installed-bash-version" { run "bash --version" [[ "${BASH_VERSION:0:1}" -ge '4' ]] || false @@ -945,6 +927,24 @@ get_value_from() { [[ "${count}" == "1" ]] } +@test "schema-validation" { + tmp="tests/checks/kubeconform.yml" + cmd="${KUBE_LINTER_BIN} lint --config e2etests/testdata/schema-validation-config.yaml --do-not-auto-add-defaults --format json ${tmp}" + run ${cmd} + + print_info "${status}" "${output}" "${cmd}" "${tmp}" + [ "$status" -eq 1 ] + + message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message') + message2=$(get_value_from "${lines[0]}" '.Reports[1].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[1].Diagnostic.Message') + count=$(get_value_from "${lines[0]}" '.Reports | length') + + # Should find 2 validation errors using builtin schema-validation check + [[ "${count}" == "2" ]] + [[ "${message1}" =~ "DaemonSet: resource is not valid:" ]] + [[ "${message2}" =~ "Pod: resource is not valid:" ]] +} + @test "sensitive-host-mounts" { tmp="tests/checks/sensitive-host-mounts.yml" cmd="${KUBE_LINTER_BIN} lint --include sensitive-host-mounts --do-not-auto-add-defaults --format json ${tmp}" From 77081e22a5d9a24e89c42d2992a50ba5a6037c37 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 14 Nov 2025 14:08:21 +0100 Subject: [PATCH 12/13] rename file Signed-off-by: Tomasz Janiszewski --- pkg/command/lint/command_test.go | 2 +- .../lint/testdata/{invalid-pvc-resources.yaml => invalid.yaml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pkg/command/lint/testdata/{invalid-pvc-resources.yaml => invalid.yaml} (100%) diff --git a/pkg/command/lint/command_test.go b/pkg/command/lint/command_test.go index 5baf47e8d..aeb4ccea6 100644 --- a/pkg/command/lint/command_test.go +++ b/pkg/command/lint/command_test.go @@ -17,7 +17,7 @@ func TestCommand_InvalidResources(t *testing.T) { output string }{ {name: "InvalidPodResource", cmd: createLintCommand("./testdata/invalid-pod-resources.yaml", "--fail-on-invalid-resource"), failure: true}, - {name: "InvalidPVCResource", cmd: createLintCommand("./testdata/invalid-pvc-resources.yaml", "--fail-on-invalid-resource"), failure: true}, + {name: "InvalidYAML", cmd: createLintCommand("./testdata/invalid.yaml", "--fail-on-invalid-resource"), failure: true}, {name: "NonexistentFile", cmd: createLintCommand("./testdata/foo-bar.yaml", "--fail-on-invalid-resource"), failure: true}, {name: "ValidPod", cmd: createLintCommand("./testdata/valid-pod.yaml", "--fail-on-invalid-resource"), failure: false}, } diff --git a/pkg/command/lint/testdata/invalid-pvc-resources.yaml b/pkg/command/lint/testdata/invalid.yaml similarity index 100% rename from pkg/command/lint/testdata/invalid-pvc-resources.yaml rename to pkg/command/lint/testdata/invalid.yaml From b14347598458da5f81441a30bf5d7964299ea737 Mon Sep 17 00:00:00 2001 From: Tomasz Janiszewski Date: Fri, 14 Nov 2025 14:09:08 +0100 Subject: [PATCH 13/13] remove duplicated tests Signed-off-by: Tomasz Janiszewski --- pkg/command/lint/command_test.go | 1 - pkg/command/lint/testdata/invalid-pod-resources.yaml | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 pkg/command/lint/testdata/invalid-pod-resources.yaml diff --git a/pkg/command/lint/command_test.go b/pkg/command/lint/command_test.go index aeb4ccea6..2fd436939 100644 --- a/pkg/command/lint/command_test.go +++ b/pkg/command/lint/command_test.go @@ -16,7 +16,6 @@ func TestCommand_InvalidResources(t *testing.T) { failure bool output string }{ - {name: "InvalidPodResource", cmd: createLintCommand("./testdata/invalid-pod-resources.yaml", "--fail-on-invalid-resource"), failure: true}, {name: "InvalidYAML", cmd: createLintCommand("./testdata/invalid.yaml", "--fail-on-invalid-resource"), failure: true}, {name: "NonexistentFile", cmd: createLintCommand("./testdata/foo-bar.yaml", "--fail-on-invalid-resource"), failure: true}, {name: "ValidPod", cmd: createLintCommand("./testdata/valid-pod.yaml", "--fail-on-invalid-resource"), failure: false}, diff --git a/pkg/command/lint/testdata/invalid-pod-resources.yaml b/pkg/command/lint/testdata/invalid-pod-resources.yaml deleted file mode 100644 index cbba61aab..000000000 --- a/pkg/command/lint/testdata/invalid-pod-resources.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: InvalidKind -metadata: - name: foo-pod - namespace: foo -spec: - invalidField: [this is invalid YAML that should fail to parse \ No newline at end of file