github.com/nginxinc/kubernetes-ingress@v1.12.5/internal/k8s/validation_test.go (about) 1 package k8s 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 9 networking "k8s.io/api/networking/v1beta1" 10 meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 "k8s.io/apimachinery/pkg/util/validation/field" 12 ) 13 14 func TestValidateIngress(t *testing.T) { 15 tests := []struct { 16 ing *networking.Ingress 17 expectedErrors []string 18 msg string 19 }{ 20 { 21 ing: &networking.Ingress{ 22 Spec: networking.IngressSpec{ 23 Rules: []networking.IngressRule{ 24 { 25 Host: "example.com", 26 }, 27 }, 28 }, 29 }, 30 expectedErrors: nil, 31 msg: "valid input", 32 }, 33 { 34 ing: &networking.Ingress{ 35 ObjectMeta: meta_v1.ObjectMeta{ 36 Annotations: map[string]string{ 37 "nginx.org/mergeable-ingress-type": "invalid", 38 }, 39 }, 40 Spec: networking.IngressSpec{ 41 Rules: []networking.IngressRule{ 42 { 43 Host: "", 44 }, 45 }, 46 }, 47 }, 48 expectedErrors: []string{ 49 `annotations.nginx.org/mergeable-ingress-type: Invalid value: "invalid": must be one of: 'master' or 'minion'`, 50 "spec.rules[0].host: Required value", 51 }, 52 msg: "invalid ingress", 53 }, 54 { 55 ing: &networking.Ingress{ 56 ObjectMeta: meta_v1.ObjectMeta{ 57 Annotations: map[string]string{ 58 "nginx.org/mergeable-ingress-type": "master", 59 }, 60 }, 61 Spec: networking.IngressSpec{ 62 Rules: []networking.IngressRule{ 63 { 64 Host: "example.com", 65 IngressRuleValue: networking.IngressRuleValue{ 66 HTTP: &networking.HTTPIngressRuleValue{ 67 Paths: []networking.HTTPIngressPath{ 68 { 69 Path: "/", 70 }, 71 }, 72 }, 73 }, 74 }, 75 }, 76 }, 77 }, 78 expectedErrors: []string{ 79 "spec.rules[0].http.paths: Too many: 1: must have at most 0 items", 80 }, 81 msg: "invalid master", 82 }, 83 { 84 ing: &networking.Ingress{ 85 ObjectMeta: meta_v1.ObjectMeta{ 86 Annotations: map[string]string{ 87 "nginx.org/mergeable-ingress-type": "minion", 88 }, 89 }, 90 Spec: networking.IngressSpec{ 91 Rules: []networking.IngressRule{ 92 { 93 Host: "example.com", 94 IngressRuleValue: networking.IngressRuleValue{}, 95 }, 96 }, 97 }, 98 }, 99 expectedErrors: []string{ 100 "spec.rules[0].http.paths: Required value: must include at least one path", 101 }, 102 msg: "invalid minion", 103 }, 104 } 105 106 for _, test := range tests { 107 allErrs := validateIngress(test.ing, false, false, false, false) 108 assertion := assertErrors("validateIngress()", test.msg, allErrs, test.expectedErrors) 109 if assertion != "" { 110 t.Error(assertion) 111 } 112 } 113 } 114 115 func TestValidateNginxIngressAnnotations(t *testing.T) { 116 tests := []struct { 117 annotations map[string]string 118 specServices map[string]bool 119 isPlus bool 120 appProtectEnabled bool 121 internalRoutesEnabled bool 122 snippetsEnabled bool 123 expectedErrors []string 124 msg string 125 }{ 126 { 127 annotations: map[string]string{}, 128 specServices: map[string]bool{}, 129 isPlus: false, 130 appProtectEnabled: false, 131 internalRoutesEnabled: false, 132 expectedErrors: nil, 133 msg: "valid no annotations", 134 }, 135 136 { 137 annotations: map[string]string{ 138 "nginx.org/lb-method": "invalid_method", 139 "nginx.org/mergeable-ingress-type": "invalid", 140 }, 141 specServices: map[string]bool{}, 142 isPlus: false, 143 appProtectEnabled: false, 144 internalRoutesEnabled: false, 145 expectedErrors: []string{ 146 `annotations.nginx.org/lb-method: Invalid value: "invalid_method": Invalid load balancing method: "invalid_method"`, 147 `annotations.nginx.org/mergeable-ingress-type: Invalid value: "invalid": must be one of: 'master' or 'minion'`, 148 }, 149 msg: "invalid multiple annotations messages in alphabetical order", 150 }, 151 152 { 153 annotations: map[string]string{ 154 "nginx.org/mergeable-ingress-type": "master", 155 }, 156 specServices: map[string]bool{}, 157 isPlus: false, 158 appProtectEnabled: false, 159 internalRoutesEnabled: false, 160 expectedErrors: nil, 161 msg: "valid input with master annotation", 162 }, 163 { 164 annotations: map[string]string{ 165 "nginx.org/mergeable-ingress-type": "minion", 166 }, 167 specServices: map[string]bool{}, 168 isPlus: false, 169 appProtectEnabled: false, 170 internalRoutesEnabled: false, 171 expectedErrors: nil, 172 msg: "valid input with minion annotation", 173 }, 174 { 175 annotations: map[string]string{ 176 "nginx.org/mergeable-ingress-type": "", 177 }, 178 specServices: map[string]bool{}, 179 isPlus: false, 180 appProtectEnabled: false, 181 internalRoutesEnabled: false, 182 expectedErrors: []string{ 183 "annotations.nginx.org/mergeable-ingress-type: Required value", 184 }, 185 msg: "invalid mergeable type annotation 1", 186 }, 187 { 188 annotations: map[string]string{ 189 "nginx.org/mergeable-ingress-type": "abc", 190 }, 191 specServices: map[string]bool{}, 192 isPlus: false, 193 appProtectEnabled: false, 194 internalRoutesEnabled: false, 195 expectedErrors: []string{ 196 `annotations.nginx.org/mergeable-ingress-type: Invalid value: "abc": must be one of: 'master' or 'minion'`, 197 }, 198 msg: "invalid mergeable type annotation 2", 199 }, 200 201 { 202 annotations: map[string]string{ 203 "nginx.org/lb-method": "random", 204 }, 205 specServices: map[string]bool{}, 206 isPlus: false, 207 appProtectEnabled: false, 208 internalRoutesEnabled: false, 209 expectedErrors: nil, 210 msg: "valid nginx.org/lb-method annotation, nginx normal", 211 }, 212 { 213 annotations: map[string]string{ 214 "nginx.org/lb-method": "least_time header", 215 }, 216 specServices: map[string]bool{}, 217 isPlus: false, 218 appProtectEnabled: false, 219 internalRoutesEnabled: false, 220 expectedErrors: []string{ 221 `annotations.nginx.org/lb-method: Invalid value: "least_time header": Invalid load balancing method: "least_time header"`, 222 }, 223 msg: "invalid nginx.org/lb-method annotation, nginx plus only", 224 }, 225 { 226 annotations: map[string]string{ 227 "nginx.org/lb-method": "invalid_method", 228 }, 229 specServices: map[string]bool{}, 230 isPlus: false, 231 appProtectEnabled: false, 232 internalRoutesEnabled: false, 233 expectedErrors: []string{ 234 `annotations.nginx.org/lb-method: Invalid value: "invalid_method": Invalid load balancing method: "invalid_method"`, 235 }, 236 msg: "invalid nginx.org/lb-method annotation", 237 }, 238 239 { 240 annotations: map[string]string{ 241 "nginx.com/health-checks": "true", 242 }, 243 specServices: map[string]bool{}, 244 isPlus: false, 245 appProtectEnabled: false, 246 internalRoutesEnabled: false, 247 expectedErrors: []string{ 248 "annotations.nginx.com/health-checks: Forbidden: annotation requires NGINX Plus", 249 }, 250 msg: "invalid nginx.com/health-checks annotation, nginx plus only", 251 }, 252 { 253 annotations: map[string]string{ 254 "nginx.com/health-checks": "true", 255 }, 256 specServices: map[string]bool{}, 257 isPlus: true, 258 appProtectEnabled: false, 259 internalRoutesEnabled: false, 260 expectedErrors: nil, 261 msg: "valid nginx.com/health-checks annotation", 262 }, 263 { 264 annotations: map[string]string{ 265 "nginx.com/health-checks": "not_a_boolean", 266 }, 267 specServices: map[string]bool{}, 268 isPlus: true, 269 appProtectEnabled: false, 270 internalRoutesEnabled: false, 271 expectedErrors: []string{ 272 `annotations.nginx.com/health-checks: Invalid value: "not_a_boolean": must be a boolean`, 273 }, 274 msg: "invalid nginx.com/health-checks annotation", 275 }, 276 277 { 278 annotations: map[string]string{ 279 "nginx.com/health-checks-mandatory": "true", 280 }, 281 specServices: map[string]bool{}, 282 isPlus: false, 283 appProtectEnabled: false, 284 internalRoutesEnabled: false, 285 expectedErrors: []string{ 286 "annotations.nginx.com/health-checks-mandatory: Forbidden: annotation requires NGINX Plus", 287 }, 288 msg: "invalid nginx.com/health-checks-mandatory annotation, nginx plus only", 289 }, 290 { 291 annotations: map[string]string{ 292 "nginx.com/health-checks": "true", 293 "nginx.com/health-checks-mandatory": "true", 294 }, 295 specServices: map[string]bool{}, 296 isPlus: true, 297 appProtectEnabled: false, 298 internalRoutesEnabled: false, 299 expectedErrors: nil, 300 msg: "valid nginx.com/health-checks-mandatory annotation", 301 }, 302 { 303 annotations: map[string]string{ 304 "nginx.com/health-checks": "true", 305 "nginx.com/health-checks-mandatory": "not_a_boolean", 306 }, 307 specServices: map[string]bool{}, 308 isPlus: true, 309 appProtectEnabled: false, 310 internalRoutesEnabled: false, 311 expectedErrors: []string{ 312 `annotations.nginx.com/health-checks-mandatory: Invalid value: "not_a_boolean": must be a boolean`, 313 }, 314 msg: "invalid nginx.com/health-checks-mandatory, must be a boolean", 315 }, 316 { 317 annotations: map[string]string{ 318 "nginx.com/health-checks-mandatory": "true", 319 }, 320 specServices: map[string]bool{}, 321 isPlus: true, 322 appProtectEnabled: false, 323 internalRoutesEnabled: false, 324 expectedErrors: []string{ 325 "annotations.nginx.com/health-checks-mandatory: Forbidden: related annotation nginx.com/health-checks: must be set", 326 }, 327 msg: "invalid nginx.com/health-checks-mandatory, related annotation nginx.com/health-checks not set", 328 }, 329 { 330 annotations: map[string]string{ 331 "nginx.com/health-checks": "false", 332 "nginx.com/health-checks-mandatory": "true", 333 }, 334 specServices: map[string]bool{}, 335 isPlus: true, 336 appProtectEnabled: false, 337 internalRoutesEnabled: false, 338 expectedErrors: []string{ 339 "annotations.nginx.com/health-checks-mandatory: Forbidden: related annotation nginx.com/health-checks: must be true", 340 }, 341 msg: "invalid nginx.com/health-checks-mandatory nginx.com/health-checks is not true", 342 }, 343 344 { 345 annotations: map[string]string{ 346 "nginx.com/health-checks-mandatory-queue": "true", 347 }, 348 specServices: map[string]bool{}, 349 isPlus: false, 350 appProtectEnabled: false, 351 internalRoutesEnabled: false, 352 expectedErrors: []string{ 353 "annotations.nginx.com/health-checks-mandatory-queue: Forbidden: annotation requires NGINX Plus", 354 }, 355 msg: "invalid nginx.com/health-checks-mandatory-queue annotation, nginx plus only", 356 }, 357 { 358 annotations: map[string]string{ 359 "nginx.com/health-checks": "true", 360 "nginx.com/health-checks-mandatory": "true", 361 "nginx.com/health-checks-mandatory-queue": "5", 362 }, 363 specServices: map[string]bool{}, 364 isPlus: true, 365 appProtectEnabled: false, 366 internalRoutesEnabled: false, 367 expectedErrors: nil, 368 msg: "valid nginx.com/health-checks-mandatory-queue annotation", 369 }, 370 { 371 annotations: map[string]string{ 372 "nginx.com/health-checks": "true", 373 "nginx.com/health-checks-mandatory": "true", 374 "nginx.com/health-checks-mandatory-queue": "not_a_number", 375 }, 376 specServices: map[string]bool{}, 377 isPlus: true, 378 appProtectEnabled: false, 379 internalRoutesEnabled: false, 380 expectedErrors: []string{ 381 `annotations.nginx.com/health-checks-mandatory-queue: Invalid value: "not_a_number": must be a non-negative integer`, 382 }, 383 msg: "invalid nginx.com/health-checks-mandatory-queue, must be a number", 384 }, 385 { 386 annotations: map[string]string{ 387 "nginx.com/health-checks-mandatory-queue": "5", 388 }, 389 specServices: map[string]bool{}, 390 isPlus: true, 391 appProtectEnabled: false, 392 internalRoutesEnabled: false, 393 expectedErrors: []string{ 394 "annotations.nginx.com/health-checks-mandatory-queue: Forbidden: related annotation nginx.com/health-checks-mandatory: must be set", 395 }, 396 msg: "invalid nginx.com/health-checks-mandatory-queue, related annotation nginx.com/health-checks-mandatory not set", 397 }, 398 { 399 annotations: map[string]string{ 400 "nginx.com/health-checks": "true", 401 "nginx.com/health-checks-mandatory": "false", 402 "nginx.com/health-checks-mandatory-queue": "5", 403 }, 404 specServices: map[string]bool{}, 405 isPlus: true, 406 appProtectEnabled: false, 407 internalRoutesEnabled: false, 408 expectedErrors: []string{ 409 "annotations.nginx.com/health-checks-mandatory-queue: Forbidden: related annotation nginx.com/health-checks-mandatory: must be true", 410 }, 411 msg: "invalid nginx.com/health-checks-mandatory-queue nginx.com/health-checks-mandatory is not true", 412 }, 413 414 { 415 annotations: map[string]string{ 416 "nginx.com/slow-start": "true", 417 }, 418 specServices: map[string]bool{}, 419 isPlus: false, 420 appProtectEnabled: false, 421 internalRoutesEnabled: false, 422 expectedErrors: []string{ 423 "annotations.nginx.com/slow-start: Forbidden: annotation requires NGINX Plus", 424 }, 425 msg: "invalid nginx.com/slow-start annotation, nginx plus only", 426 }, 427 { 428 annotations: map[string]string{ 429 "nginx.com/slow-start": "60s", 430 }, 431 specServices: map[string]bool{}, 432 isPlus: true, 433 appProtectEnabled: false, 434 internalRoutesEnabled: false, 435 expectedErrors: nil, 436 msg: "valid nginx.com/slow-start annotation", 437 }, 438 { 439 annotations: map[string]string{ 440 "nginx.com/slow-start": "not_a_time", 441 }, 442 specServices: map[string]bool{}, 443 isPlus: true, 444 appProtectEnabled: false, 445 internalRoutesEnabled: false, 446 expectedErrors: []string{ 447 `annotations.nginx.com/slow-start: Invalid value: "not_a_time": must be a time`, 448 }, 449 msg: "invalid nginx.com/slow-start annotation", 450 }, 451 452 { 453 annotations: map[string]string{ 454 "nginx.org/server-tokens": "true", 455 }, 456 specServices: map[string]bool{}, 457 isPlus: false, 458 appProtectEnabled: false, 459 internalRoutesEnabled: false, 460 expectedErrors: nil, 461 msg: "valid nginx.org/server-tokens annotation, nginx", 462 }, 463 { 464 annotations: map[string]string{ 465 "nginx.org/server-tokens": "custom_setting", 466 }, 467 specServices: map[string]bool{}, 468 isPlus: true, 469 appProtectEnabled: false, 470 internalRoutesEnabled: false, 471 expectedErrors: nil, 472 msg: "valid nginx.org/server-tokens annotation, nginx plus", 473 }, 474 { 475 annotations: map[string]string{ 476 "nginx.org/server-tokens": "custom_setting", 477 }, 478 specServices: map[string]bool{}, 479 isPlus: false, 480 appProtectEnabled: false, 481 internalRoutesEnabled: false, 482 expectedErrors: []string{ 483 `annotations.nginx.org/server-tokens: Invalid value: "custom_setting": must be a boolean`, 484 }, 485 msg: "invalid nginx.org/server-tokens annotation, must be a boolean", 486 }, 487 488 { 489 annotations: map[string]string{ 490 "nginx.org/server-snippets": "snippet-1", 491 }, 492 specServices: map[string]bool{}, 493 isPlus: false, 494 appProtectEnabled: false, 495 internalRoutesEnabled: false, 496 snippetsEnabled: true, 497 expectedErrors: nil, 498 msg: "valid nginx.org/server-snippets annotation, single-value", 499 }, 500 { 501 annotations: map[string]string{ 502 "nginx.org/server-snippets": "snippet-1\nsnippet-2\nsnippet-3", 503 }, 504 specServices: map[string]bool{}, 505 isPlus: false, 506 appProtectEnabled: false, 507 internalRoutesEnabled: false, 508 snippetsEnabled: true, 509 expectedErrors: nil, 510 msg: "valid nginx.org/server-snippets annotation, multi-value", 511 }, 512 { 513 annotations: map[string]string{ 514 "nginx.org/server-snippets": "snippet-1", 515 }, 516 specServices: map[string]bool{}, 517 isPlus: false, 518 appProtectEnabled: false, 519 internalRoutesEnabled: false, 520 snippetsEnabled: false, 521 expectedErrors: []string{ 522 `annotations.nginx.org/server-snippets: Forbidden: snippet specified but snippets feature is not enabled`, 523 }, 524 msg: "invalid nginx.org/server-snippets annotation when snippets are disabled", 525 }, 526 527 { 528 annotations: map[string]string{ 529 "nginx.org/location-snippets": "snippet-1", 530 }, 531 specServices: map[string]bool{}, 532 isPlus: false, 533 appProtectEnabled: false, 534 internalRoutesEnabled: false, 535 snippetsEnabled: true, 536 expectedErrors: nil, 537 msg: "valid nginx.org/location-snippets annotation, single-value", 538 }, 539 { 540 annotations: map[string]string{ 541 "nginx.org/location-snippets": "snippet-1\nsnippet-2\nsnippet-3", 542 }, 543 specServices: map[string]bool{}, 544 isPlus: false, 545 appProtectEnabled: false, 546 internalRoutesEnabled: false, 547 snippetsEnabled: true, 548 expectedErrors: nil, 549 msg: "valid nginx.org/location-snippets annotation, multi-value", 550 }, 551 { 552 annotations: map[string]string{ 553 "nginx.org/location-snippets": "snippet-1", 554 }, 555 specServices: map[string]bool{}, 556 isPlus: false, 557 appProtectEnabled: false, 558 internalRoutesEnabled: false, 559 snippetsEnabled: false, 560 expectedErrors: []string{ 561 `annotations.nginx.org/location-snippets: Forbidden: snippet specified but snippets feature is not enabled`, 562 }, 563 msg: "invalid nginx.org/location-snippets annotation when snippets are disabled", 564 }, 565 566 { 567 annotations: map[string]string{ 568 "nginx.org/proxy-connect-timeout": "10s", 569 }, 570 specServices: map[string]bool{}, 571 isPlus: false, 572 appProtectEnabled: false, 573 internalRoutesEnabled: false, 574 expectedErrors: nil, 575 msg: "valid nginx.org/proxy-connect-timeout annotation", 576 }, 577 { 578 annotations: map[string]string{ 579 "nginx.org/proxy-connect-timeout": "not_a_time", 580 }, 581 specServices: map[string]bool{}, 582 isPlus: false, 583 appProtectEnabled: false, 584 internalRoutesEnabled: false, 585 expectedErrors: []string{ 586 `annotations.nginx.org/proxy-connect-timeout: Invalid value: "not_a_time": must be a time`, 587 }, 588 msg: "invalid nginx.org/proxy-connect-timeout annotation", 589 }, 590 591 { 592 annotations: map[string]string{ 593 "nginx.org/proxy-read-timeout": "10s", 594 }, 595 specServices: map[string]bool{}, 596 isPlus: false, 597 appProtectEnabled: false, 598 internalRoutesEnabled: false, 599 expectedErrors: nil, 600 msg: "valid nginx.org/proxy-read-timeout annotation", 601 }, 602 { 603 annotations: map[string]string{ 604 "nginx.org/proxy-read-timeout": "not_a_time", 605 }, 606 specServices: map[string]bool{}, 607 isPlus: false, 608 appProtectEnabled: false, 609 internalRoutesEnabled: false, 610 expectedErrors: []string{ 611 `annotations.nginx.org/proxy-read-timeout: Invalid value: "not_a_time": must be a time`, 612 }, 613 msg: "invalid nginx.org/proxy-read-timeout annotation", 614 }, 615 616 { 617 annotations: map[string]string{ 618 "nginx.org/proxy-send-timeout": "10s", 619 }, 620 specServices: map[string]bool{}, 621 isPlus: false, 622 appProtectEnabled: false, 623 internalRoutesEnabled: false, 624 expectedErrors: nil, 625 msg: "valid nginx.org/proxy-send-timeout annotation", 626 }, 627 { 628 annotations: map[string]string{ 629 "nginx.org/proxy-send-timeout": "not_a_time", 630 }, 631 specServices: map[string]bool{}, 632 isPlus: false, 633 appProtectEnabled: false, 634 internalRoutesEnabled: false, 635 expectedErrors: []string{ 636 `annotations.nginx.org/proxy-send-timeout: Invalid value: "not_a_time": must be a time`, 637 }, 638 msg: "invalid nginx.org/proxy-send-timeout annotation", 639 }, 640 641 { 642 annotations: map[string]string{ 643 "nginx.org/proxy-hide-headers": "header-1", 644 }, 645 specServices: map[string]bool{}, 646 isPlus: false, 647 appProtectEnabled: false, 648 internalRoutesEnabled: false, 649 expectedErrors: nil, 650 msg: "valid nginx.org/proxy-hide-headers annotation, single-value", 651 }, 652 { 653 annotations: map[string]string{ 654 "nginx.org/proxy-hide-headers": "header-1,header-2,header-3", 655 }, 656 specServices: map[string]bool{}, 657 isPlus: false, 658 appProtectEnabled: false, 659 internalRoutesEnabled: false, 660 expectedErrors: nil, 661 msg: "valid nginx.org/proxy-hide-headers annotation, multi-value", 662 }, 663 664 { 665 annotations: map[string]string{ 666 "nginx.org/proxy-pass-headers": "header-1", 667 }, 668 specServices: map[string]bool{}, 669 isPlus: false, 670 appProtectEnabled: false, 671 internalRoutesEnabled: false, 672 expectedErrors: nil, 673 msg: "valid nginx.org/proxy-pass-headers annotation, single-value", 674 }, 675 { 676 annotations: map[string]string{ 677 "nginx.org/proxy-pass-headers": "header-1,header-2,header-3", 678 }, 679 specServices: map[string]bool{}, 680 isPlus: false, 681 appProtectEnabled: false, 682 internalRoutesEnabled: false, 683 expectedErrors: nil, 684 msg: "valid nginx.org/proxy-pass-headers annotation, multi-value", 685 }, 686 687 { 688 annotations: map[string]string{ 689 "nginx.org/client-max-body-size": "16M", 690 }, 691 specServices: map[string]bool{}, 692 isPlus: false, 693 appProtectEnabled: false, 694 internalRoutesEnabled: false, 695 expectedErrors: nil, 696 msg: "valid nginx.org/client-max-body-size annotation", 697 }, 698 { 699 annotations: map[string]string{ 700 "nginx.org/client-max-body-size": "not_an_offset", 701 }, 702 specServices: map[string]bool{}, 703 isPlus: false, 704 appProtectEnabled: false, 705 internalRoutesEnabled: false, 706 expectedErrors: []string{ 707 `annotations.nginx.org/client-max-body-size: Invalid value: "not_an_offset": must be an offset`, 708 }, 709 msg: "invalid nginx.org/client-max-body-size annotation", 710 }, 711 712 { 713 annotations: map[string]string{ 714 "nginx.org/redirect-to-https": "true", 715 }, 716 specServices: map[string]bool{}, 717 isPlus: false, 718 appProtectEnabled: false, 719 internalRoutesEnabled: false, 720 expectedErrors: nil, 721 msg: "valid nginx.org/redirect-to-https annotation", 722 }, 723 { 724 annotations: map[string]string{ 725 "nginx.org/redirect-to-https": "not_a_boolean", 726 }, 727 specServices: map[string]bool{}, 728 isPlus: false, 729 appProtectEnabled: false, 730 internalRoutesEnabled: false, 731 expectedErrors: []string{ 732 `annotations.nginx.org/redirect-to-https: Invalid value: "not_a_boolean": must be a boolean`, 733 }, 734 msg: "invalid nginx.org/redirect-to-https annotation", 735 }, 736 737 { 738 annotations: map[string]string{ 739 "ingress.kubernetes.io/ssl-redirect": "true", 740 }, 741 specServices: map[string]bool{}, 742 isPlus: false, 743 appProtectEnabled: false, 744 internalRoutesEnabled: false, 745 expectedErrors: nil, 746 msg: "valid ingress.kubernetes.io/ssl-redirect annotation", 747 }, 748 { 749 annotations: map[string]string{ 750 "ingress.kubernetes.io/ssl-redirect": "not_a_boolean", 751 }, 752 specServices: map[string]bool{}, 753 isPlus: false, 754 appProtectEnabled: false, 755 internalRoutesEnabled: false, 756 expectedErrors: []string{ 757 `annotations.ingress.kubernetes.io/ssl-redirect: Invalid value: "not_a_boolean": must be a boolean`, 758 }, 759 msg: "invalid ingress.kubernetes.io/ssl-redirect annotation", 760 }, 761 762 { 763 annotations: map[string]string{ 764 "nginx.org/proxy-buffering": "true", 765 }, 766 specServices: map[string]bool{}, 767 isPlus: false, 768 appProtectEnabled: false, 769 internalRoutesEnabled: false, 770 expectedErrors: nil, 771 msg: "valid nginx.org/proxy-buffering annotation", 772 }, 773 { 774 annotations: map[string]string{ 775 "nginx.org/proxy-buffering": "not_a_boolean", 776 }, 777 specServices: map[string]bool{}, 778 isPlus: false, 779 appProtectEnabled: false, 780 internalRoutesEnabled: false, 781 expectedErrors: []string{ 782 `annotations.nginx.org/proxy-buffering: Invalid value: "not_a_boolean": must be a boolean`, 783 }, 784 msg: "invalid nginx.org/proxy-buffering annotation", 785 }, 786 787 { 788 annotations: map[string]string{ 789 "nginx.org/hsts": "true", 790 }, 791 specServices: map[string]bool{}, 792 isPlus: false, 793 appProtectEnabled: false, 794 internalRoutesEnabled: false, 795 expectedErrors: nil, 796 msg: "valid nginx.org/hsts annotation", 797 }, 798 { 799 annotations: map[string]string{ 800 "nginx.org/hsts": "not_a_boolean", 801 }, 802 specServices: map[string]bool{}, 803 isPlus: false, 804 appProtectEnabled: false, 805 internalRoutesEnabled: false, 806 expectedErrors: []string{ 807 `annotations.nginx.org/hsts: Invalid value: "not_a_boolean": must be a boolean`, 808 }, 809 msg: "invalid nginx.org/hsts annotation", 810 }, 811 812 { 813 annotations: map[string]string{ 814 "nginx.org/hsts": "true", 815 "nginx.org/hsts-max-age": "120", 816 }, 817 specServices: map[string]bool{}, 818 isPlus: false, 819 appProtectEnabled: false, 820 internalRoutesEnabled: false, 821 expectedErrors: nil, 822 msg: "valid nginx.org/hsts-max-age annotation", 823 }, 824 { 825 annotations: map[string]string{ 826 "nginx.org/hsts": "false", 827 "nginx.org/hsts-max-age": "120", 828 }, 829 specServices: map[string]bool{}, 830 isPlus: false, 831 appProtectEnabled: false, 832 internalRoutesEnabled: false, 833 expectedErrors: nil, 834 msg: "valid nginx.org/hsts-max-age nginx.org/hsts can be false", 835 }, 836 { 837 annotations: map[string]string{ 838 "nginx.org/hsts": "true", 839 "nginx.org/hsts-max-age": "not_a_number", 840 }, 841 specServices: map[string]bool{}, 842 isPlus: false, 843 appProtectEnabled: false, 844 internalRoutesEnabled: false, 845 expectedErrors: []string{ 846 `annotations.nginx.org/hsts-max-age: Invalid value: "not_a_number": must be an integer`, 847 }, 848 msg: "invalid nginx.org/hsts-max-age, must be a number", 849 }, 850 { 851 annotations: map[string]string{ 852 "nginx.org/hsts-max-age": "true", 853 }, 854 specServices: map[string]bool{}, 855 isPlus: false, 856 appProtectEnabled: false, 857 internalRoutesEnabled: false, 858 expectedErrors: []string{ 859 "annotations.nginx.org/hsts-max-age: Forbidden: related annotation nginx.org/hsts: must be set", 860 }, 861 msg: "invalid nginx.org/hsts-max-age, related annotation nginx.org/hsts not set", 862 }, 863 864 { 865 annotations: map[string]string{ 866 "nginx.org/hsts": "true", 867 "nginx.org/hsts-include-subdomains": "true", 868 }, 869 specServices: map[string]bool{}, 870 isPlus: false, 871 appProtectEnabled: false, 872 internalRoutesEnabled: false, 873 expectedErrors: nil, 874 msg: "valid nginx.org/hsts-include-subdomains annotation", 875 }, 876 { 877 annotations: map[string]string{ 878 "nginx.org/hsts": "false", 879 "nginx.org/hsts-include-subdomains": "true", 880 }, 881 specServices: map[string]bool{}, 882 isPlus: false, 883 appProtectEnabled: false, 884 internalRoutesEnabled: false, 885 expectedErrors: nil, 886 msg: "valid nginx.org/hsts-include-subdomains, nginx.org/hsts can be false", 887 }, 888 { 889 annotations: map[string]string{ 890 "nginx.org/hsts": "true", 891 "nginx.org/hsts-include-subdomains": "not_a_boolean", 892 }, 893 specServices: map[string]bool{}, 894 isPlus: false, 895 appProtectEnabled: false, 896 internalRoutesEnabled: false, 897 expectedErrors: []string{ 898 `annotations.nginx.org/hsts-include-subdomains: Invalid value: "not_a_boolean": must be a boolean`, 899 }, 900 msg: "invalid nginx.org/hsts-include-subdomains, must be a boolean", 901 }, 902 { 903 annotations: map[string]string{ 904 "nginx.org/hsts-include-subdomains": "true", 905 }, 906 specServices: map[string]bool{}, 907 isPlus: false, 908 appProtectEnabled: false, 909 internalRoutesEnabled: false, 910 expectedErrors: []string{ 911 "annotations.nginx.org/hsts-include-subdomains: Forbidden: related annotation nginx.org/hsts: must be set", 912 }, 913 msg: "invalid nginx.org/hsts-include-subdomains, related annotation nginx.org/hsts not set", 914 }, 915 916 { 917 annotations: map[string]string{ 918 "nginx.org/hsts": "true", 919 "nginx.org/hsts-behind-proxy": "true", 920 }, 921 specServices: map[string]bool{}, 922 isPlus: false, 923 appProtectEnabled: false, 924 internalRoutesEnabled: false, 925 expectedErrors: nil, 926 msg: "valid nginx.org/hsts-behind-proxy annotation", 927 }, 928 { 929 annotations: map[string]string{ 930 "nginx.org/hsts": "false", 931 "nginx.org/hsts-behind-proxy": "true", 932 }, 933 specServices: map[string]bool{}, 934 isPlus: false, 935 appProtectEnabled: false, 936 internalRoutesEnabled: false, 937 expectedErrors: nil, 938 msg: "valid nginx.org/hsts-behind-proxy, nginx.org/hsts can be false", 939 }, 940 { 941 annotations: map[string]string{ 942 "nginx.org/hsts": "true", 943 "nginx.org/hsts-behind-proxy": "not_a_boolean", 944 }, 945 specServices: map[string]bool{}, 946 isPlus: false, 947 appProtectEnabled: false, 948 internalRoutesEnabled: false, 949 expectedErrors: []string{ 950 `annotations.nginx.org/hsts-behind-proxy: Invalid value: "not_a_boolean": must be a boolean`, 951 }, 952 msg: "invalid nginx.org/hsts-behind-proxy, must be a boolean", 953 }, 954 { 955 annotations: map[string]string{ 956 "nginx.org/hsts-behind-proxy": "true", 957 }, 958 specServices: map[string]bool{}, 959 isPlus: false, 960 appProtectEnabled: false, 961 internalRoutesEnabled: false, 962 expectedErrors: []string{ 963 "annotations.nginx.org/hsts-behind-proxy: Forbidden: related annotation nginx.org/hsts: must be set", 964 }, 965 msg: "invalid nginx.org/hsts-behind-proxy, related annotation nginx.org/hsts not set", 966 }, 967 968 { 969 annotations: map[string]string{ 970 "nginx.org/proxy-buffers": "8 8k", 971 }, 972 specServices: map[string]bool{}, 973 isPlus: false, 974 appProtectEnabled: false, 975 internalRoutesEnabled: false, 976 expectedErrors: nil, 977 msg: "valid nginx.org/proxy-buffers annotation", 978 }, 979 { 980 annotations: map[string]string{ 981 "nginx.org/proxy-buffers": "not_a_proxy_buffers_spec", 982 }, 983 specServices: map[string]bool{}, 984 isPlus: false, 985 appProtectEnabled: false, 986 internalRoutesEnabled: false, 987 expectedErrors: []string{ 988 `annotations.nginx.org/proxy-buffers: Invalid value: "not_a_proxy_buffers_spec": must be a proxy buffer spec`, 989 }, 990 msg: "invalid nginx.org/proxy-buffers annotation", 991 }, 992 993 { 994 annotations: map[string]string{ 995 "nginx.org/proxy-buffer-size": "16k", 996 }, 997 specServices: map[string]bool{}, 998 isPlus: false, 999 appProtectEnabled: false, 1000 internalRoutesEnabled: false, 1001 expectedErrors: nil, 1002 msg: "valid nginx.org/proxy-buffer-size annotation", 1003 }, 1004 { 1005 annotations: map[string]string{ 1006 "nginx.org/proxy-buffer-size": "not_a_size", 1007 }, 1008 specServices: map[string]bool{}, 1009 isPlus: false, 1010 appProtectEnabled: false, 1011 internalRoutesEnabled: false, 1012 expectedErrors: []string{ 1013 `annotations.nginx.org/proxy-buffer-size: Invalid value: "not_a_size": must be a size`, 1014 }, 1015 msg: "invalid nginx.org/proxy-buffer-size annotation", 1016 }, 1017 1018 { 1019 annotations: map[string]string{ 1020 "nginx.org/proxy-max-temp-file-size": "128M", 1021 }, 1022 specServices: map[string]bool{}, 1023 isPlus: false, 1024 appProtectEnabled: false, 1025 internalRoutesEnabled: false, 1026 expectedErrors: nil, 1027 msg: "valid nginx.org/proxy-max-temp-file-size annotation", 1028 }, 1029 { 1030 annotations: map[string]string{ 1031 "nginx.org/proxy-max-temp-file-size": "not_a_size", 1032 }, 1033 specServices: map[string]bool{}, 1034 isPlus: false, 1035 appProtectEnabled: false, 1036 internalRoutesEnabled: false, 1037 expectedErrors: []string{ 1038 `annotations.nginx.org/proxy-max-temp-file-size: Invalid value: "not_a_size": must be a size`, 1039 }, 1040 msg: "invalid nginx.org/proxy-max-temp-file-size annotation", 1041 }, 1042 1043 { 1044 annotations: map[string]string{ 1045 "nginx.org/upstream-zone-size": "512k", 1046 }, 1047 specServices: map[string]bool{}, 1048 isPlus: false, 1049 appProtectEnabled: false, 1050 internalRoutesEnabled: false, 1051 expectedErrors: nil, 1052 msg: "valid nginx.org/upstream-zone-size annotation", 1053 }, 1054 { 1055 annotations: map[string]string{ 1056 "nginx.org/upstream-zone-size": "not a size", 1057 }, 1058 specServices: map[string]bool{}, 1059 isPlus: false, 1060 appProtectEnabled: false, 1061 internalRoutesEnabled: false, 1062 expectedErrors: []string{ 1063 `annotations.nginx.org/upstream-zone-size: Invalid value: "not a size": must be a size`, 1064 }, 1065 msg: "invalid nginx.org/upstream-zone-size annotation", 1066 }, 1067 1068 { 1069 annotations: map[string]string{ 1070 "nginx.com/jwt-realm": "true", 1071 }, 1072 specServices: map[string]bool{}, 1073 isPlus: false, 1074 appProtectEnabled: false, 1075 internalRoutesEnabled: false, 1076 expectedErrors: []string{ 1077 "annotations.nginx.com/jwt-realm: Forbidden: annotation requires NGINX Plus", 1078 }, 1079 msg: "invalid nginx.com/jwt-realm annotation, nginx plus only", 1080 }, 1081 { 1082 annotations: map[string]string{ 1083 "nginx.com/jwt-realm": "my-jwt-realm", 1084 }, 1085 specServices: map[string]bool{}, 1086 isPlus: true, 1087 appProtectEnabled: false, 1088 internalRoutesEnabled: false, 1089 expectedErrors: nil, 1090 msg: "valid nginx.com/jwt-realm annotation", 1091 }, 1092 1093 { 1094 annotations: map[string]string{ 1095 "nginx.com/jwt-key": "true", 1096 }, 1097 specServices: map[string]bool{}, 1098 isPlus: false, 1099 appProtectEnabled: false, 1100 internalRoutesEnabled: false, 1101 expectedErrors: []string{ 1102 "annotations.nginx.com/jwt-key: Forbidden: annotation requires NGINX Plus", 1103 }, 1104 msg: "invalid nginx.com/jwt-key annotation, nginx plus only", 1105 }, 1106 { 1107 annotations: map[string]string{ 1108 "nginx.com/jwt-key": "my-jwk", 1109 }, 1110 specServices: map[string]bool{}, 1111 isPlus: true, 1112 appProtectEnabled: false, 1113 internalRoutesEnabled: false, 1114 expectedErrors: nil, 1115 msg: "valid nginx.com/jwt-key annotation", 1116 }, 1117 1118 { 1119 annotations: map[string]string{ 1120 "nginx.com/jwt-token": "true", 1121 }, 1122 specServices: map[string]bool{}, 1123 isPlus: false, 1124 appProtectEnabled: false, 1125 internalRoutesEnabled: false, 1126 expectedErrors: []string{ 1127 "annotations.nginx.com/jwt-token: Forbidden: annotation requires NGINX Plus", 1128 }, 1129 msg: "invalid nginx.com/jwt-token annotation, nginx plus only", 1130 }, 1131 { 1132 annotations: map[string]string{ 1133 "nginx.com/jwt-token": "$cookie_auth_token", 1134 }, 1135 specServices: map[string]bool{}, 1136 isPlus: true, 1137 appProtectEnabled: false, 1138 internalRoutesEnabled: false, 1139 expectedErrors: nil, 1140 msg: "valid nginx.com/jwt-token annotation", 1141 }, 1142 1143 { 1144 annotations: map[string]string{ 1145 "nginx.com/jwt-login-url": "true", 1146 }, 1147 specServices: map[string]bool{}, 1148 isPlus: false, 1149 appProtectEnabled: false, 1150 internalRoutesEnabled: false, 1151 expectedErrors: []string{ 1152 "annotations.nginx.com/jwt-login-url: Forbidden: annotation requires NGINX Plus", 1153 }, 1154 msg: "invalid nginx.com/jwt-login-url annotation, nginx plus only", 1155 }, 1156 { 1157 annotations: map[string]string{ 1158 "nginx.com/jwt-login-url": "https://login.example.com", 1159 }, 1160 specServices: map[string]bool{}, 1161 isPlus: true, 1162 appProtectEnabled: false, 1163 internalRoutesEnabled: false, 1164 expectedErrors: nil, 1165 msg: "valid nginx.com/jwt-login-url annotation", 1166 }, 1167 1168 { 1169 annotations: map[string]string{ 1170 "nginx.org/listen-ports": "80,8080,9090", 1171 }, 1172 specServices: map[string]bool{}, 1173 isPlus: false, 1174 appProtectEnabled: false, 1175 internalRoutesEnabled: false, 1176 expectedErrors: nil, 1177 msg: "valid nginx.org/listen-ports annotation", 1178 }, 1179 { 1180 annotations: map[string]string{ 1181 "nginx.org/listen-ports": "not_a_port_list", 1182 }, 1183 specServices: map[string]bool{}, 1184 isPlus: false, 1185 appProtectEnabled: false, 1186 internalRoutesEnabled: false, 1187 expectedErrors: []string{ 1188 `annotations.nginx.org/listen-ports: Invalid value: "not_a_port_list": must be a comma-separated list of port numbers`, 1189 }, 1190 msg: "invalid nginx.org/listen-ports annotation", 1191 }, 1192 1193 { 1194 annotations: map[string]string{ 1195 "nginx.org/listen-ports-ssl": "443,8443", 1196 }, 1197 specServices: map[string]bool{}, 1198 isPlus: false, 1199 appProtectEnabled: false, 1200 internalRoutesEnabled: false, 1201 expectedErrors: nil, 1202 msg: "valid nginx.org/listen-ports-ssl annotation", 1203 }, 1204 { 1205 annotations: map[string]string{ 1206 "nginx.org/listen-ports-ssl": "not_a_port_list", 1207 }, 1208 specServices: map[string]bool{}, 1209 isPlus: false, 1210 appProtectEnabled: false, 1211 internalRoutesEnabled: false, 1212 expectedErrors: []string{ 1213 `annotations.nginx.org/listen-ports-ssl: Invalid value: "not_a_port_list": must be a comma-separated list of port numbers`, 1214 }, 1215 msg: "invalid nginx.org/listen-ports-ssl annotation", 1216 }, 1217 1218 { 1219 annotations: map[string]string{ 1220 "nginx.org/keepalive": "1000", 1221 }, 1222 specServices: map[string]bool{}, 1223 isPlus: false, 1224 appProtectEnabled: false, 1225 internalRoutesEnabled: false, 1226 expectedErrors: nil, 1227 msg: "valid nginx.org/keepalive annotation", 1228 }, 1229 { 1230 annotations: map[string]string{ 1231 "nginx.org/keepalive": "not_a_number", 1232 }, 1233 specServices: map[string]bool{}, 1234 isPlus: false, 1235 appProtectEnabled: false, 1236 internalRoutesEnabled: false, 1237 expectedErrors: []string{ 1238 `annotations.nginx.org/keepalive: Invalid value: "not_a_number": must be an integer`, 1239 }, 1240 msg: "invalid nginx.org/keepalive annotation", 1241 }, 1242 1243 { 1244 annotations: map[string]string{ 1245 "nginx.org/max-fails": "5", 1246 }, 1247 specServices: map[string]bool{}, 1248 isPlus: false, 1249 appProtectEnabled: false, 1250 internalRoutesEnabled: false, 1251 expectedErrors: nil, 1252 msg: "valid nginx.org/max-fails annotation", 1253 }, 1254 { 1255 annotations: map[string]string{ 1256 "nginx.org/max-fails": "-100", 1257 }, 1258 specServices: map[string]bool{}, 1259 isPlus: false, 1260 appProtectEnabled: false, 1261 internalRoutesEnabled: false, 1262 expectedErrors: []string{ 1263 `annotations.nginx.org/max-fails: Invalid value: "-100": must be a non-negative integer`, 1264 }, 1265 msg: "invalid nginx.org/max-fails annotation, negative number", 1266 }, 1267 { 1268 annotations: map[string]string{ 1269 "nginx.org/max-fails": "not_a_number", 1270 }, 1271 specServices: map[string]bool{}, 1272 isPlus: false, 1273 appProtectEnabled: false, 1274 internalRoutesEnabled: false, 1275 expectedErrors: []string{ 1276 `annotations.nginx.org/max-fails: Invalid value: "not_a_number": must be a non-negative integer`, 1277 }, 1278 msg: "invalid nginx.org/max-fails annotation, not a number", 1279 }, 1280 1281 { 1282 annotations: map[string]string{ 1283 "nginx.org/max-conns": "10", 1284 }, 1285 specServices: map[string]bool{}, 1286 isPlus: false, 1287 appProtectEnabled: false, 1288 internalRoutesEnabled: false, 1289 expectedErrors: nil, 1290 msg: "valid nginx.org/max-conns annotation", 1291 }, 1292 { 1293 annotations: map[string]string{ 1294 "nginx.org/max-conns": "-100", 1295 }, 1296 specServices: map[string]bool{}, 1297 isPlus: false, 1298 appProtectEnabled: false, 1299 internalRoutesEnabled: false, 1300 expectedErrors: []string{ 1301 `annotations.nginx.org/max-conns: Invalid value: "-100": must be a non-negative integer`, 1302 }, 1303 msg: "invalid nginx.org/max-conns annotation, negative number", 1304 }, 1305 { 1306 annotations: map[string]string{ 1307 "nginx.org/max-conns": "not_a_number", 1308 }, 1309 specServices: map[string]bool{}, 1310 isPlus: false, 1311 appProtectEnabled: false, 1312 internalRoutesEnabled: false, 1313 expectedErrors: []string{ 1314 `annotations.nginx.org/max-conns: Invalid value: "not_a_number": must be a non-negative integer`, 1315 }, 1316 msg: "invalid nginx.org/max-conns annotation", 1317 }, 1318 1319 { 1320 annotations: map[string]string{ 1321 "nginx.org/fail-timeout": "10s", 1322 }, 1323 specServices: map[string]bool{}, 1324 isPlus: false, 1325 appProtectEnabled: false, 1326 internalRoutesEnabled: false, 1327 expectedErrors: nil, 1328 msg: "valid nginx.org/fail-timeout annotation", 1329 }, 1330 { 1331 annotations: map[string]string{ 1332 "nginx.org/fail-timeout": "not_a_time", 1333 }, 1334 specServices: map[string]bool{}, 1335 isPlus: false, 1336 appProtectEnabled: false, 1337 internalRoutesEnabled: false, 1338 expectedErrors: []string{ 1339 `annotations.nginx.org/fail-timeout: Invalid value: "not_a_time": must be a time`, 1340 }, 1341 msg: "invalid nginx.org/fail-timeout annotation", 1342 }, 1343 1344 { 1345 annotations: map[string]string{ 1346 "appprotect.f5.com/app-protect-enable": "true", 1347 }, 1348 specServices: map[string]bool{}, 1349 isPlus: true, 1350 appProtectEnabled: false, 1351 internalRoutesEnabled: false, 1352 expectedErrors: []string{ 1353 "annotations.appprotect.f5.com/app-protect-enable: Forbidden: annotation requires AppProtect", 1354 }, 1355 msg: "invalid appprotect.f5.com/app-protect-enable annotation, requires app protect", 1356 }, 1357 { 1358 annotations: map[string]string{ 1359 "appprotect.f5.com/app-protect-enable": "true", 1360 }, 1361 specServices: map[string]bool{}, 1362 isPlus: true, 1363 appProtectEnabled: true, 1364 internalRoutesEnabled: false, 1365 expectedErrors: nil, 1366 msg: "valid appprotect.f5.com/app-protect-enable annotation", 1367 }, 1368 { 1369 annotations: map[string]string{ 1370 "appprotect.f5.com/app-protect-enable": "not_a_boolean", 1371 }, 1372 specServices: map[string]bool{}, 1373 isPlus: true, 1374 appProtectEnabled: true, 1375 internalRoutesEnabled: false, 1376 expectedErrors: []string{ 1377 `annotations.appprotect.f5.com/app-protect-enable: Invalid value: "not_a_boolean": must be a boolean`, 1378 }, 1379 msg: "invalid appprotect.f5.com/app-protect-enable annotation", 1380 }, 1381 1382 { 1383 annotations: map[string]string{ 1384 "appprotect.f5.com/app-protect-security-log-enable": "true", 1385 }, 1386 specServices: map[string]bool{}, 1387 isPlus: true, 1388 appProtectEnabled: false, 1389 internalRoutesEnabled: false, 1390 expectedErrors: []string{ 1391 "annotations.appprotect.f5.com/app-protect-security-log-enable: Forbidden: annotation requires AppProtect", 1392 }, 1393 msg: "invalid appprotect.f5.com/app-protect-security-log-enable annotation, requires app protect", 1394 }, 1395 { 1396 annotations: map[string]string{ 1397 "appprotect.f5.com/app-protect-security-log-enable": "true", 1398 }, 1399 specServices: map[string]bool{}, 1400 isPlus: true, 1401 appProtectEnabled: true, 1402 internalRoutesEnabled: false, 1403 expectedErrors: nil, 1404 msg: "valid appprotect.f5.com/app-protect-security-log-enable annotation", 1405 }, 1406 { 1407 annotations: map[string]string{ 1408 "appprotect.f5.com/app-protect-security-log-enable": "not_a_boolean", 1409 }, 1410 specServices: map[string]bool{}, 1411 isPlus: true, 1412 appProtectEnabled: true, 1413 internalRoutesEnabled: false, 1414 expectedErrors: []string{ 1415 `annotations.appprotect.f5.com/app-protect-security-log-enable: Invalid value: "not_a_boolean": must be a boolean`, 1416 }, 1417 msg: "invalid appprotect.f5.com/app-protect-security-log-enable annotation", 1418 }, 1419 1420 { 1421 annotations: map[string]string{ 1422 "nsm.nginx.com/internal-route": "true", 1423 }, 1424 specServices: map[string]bool{}, 1425 isPlus: true, 1426 appProtectEnabled: false, 1427 internalRoutesEnabled: false, 1428 expectedErrors: []string{ 1429 "annotations.nsm.nginx.com/internal-route: Forbidden: annotation requires Internal Routes enabled", 1430 }, 1431 msg: "invalid nsm.nginx.com/internal-route annotation, requires internal routes", 1432 }, 1433 { 1434 annotations: map[string]string{ 1435 "nsm.nginx.com/internal-route": "true", 1436 }, 1437 specServices: map[string]bool{}, 1438 isPlus: true, 1439 appProtectEnabled: false, 1440 internalRoutesEnabled: true, 1441 expectedErrors: nil, 1442 msg: "valid nsm.nginx.com/internal-route annotation", 1443 }, 1444 { 1445 annotations: map[string]string{ 1446 "nsm.nginx.com/internal-route": "not_a_boolean", 1447 }, 1448 specServices: map[string]bool{}, 1449 isPlus: true, 1450 appProtectEnabled: false, 1451 internalRoutesEnabled: true, 1452 expectedErrors: []string{ 1453 `annotations.nsm.nginx.com/internal-route: Invalid value: "not_a_boolean": must be a boolean`, 1454 }, 1455 msg: "invalid nsm.nginx.com/internal-route annotation", 1456 }, 1457 1458 { 1459 annotations: map[string]string{ 1460 "nginx.org/websocket-services": "service-1", 1461 }, 1462 specServices: map[string]bool{ 1463 "service-1": true, 1464 }, 1465 isPlus: false, 1466 appProtectEnabled: false, 1467 internalRoutesEnabled: false, 1468 expectedErrors: nil, 1469 msg: "valid nginx.org/websocket-services annotation, single-value", 1470 }, 1471 { 1472 annotations: map[string]string{ 1473 "nginx.org/websocket-services": "service-1,service-2", 1474 }, 1475 specServices: map[string]bool{ 1476 "service-1": true, 1477 "service-2": true, 1478 }, 1479 isPlus: false, 1480 appProtectEnabled: false, 1481 internalRoutesEnabled: false, 1482 expectedErrors: nil, 1483 msg: "valid nginx.org/websocket-services annotation, multi-value", 1484 }, 1485 { 1486 annotations: map[string]string{ 1487 "nginx.org/websocket-services": "service-1,service-2", 1488 }, 1489 specServices: map[string]bool{ 1490 "service-1": true, 1491 }, 1492 isPlus: false, 1493 appProtectEnabled: false, 1494 internalRoutesEnabled: false, 1495 expectedErrors: []string{ 1496 `annotations.nginx.org/websocket-services: Invalid value: "service-1,service-2": must be a comma-separated list of services. The following services were not found: service-2`, 1497 }, 1498 msg: "invalid nginx.org/websocket-services annotation, service does not exist", 1499 }, 1500 1501 { 1502 annotations: map[string]string{ 1503 "nginx.org/ssl-services": "service-1", 1504 }, 1505 specServices: map[string]bool{ 1506 "service-1": true, 1507 }, 1508 isPlus: false, 1509 appProtectEnabled: false, 1510 internalRoutesEnabled: false, 1511 expectedErrors: nil, 1512 msg: "valid nginx.org/ssl-services annotation, single-value", 1513 }, 1514 { 1515 annotations: map[string]string{ 1516 "nginx.org/ssl-services": "service-1,service-2", 1517 }, 1518 specServices: map[string]bool{ 1519 "service-1": true, 1520 "service-2": true, 1521 }, 1522 isPlus: false, 1523 appProtectEnabled: false, 1524 internalRoutesEnabled: false, 1525 expectedErrors: nil, 1526 msg: "valid nginx.org/ssl-services annotation, multi-value", 1527 }, 1528 { 1529 annotations: map[string]string{ 1530 "nginx.org/ssl-services": "service-1,service-2", 1531 }, 1532 specServices: map[string]bool{ 1533 "service-1": true, 1534 }, 1535 isPlus: false, 1536 appProtectEnabled: false, 1537 internalRoutesEnabled: false, 1538 expectedErrors: []string{ 1539 `annotations.nginx.org/ssl-services: Invalid value: "service-1,service-2": must be a comma-separated list of services. The following services were not found: service-2`, 1540 }, 1541 msg: "invalid nginx.org/ssl-services annotation, service does not exist", 1542 }, 1543 1544 { 1545 annotations: map[string]string{ 1546 "nginx.org/grpc-services": "service-1", 1547 }, 1548 specServices: map[string]bool{ 1549 "service-1": true, 1550 }, 1551 isPlus: false, 1552 appProtectEnabled: false, 1553 internalRoutesEnabled: false, 1554 expectedErrors: nil, 1555 msg: "valid nginx.org/grpc-services annotation, single-value", 1556 }, 1557 { 1558 annotations: map[string]string{ 1559 "nginx.org/grpc-services": "service-1,service-2", 1560 }, 1561 specServices: map[string]bool{ 1562 "service-1": true, 1563 "service-2": true, 1564 }, 1565 isPlus: false, 1566 appProtectEnabled: false, 1567 internalRoutesEnabled: false, 1568 expectedErrors: nil, 1569 msg: "valid nginx.org/grpc-services annotation, multi-value", 1570 }, 1571 { 1572 annotations: map[string]string{ 1573 "nginx.org/grpc-services": "service-1,service-2", 1574 }, 1575 specServices: map[string]bool{ 1576 "service-1": true, 1577 }, 1578 isPlus: false, 1579 appProtectEnabled: false, 1580 internalRoutesEnabled: false, 1581 expectedErrors: []string{ 1582 `annotations.nginx.org/grpc-services: Invalid value: "service-1,service-2": must be a comma-separated list of services. The following services were not found: service-2`, 1583 }, 1584 msg: "invalid nginx.org/grpc-services annotation, service does not exist", 1585 }, 1586 1587 { 1588 annotations: map[string]string{ 1589 "nginx.org/rewrites": "serviceName=service-1 rewrite=rewrite-1", 1590 }, 1591 specServices: map[string]bool{}, 1592 isPlus: false, 1593 appProtectEnabled: false, 1594 internalRoutesEnabled: false, 1595 expectedErrors: nil, 1596 msg: "valid nginx.org/rewrites annotation, single-value", 1597 }, 1598 { 1599 annotations: map[string]string{ 1600 "nginx.org/rewrites": "serviceName=service-1 rewrite=rewrite-1;serviceName=service-2 rewrite=rewrite-2", 1601 }, 1602 specServices: map[string]bool{}, 1603 isPlus: false, 1604 appProtectEnabled: false, 1605 internalRoutesEnabled: false, 1606 expectedErrors: nil, 1607 msg: "valid nginx.org/rewrites annotation, multi-value", 1608 }, 1609 { 1610 annotations: map[string]string{ 1611 "nginx.org/rewrites": "not_a_rewrite", 1612 }, 1613 specServices: map[string]bool{}, 1614 isPlus: true, 1615 appProtectEnabled: false, 1616 internalRoutesEnabled: true, 1617 expectedErrors: []string{ 1618 `annotations.nginx.org/rewrites: Invalid value: "not_a_rewrite": must be a semicolon-separated list of rewrites`, 1619 }, 1620 msg: "invalid nginx.org/rewrites annotation", 1621 }, 1622 1623 { 1624 annotations: map[string]string{ 1625 "nginx.com/sticky-cookie-services": "true", 1626 }, 1627 specServices: map[string]bool{}, 1628 isPlus: false, 1629 appProtectEnabled: false, 1630 internalRoutesEnabled: false, 1631 expectedErrors: []string{ 1632 "annotations.nginx.com/sticky-cookie-services: Forbidden: annotation requires NGINX Plus", 1633 }, 1634 msg: "invalid nginx.com/sticky-cookie-services annotation, nginx plus only", 1635 }, 1636 { 1637 annotations: map[string]string{ 1638 "nginx.com/sticky-cookie-services": "serviceName=service-1 srv_id expires=1h path=/service-1", 1639 }, 1640 specServices: map[string]bool{}, 1641 isPlus: true, 1642 appProtectEnabled: false, 1643 internalRoutesEnabled: false, 1644 expectedErrors: nil, 1645 msg: "valid nginx.com/sticky-cookie-services annotation, single-value", 1646 }, 1647 { 1648 annotations: map[string]string{ 1649 "nginx.com/sticky-cookie-services": "serviceName=service-1 srv_id expires=1h path=/service-1;serviceName=service-2 srv_id expires=2h path=/service-2", 1650 }, 1651 specServices: map[string]bool{}, 1652 isPlus: true, 1653 appProtectEnabled: false, 1654 internalRoutesEnabled: false, 1655 expectedErrors: nil, 1656 msg: "valid nginx.com/sticky-cookie-services annotation, multi-value", 1657 }, 1658 { 1659 annotations: map[string]string{ 1660 "nginx.com/sticky-cookie-services": "not_a_rewrite", 1661 }, 1662 specServices: map[string]bool{}, 1663 isPlus: true, 1664 appProtectEnabled: false, 1665 internalRoutesEnabled: false, 1666 expectedErrors: []string{ 1667 `annotations.nginx.com/sticky-cookie-services: Invalid value: "not_a_rewrite": must be a semicolon-separated list of sticky services`, 1668 }, 1669 msg: "invalid nginx.com/sticky-cookie-services annotation", 1670 }, 1671 } 1672 1673 for _, test := range tests { 1674 t.Run(test.msg, func(t *testing.T) { 1675 allErrs := validateIngressAnnotations( 1676 test.annotations, 1677 test.specServices, 1678 test.isPlus, 1679 test.appProtectEnabled, 1680 test.internalRoutesEnabled, 1681 field.NewPath("annotations"), 1682 test.snippetsEnabled, 1683 ) 1684 assertion := assertErrors("validateIngressAnnotations()", test.msg, allErrs, test.expectedErrors) 1685 if assertion != "" { 1686 t.Error(assertion) 1687 } 1688 }) 1689 } 1690 } 1691 1692 func TestValidateIngressSpec(t *testing.T) { 1693 tests := []struct { 1694 spec *networking.IngressSpec 1695 expectedErrors []string 1696 msg string 1697 }{ 1698 { 1699 spec: &networking.IngressSpec{ 1700 Rules: []networking.IngressRule{ 1701 { 1702 Host: "foo.example.com", 1703 }, 1704 }, 1705 }, 1706 expectedErrors: nil, 1707 msg: "valid input", 1708 }, 1709 { 1710 spec: &networking.IngressSpec{ 1711 Rules: []networking.IngressRule{}, 1712 }, 1713 expectedErrors: []string{ 1714 "spec.rules: Required value", 1715 }, 1716 msg: "zero rules", 1717 }, 1718 { 1719 spec: &networking.IngressSpec{ 1720 Rules: []networking.IngressRule{ 1721 { 1722 Host: "", 1723 }, 1724 }, 1725 }, 1726 expectedErrors: []string{ 1727 "spec.rules[0].host: Required value", 1728 }, 1729 msg: "empty host", 1730 }, 1731 { 1732 spec: &networking.IngressSpec{ 1733 Rules: []networking.IngressRule{ 1734 { 1735 Host: "foo.example.com", 1736 }, 1737 { 1738 Host: "foo.example.com", 1739 }, 1740 }, 1741 }, 1742 expectedErrors: []string{ 1743 `spec.rules[1].host: Duplicate value: "foo.example.com"`, 1744 }, 1745 msg: "duplicated host", 1746 }, 1747 } 1748 1749 for _, test := range tests { 1750 allErrs := validateIngressSpec(test.spec, field.NewPath("spec")) 1751 assertion := assertErrors("validateIngressSpec()", test.msg, allErrs, test.expectedErrors) 1752 if assertion != "" { 1753 t.Error(assertion) 1754 } 1755 } 1756 } 1757 1758 func TestValidateMasterSpec(t *testing.T) { 1759 tests := []struct { 1760 spec *networking.IngressSpec 1761 expectedErrors []string 1762 msg string 1763 }{ 1764 { 1765 spec: &networking.IngressSpec{ 1766 Rules: []networking.IngressRule{ 1767 { 1768 Host: "foo.example.com", 1769 IngressRuleValue: networking.IngressRuleValue{ 1770 HTTP: &networking.HTTPIngressRuleValue{ 1771 Paths: []networking.HTTPIngressPath{}, 1772 }, 1773 }, 1774 }, 1775 }, 1776 }, 1777 expectedErrors: nil, 1778 msg: "valid input", 1779 }, 1780 { 1781 spec: &networking.IngressSpec{ 1782 Rules: []networking.IngressRule{ 1783 { 1784 Host: "foo.example.com", 1785 }, 1786 { 1787 Host: "bar.example.com", 1788 }, 1789 }, 1790 }, 1791 expectedErrors: []string{ 1792 "spec.rules: Too many: 2: must have at most 1 items", 1793 }, 1794 msg: "too many hosts", 1795 }, 1796 { 1797 spec: &networking.IngressSpec{ 1798 Rules: []networking.IngressRule{ 1799 { 1800 Host: "foo.example.com", 1801 IngressRuleValue: networking.IngressRuleValue{ 1802 HTTP: &networking.HTTPIngressRuleValue{ 1803 Paths: []networking.HTTPIngressPath{ 1804 { 1805 Path: "/", 1806 }, 1807 }, 1808 }, 1809 }, 1810 }, 1811 }, 1812 }, 1813 expectedErrors: []string{ 1814 "spec.rules[0].http.paths: Too many: 1: must have at most 0 items", 1815 }, 1816 msg: "too many paths", 1817 }, 1818 } 1819 1820 for _, test := range tests { 1821 allErrs := validateMasterSpec(test.spec, field.NewPath("spec")) 1822 assertion := assertErrors("validateMasterSpec()", test.msg, allErrs, test.expectedErrors) 1823 if assertion != "" { 1824 t.Error(assertion) 1825 } 1826 } 1827 } 1828 1829 func TestValidateMinionSpec(t *testing.T) { 1830 tests := []struct { 1831 spec *networking.IngressSpec 1832 expectedErrors []string 1833 msg string 1834 }{ 1835 { 1836 spec: &networking.IngressSpec{ 1837 Rules: []networking.IngressRule{ 1838 { 1839 Host: "foo.example.com", 1840 IngressRuleValue: networking.IngressRuleValue{ 1841 HTTP: &networking.HTTPIngressRuleValue{ 1842 Paths: []networking.HTTPIngressPath{ 1843 { 1844 Path: "/", 1845 }, 1846 }, 1847 }, 1848 }, 1849 }, 1850 }, 1851 }, 1852 expectedErrors: nil, 1853 msg: "valid input", 1854 }, 1855 { 1856 spec: &networking.IngressSpec{ 1857 Rules: []networking.IngressRule{ 1858 { 1859 Host: "foo.example.com", 1860 }, 1861 { 1862 Host: "bar.example.com", 1863 }, 1864 }, 1865 }, 1866 expectedErrors: []string{ 1867 "spec.rules: Too many: 2: must have at most 1 items", 1868 }, 1869 msg: "too many hosts", 1870 }, 1871 { 1872 spec: &networking.IngressSpec{ 1873 Rules: []networking.IngressRule{ 1874 { 1875 Host: "foo.example.com", 1876 IngressRuleValue: networking.IngressRuleValue{ 1877 HTTP: &networking.HTTPIngressRuleValue{ 1878 Paths: []networking.HTTPIngressPath{}, 1879 }, 1880 }, 1881 }, 1882 }, 1883 }, 1884 expectedErrors: []string{ 1885 "spec.rules[0].http.paths: Required value: must include at least one path", 1886 }, 1887 msg: "too few paths", 1888 }, 1889 { 1890 spec: &networking.IngressSpec{ 1891 TLS: []networking.IngressTLS{ 1892 { 1893 Hosts: []string{"foo.example.com"}, 1894 }, 1895 }, 1896 Rules: []networking.IngressRule{ 1897 { 1898 Host: "foo.example.com", 1899 IngressRuleValue: networking.IngressRuleValue{ 1900 HTTP: &networking.HTTPIngressRuleValue{ 1901 Paths: []networking.HTTPIngressPath{ 1902 { 1903 Path: "/", 1904 }, 1905 }, 1906 }, 1907 }, 1908 }, 1909 }, 1910 }, 1911 expectedErrors: []string{ 1912 "spec.tls: Too many: 1: must have at most 0 items", 1913 }, 1914 msg: "tls is forbidden", 1915 }, 1916 } 1917 1918 for _, test := range tests { 1919 allErrs := validateMinionSpec(test.spec, field.NewPath("spec")) 1920 assertion := assertErrors("validateMinionSpec()", test.msg, allErrs, test.expectedErrors) 1921 if assertion != "" { 1922 t.Error(assertion) 1923 } 1924 } 1925 } 1926 1927 func assertErrors(funcName string, msg string, allErrs field.ErrorList, expectedErrors []string) string { 1928 errors := errorListToStrings(allErrs) 1929 if !reflect.DeepEqual(errors, expectedErrors) { 1930 result := strings.Join(errors, "\n") 1931 expected := strings.Join(expectedErrors, "\n") 1932 1933 return fmt.Sprintf("%s returned \n%s \nbut expected \n%s \nfor the case of %s", funcName, result, expected, msg) 1934 } 1935 1936 return "" 1937 } 1938 1939 func errorListToStrings(list field.ErrorList) []string { 1940 var result []string 1941 1942 for _, e := range list { 1943 result = append(result, e.Error()) 1944 } 1945 1946 return result 1947 }