Conformance tests for server side field validation

This commit is contained in:
Kevin Delgado 2023-01-20 07:04:19 +00:00
parent 2ca95b4df9
commit f45505d19a
2 changed files with 753 additions and 0 deletions

View File

@ -3264,4 +3264,32 @@
was configured with a subpath.
release: v1.12
file: test/e2e/storage/subpath.go
- testname: 'Server side field validation, typed object'
codename: '[sig-api-machinery] Field validation should detect unknown/duplicate fields [Conformance]'
description: It should reject the request if a typed object has unknown or duplicate fields.
release: v1.27
- testname: 'Server side field validation, typed unknown metadata'
codename: '[sig-api-machinery] Field validation should detect unknown metadata fields [Conformance]'
description: It should reject the request if a typed object has unknown fields in the metadata.
release: v1.27
- testname: 'Server side field validation, valid CR with validation schema'
codename: '[sig-api-machinery] Field validation should allow valid CRs for CRDs with validation schema [Conformance]'
description: When a CRD has a validation schema, it should succeed when a valid CR is applied.
release: v1.27
- testname: 'Server side field validation, unknown fields CR no validation schema'
codename: '[sig-api-machinery] Field validation should allow CRs with unknown fields for CRDs without validation schema [Conformance]'
description: When a CRD does not have a validation schema, it should succeed when a CR with unknown fields is applied.
release: v1.27
- testname: 'Server side field validation, unknown fields CR fails validation'
codename: '[sig-api-machinery] Field validation should reject CRs with unknown fields for CRDs with validation schema [Conformance]'
description: When a CRD does have a validation schema, it should reject CRs with unknown fields.
release: v1.27
- testname: 'Server side field validation, unknown metadata'
codename: '[sig-api-machinery] Field validation should reject CRs with unknown metadata [Conformance]'
description: The server should reject CRs with unknown metadata fields in both the root and embedded objects of a CR.
release: v1.27
- testname: 'Server side field validation, CR duplicates'
codename: '[sig-api-machinery] Field validation should reject CRs with duplicate fields [Conformance]'
description: The server should reject CRs with duplicate fields even when preserving unknown fields.
release: v1.27

View File

@ -0,0 +1,725 @@
/*
Copyright 2023 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 apimachinery
import (
// ensure libs have a chance to initialize
"context"
"encoding/json"
"fmt"
"strings"
"github.com/onsi/ginkgo/v2"
_ "github.com/stretchr/testify/assert"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextensionclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
"k8s.io/kubernetes/test/e2e/framework"
admissionapi "k8s.io/pod-security-admission/api"
)
var _ = SIGDescribe("FieldValidation", func() {
f := framework.NewDefaultFramework("field-validation")
f.NamespacePodSecurityEnforceLevel = admissionapi.LevelBaseline
var client clientset.Interface
var ns string
ginkgo.BeforeEach(func() {
client = f.ClientSet
ns = f.Namespace.Name
})
ginkgo.AfterEach(func(ctx context.Context) {
_ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment", metav1.DeleteOptions{})
_ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment-shared-unset", metav1.DeleteOptions{})
_ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment-shared-map-item-removal", metav1.DeleteOptions{})
_ = client.CoreV1().Pods(ns).Delete(ctx, "test-pod", metav1.DeleteOptions{})
})
/*
Release: v1.27
Testname: Server side field validation, typed object
Description: It should reject the request if a typed object has unknown or duplicate fields.
*/
framework.ConformanceIt("should detect unknown and duplicate fields of a typed object", func(ctx context.Context) {
ginkgo.By("apply creating a deployment")
invalidMetaDeployment := `{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "my-dep",
"labels": {"app": "nginx"}
},
"spec": {
"unknownField": "foo",
"replicas": 2,
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [{
"name": "nginx",
"image": "nginx:latest"
}]
}
}
}
}`
_, err := client.CoreV1().RESTClient().Post().
AbsPath("/apis/apps/v1").
Namespace(ns).
Resource("deployments").
Param("fieldManager", "field_validation_mgr").
Param("fieldValidation", "Strict").
Body([]byte(invalidMetaDeployment)).
Do(ctx).
Get()
if !(strings.Contains(err.Error(), `strict decoding error: unknown field "spec.unknownField", duplicate field "spec.replicas"`)) {
framework.Failf("error missing unknown/duplicate field field, got: %v", err)
}
})
/*
Release: v1.27
Testname: Server side field validation, typed unknown metadata
Description: It should reject the request if a typed object has unknown fields in the metadata.
*/
framework.ConformanceIt("should detect unknown metadata fields of a typed object", func(ctx context.Context) {
ginkgo.By("apply creating a deployment")
invalidMetaDeployment := `{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "my-dep",
"unknownMeta": "foo",
"labels": {"app": "nginx"}
},
"spec": {
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [{
"name": "nginx",
"image": "nginx:latest"
}]
}
}
}
}`
_, err := client.CoreV1().RESTClient().Post().
AbsPath("/apis/apps/v1").
Namespace(ns).
Resource("deployments").
Param("fieldManager", "field_validation_mgr").
Param("fieldValidation", "Strict").
Body([]byte(invalidMetaDeployment)).
Do(ctx).
Get()
if !(strings.Contains(err.Error(), `strict decoding error: unknown field "metadata.unknownMeta"`)) {
framework.Failf("error missing unknown metadata field, got: %v", err)
}
})
/*
Release: v1.27
Testname: Server side field validation, valid CR with validation schema
Description: When a CRD has a validation schema, it should succeed when a valid CR is applied.
*/
framework.ConformanceIt("should create/apply a valid CR for CRD with validation schema", func(ctx context.Context) {
config, err := framework.LoadConfig()
if err != nil {
framework.Failf("%s", err)
}
apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
var c apiextensionsv1.CustomResourceValidation
err = json.Unmarshal([]byte(`{
"openAPIV3Schema": {
"type": "object",
"properties": {
"spec": {
"type": "object",
"properties": {
"foo": {
"type": "string"
},
"cronSpec": {
"type": "string",
"pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
},
"ports": {
"type": "array",
"x-kubernetes-list-map-keys": [
"containerPort",
"protocol"
],
"x-kubernetes-list-type": "map",
"items": {
"properties": {
"containerPort": {
"format": "int32",
"type": "integer"
},
"hostIP": {
"type": "string"
},
"hostPort": {
"format": "int32",
"type": "integer"
},
"name": {
"type": "string"
},
"protocol": {
"type": "string"
}
},
"required": [
"containerPort",
"protocol"
],
"type": "object"
}
}
}
}
}
}
}`), &c)
if err != nil {
framework.Failf("%v", err)
}
for i := range noxuDefinition.Spec.Versions {
noxuDefinition.Spec.Versions[i].Schema = &c
}
noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
if err != nil {
framework.Failf("cannot create crd %s", err)
}
kind := noxuDefinition.Spec.Names.Kind
apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
name := "mytest"
rest := apiExtensionClient.Discovery().RESTClient()
yamlBody := []byte(fmt.Sprintf(`
apiVersion: %s
kind: %s
metadata:
name: %s
finalizers:
- test-finalizer
spec:
foo: foo1
cronSpec: "* * * * */5"
ports:
- name: x
containerPort: 80
protocol: TCP`, apiVersion, kind, name))
_, err = rest.Patch(types.ApplyPatchType).
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
Name(name).
Param("fieldManager", "field_validation_mgr").
Param("fieldValidation", "Strict").
Body(yamlBody).
DoRaw(ctx)
if err != nil {
framework.Failf("%v", err)
}
})
/*
Release: v1.27
Testname: Server side field validation, unknown fields CR no validation schema
Description: When a CRD does not have a validation schema, it should succeed when a CR with unknown fields is applied.
*/
framework.ConformanceIt("should create/apply a CR with unknown fields for CRD with no validation schema", func(ctx context.Context) {
config, err := framework.LoadConfig()
if err != nil {
framework.Failf("%s", err)
}
apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
if err != nil {
framework.Failf("cannot create crd %s", err)
}
kind := noxuDefinition.Spec.Names.Kind
apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
name := "mytest"
rest := apiExtensionClient.Discovery().RESTClient()
yamlBody := []byte(fmt.Sprintf(`
apiVersion: %s
kind: %s
metadata:
name: %s
finalizers:
- test-finalizer
spec:
unknown: uk1
cronSpec: "* * * * */5"
ports:
- name: x
containerPort: 80
protocol: TCP`, apiVersion, kind, name))
_, err = rest.Patch(types.ApplyPatchType).
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
Name(name).
Param("fieldManager", "field_validation_mgr").
Param("fieldValidation", "Strict").
Body(yamlBody).
DoRaw(ctx)
if err != nil {
framework.Failf("%v", err)
}
})
/*
Release: v1.27
Testname: Server side field validation, unknown fields CR fails validation
Description: When a CRD does have a validation schema, it should reject CRs with unknown fields.
*/
framework.ConformanceIt("should create/apply an invalid CR with extra properties for CRD with validation schema", func(ctx context.Context) {
config, err := framework.LoadConfig()
if err != nil {
framework.Failf("%s", err)
}
apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
var c apiextensionsv1.CustomResourceValidation
err = json.Unmarshal([]byte(`{
"openAPIV3Schema": {
"type": "object",
"properties": {
"spec": {
"type": "object",
"properties": {
"foo": {
"type": "string"
},
"cronSpec": {
"type": "string",
"pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
},
"ports": {
"type": "array",
"x-kubernetes-list-map-keys": [
"containerPort",
"protocol"
],
"x-kubernetes-list-type": "map",
"items": {
"properties": {
"containerPort": {
"format": "int32",
"type": "integer"
},
"hostIP": {
"type": "string"
},
"hostPort": {
"format": "int32",
"type": "integer"
},
"name": {
"type": "string"
},
"protocol": {
"type": "string"
}
},
"required": [
"containerPort",
"protocol"
],
"type": "object"
}
}
}
}
}
}
}`), &c)
if err != nil {
framework.Failf("%v", err)
}
klog.Warningf("props: %v\n", c.OpenAPIV3Schema)
for i := range noxuDefinition.Spec.Versions {
noxuDefinition.Spec.Versions[i].Schema = &c
}
noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
if err != nil {
framework.Failf("cannot create crd %s", err)
}
kind := noxuDefinition.Spec.Names.Kind
apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
name := "mytest"
rest := apiExtensionClient.Discovery().RESTClient()
yamlBody := []byte(fmt.Sprintf(`
apiVersion: %s
kind: %s
metadata:
name: %s
finalizers:
- test-finalizer
unknownField: unknown
spec:
foo: foo1
cronSpec: "* * * * */5"
ports:
- name: x
containerPort: 80
protocol: TCP`, apiVersion, kind, name))
result, err := rest.Patch(types.ApplyPatchType).
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
Name(name).
Param("fieldManager", "field_validation_mgr").
Param("fieldValidation", "Strict").
Body(yamlBody).
DoRaw(ctx)
if !(strings.Contains(string(result), `.unknownField: field not declared in schema`)) {
framework.Failf("error missing unknown field: %v:\n%v", err, string(result))
}
})
/*
Release: v1.27
Testname: Server side field validation, unknown metadata
Description: The server should reject CRs with unknown metadata fields in both the root and embedded objects
of a CR.
*/
framework.ConformanceIt("should detect unknown metadata fields in both the root and embedded object of a CR", func(ctx context.Context) {
config, err := framework.LoadConfig()
if err != nil {
framework.Failf("%s", err)
}
apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
var c apiextensionsv1.CustomResourceValidation
err = json.Unmarshal([]byte(`{
"openAPIV3Schema": {
"type": "object",
"properties": {
"spec": {
"type": "object",
"x-kubernetes-preserve-unknown-fields": true,
"properties": {
"template": {
"type": "object",
"x-kubernetes-embedded-resource": true,
"properties": {
"metadata": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"spec": {
"type": "object"
}
}
},
"foo": {
"type": "string"
},
"cronSpec": {
"type": "string",
"pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
},
"ports": {
"type": "array",
"x-kubernetes-list-map-keys": [
"containerPort",
"protocol"
],
"x-kubernetes-list-type": "map",
"items": {
"properties": {
"containerPort": {
"format": "int32",
"type": "integer"
},
"hostIP": {
"type": "string"
},
"hostPort": {
"format": "int32",
"type": "integer"
},
"name": {
"type": "string"
},
"protocol": {
"type": "string"
}
},
"required": [
"containerPort",
"protocol"
],
"type": "object"
}
}
}
}
}
}
}`), &c)
if err != nil {
framework.Failf("%v", err)
}
for i := range noxuDefinition.Spec.Versions {
noxuDefinition.Spec.Versions[i].Schema = &c
}
noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
if err != nil {
framework.Failf("cannot create crd %s", err)
}
kind := noxuDefinition.Spec.Names.Kind
apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
name := "mytest"
rest := apiExtensionClient.Discovery().RESTClient()
yamlBody := []byte(fmt.Sprintf(`
apiVersion: %s
kind: %s
metadata:
name: %s
unknownMeta: unknown
finalizers:
- test-finalizer
spec:
template:
apiversion: foo/v1
kind: Sub
metadata:
unknownSubMeta: unknown
name: subobject
namespace: %s
foo: foo1
cronSpec: "* * * * */5"
ports:
- name: x
containerPort: 80
protocol: TCP`, apiVersion, kind, name, ns))
result, err := rest.Patch(types.ApplyPatchType).
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
Name(name).
Param("fieldManager", "field_validation_mgr").
Param("fieldValidation", "Strict").
Body(yamlBody).
DoRaw(ctx)
if !(strings.Contains(string(result), `.spec.template.metadata.unknownSubMeta: field not declared in schema`) || strings.Contains(string(result), `.metadata.unknownMeta: field not declared in schema`)) {
framework.Failf("error missing duplicate field: %v:\n%v", err, string(result))
}
})
/*
Release: v1.27
Testname: Server side field validation, CR duplicates
Description: The server should reject CRs with duplicate fields even when preserving unknown fields.
*/
framework.ConformanceIt("should detect duplicates in a CR when preserving unknown fields", func(ctx context.Context) {
config, err := framework.LoadConfig()
if err != nil {
framework.Failf("%s", err)
}
apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
framework.Failf("%s", err)
}
noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
var c apiextensionsv1.CustomResourceValidation
err = json.Unmarshal([]byte(`{
"openAPIV3Schema": {
"type": "object",
"properties": {
"spec": {
"type": "object",
"x-kubernetes-preserve-unknown-fields": true,
"properties": {
"foo": {
"type": "string"
},
"cronSpec": {
"type": "string",
"pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
},
"ports": {
"type": "array",
"x-kubernetes-list-map-keys": [
"containerPort",
"protocol"
],
"x-kubernetes-list-type": "map",
"items": {
"properties": {
"containerPort": {
"format": "int32",
"type": "integer"
},
"hostIP": {
"type": "string"
},
"hostPort": {
"format": "int32",
"type": "integer"
},
"name": {
"type": "string"
},
"protocol": {
"type": "string"
}
},
"required": [
"containerPort",
"protocol"
],
"type": "object"
}
}
}
}
}
}
}`), &c)
if err != nil {
framework.Failf("%s", err)
}
for i := range noxuDefinition.Spec.Versions {
noxuDefinition.Spec.Versions[i].Schema = &c
}
noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
if err != nil {
framework.Failf("cannot create crd %s", err)
}
kind := noxuDefinition.Spec.Names.Kind
apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
name := "mytest"
rest := apiExtensionClient.Discovery().RESTClient()
yamlBody := []byte(fmt.Sprintf(`
apiVersion: %s
kind: %s
metadata:
name: %s
finalizers:
- test-finalizer
spec:
unknown: uk1
foo: foo1
foo: foo2
cronSpec: "* * * * */5"
ports:
- name: x
containerPort: 80
protocol: TCP`, apiVersion, kind, name))
result, err := rest.Patch(types.ApplyPatchType).
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
Name(name).
Param("fieldManager", "field_validation_mgr").
Param("fieldValidation", "Strict").
Body(yamlBody).
DoRaw(ctx)
if !(strings.Contains(string(result), `line 11: key \"foo\" already set in map`)) {
framework.Failf("error missing duplicate field: %v:\n%v", err, string(result))
}
})
})