k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/apimachinery/field_validation.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package apimachinery 18 19 import ( 20 // ensure libs have a chance to initialize 21 "context" 22 "encoding/json" 23 "fmt" 24 "strings" 25 26 "github.com/onsi/ginkgo/v2" 27 _ "github.com/stretchr/testify/assert" 28 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 29 apiextensionclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 30 "k8s.io/apiextensions-apiserver/test/integration/fixtures" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/types" 33 "k8s.io/client-go/dynamic" 34 clientset "k8s.io/client-go/kubernetes" 35 "k8s.io/klog/v2" 36 "k8s.io/kubernetes/test/e2e/framework" 37 admissionapi "k8s.io/pod-security-admission/api" 38 ) 39 40 var _ = SIGDescribe("FieldValidation", func() { 41 f := framework.NewDefaultFramework("field-validation") 42 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 43 44 var client clientset.Interface 45 var ns string 46 47 ginkgo.BeforeEach(func() { 48 client = f.ClientSet 49 ns = f.Namespace.Name 50 }) 51 52 ginkgo.AfterEach(func(ctx context.Context) { 53 _ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment", metav1.DeleteOptions{}) 54 _ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment-shared-unset", metav1.DeleteOptions{}) 55 _ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment-shared-map-item-removal", metav1.DeleteOptions{}) 56 _ = client.CoreV1().Pods(ns).Delete(ctx, "test-pod", metav1.DeleteOptions{}) 57 }) 58 59 /* 60 Release: v1.27 61 Testname: Server side field validation, typed object 62 Description: It should reject the request if a typed object has unknown or duplicate fields. 63 */ 64 framework.ConformanceIt("should detect unknown and duplicate fields of a typed object", func(ctx context.Context) { 65 ginkgo.By("apply creating a deployment") 66 invalidMetaDeployment := `{ 67 "apiVersion": "apps/v1", 68 "kind": "Deployment", 69 "metadata": { 70 "name": "my-dep", 71 "labels": {"app": "nginx"} 72 }, 73 "spec": { 74 "unknownField": "foo", 75 "replicas": 2, 76 "replicas": 3, 77 "selector": { 78 "matchLabels": { 79 "app": "nginx" 80 } 81 }, 82 "template": { 83 "metadata": { 84 "labels": { 85 "app": "nginx" 86 } 87 }, 88 "spec": { 89 "containers": [{ 90 "name": "nginx", 91 "image": "nginx:latest" 92 }] 93 } 94 } 95 } 96 }` 97 _, err := client.CoreV1().RESTClient().Post(). 98 AbsPath("/apis/apps/v1"). 99 Namespace(ns). 100 Resource("deployments"). 101 Param("fieldManager", "field_validation_mgr"). 102 Param("fieldValidation", "Strict"). 103 Body([]byte(invalidMetaDeployment)). 104 Do(ctx). 105 Get() 106 if !(strings.Contains(err.Error(), `strict decoding error: unknown field "spec.unknownField", duplicate field "spec.replicas"`)) { 107 framework.Failf("error missing unknown/duplicate field field, got: %v", err) 108 } 109 110 }) 111 112 /* 113 Release: v1.27 114 Testname: Server side field validation, typed unknown metadata 115 Description: It should reject the request if a typed object has unknown fields in the metadata. 116 */ 117 framework.ConformanceIt("should detect unknown metadata fields of a typed object", func(ctx context.Context) { 118 ginkgo.By("apply creating a deployment") 119 invalidMetaDeployment := `{ 120 "apiVersion": "apps/v1", 121 "kind": "Deployment", 122 "metadata": { 123 "name": "my-dep", 124 "unknownMeta": "foo", 125 "labels": {"app": "nginx"} 126 }, 127 "spec": { 128 "selector": { 129 "matchLabels": { 130 "app": "nginx" 131 } 132 }, 133 "template": { 134 "metadata": { 135 "labels": { 136 "app": "nginx" 137 } 138 }, 139 "spec": { 140 "containers": [{ 141 "name": "nginx", 142 "image": "nginx:latest" 143 }] 144 } 145 } 146 } 147 }` 148 _, err := client.CoreV1().RESTClient().Post(). 149 AbsPath("/apis/apps/v1"). 150 Namespace(ns). 151 Resource("deployments"). 152 Param("fieldManager", "field_validation_mgr"). 153 Param("fieldValidation", "Strict"). 154 Body([]byte(invalidMetaDeployment)). 155 Do(ctx). 156 Get() 157 if !(strings.Contains(err.Error(), `strict decoding error: unknown field "metadata.unknownMeta"`)) { 158 framework.Failf("error missing unknown metadata field, got: %v", err) 159 } 160 161 }) 162 163 /* 164 Release: v1.27 165 Testname: Server side field validation, valid CR with validation schema 166 Description: When a CRD has a validation schema, it should succeed when a valid CR is applied. 167 */ 168 framework.ConformanceIt("should create/apply a valid CR for CRD with validation schema", func(ctx context.Context) { 169 config, err := framework.LoadConfig() 170 if err != nil { 171 framework.Failf("%s", err) 172 } 173 apiExtensionClient, err := apiextensionclientset.NewForConfig(config) 174 if err != nil { 175 framework.Failf("%s", err) 176 } 177 dynamicClient, err := dynamic.NewForConfig(config) 178 if err != nil { 179 framework.Failf("%s", err) 180 } 181 182 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped) 183 184 var c apiextensionsv1.CustomResourceValidation 185 err = json.Unmarshal([]byte(`{ 186 "openAPIV3Schema": { 187 "type": "object", 188 "properties": { 189 "spec": { 190 "type": "object", 191 "properties": { 192 "foo": { 193 "type": "string" 194 }, 195 "cronSpec": { 196 "type": "string", 197 "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$" 198 }, 199 "ports": { 200 "type": "array", 201 "x-kubernetes-list-map-keys": [ 202 "containerPort", 203 "protocol" 204 ], 205 "x-kubernetes-list-type": "map", 206 "items": { 207 "properties": { 208 "containerPort": { 209 "format": "int32", 210 "type": "integer" 211 }, 212 "hostIP": { 213 "type": "string" 214 }, 215 "hostPort": { 216 "format": "int32", 217 "type": "integer" 218 }, 219 "name": { 220 "type": "string" 221 }, 222 "protocol": { 223 "type": "string" 224 } 225 }, 226 "required": [ 227 "containerPort", 228 "protocol" 229 ], 230 "type": "object" 231 } 232 } 233 } 234 } 235 } 236 } 237 }`), &c) 238 if err != nil { 239 framework.Failf("%v", err) 240 } 241 for i := range noxuDefinition.Spec.Versions { 242 noxuDefinition.Spec.Versions[i].Schema = &c 243 } 244 245 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient) 246 if err != nil { 247 framework.Failf("cannot create crd %s", err) 248 } 249 250 defer func() { 251 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient) 252 framework.ExpectNoError(err, "deleting CustomResourceDefinition") 253 }() 254 255 kind := noxuDefinition.Spec.Names.Kind 256 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name 257 name := "mytest" 258 259 rest := apiExtensionClient.Discovery().RESTClient() 260 yamlBody := []byte(fmt.Sprintf(` 261 apiVersion: %s 262 kind: %s 263 metadata: 264 name: %s 265 spec: 266 foo: foo1 267 cronSpec: "* * * * */5" 268 ports: 269 - name: x 270 containerPort: 80 271 protocol: TCP`, apiVersion, kind, name)) 272 _, err = rest.Patch(types.ApplyPatchType). 273 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural). 274 Name(name). 275 Param("fieldManager", "field_validation_mgr"). 276 Param("fieldValidation", "Strict"). 277 Body(yamlBody). 278 DoRaw(ctx) 279 if err != nil { 280 framework.Failf("%v", err) 281 } 282 }) 283 284 /* 285 Release: v1.27 286 Testname: Server side field validation, unknown fields CR no validation schema 287 Description: When a CRD does not have a validation schema, it should succeed when a CR with unknown fields is applied. 288 */ 289 framework.ConformanceIt("should create/apply a CR with unknown fields for CRD with no validation schema", func(ctx context.Context) { 290 config, err := framework.LoadConfig() 291 if err != nil { 292 framework.Failf("%s", err) 293 } 294 apiExtensionClient, err := apiextensionclientset.NewForConfig(config) 295 if err != nil { 296 framework.Failf("%s", err) 297 } 298 dynamicClient, err := dynamic.NewForConfig(config) 299 if err != nil { 300 framework.Failf("%s", err) 301 } 302 303 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped) 304 305 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient) 306 if err != nil { 307 framework.Failf("cannot create crd %s", err) 308 } 309 310 defer func() { 311 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient) 312 framework.ExpectNoError(err, "deleting CustomResourceDefinition") 313 }() 314 315 kind := noxuDefinition.Spec.Names.Kind 316 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name 317 name := "mytest" 318 319 rest := apiExtensionClient.Discovery().RESTClient() 320 yamlBody := []byte(fmt.Sprintf(` 321 apiVersion: %s 322 kind: %s 323 metadata: 324 name: %s 325 spec: 326 unknown: uk1 327 cronSpec: "* * * * */5" 328 ports: 329 - name: x 330 containerPort: 80 331 protocol: TCP`, apiVersion, kind, name)) 332 _, err = rest.Patch(types.ApplyPatchType). 333 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural). 334 Name(name). 335 Param("fieldManager", "field_validation_mgr"). 336 Param("fieldValidation", "Strict"). 337 Body(yamlBody). 338 DoRaw(ctx) 339 if err != nil { 340 framework.Failf("%v", err) 341 } 342 343 }) 344 345 /* 346 Release: v1.27 347 Testname: Server side field validation, unknown fields CR fails validation 348 Description: When a CRD does have a validation schema, it should reject CRs with unknown fields. 349 */ 350 framework.ConformanceIt("should create/apply an invalid CR with extra properties for CRD with validation schema", func(ctx context.Context) { 351 config, err := framework.LoadConfig() 352 if err != nil { 353 framework.Failf("%s", err) 354 } 355 apiExtensionClient, err := apiextensionclientset.NewForConfig(config) 356 if err != nil { 357 framework.Failf("%s", err) 358 } 359 dynamicClient, err := dynamic.NewForConfig(config) 360 if err != nil { 361 framework.Failf("%s", err) 362 } 363 364 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped) 365 366 var c apiextensionsv1.CustomResourceValidation 367 err = json.Unmarshal([]byte(`{ 368 "openAPIV3Schema": { 369 "type": "object", 370 "properties": { 371 "spec": { 372 "type": "object", 373 "properties": { 374 "foo": { 375 "type": "string" 376 }, 377 "cronSpec": { 378 "type": "string", 379 "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$" 380 }, 381 "ports": { 382 "type": "array", 383 "x-kubernetes-list-map-keys": [ 384 "containerPort", 385 "protocol" 386 ], 387 "x-kubernetes-list-type": "map", 388 "items": { 389 "properties": { 390 "containerPort": { 391 "format": "int32", 392 "type": "integer" 393 }, 394 "hostIP": { 395 "type": "string" 396 }, 397 "hostPort": { 398 "format": "int32", 399 "type": "integer" 400 }, 401 "name": { 402 "type": "string" 403 }, 404 "protocol": { 405 "type": "string" 406 } 407 }, 408 "required": [ 409 "containerPort", 410 "protocol" 411 ], 412 "type": "object" 413 } 414 } 415 } 416 } 417 } 418 } 419 }`), &c) 420 if err != nil { 421 framework.Failf("%v", err) 422 } 423 klog.Warningf("props: %v\n", c.OpenAPIV3Schema) 424 for i := range noxuDefinition.Spec.Versions { 425 noxuDefinition.Spec.Versions[i].Schema = &c 426 } 427 428 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient) 429 if err != nil { 430 framework.Failf("cannot create crd %s", err) 431 } 432 433 defer func() { 434 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient) 435 framework.ExpectNoError(err, "deleting CustomResourceDefinition") 436 }() 437 438 kind := noxuDefinition.Spec.Names.Kind 439 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name 440 name := "mytest" 441 442 rest := apiExtensionClient.Discovery().RESTClient() 443 yamlBody := []byte(fmt.Sprintf(` 444 apiVersion: %s 445 kind: %s 446 metadata: 447 name: %s 448 unknownField: unknown 449 spec: 450 foo: foo1 451 cronSpec: "* * * * */5" 452 ports: 453 - name: x 454 containerPort: 80 455 protocol: TCP`, apiVersion, kind, name)) 456 result, err := rest.Patch(types.ApplyPatchType). 457 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural). 458 Name(name). 459 Param("fieldManager", "field_validation_mgr"). 460 Param("fieldValidation", "Strict"). 461 Body(yamlBody). 462 DoRaw(ctx) 463 if !(strings.Contains(string(result), `.unknownField: field not declared in schema`)) { 464 framework.Failf("error missing unknown field: %v:\n%v", err, string(result)) 465 } 466 }) 467 468 /* 469 Release: v1.27 470 Testname: Server side field validation, unknown metadata 471 Description: The server should reject CRs with unknown metadata fields in both the root and embedded objects 472 of a CR. 473 */ 474 framework.ConformanceIt("should detect unknown metadata fields in both the root and embedded object of a CR", func(ctx context.Context) { 475 config, err := framework.LoadConfig() 476 if err != nil { 477 framework.Failf("%s", err) 478 } 479 apiExtensionClient, err := apiextensionclientset.NewForConfig(config) 480 if err != nil { 481 framework.Failf("%s", err) 482 } 483 dynamicClient, err := dynamic.NewForConfig(config) 484 if err != nil { 485 framework.Failf("%s", err) 486 } 487 488 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped) 489 490 var c apiextensionsv1.CustomResourceValidation 491 err = json.Unmarshal([]byte(`{ 492 "openAPIV3Schema": { 493 "type": "object", 494 "properties": { 495 "spec": { 496 "type": "object", 497 "x-kubernetes-preserve-unknown-fields": true, 498 "properties": { 499 "template": { 500 "type": "object", 501 "x-kubernetes-embedded-resource": true, 502 "properties": { 503 "metadata": { 504 "type": "object", 505 "properties": { 506 "name": { 507 "type": "string" 508 } 509 } 510 }, 511 "spec": { 512 "type": "object" 513 } 514 } 515 516 }, 517 "foo": { 518 "type": "string" 519 }, 520 "cronSpec": { 521 "type": "string", 522 "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$" 523 }, 524 "ports": { 525 "type": "array", 526 "x-kubernetes-list-map-keys": [ 527 "containerPort", 528 "protocol" 529 ], 530 "x-kubernetes-list-type": "map", 531 "items": { 532 "properties": { 533 "containerPort": { 534 "format": "int32", 535 "type": "integer" 536 }, 537 "hostIP": { 538 "type": "string" 539 }, 540 "hostPort": { 541 "format": "int32", 542 "type": "integer" 543 }, 544 "name": { 545 "type": "string" 546 }, 547 "protocol": { 548 "type": "string" 549 } 550 }, 551 "required": [ 552 "containerPort", 553 "protocol" 554 ], 555 "type": "object" 556 } 557 } 558 } 559 } 560 } 561 } 562 }`), &c) 563 if err != nil { 564 framework.Failf("%v", err) 565 } 566 for i := range noxuDefinition.Spec.Versions { 567 noxuDefinition.Spec.Versions[i].Schema = &c 568 } 569 570 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient) 571 if err != nil { 572 framework.Failf("cannot create crd %s", err) 573 } 574 575 defer func() { 576 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient) 577 framework.ExpectNoError(err, "deleting CustomResourceDefinition") 578 }() 579 580 kind := noxuDefinition.Spec.Names.Kind 581 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name 582 name := "mytest" 583 584 rest := apiExtensionClient.Discovery().RESTClient() 585 yamlBody := []byte(fmt.Sprintf(` 586 apiVersion: %s 587 kind: %s 588 metadata: 589 name: %s 590 unknownMeta: unknown 591 spec: 592 template: 593 apiversion: foo/v1 594 kind: Sub 595 metadata: 596 unknownSubMeta: unknown 597 name: subobject 598 namespace: %s 599 foo: foo1 600 cronSpec: "* * * * */5" 601 ports: 602 - name: x 603 containerPort: 80 604 protocol: TCP`, apiVersion, kind, name, ns)) 605 result, err := rest.Patch(types.ApplyPatchType). 606 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural). 607 Name(name). 608 Param("fieldManager", "field_validation_mgr"). 609 Param("fieldValidation", "Strict"). 610 Body(yamlBody). 611 DoRaw(ctx) 612 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`)) { 613 framework.Failf("error missing duplicate field: %v:\n%v", err, string(result)) 614 } 615 }) 616 617 /* 618 Release: v1.27 619 Testname: Server side field validation, CR duplicates 620 Description: The server should reject CRs with duplicate fields even when preserving unknown fields. 621 */ 622 framework.ConformanceIt("should detect duplicates in a CR when preserving unknown fields", func(ctx context.Context) { 623 config, err := framework.LoadConfig() 624 if err != nil { 625 framework.Failf("%s", err) 626 } 627 apiExtensionClient, err := apiextensionclientset.NewForConfig(config) 628 if err != nil { 629 framework.Failf("%s", err) 630 } 631 dynamicClient, err := dynamic.NewForConfig(config) 632 if err != nil { 633 framework.Failf("%s", err) 634 } 635 636 noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped) 637 638 var c apiextensionsv1.CustomResourceValidation 639 err = json.Unmarshal([]byte(`{ 640 "openAPIV3Schema": { 641 "type": "object", 642 "properties": { 643 "spec": { 644 "type": "object", 645 "x-kubernetes-preserve-unknown-fields": true, 646 "properties": { 647 "foo": { 648 "type": "string" 649 }, 650 "cronSpec": { 651 "type": "string", 652 "pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$" 653 }, 654 "ports": { 655 "type": "array", 656 "x-kubernetes-list-map-keys": [ 657 "containerPort", 658 "protocol" 659 ], 660 "x-kubernetes-list-type": "map", 661 "items": { 662 "properties": { 663 "containerPort": { 664 "format": "int32", 665 "type": "integer" 666 }, 667 "hostIP": { 668 "type": "string" 669 }, 670 "hostPort": { 671 "format": "int32", 672 "type": "integer" 673 }, 674 "name": { 675 "type": "string" 676 }, 677 "protocol": { 678 "type": "string" 679 } 680 }, 681 "required": [ 682 "containerPort", 683 "protocol" 684 ], 685 "type": "object" 686 } 687 } 688 } 689 } 690 } 691 } 692 }`), &c) 693 if err != nil { 694 framework.Failf("%s", err) 695 } 696 for i := range noxuDefinition.Spec.Versions { 697 noxuDefinition.Spec.Versions[i].Schema = &c 698 } 699 700 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient) 701 if err != nil { 702 framework.Failf("cannot create crd %s", err) 703 } 704 705 defer func() { 706 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient) 707 framework.ExpectNoError(err, "deleting CustomResourceDefinition") 708 }() 709 710 kind := noxuDefinition.Spec.Names.Kind 711 apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name 712 name := "mytest" 713 714 rest := apiExtensionClient.Discovery().RESTClient() 715 yamlBody := []byte(fmt.Sprintf(` 716 apiVersion: %s 717 kind: %s 718 metadata: 719 name: %s 720 spec: 721 unknown: uk1 722 foo: foo1 723 foo: foo2 724 cronSpec: "* * * * */5" 725 ports: 726 - name: x 727 containerPort: 80 728 protocol: TCP`, apiVersion, kind, name)) 729 result, err := rest.Patch(types.ApplyPatchType). 730 AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural). 731 Name(name). 732 Param("fieldManager", "field_validation_mgr"). 733 Param("fieldValidation", "Strict"). 734 Body(yamlBody). 735 DoRaw(ctx) 736 if !(strings.Contains(string(result), `line 9: key \"foo\" already set in map`)) { 737 framework.Failf("error missing duplicate field: %v:\n%v", err, string(result)) 738 } 739 }) 740 })