k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/certificates/validation/validation_test.go (about) 1 /* 2 Copyright 2020 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 validation 18 19 import ( 20 "crypto/ed25519" 21 "crypto/rand" 22 "crypto/x509" 23 "crypto/x509/pkix" 24 "encoding/pem" 25 "fmt" 26 "math/big" 27 mathrand "math/rand" 28 "reflect" 29 "regexp" 30 "strings" 31 "testing" 32 "time" 33 34 "github.com/google/go-cmp/cmp" 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/apimachinery/pkg/util/sets" 37 "k8s.io/apimachinery/pkg/util/validation/field" 38 "k8s.io/client-go/util/certificate/csr" 39 capi "k8s.io/kubernetes/pkg/apis/certificates" 40 "k8s.io/kubernetes/pkg/apis/core" 41 "k8s.io/utils/pointer" 42 ) 43 44 var ( 45 validObjectMeta = metav1.ObjectMeta{Name: "testcsr"} 46 validSignerName = "example.com/valid-name" 47 validUsages = []capi.KeyUsage{capi.UsageKeyEncipherment} 48 ) 49 50 func TestValidateCertificateSigningRequestCreate(t *testing.T) { 51 specPath := field.NewPath("spec") 52 // maxLengthSignerName is a signerName that is of maximum length, utilising 53 // the max length specifications defined in validation.go. 54 // It is of the form <fqdn(253)>/<resource-namespace(63)>.<resource-name(253)> 55 maxLengthFQDN := fmt.Sprintf("%s.%s.%s.%s", repeatString("a", 63), repeatString("a", 63), repeatString("a", 63), repeatString("a", 61)) 56 maxLengthSignerName := fmt.Sprintf("%s/%s.%s", maxLengthFQDN, repeatString("a", 63), repeatString("a", 253)) 57 tests := map[string]struct { 58 csr capi.CertificateSigningRequest 59 errs field.ErrorList 60 }{ 61 "CSR with empty request data should fail": { 62 csr: capi.CertificateSigningRequest{ 63 ObjectMeta: validObjectMeta, 64 Spec: capi.CertificateSigningRequestSpec{ 65 Usages: validUsages, 66 SignerName: validSignerName, 67 }, 68 }, 69 errs: field.ErrorList{ 70 field.Invalid(specPath.Child("request"), []byte(nil), "PEM block type must be CERTIFICATE REQUEST"), 71 }, 72 }, 73 "CSR with invalid request data should fail": { 74 csr: capi.CertificateSigningRequest{ 75 ObjectMeta: validObjectMeta, 76 Spec: capi.CertificateSigningRequestSpec{ 77 Usages: validUsages, 78 SignerName: validSignerName, 79 Request: []byte("invalid data"), 80 }, 81 }, 82 errs: field.ErrorList{ 83 field.Invalid(specPath.Child("request"), []byte("invalid data"), "PEM block type must be CERTIFICATE REQUEST"), 84 }, 85 }, 86 "CSR with no usages should fail": { 87 csr: capi.CertificateSigningRequest{ 88 ObjectMeta: validObjectMeta, 89 Spec: capi.CertificateSigningRequestSpec{ 90 SignerName: validSignerName, 91 Request: newCSRPEM(t), 92 }, 93 }, 94 errs: field.ErrorList{ 95 field.Required(specPath.Child("usages"), ""), 96 }, 97 }, 98 "CSR with no signerName set should fail": { 99 csr: capi.CertificateSigningRequest{ 100 ObjectMeta: validObjectMeta, 101 Spec: capi.CertificateSigningRequestSpec{ 102 Usages: validUsages, 103 Request: newCSRPEM(t), 104 }, 105 }, 106 errs: field.ErrorList{ 107 field.Required(specPath.Child("signerName"), ""), 108 }, 109 }, 110 "signerName contains no '/'": { 111 csr: capi.CertificateSigningRequest{ 112 ObjectMeta: validObjectMeta, 113 Spec: capi.CertificateSigningRequestSpec{ 114 Usages: validUsages, 115 Request: newCSRPEM(t), 116 SignerName: "an-invalid-signer-name", 117 }, 118 }, 119 errs: field.ErrorList{ 120 field.Invalid(specPath.Child("signerName"), "an-invalid-signer-name", "must be a fully qualified domain and path of the form 'example.com/signer-name'"), 121 }, 122 }, 123 "signerName contains two '/'": { 124 csr: capi.CertificateSigningRequest{ 125 ObjectMeta: validObjectMeta, 126 Spec: capi.CertificateSigningRequestSpec{ 127 Usages: validUsages, 128 Request: newCSRPEM(t), 129 SignerName: "an-invalid-signer-name.com/something/else", 130 }, 131 }, 132 errs: field.ErrorList{ 133 field.Invalid(specPath.Child("signerName"), "an-invalid-signer-name.com/something/else", "must be a fully qualified domain and path of the form 'example.com/signer-name'"), 134 }, 135 }, 136 "signerName domain component is not fully qualified": { 137 csr: capi.CertificateSigningRequest{ 138 ObjectMeta: validObjectMeta, 139 Spec: capi.CertificateSigningRequestSpec{ 140 Usages: validUsages, 141 Request: newCSRPEM(t), 142 SignerName: "example/some-signer-name", 143 }, 144 }, 145 errs: field.ErrorList{ 146 field.Invalid(specPath.Child("signerName"), "example", "should be a domain with at least two segments separated by dots"), 147 }, 148 }, 149 "signerName path component is empty": { 150 csr: capi.CertificateSigningRequest{ 151 ObjectMeta: validObjectMeta, 152 Spec: capi.CertificateSigningRequestSpec{ 153 Usages: validUsages, 154 Request: newCSRPEM(t), 155 SignerName: "example.com/", 156 }, 157 }, 158 errs: field.ErrorList{ 159 field.Invalid(specPath.Child("signerName"), "", `validating label "": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), 160 }, 161 }, 162 "signerName path component ends with a symbol": { 163 csr: capi.CertificateSigningRequest{ 164 ObjectMeta: validObjectMeta, 165 Spec: capi.CertificateSigningRequestSpec{ 166 Usages: validUsages, 167 Request: newCSRPEM(t), 168 SignerName: "example.com/something-", 169 }, 170 }, 171 errs: field.ErrorList{ 172 field.Invalid(specPath.Child("signerName"), "something-", `validating label "something-": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), 173 }, 174 }, 175 "signerName path component is a symbol": { 176 csr: capi.CertificateSigningRequest{ 177 ObjectMeta: validObjectMeta, 178 Spec: capi.CertificateSigningRequestSpec{ 179 Usages: validUsages, 180 Request: newCSRPEM(t), 181 SignerName: "example.com/-", 182 }, 183 }, 184 errs: field.ErrorList{ 185 field.Invalid(specPath.Child("signerName"), "-", `validating label "-": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), 186 }, 187 }, 188 "signerName path component contains no '.' but is valid": { 189 csr: capi.CertificateSigningRequest{ 190 ObjectMeta: validObjectMeta, 191 Spec: capi.CertificateSigningRequestSpec{ 192 Usages: validUsages, 193 Request: newCSRPEM(t), 194 SignerName: "example.com/some-signer-name", 195 }, 196 }, 197 }, 198 "signerName with a total length greater than 571 characters should be rejected": { 199 csr: capi.CertificateSigningRequest{ 200 ObjectMeta: validObjectMeta, 201 Spec: capi.CertificateSigningRequestSpec{ 202 Usages: validUsages, 203 Request: newCSRPEM(t), 204 // this string is longer than the max signerName limit (635 chars) 205 SignerName: maxLengthSignerName + ".toolong", 206 }, 207 }, 208 errs: field.ErrorList{ 209 field.TooLong(specPath.Child("signerName"), maxLengthSignerName+".toolong", len(maxLengthSignerName)), 210 }, 211 }, 212 "signerName with a fqdn greater than 253 characters should be rejected": { 213 csr: capi.CertificateSigningRequest{ 214 ObjectMeta: validObjectMeta, 215 Spec: capi.CertificateSigningRequestSpec{ 216 Usages: validUsages, 217 Request: newCSRPEM(t), 218 // this string is longer than the max signerName limit (635 chars) 219 SignerName: fmt.Sprintf("%s.extra/valid-path", maxLengthFQDN), 220 }, 221 }, 222 errs: field.ErrorList{ 223 field.TooLong(specPath.Child("signerName"), fmt.Sprintf("%s.extra", maxLengthFQDN), len(maxLengthFQDN)), 224 }, 225 }, 226 "signerName can have a longer path if the domain component is less than the max length": { 227 csr: capi.CertificateSigningRequest{ 228 ObjectMeta: validObjectMeta, 229 Spec: capi.CertificateSigningRequestSpec{ 230 Usages: validUsages, 231 Request: newCSRPEM(t), 232 SignerName: fmt.Sprintf("abc.io/%s.%s", repeatString("a", 253), repeatString("a", 253)), 233 }, 234 }, 235 }, 236 "signerName with a domain label greater than 63 characters will fail": { 237 csr: capi.CertificateSigningRequest{ 238 ObjectMeta: validObjectMeta, 239 Spec: capi.CertificateSigningRequestSpec{ 240 Usages: validUsages, 241 Request: newCSRPEM(t), 242 SignerName: fmt.Sprintf("%s.example.io/valid-path", repeatString("a", 66)), 243 }, 244 }, 245 errs: field.ErrorList{ 246 field.Invalid(specPath.Child("signerName"), fmt.Sprintf("%s.example.io", repeatString("a", 66)), fmt.Sprintf(`validating label "%s": must be no more than 63 characters`, repeatString("a", 66))), 247 }, 248 }, 249 "signerName of max length in format <fully-qualified-domain-name>/<resource-namespace>.<resource-name> is valid": { 250 // ensure signerName is of the form domain.com/something and up to 571 characters. 251 // This length and format is specified to accommodate signerNames like: 252 // <fqdn>/<resource-namespace>.<resource-name>. 253 // The max length of a FQDN is 253 characters (DNS1123Subdomain max length) 254 // The max length of a namespace name is 63 characters (DNS1123Label max length) 255 // The max length of a resource name is 253 characters (DNS1123Subdomain max length) 256 // We then add an additional 2 characters to account for the one '.' and one '/'. 257 csr: capi.CertificateSigningRequest{ 258 ObjectMeta: validObjectMeta, 259 Spec: capi.CertificateSigningRequestSpec{ 260 Usages: validUsages, 261 Request: newCSRPEM(t), 262 SignerName: maxLengthSignerName, 263 }, 264 }, 265 }, 266 "negative duration": { 267 csr: capi.CertificateSigningRequest{ 268 ObjectMeta: validObjectMeta, 269 Spec: capi.CertificateSigningRequestSpec{ 270 Usages: validUsages, 271 Request: newCSRPEM(t), 272 SignerName: validSignerName, 273 ExpirationSeconds: pointer.Int32(-1), 274 }, 275 }, 276 errs: field.ErrorList{ 277 field.Invalid(specPath.Child("expirationSeconds"), int32(-1), "may not specify a duration less than 600 seconds (10 minutes)"), 278 }, 279 }, 280 "zero duration": { 281 csr: capi.CertificateSigningRequest{ 282 ObjectMeta: validObjectMeta, 283 Spec: capi.CertificateSigningRequestSpec{ 284 Usages: validUsages, 285 Request: newCSRPEM(t), 286 SignerName: validSignerName, 287 ExpirationSeconds: pointer.Int32(0), 288 }, 289 }, 290 errs: field.ErrorList{ 291 field.Invalid(specPath.Child("expirationSeconds"), int32(0), "may not specify a duration less than 600 seconds (10 minutes)"), 292 }, 293 }, 294 "one duration": { 295 csr: capi.CertificateSigningRequest{ 296 ObjectMeta: validObjectMeta, 297 Spec: capi.CertificateSigningRequestSpec{ 298 Usages: validUsages, 299 Request: newCSRPEM(t), 300 SignerName: validSignerName, 301 ExpirationSeconds: pointer.Int32(1), 302 }, 303 }, 304 errs: field.ErrorList{ 305 field.Invalid(specPath.Child("expirationSeconds"), int32(1), "may not specify a duration less than 600 seconds (10 minutes)"), 306 }, 307 }, 308 "too short duration": { 309 csr: capi.CertificateSigningRequest{ 310 ObjectMeta: validObjectMeta, 311 Spec: capi.CertificateSigningRequestSpec{ 312 Usages: validUsages, 313 Request: newCSRPEM(t), 314 SignerName: validSignerName, 315 ExpirationSeconds: csr.DurationToExpirationSeconds(time.Minute), 316 }, 317 }, 318 errs: field.ErrorList{ 319 field.Invalid(specPath.Child("expirationSeconds"), *csr.DurationToExpirationSeconds(time.Minute), "may not specify a duration less than 600 seconds (10 minutes)"), 320 }, 321 }, 322 "valid duration": { 323 csr: capi.CertificateSigningRequest{ 324 ObjectMeta: validObjectMeta, 325 Spec: capi.CertificateSigningRequestSpec{ 326 Usages: validUsages, 327 Request: newCSRPEM(t), 328 SignerName: validSignerName, 329 ExpirationSeconds: csr.DurationToExpirationSeconds(10 * time.Minute), 330 }, 331 }, 332 }, 333 "missing usages": { 334 csr: capi.CertificateSigningRequest{ 335 ObjectMeta: validObjectMeta, 336 Spec: capi.CertificateSigningRequestSpec{ 337 Usages: []capi.KeyUsage{}, 338 Request: newCSRPEM(t), 339 SignerName: validSignerName, 340 }, 341 }, 342 errs: field.ErrorList{ 343 field.Required(specPath.Child("usages"), ""), 344 }, 345 }, 346 "unknown and duplicate usages": { 347 csr: capi.CertificateSigningRequest{ 348 ObjectMeta: validObjectMeta, 349 Spec: capi.CertificateSigningRequestSpec{ 350 Usages: []capi.KeyUsage{"unknown", "unknown"}, 351 Request: newCSRPEM(t), 352 SignerName: validSignerName, 353 }, 354 }, 355 errs: field.ErrorList{ 356 field.NotSupported(specPath.Child("usages").Index(0), capi.KeyUsage("unknown"), allValidUsages.List()), 357 field.NotSupported(specPath.Child("usages").Index(1), capi.KeyUsage("unknown"), allValidUsages.List()), 358 field.Duplicate(specPath.Child("usages").Index(1), capi.KeyUsage("unknown")), 359 }, 360 }, 361 } 362 for name, test := range tests { 363 t.Run(name, func(t *testing.T) { 364 el := ValidateCertificateSigningRequestCreate(&test.csr) 365 if !reflect.DeepEqual(el, test.errs) { 366 t.Errorf("returned and expected errors did not match - expected\n%v\nbut got\n%v", test.errs.ToAggregate(), el.ToAggregate()) 367 } 368 }) 369 } 370 } 371 372 func repeatString(s string, num int) string { 373 l := make([]string, num) 374 for i := 0; i < num; i++ { 375 l[i] = s 376 } 377 return strings.Join(l, "") 378 } 379 380 func newCSRPEM(t *testing.T) []byte { 381 template := &x509.CertificateRequest{ 382 Subject: pkix.Name{ 383 Organization: []string{"testing-org"}, 384 }, 385 } 386 387 _, key, err := ed25519.GenerateKey(rand.Reader) 388 if err != nil { 389 t.Fatal(err) 390 } 391 392 csrDER, err := x509.CreateCertificateRequest(rand.Reader, template, key) 393 if err != nil { 394 t.Fatal(err) 395 } 396 397 csrPemBlock := &pem.Block{ 398 Type: "CERTIFICATE REQUEST", 399 Bytes: csrDER, 400 } 401 402 p := pem.EncodeToMemory(csrPemBlock) 403 if p == nil { 404 t.Fatal("invalid pem block") 405 } 406 407 return p 408 } 409 410 func Test_getValidationOptions(t *testing.T) { 411 tests := []struct { 412 name string 413 newCSR *capi.CertificateSigningRequest 414 oldCSR *capi.CertificateSigningRequest 415 want certificateValidationOptions 416 }{{ 417 name: "strict create", 418 oldCSR: nil, 419 want: certificateValidationOptions{}, 420 }, { 421 name: "strict update", 422 oldCSR: &capi.CertificateSigningRequest{}, 423 want: certificateValidationOptions{}, 424 }, { 425 name: "compatible update, approved+denied", 426 oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ 427 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateDenied}}, 428 }}, 429 want: certificateValidationOptions{ 430 allowBothApprovedAndDenied: true, 431 }, 432 }, { 433 name: "compatible update, legacy signerName", 434 oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{SignerName: capi.LegacyUnknownSignerName}}, 435 want: certificateValidationOptions{ 436 allowLegacySignerName: true, 437 }, 438 }, { 439 name: "compatible update, duplicate condition types", 440 oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ 441 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateApproved}}, 442 }}, 443 want: certificateValidationOptions{ 444 allowDuplicateConditionTypes: true, 445 }, 446 }, { 447 name: "compatible update, empty condition types", 448 oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ 449 Conditions: []capi.CertificateSigningRequestCondition{{}}, 450 }}, 451 want: certificateValidationOptions{ 452 allowEmptyConditionType: true, 453 }, 454 }, { 455 name: "compatible update, no diff to certificate", 456 newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ 457 Certificate: validCertificate, 458 }}, 459 oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ 460 Certificate: validCertificate, 461 }}, 462 want: certificateValidationOptions{ 463 allowArbitraryCertificate: true, 464 }, 465 }, { 466 name: "compatible update, existing invalid certificate", 467 newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ 468 Certificate: []byte(`new - no PEM blocks`), 469 }}, 470 oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ 471 Certificate: []byte(`old - no PEM blocks`), 472 }}, 473 want: certificateValidationOptions{ 474 allowArbitraryCertificate: true, 475 }, 476 }, { 477 name: "compatible update, existing unknown usages", 478 oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"unknown"}}}, 479 want: certificateValidationOptions{ 480 allowUnknownUsages: true, 481 }, 482 }, { 483 name: "compatible update, existing duplicate usages", 484 oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"any", "any"}}}, 485 want: certificateValidationOptions{ 486 allowDuplicateUsages: true, 487 }, 488 }} 489 for _, tt := range tests { 490 t.Run(tt.name, func(t *testing.T) { 491 if got := getValidationOptions(tt.newCSR, tt.oldCSR); !reflect.DeepEqual(got, tt.want) { 492 t.Errorf("got %#v\nwant %#v", got, tt.want) 493 } 494 }) 495 } 496 } 497 498 func TestValidateCertificateSigningRequestUpdate(t *testing.T) { 499 validUpdateMeta := validObjectMeta 500 validUpdateMeta.ResourceVersion = "1" 501 502 validUpdateMetaWithFinalizers := validUpdateMeta 503 validUpdateMetaWithFinalizers.Finalizers = []string{"foo"} 504 505 validSpec := capi.CertificateSigningRequestSpec{ 506 Usages: validUsages, 507 Request: newCSRPEM(t), 508 SignerName: "example.com/something", 509 } 510 511 tests := []struct { 512 name string 513 newCSR *capi.CertificateSigningRequest 514 oldCSR *capi.CertificateSigningRequest 515 errs []string 516 }{{ 517 name: "no-op", 518 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 519 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 520 }, { 521 name: "finalizer change with invalid status", 522 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, 523 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, 524 }, { 525 name: "add Approved condition", 526 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 527 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 528 }}, 529 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 530 errs: []string{ 531 `status.conditions: Forbidden: updates may not add a condition of type "Approved"`, 532 }, 533 }, { 534 name: "remove Approved condition", 535 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 536 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 537 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 538 }}, 539 errs: []string{ 540 `status.conditions: Forbidden: updates may not remove a condition of type "Approved"`, 541 }, 542 }, { 543 name: "add Denied condition", 544 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 545 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, 546 }}, 547 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 548 errs: []string{ 549 `status.conditions: Forbidden: updates may not add a condition of type "Denied"`, 550 }, 551 }, { 552 name: "remove Denied condition", 553 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 554 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 555 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, 556 }}, 557 errs: []string{ 558 `status.conditions: Forbidden: updates may not remove a condition of type "Denied"`, 559 }, 560 }, { 561 name: "add Failed condition", 562 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 563 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, 564 }}, 565 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 566 errs: []string{}, 567 }, { 568 name: "remove Failed condition", 569 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 570 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 571 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, 572 }}, 573 errs: []string{ 574 `status.conditions: Forbidden: updates may not remove a condition of type "Failed"`, 575 }, 576 }, { 577 name: "set certificate", 578 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 579 Certificate: validCertificate, 580 }}, 581 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 582 errs: []string{ 583 `status.certificate: Forbidden: updates may not set certificate content`, 584 }, 585 }} 586 587 for _, tt := range tests { 588 t.Run(tt.name, func(t *testing.T) { 589 gotErrs := sets.NewString() 590 for _, err := range ValidateCertificateSigningRequestUpdate(tt.newCSR, tt.oldCSR) { 591 gotErrs.Insert(err.Error()) 592 } 593 wantErrs := sets.NewString(tt.errs...) 594 for _, missing := range wantErrs.Difference(gotErrs).List() { 595 t.Errorf("missing expected error: %s", missing) 596 } 597 for _, unexpected := range gotErrs.Difference(wantErrs).List() { 598 t.Errorf("unexpected error: %s", unexpected) 599 } 600 }) 601 } 602 } 603 604 func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) { 605 validUpdateMeta := validObjectMeta 606 validUpdateMeta.ResourceVersion = "1" 607 608 validUpdateMetaWithFinalizers := validUpdateMeta 609 validUpdateMetaWithFinalizers.Finalizers = []string{"foo"} 610 611 validSpec := capi.CertificateSigningRequestSpec{ 612 Usages: validUsages, 613 Request: newCSRPEM(t), 614 SignerName: "example.com/something", 615 } 616 617 tests := []struct { 618 name string 619 newCSR *capi.CertificateSigningRequest 620 oldCSR *capi.CertificateSigningRequest 621 errs []string 622 }{{ 623 name: "no-op", 624 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 625 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 626 }, { 627 name: "finalizer change with invalid status", 628 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, 629 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, 630 }, { 631 name: "finalizer change with duplicate and unknown usages", 632 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: capi.CertificateSigningRequestSpec{ 633 Usages: []capi.KeyUsage{"unknown", "unknown"}, 634 Request: newCSRPEM(t), 635 SignerName: validSignerName, 636 }}, 637 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: capi.CertificateSigningRequestSpec{ 638 Usages: []capi.KeyUsage{"unknown", "unknown"}, 639 Request: newCSRPEM(t), 640 SignerName: validSignerName, 641 }}, 642 }, { 643 name: "add Approved condition", 644 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 645 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 646 }}, 647 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 648 errs: []string{ 649 `status.conditions: Forbidden: updates may not add a condition of type "Approved"`, 650 }, 651 }, { 652 name: "remove Approved condition", 653 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 654 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 655 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 656 }}, 657 errs: []string{ 658 `status.conditions: Forbidden: updates may not remove a condition of type "Approved"`, 659 }, 660 }, { 661 name: "add Denied condition", 662 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 663 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, 664 }}, 665 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 666 errs: []string{ 667 `status.conditions: Forbidden: updates may not add a condition of type "Denied"`, 668 }, 669 }, { 670 name: "remove Denied condition", 671 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 672 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 673 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, 674 }}, 675 errs: []string{ 676 `status.conditions: Forbidden: updates may not remove a condition of type "Denied"`, 677 }, 678 }, { 679 name: "add Failed condition", 680 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 681 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, 682 }}, 683 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 684 errs: []string{}, 685 }, { 686 name: "remove Failed condition", 687 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 688 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 689 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, 690 }}, 691 errs: []string{ 692 `status.conditions: Forbidden: updates may not remove a condition of type "Failed"`, 693 }, 694 }, { 695 name: "set valid certificate", 696 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 697 Certificate: validCertificate, 698 }}, 699 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 700 errs: []string{}, 701 }, { 702 name: "set invalid certificate", 703 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 704 Certificate: invalidCertificateNoPEM, 705 }}, 706 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 707 errs: []string{ 708 `status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`, 709 }, 710 }, { 711 name: "reset certificate", 712 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 713 Certificate: invalidCertificateNonCertificatePEM, 714 }}, 715 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 716 Certificate: invalidCertificateNoPEM, 717 }}, 718 errs: []string{ 719 `status.certificate: Forbidden: updates may not modify existing certificate content`, 720 }, 721 }} 722 723 for _, tt := range tests { 724 t.Run(tt.name, func(t *testing.T) { 725 gotErrs := sets.NewString() 726 for _, err := range ValidateCertificateSigningRequestStatusUpdate(tt.newCSR, tt.oldCSR) { 727 gotErrs.Insert(err.Error()) 728 } 729 wantErrs := sets.NewString(tt.errs...) 730 for _, missing := range wantErrs.Difference(gotErrs).List() { 731 t.Errorf("missing expected error: %s", missing) 732 } 733 for _, unexpected := range gotErrs.Difference(wantErrs).List() { 734 t.Errorf("unexpected error: %s", unexpected) 735 } 736 }) 737 } 738 } 739 740 func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) { 741 validUpdateMeta := validObjectMeta 742 validUpdateMeta.ResourceVersion = "1" 743 744 validUpdateMetaWithFinalizers := validUpdateMeta 745 validUpdateMetaWithFinalizers.Finalizers = []string{"foo"} 746 747 validSpec := capi.CertificateSigningRequestSpec{ 748 Usages: validUsages, 749 Request: newCSRPEM(t), 750 SignerName: "example.com/something", 751 } 752 753 tests := []struct { 754 name string 755 newCSR *capi.CertificateSigningRequest 756 oldCSR *capi.CertificateSigningRequest 757 errs []string 758 }{{ 759 name: "no-op", 760 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 761 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 762 }, { 763 name: "finalizer change with invalid certificate", 764 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, 765 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}}, 766 }, { 767 name: "add Approved condition", 768 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 769 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 770 }}, 771 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 772 }, { 773 name: "remove Approved condition", 774 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 775 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 776 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 777 }}, 778 errs: []string{ 779 `status.conditions: Forbidden: updates may not remove a condition of type "Approved"`, 780 }, 781 }, { 782 name: "add Denied condition", 783 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 784 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, 785 }}, 786 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 787 }, { 788 name: "remove Denied condition", 789 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 790 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 791 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, 792 }}, 793 errs: []string{ 794 `status.conditions: Forbidden: updates may not remove a condition of type "Denied"`, 795 }, 796 }, { 797 name: "add Failed condition", 798 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 799 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, 800 }}, 801 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 802 errs: []string{}, 803 }, { 804 name: "remove Failed condition", 805 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec}, 806 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 807 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, 808 }}, 809 errs: []string{ 810 `status.conditions: Forbidden: updates may not remove a condition of type "Failed"`, 811 }, 812 }, { 813 name: "set certificate", 814 newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ 815 Certificate: validCertificate, 816 }}, 817 oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, 818 errs: []string{ 819 `status.certificate: Forbidden: updates may not set certificate content`, 820 }, 821 }} 822 823 for _, tt := range tests { 824 t.Run(tt.name, func(t *testing.T) { 825 gotErrs := sets.NewString() 826 for _, err := range ValidateCertificateSigningRequestApprovalUpdate(tt.newCSR, tt.oldCSR) { 827 gotErrs.Insert(err.Error()) 828 } 829 wantErrs := sets.NewString(tt.errs...) 830 for _, missing := range wantErrs.Difference(gotErrs).List() { 831 t.Errorf("missing expected error: %s", missing) 832 } 833 for _, unexpected := range gotErrs.Difference(wantErrs).List() { 834 t.Errorf("unexpected error: %s", unexpected) 835 } 836 }) 837 } 838 } 839 840 // Test_validateCertificateSigningRequestOptions verifies validation options are effective in tolerating specific aspects of CSRs 841 func Test_validateCertificateSigningRequestOptions(t *testing.T) { 842 validSpec := capi.CertificateSigningRequestSpec{ 843 Usages: validUsages, 844 Request: newCSRPEM(t), 845 SignerName: "example.com/something", 846 } 847 848 tests := []struct { 849 // testcase name 850 name string 851 852 // csr being validated 853 csr *capi.CertificateSigningRequest 854 855 // options that allow the csr to pass validation 856 lenientOpts certificateValidationOptions 857 858 // regexes matching expected errors when validating strictly 859 strictRegexes []regexp.Regexp 860 861 // expected errors (after filtering out errors matched by strictRegexes) when validating strictly 862 strictErrs []string 863 }{ 864 // valid strict cases 865 { 866 name: "no status", 867 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec}, 868 }, { 869 name: "approved condition", 870 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 871 Status: capi.CertificateSigningRequestStatus{ 872 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 873 }, 874 }, 875 }, { 876 name: "denied condition", 877 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 878 Status: capi.CertificateSigningRequestStatus{ 879 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, 880 }, 881 }, 882 }, { 883 name: "failed condition", 884 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 885 Status: capi.CertificateSigningRequestStatus{ 886 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, 887 }, 888 }, 889 }, { 890 name: "approved+issued", 891 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 892 Status: capi.CertificateSigningRequestStatus{ 893 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 894 Certificate: validCertificate, 895 }, 896 }, 897 }, 898 899 // legacy signer 900 { 901 name: "legacy signer", 902 csr: &capi.CertificateSigningRequest{ 903 ObjectMeta: validObjectMeta, 904 Spec: func() capi.CertificateSigningRequestSpec { 905 specCopy := validSpec 906 specCopy.SignerName = capi.LegacyUnknownSignerName 907 return specCopy 908 }(), 909 }, 910 lenientOpts: certificateValidationOptions{allowLegacySignerName: true}, 911 strictErrs: []string{`spec.signerName: Invalid value: "kubernetes.io/legacy-unknown": the legacy signerName is not allowed via this API version`}, 912 }, 913 914 // invalid condition cases 915 { 916 name: "empty condition type", 917 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 918 Status: capi.CertificateSigningRequestStatus{ 919 Conditions: []capi.CertificateSigningRequestCondition{{Status: core.ConditionTrue}}, 920 }, 921 }, 922 lenientOpts: certificateValidationOptions{allowEmptyConditionType: true}, 923 strictErrs: []string{`status.conditions[0].type: Required value`}, 924 }, { 925 name: "approved and denied", 926 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 927 Status: capi.CertificateSigningRequestStatus{ 928 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}, {Type: capi.CertificateDenied, Status: core.ConditionTrue}}, 929 }, 930 }, 931 lenientOpts: certificateValidationOptions{allowBothApprovedAndDenied: true}, 932 strictErrs: []string{`status.conditions[1].type: Invalid value: "Denied": Approved and Denied conditions are mutually exclusive`}, 933 }, { 934 name: "duplicate condition", 935 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 936 Status: capi.CertificateSigningRequestStatus{ 937 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}, {Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 938 }, 939 }, 940 lenientOpts: certificateValidationOptions{allowDuplicateConditionTypes: true}, 941 strictErrs: []string{`status.conditions[1].type: Duplicate value: "Approved"`}, 942 }, 943 944 // invalid allowArbitraryCertificate cases 945 { 946 name: "status.certificate, no PEM", 947 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 948 Status: capi.CertificateSigningRequestStatus{ 949 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 950 Certificate: invalidCertificateNoPEM, 951 }, 952 }, 953 lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, 954 strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`}, 955 }, { 956 name: "status.certificate, non-CERTIFICATE PEM", 957 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 958 Status: capi.CertificateSigningRequestStatus{ 959 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 960 Certificate: invalidCertificateNonCertificatePEM, 961 }, 962 }, 963 lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, 964 strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": only CERTIFICATE PEM blocks are allowed, found "CERTIFICATE1"`}, 965 }, { 966 name: "status.certificate, PEM headers", 967 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 968 Status: capi.CertificateSigningRequestStatus{ 969 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 970 Certificate: invalidCertificatePEMHeaders, 971 }, 972 }, 973 lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, 974 strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": no PEM block headers are permitted`}, 975 }, { 976 name: "status.certificate, non-base64 PEM", 977 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 978 Status: capi.CertificateSigningRequestStatus{ 979 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 980 Certificate: invalidCertificateNonBase64PEM, 981 }, 982 }, 983 lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, 984 strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`}, 985 }, { 986 name: "status.certificate, empty PEM block", 987 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 988 Status: capi.CertificateSigningRequestStatus{ 989 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 990 Certificate: invalidCertificateEmptyPEM, 991 }, 992 }, 993 lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, 994 strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": found CERTIFICATE PEM block containing 0 certificates`}, 995 }, { 996 name: "status.certificate, non-ASN1 data", 997 csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec, 998 Status: capi.CertificateSigningRequestStatus{ 999 Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, 1000 Certificate: invalidCertificateNonASN1Data, 1001 }, 1002 }, 1003 lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true}, 1004 strictRegexes: []regexp.Regexp{*regexp.MustCompile(`status.certificate: Invalid value: "\<certificate data\>": (asn1: structure error: sequence tag mismatch|x509: invalid RDNSequence)`)}, 1005 }, 1006 } 1007 1008 for _, tt := range tests { 1009 t.Run(tt.name, func(t *testing.T) { 1010 // make sure the lenient options validate with no errors 1011 for _, err := range validateCertificateSigningRequest(tt.csr, tt.lenientOpts) { 1012 t.Errorf("unexpected error with lenient options: %s", err.Error()) 1013 } 1014 1015 // make sure the strict options produce the expected errors 1016 gotErrs := sets.NewString() 1017 for _, err := range validateCertificateSigningRequest(tt.csr, certificateValidationOptions{}) { 1018 gotErrs.Insert(err.Error()) 1019 } 1020 1021 // filter errors matching strictRegexes and ensure every strictRegex matches at least one error 1022 for _, expectedRegex := range tt.strictRegexes { 1023 matched := false 1024 for _, err := range gotErrs.List() { 1025 if expectedRegex.MatchString(err) { 1026 gotErrs.Delete(err) 1027 matched = true 1028 } 1029 } 1030 if !matched { 1031 t.Errorf("missing expected error matching: %s", expectedRegex.String()) 1032 } 1033 } 1034 1035 wantErrs := sets.NewString(tt.strictErrs...) 1036 for _, missing := range wantErrs.Difference(gotErrs).List() { 1037 t.Errorf("missing expected strict error: %s", missing) 1038 } 1039 for _, unexpected := range gotErrs.Difference(wantErrs).List() { 1040 t.Errorf("unexpected errors: %s", unexpected) 1041 } 1042 }) 1043 } 1044 } 1045 1046 func mustMakeCertificate(t *testing.T, template *x509.Certificate) []byte { 1047 gen := mathrand.New(mathrand.NewSource(12345)) 1048 1049 pub, priv, err := ed25519.GenerateKey(gen) 1050 if err != nil { 1051 t.Fatalf("Error while generating key: %v", err) 1052 } 1053 1054 cert, err := x509.CreateCertificate(gen, template, template, pub, priv) 1055 if err != nil { 1056 t.Fatalf("Error while making certificate: %v", err) 1057 } 1058 1059 return cert 1060 } 1061 1062 func mustMakePEMBlock(blockType string, headers map[string]string, data []byte) string { 1063 return string(pem.EncodeToMemory(&pem.Block{ 1064 Type: blockType, 1065 Headers: headers, 1066 Bytes: data, 1067 })) 1068 } 1069 1070 func TestValidateClusterTrustBundle(t *testing.T) { 1071 goodCert1 := mustMakeCertificate(t, &x509.Certificate{ 1072 SerialNumber: big.NewInt(0), 1073 Subject: pkix.Name{ 1074 CommonName: "root1", 1075 }, 1076 IsCA: true, 1077 BasicConstraintsValid: true, 1078 }) 1079 1080 goodCert2 := mustMakeCertificate(t, &x509.Certificate{ 1081 SerialNumber: big.NewInt(0), 1082 Subject: pkix.Name{ 1083 CommonName: "root2", 1084 }, 1085 IsCA: true, 1086 BasicConstraintsValid: true, 1087 }) 1088 1089 badNotCACert := mustMakeCertificate(t, &x509.Certificate{ 1090 SerialNumber: big.NewInt(0), 1091 Subject: pkix.Name{ 1092 CommonName: "root3", 1093 }, 1094 }) 1095 1096 goodCert1Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert1)) 1097 goodCert2Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert2)) 1098 1099 goodCert1AlternateBlock := strings.ReplaceAll(goodCert1Block, "\n", "\n\t\n") 1100 1101 badNotCACertBlock := string(mustMakePEMBlock("CERTIFICATE", nil, badNotCACert)) 1102 1103 badBlockHeadersBlock := string(mustMakePEMBlock("CERTIFICATE", map[string]string{"key": "value"}, goodCert1)) 1104 badBlockTypeBlock := string(mustMakePEMBlock("NOTACERTIFICATE", nil, goodCert1)) 1105 badNonParseableBlock := string(mustMakePEMBlock("CERTIFICATE", nil, []byte("this is not a certificate"))) 1106 1107 badTooBigBundle := "" 1108 for i := 0; i < (core.MaxSecretSize/len(goodCert1Block))+1; i++ { 1109 badTooBigBundle += goodCert1Block + "\n" 1110 } 1111 1112 testCases := []struct { 1113 description string 1114 bundle *capi.ClusterTrustBundle 1115 opts ValidateClusterTrustBundleOptions 1116 wantErrors field.ErrorList 1117 }{ 1118 { 1119 description: "valid, no signer name", 1120 bundle: &capi.ClusterTrustBundle{ 1121 ObjectMeta: metav1.ObjectMeta{ 1122 Name: "foo", 1123 }, 1124 Spec: capi.ClusterTrustBundleSpec{ 1125 TrustBundle: goodCert1Block, 1126 }, 1127 }, 1128 }, 1129 { 1130 description: "invalid, too big", 1131 bundle: &capi.ClusterTrustBundle{ 1132 ObjectMeta: metav1.ObjectMeta{ 1133 Name: "foo", 1134 }, 1135 Spec: capi.ClusterTrustBundleSpec{ 1136 TrustBundle: badTooBigBundle, 1137 }, 1138 }, 1139 wantErrors: field.ErrorList{ 1140 field.TooLong(field.NewPath("spec", "trustBundle"), fmt.Sprintf("<value omitted, len %d>", len(badTooBigBundle)), core.MaxSecretSize), 1141 }, 1142 }, 1143 { 1144 description: "invalid, no signer name, invalid name", 1145 bundle: &capi.ClusterTrustBundle{ 1146 ObjectMeta: metav1.ObjectMeta{ 1147 Name: "k8s.io:bar:foo", 1148 }, 1149 Spec: capi.ClusterTrustBundleSpec{ 1150 TrustBundle: goodCert1Block, 1151 }, 1152 }, 1153 wantErrors: field.ErrorList{ 1154 field.Invalid(field.NewPath("metadata", "name"), "k8s.io:bar:foo", "ClusterTrustBundle without signer name must not have \":\" in its name"), 1155 }, 1156 }, { 1157 description: "valid, with signer name", 1158 bundle: &capi.ClusterTrustBundle{ 1159 ObjectMeta: metav1.ObjectMeta{ 1160 Name: "k8s.io:foo:bar", 1161 }, 1162 Spec: capi.ClusterTrustBundleSpec{ 1163 SignerName: "k8s.io/foo", 1164 TrustBundle: goodCert1Block, 1165 }, 1166 }, 1167 }, { 1168 description: "invalid, with signer name, missing name prefix", 1169 bundle: &capi.ClusterTrustBundle{ 1170 ObjectMeta: metav1.ObjectMeta{ 1171 Name: "look-ma-no-prefix", 1172 }, 1173 Spec: capi.ClusterTrustBundleSpec{ 1174 SignerName: "k8s.io/foo", 1175 TrustBundle: goodCert1Block, 1176 }, 1177 }, 1178 wantErrors: field.ErrorList{ 1179 field.Invalid(field.NewPath("metadata", "name"), "look-ma-no-prefix", "ClusterTrustBundle for signerName k8s.io/foo must be named with prefix k8s.io:foo:"), 1180 }, 1181 }, { 1182 description: "invalid, with signer name, empty name suffix", 1183 bundle: &capi.ClusterTrustBundle{ 1184 ObjectMeta: metav1.ObjectMeta{ 1185 Name: "k8s.io:foo:", 1186 }, 1187 Spec: capi.ClusterTrustBundleSpec{ 1188 SignerName: "k8s.io/foo", 1189 TrustBundle: goodCert1Block, 1190 }, 1191 }, 1192 wantErrors: field.ErrorList{ 1193 field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), 1194 }, 1195 }, { 1196 description: "invalid, with signer name, bad name suffix", 1197 bundle: &capi.ClusterTrustBundle{ 1198 ObjectMeta: metav1.ObjectMeta{ 1199 Name: "k8s.io:foo:123notvalidDNSSubdomain", 1200 }, 1201 Spec: capi.ClusterTrustBundleSpec{ 1202 SignerName: "k8s.io/foo", 1203 TrustBundle: goodCert1Block, 1204 }, 1205 }, 1206 wantErrors: field.ErrorList{ 1207 field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:123notvalidDNSSubdomain", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), 1208 }, 1209 }, { 1210 description: "valid, with signer name, with inter-block garbage", 1211 bundle: &capi.ClusterTrustBundle{ 1212 ObjectMeta: metav1.ObjectMeta{ 1213 Name: "k8s.io:foo:abc", 1214 }, 1215 Spec: capi.ClusterTrustBundleSpec{ 1216 SignerName: "k8s.io/foo", 1217 TrustBundle: "garbage\n" + goodCert1Block + "\ngarbage\n" + goodCert2Block, 1218 }, 1219 }, 1220 }, { 1221 description: "invalid, no signer name, no trust anchors", 1222 bundle: &capi.ClusterTrustBundle{ 1223 ObjectMeta: metav1.ObjectMeta{ 1224 Name: "foo", 1225 }, 1226 Spec: capi.ClusterTrustBundleSpec{}, 1227 }, 1228 wantErrors: field.ErrorList{ 1229 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"), 1230 }, 1231 }, { 1232 description: "invalid, no trust anchors", 1233 bundle: &capi.ClusterTrustBundle{ 1234 ObjectMeta: metav1.ObjectMeta{ 1235 Name: "k8s.io:foo:abc", 1236 }, 1237 Spec: capi.ClusterTrustBundleSpec{ 1238 SignerName: "k8s.io/foo", 1239 }, 1240 }, 1241 wantErrors: field.ErrorList{ 1242 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"), 1243 }, 1244 }, { 1245 description: "invalid, bad signer name", 1246 bundle: &capi.ClusterTrustBundle{ 1247 ObjectMeta: metav1.ObjectMeta{ 1248 Name: "invalid:foo", 1249 }, 1250 Spec: capi.ClusterTrustBundleSpec{ 1251 SignerName: "invalid", 1252 TrustBundle: goodCert1Block, 1253 }, 1254 }, 1255 wantErrors: field.ErrorList{ 1256 field.Invalid(field.NewPath("spec", "signerName"), "invalid", "must be a fully qualified domain and path of the form 'example.com/signer-name'"), 1257 }, 1258 }, { 1259 description: "invalid, no blocks", 1260 bundle: &capi.ClusterTrustBundle{ 1261 ObjectMeta: metav1.ObjectMeta{ 1262 Name: "foo", 1263 }, 1264 Spec: capi.ClusterTrustBundleSpec{ 1265 TrustBundle: "non block garbage", 1266 }, 1267 }, 1268 wantErrors: field.ErrorList{ 1269 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"), 1270 }, 1271 }, { 1272 description: "invalid, bad block type", 1273 bundle: &capi.ClusterTrustBundle{ 1274 ObjectMeta: metav1.ObjectMeta{ 1275 Name: "foo", 1276 }, 1277 Spec: capi.ClusterTrustBundleSpec{ 1278 TrustBundle: goodCert1Block + "\n" + badBlockTypeBlock, 1279 }, 1280 }, 1281 wantErrors: field.ErrorList{ 1282 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 1 has bad block type: NOTACERTIFICATE"), 1283 }, 1284 }, { 1285 description: "invalid, block with headers", 1286 bundle: &capi.ClusterTrustBundle{ 1287 ObjectMeta: metav1.ObjectMeta{ 1288 Name: "foo", 1289 }, 1290 Spec: capi.ClusterTrustBundleSpec{ 1291 TrustBundle: goodCert1Block + "\n" + badBlockHeadersBlock, 1292 }, 1293 }, 1294 wantErrors: field.ErrorList{ 1295 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 1 has PEM block headers"), 1296 }, 1297 }, { 1298 description: "invalid, cert is not a CA cert", 1299 bundle: &capi.ClusterTrustBundle{ 1300 ObjectMeta: metav1.ObjectMeta{ 1301 Name: "foo", 1302 }, 1303 Spec: capi.ClusterTrustBundleSpec{ 1304 TrustBundle: badNotCACertBlock, 1305 }, 1306 }, 1307 wantErrors: field.ErrorList{ 1308 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 0 does not have the CA bit set"), 1309 }, 1310 }, { 1311 description: "invalid, duplicated blocks", 1312 bundle: &capi.ClusterTrustBundle{ 1313 ObjectMeta: metav1.ObjectMeta{ 1314 Name: "foo", 1315 }, 1316 Spec: capi.ClusterTrustBundleSpec{ 1317 TrustBundle: goodCert1Block + "\n" + goodCert1AlternateBlock, 1318 }, 1319 }, 1320 wantErrors: field.ErrorList{ 1321 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "duplicate trust anchor (indices [0 1])"), 1322 }, 1323 }, { 1324 description: "invalid, non-certificate entry", 1325 bundle: &capi.ClusterTrustBundle{ 1326 ObjectMeta: metav1.ObjectMeta{ 1327 Name: "foo", 1328 }, 1329 Spec: capi.ClusterTrustBundleSpec{ 1330 TrustBundle: goodCert1Block + "\n" + badNonParseableBlock, 1331 }, 1332 }, 1333 wantErrors: field.ErrorList{ 1334 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 1 does not parse as X.509"), 1335 }, 1336 }, { 1337 description: "allow any old garbage in the PEM field if we suppress parsing", 1338 bundle: &capi.ClusterTrustBundle{ 1339 ObjectMeta: metav1.ObjectMeta{ 1340 Name: "foo", 1341 }, 1342 Spec: capi.ClusterTrustBundleSpec{ 1343 TrustBundle: "garbage", 1344 }, 1345 }, 1346 opts: ValidateClusterTrustBundleOptions{ 1347 SuppressBundleParsing: true, 1348 }, 1349 }} 1350 for _, tc := range testCases { 1351 t.Run(tc.description, func(t *testing.T) { 1352 gotErrors := ValidateClusterTrustBundle(tc.bundle, tc.opts) 1353 if diff := cmp.Diff(gotErrors, tc.wantErrors); diff != "" { 1354 t.Fatalf("Unexpected error output from Validate; diff (-got +want)\n%s", diff) 1355 } 1356 1357 // When there are no changes to the object, 1358 // ValidateClusterTrustBundleUpdate should not report errors about 1359 // the TrustBundle field. 1360 tc.bundle.ObjectMeta.ResourceVersion = "1" 1361 newBundle := tc.bundle.DeepCopy() 1362 newBundle.ObjectMeta.ResourceVersion = "2" 1363 gotErrors = ValidateClusterTrustBundleUpdate(newBundle, tc.bundle) 1364 1365 var filteredWantErrors field.ErrorList 1366 for _, err := range tc.wantErrors { 1367 if err.Field != "spec.trustBundle" { 1368 filteredWantErrors = append(filteredWantErrors, err) 1369 } 1370 } 1371 1372 if diff := cmp.Diff(gotErrors, filteredWantErrors); diff != "" { 1373 t.Fatalf("Unexpected error output from ValidateUpdate; diff (-got +want)\n%s", diff) 1374 } 1375 }) 1376 } 1377 } 1378 1379 func TestValidateClusterTrustBundleUpdate(t *testing.T) { 1380 goodCert1 := mustMakeCertificate(t, &x509.Certificate{ 1381 SerialNumber: big.NewInt(0), 1382 Subject: pkix.Name{ 1383 CommonName: "root1", 1384 }, 1385 IsCA: true, 1386 BasicConstraintsValid: true, 1387 }) 1388 1389 goodCert2 := mustMakeCertificate(t, &x509.Certificate{ 1390 SerialNumber: big.NewInt(0), 1391 Subject: pkix.Name{ 1392 CommonName: "root2", 1393 }, 1394 IsCA: true, 1395 BasicConstraintsValid: true, 1396 }) 1397 1398 goodCert1Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert1)) 1399 goodCert2Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert2)) 1400 1401 testCases := []struct { 1402 description string 1403 oldBundle, newBundle *capi.ClusterTrustBundle 1404 wantErrors field.ErrorList 1405 }{{ 1406 description: "changing signer name disallowed", 1407 oldBundle: &capi.ClusterTrustBundle{ 1408 ObjectMeta: metav1.ObjectMeta{ 1409 Name: "k8s.io:foo:bar", 1410 }, 1411 Spec: capi.ClusterTrustBundleSpec{ 1412 SignerName: "k8s.io/foo", 1413 TrustBundle: goodCert1Block, 1414 }, 1415 }, 1416 newBundle: &capi.ClusterTrustBundle{ 1417 ObjectMeta: metav1.ObjectMeta{ 1418 Name: "k8s.io:foo:bar", 1419 }, 1420 Spec: capi.ClusterTrustBundleSpec{ 1421 SignerName: "k8s.io/bar", 1422 TrustBundle: goodCert1Block, 1423 }, 1424 }, 1425 wantErrors: field.ErrorList{ 1426 field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:bar", "ClusterTrustBundle for signerName k8s.io/bar must be named with prefix k8s.io:bar:"), 1427 field.Invalid(field.NewPath("spec", "signerName"), "k8s.io/bar", "field is immutable"), 1428 }, 1429 }, { 1430 description: "adding certificate allowed", 1431 oldBundle: &capi.ClusterTrustBundle{ 1432 ObjectMeta: metav1.ObjectMeta{ 1433 Name: "k8s.io:foo:bar", 1434 }, 1435 Spec: capi.ClusterTrustBundleSpec{ 1436 SignerName: "k8s.io/foo", 1437 TrustBundle: goodCert1Block, 1438 }, 1439 }, 1440 newBundle: &capi.ClusterTrustBundle{ 1441 ObjectMeta: metav1.ObjectMeta{ 1442 Name: "k8s.io:foo:bar", 1443 }, 1444 Spec: capi.ClusterTrustBundleSpec{ 1445 SignerName: "k8s.io/foo", 1446 TrustBundle: goodCert1Block + "\n" + goodCert2Block, 1447 }, 1448 }, 1449 }, { 1450 description: "emptying trustBundle disallowed", 1451 oldBundle: &capi.ClusterTrustBundle{ 1452 ObjectMeta: metav1.ObjectMeta{ 1453 Name: "k8s.io:foo:bar", 1454 }, 1455 Spec: capi.ClusterTrustBundleSpec{ 1456 SignerName: "k8s.io/foo", 1457 TrustBundle: goodCert1Block, 1458 }, 1459 }, 1460 newBundle: &capi.ClusterTrustBundle{ 1461 ObjectMeta: metav1.ObjectMeta{ 1462 Name: "k8s.io:foo:bar", 1463 }, 1464 Spec: capi.ClusterTrustBundleSpec{ 1465 SignerName: "k8s.io/foo", 1466 TrustBundle: "", 1467 }, 1468 }, 1469 wantErrors: field.ErrorList{ 1470 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"), 1471 }, 1472 }, { 1473 description: "emptying trustBundle (replace with non-block garbage) disallowed", 1474 oldBundle: &capi.ClusterTrustBundle{ 1475 ObjectMeta: metav1.ObjectMeta{ 1476 Name: "k8s.io:foo:bar", 1477 }, 1478 Spec: capi.ClusterTrustBundleSpec{ 1479 SignerName: "k8s.io/foo", 1480 TrustBundle: goodCert1Block, 1481 }, 1482 }, 1483 newBundle: &capi.ClusterTrustBundle{ 1484 ObjectMeta: metav1.ObjectMeta{ 1485 Name: "k8s.io:foo:bar", 1486 }, 1487 Spec: capi.ClusterTrustBundleSpec{ 1488 SignerName: "k8s.io/foo", 1489 TrustBundle: "non block garbage", 1490 }, 1491 }, 1492 wantErrors: field.ErrorList{ 1493 field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"), 1494 }, 1495 }} 1496 1497 for _, tc := range testCases { 1498 t.Run(tc.description, func(t *testing.T) { 1499 tc.oldBundle.ObjectMeta.ResourceVersion = "1" 1500 tc.newBundle.ObjectMeta.ResourceVersion = "2" 1501 gotErrors := ValidateClusterTrustBundleUpdate(tc.newBundle, tc.oldBundle) 1502 if diff := cmp.Diff(gotErrors, tc.wantErrors); diff != "" { 1503 t.Errorf("Unexpected error output from ValidateUpdate; diff (-got +want)\n%s", diff) 1504 } 1505 }) 1506 } 1507 } 1508 1509 var ( 1510 validCertificate = []byte(` 1511 Leading non-PEM content 1512 -----BEGIN CERTIFICATE----- 1513 MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw 1514 GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx 1515 MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB 1516 BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb 1517 KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC 1518 BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU 1519 K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q 1520 a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5 1521 MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps= 1522 -----END CERTIFICATE----- 1523 Intermediate non-PEM content 1524 -----BEGIN CERTIFICATE----- 1525 MIIBqDCCAU6gAwIBAgIUfqZtjoFgczZ+oQZbEC/BDSS2J6wwCgYIKoZIzj0EAwIw 1526 EjEQMA4GA1UEAxMHUm9vdC1DQTAgFw0xNjEwMTEwNTA2MDBaGA8yMTE2MDkxNzA1 1527 MDYwMFowGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMFkwEwYHKoZIzj0CAQYI 1528 KoZIzj0DAQcDQgAEyWHEMMCctJg8Xa5YWLqaCPbk3MjB+uvXac42JM9pj4k9jedD 1529 kpUJRkWIPzgJI8Zk/3cSzluUTixP6JBSDKtwwaN4MHYwDgYDVR0PAQH/BAQDAgGm 1530 MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE 1531 FF+p0JcY31pz+mjNZnjv0Gum92vZMB8GA1UdIwQYMBaAFB7P6+i4/pfNjqZgJv/b 1532 dgA7Fe4tMAoGCCqGSM49BAMCA0gAMEUCIQCTT1YWQZaAqfQ2oBxzOkJE2BqLFxhz 1533 3smQlrZ5gCHddwIgcvT7puhYOzAgcvMn9+SZ1JOyZ7edODjshCVCRnuHK2c= 1534 -----END CERTIFICATE----- 1535 Trailing non-PEM content 1536 `) 1537 1538 invalidCertificateNoPEM = []byte(`no PEM content`) 1539 1540 invalidCertificateNonCertificatePEM = []byte(` 1541 Leading non-PEM content 1542 -----BEGIN CERTIFICATE1----- 1543 MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw 1544 GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx 1545 MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB 1546 BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb 1547 KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC 1548 BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU 1549 K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q 1550 a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5 1551 MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps= 1552 -----END CERTIFICATE1----- 1553 Trailing non-PEM content 1554 `) 1555 1556 invalidCertificatePEMHeaders = []byte(` 1557 Leading non-PEM content 1558 -----BEGIN CERTIFICATE----- 1559 Some-Header: Some-Value 1560 MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw 1561 GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx 1562 MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB 1563 BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb 1564 KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC 1565 BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU 1566 K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q 1567 a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5 1568 MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps= 1569 -----END CERTIFICATE----- 1570 Trailing non-PEM content 1571 `) 1572 1573 invalidCertificateNonBase64PEM = []byte(` 1574 Leading non-PEM content 1575 -----BEGIN CERTIFICATE----- 1576 MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw 1577 GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx 1578 MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB 1579 BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb 1580 KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC 1581 BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU 1582 K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q 1583 a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5 1584 MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1d????????? 1585 -----END CERTIFICATE----- 1586 Trailing non-PEM content 1587 `) 1588 1589 invalidCertificateEmptyPEM = []byte(` 1590 Leading non-PEM content 1591 -----BEGIN CERTIFICATE----- 1592 -----END CERTIFICATE----- 1593 Trailing non-PEM content 1594 `) 1595 1596 // first character is invalid 1597 invalidCertificateNonASN1Data = []byte(` 1598 Leading non-PEM content 1599 -----BEGIN CERTIFICATE----- 1600 MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw 1601 GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx 1602 MTYwOTE3MDUwNjAwWjAUNRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB 1603 BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb 1604 KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC 1605 BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU 1606 K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q 1607 a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5 1608 MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps= 1609 -----END CERTIFICATE----- 1610 Trailing non-PEM content 1611 `) 1612 )