1/* 2Copyright 2016 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package validation 18 19import ( 20 "bytes" 21 "crypto/x509" 22 "encoding/pem" 23 "fmt" 24 "strings" 25 26 v1 "k8s.io/api/core/v1" 27 apiequality "k8s.io/apimachinery/pkg/api/equality" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/util/diff" 30 "k8s.io/apimachinery/pkg/util/sets" 31 utilvalidation "k8s.io/apimachinery/pkg/util/validation" 32 "k8s.io/apimachinery/pkg/util/validation/field" 33 utilcert "k8s.io/client-go/util/cert" 34 "k8s.io/kubernetes/pkg/apis/certificates" 35 certificatesv1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1" 36 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" 37) 38 39var ( 40 // trueConditionTypes is the set of condition types which may only have a status of True if present 41 trueConditionTypes = sets.NewString( 42 string(certificates.CertificateApproved), 43 string(certificates.CertificateDenied), 44 string(certificates.CertificateFailed), 45 ) 46 47 trueStatusOnly = sets.NewString(string(v1.ConditionTrue)) 48 allStatusValues = sets.NewString(string(v1.ConditionTrue), string(v1.ConditionFalse), string(v1.ConditionUnknown)) 49) 50 51type certificateValidationOptions struct { 52 // The following allow modifications only permitted via certain update paths 53 54 // allow populating/modifying Approved/Denied conditions 55 allowSettingApprovalConditions bool 56 // allow populating status.certificate 57 allowSettingCertificate bool 58 59 // allow Approved and Denied conditions to be exist. 60 // we tolerate this when the problem is already present in the persisted object for compatibility. 61 allowBothApprovedAndDenied bool 62 63 // The following are bad things we tolerate for compatibility reasons: 64 // * in requests made via the v1beta1 API 65 // * in update requests where the problem is already present in the persisted object 66 67 // allow modifying status.certificate on an update where the old object has a different certificate 68 allowResettingCertificate bool 69 // allow the legacy-unknown signerName 70 allowLegacySignerName bool 71 // allow conditions with duplicate types 72 allowDuplicateConditionTypes bool 73 // allow conditions with "" types 74 allowEmptyConditionType bool 75 // allow arbitrary content in status.certificate 76 allowArbitraryCertificate bool 77 // allow usages values outside the known set 78 allowUnknownUsages bool 79 // allow duplicate usages values 80 allowDuplicateUsages bool 81} 82 83// validateCSR validates the signature and formatting of a base64-wrapped, 84// PEM-encoded PKCS#10 certificate signing request. If this is invalid, we must 85// not accept the CSR for further processing. 86func validateCSR(obj *certificates.CertificateSigningRequest) error { 87 csr, err := certificates.ParseCSR(obj.Spec.Request) 88 if err != nil { 89 return err 90 } 91 // check that the signature is valid 92 err = csr.CheckSignature() 93 if err != nil { 94 return err 95 } 96 return nil 97} 98 99func validateCertificate(pemData []byte) error { 100 if len(pemData) == 0 { 101 return nil 102 } 103 104 blocks := 0 105 for { 106 block, remainingData := pem.Decode(pemData) 107 if block == nil { 108 break 109 } 110 111 if block.Type != utilcert.CertificateBlockType { 112 return fmt.Errorf("only CERTIFICATE PEM blocks are allowed, found %q", block.Type) 113 } 114 if len(block.Headers) != 0 { 115 return fmt.Errorf("no PEM block headers are permitted") 116 } 117 blocks++ 118 119 certs, err := x509.ParseCertificates(block.Bytes) 120 if err != nil { 121 return err 122 } 123 if len(certs) == 0 { 124 return fmt.Errorf("found CERTIFICATE PEM block containing 0 certificates") 125 } 126 127 pemData = remainingData 128 } 129 130 if blocks == 0 { 131 return fmt.Errorf("must contain at least one CERTIFICATE PEM block") 132 } 133 134 return nil 135} 136 137// We don't care what you call your certificate requests. 138func ValidateCertificateRequestName(name string, prefix bool) []string { 139 return nil 140} 141 142func ValidateCertificateSigningRequestCreate(csr *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList { 143 opts := getValidationOptions(version, csr, nil) 144 return validateCertificateSigningRequest(csr, opts) 145} 146 147var ( 148 allValidUsages = sets.NewString( 149 string(certificates.UsageSigning), 150 string(certificates.UsageDigitalSignature), 151 string(certificates.UsageContentCommitment), 152 string(certificates.UsageKeyEncipherment), 153 string(certificates.UsageKeyAgreement), 154 string(certificates.UsageDataEncipherment), 155 string(certificates.UsageCertSign), 156 string(certificates.UsageCRLSign), 157 string(certificates.UsageEncipherOnly), 158 string(certificates.UsageDecipherOnly), 159 string(certificates.UsageAny), 160 string(certificates.UsageServerAuth), 161 string(certificates.UsageClientAuth), 162 string(certificates.UsageCodeSigning), 163 string(certificates.UsageEmailProtection), 164 string(certificates.UsageSMIME), 165 string(certificates.UsageIPsecEndSystem), 166 string(certificates.UsageIPsecTunnel), 167 string(certificates.UsageIPsecUser), 168 string(certificates.UsageTimestamping), 169 string(certificates.UsageOCSPSigning), 170 string(certificates.UsageMicrosoftSGC), 171 string(certificates.UsageNetscapeSGC), 172 ) 173) 174 175func validateCertificateSigningRequest(csr *certificates.CertificateSigningRequest, opts certificateValidationOptions) field.ErrorList { 176 isNamespaced := false 177 allErrs := apivalidation.ValidateObjectMeta(&csr.ObjectMeta, isNamespaced, ValidateCertificateRequestName, field.NewPath("metadata")) 178 179 specPath := field.NewPath("spec") 180 err := validateCSR(csr) 181 if err != nil { 182 allErrs = append(allErrs, field.Invalid(specPath.Child("request"), csr.Spec.Request, fmt.Sprintf("%v", err))) 183 } 184 if len(csr.Spec.Usages) == 0 { 185 allErrs = append(allErrs, field.Required(specPath.Child("usages"), "usages must be provided")) 186 } 187 if !opts.allowUnknownUsages { 188 for i, usage := range csr.Spec.Usages { 189 if !allValidUsages.Has(string(usage)) { 190 allErrs = append(allErrs, field.NotSupported(specPath.Child("usages").Index(i), usage, allValidUsages.List())) 191 } 192 } 193 } 194 if !opts.allowDuplicateUsages { 195 seen := make(map[certificates.KeyUsage]bool, len(csr.Spec.Usages)) 196 for i, usage := range csr.Spec.Usages { 197 if seen[usage] { 198 allErrs = append(allErrs, field.Duplicate(specPath.Child("usages").Index(i), usage)) 199 } 200 seen[usage] = true 201 } 202 } 203 if !opts.allowLegacySignerName && csr.Spec.SignerName == certificates.LegacyUnknownSignerName { 204 allErrs = append(allErrs, field.Invalid(specPath.Child("signerName"), csr.Spec.SignerName, "the legacy signerName is not allowed via this API version")) 205 } else { 206 allErrs = append(allErrs, ValidateCertificateSigningRequestSignerName(specPath.Child("signerName"), csr.Spec.SignerName)...) 207 } 208 if csr.Spec.ExpirationSeconds != nil && *csr.Spec.ExpirationSeconds < 600 { 209 allErrs = append(allErrs, field.Invalid(specPath.Child("expirationSeconds"), *csr.Spec.ExpirationSeconds, "may not specify a duration less than 600 seconds (10 minutes)")) 210 } 211 allErrs = append(allErrs, validateConditions(field.NewPath("status", "conditions"), csr, opts)...) 212 213 if !opts.allowArbitraryCertificate { 214 if err := validateCertificate(csr.Status.Certificate); err != nil { 215 allErrs = append(allErrs, field.Invalid(field.NewPath("status", "certificate"), "<certificate data>", err.Error())) 216 } 217 } 218 219 return allErrs 220} 221 222func validateConditions(fldPath *field.Path, csr *certificates.CertificateSigningRequest, opts certificateValidationOptions) field.ErrorList { 223 allErrs := field.ErrorList{} 224 225 seenTypes := map[certificates.RequestConditionType]bool{} 226 hasApproved := false 227 hasDenied := false 228 229 for i, c := range csr.Status.Conditions { 230 231 if !opts.allowEmptyConditionType { 232 if len(c.Type) == 0 { 233 allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("type"), "")) 234 } 235 } 236 237 allowedStatusValues := allStatusValues 238 if trueConditionTypes.Has(string(c.Type)) { 239 allowedStatusValues = trueStatusOnly 240 } 241 switch { 242 case c.Status == "": 243 allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("status"), "")) 244 case !allowedStatusValues.Has(string(c.Status)): 245 allErrs = append(allErrs, field.NotSupported(fldPath.Index(i).Child("status"), c.Status, allowedStatusValues.List())) 246 } 247 248 if !opts.allowBothApprovedAndDenied { 249 switch c.Type { 250 case certificates.CertificateApproved: 251 hasApproved = true 252 if hasDenied { 253 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), c.Type, "Approved and Denied conditions are mutually exclusive")) 254 } 255 case certificates.CertificateDenied: 256 hasDenied = true 257 if hasApproved { 258 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), c.Type, "Approved and Denied conditions are mutually exclusive")) 259 } 260 } 261 } 262 263 if !opts.allowDuplicateConditionTypes { 264 if seenTypes[c.Type] { 265 allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), c.Type)) 266 } 267 seenTypes[c.Type] = true 268 } 269 } 270 271 return allErrs 272} 273 274// ensure signerName is of the form domain.com/something and up to 571 characters. 275// This length and format is specified to accommodate signerNames like: 276// <fqdn>/<resource-namespace>.<resource-name>. 277// The max length of a FQDN is 253 characters (DNS1123Subdomain max length) 278// The max length of a namespace name is 63 characters (DNS1123Label max length) 279// The max length of a resource name is 253 characters (DNS1123Subdomain max length) 280// We then add an additional 2 characters to account for the one '.' and one '/'. 281func ValidateCertificateSigningRequestSignerName(fldPath *field.Path, signerName string) field.ErrorList { 282 var el field.ErrorList 283 if len(signerName) == 0 { 284 el = append(el, field.Required(fldPath, "signerName must be provided")) 285 return el 286 } 287 288 segments := strings.Split(signerName, "/") 289 // validate that there is one '/' in the signerName. 290 // we do this after validating the domain segment to provide more info to the user. 291 if len(segments) != 2 { 292 el = append(el, field.Invalid(fldPath, signerName, "must be a fully qualified domain and path of the form 'example.com/signer-name'")) 293 // return early here as we should not continue attempting to validate a missing or malformed path segment 294 // (i.e. one containing multiple or zero `/`) 295 return el 296 } 297 298 // validate that segments[0] is less than 253 characters altogether 299 maxDomainSegmentLength := utilvalidation.DNS1123SubdomainMaxLength 300 if len(segments[0]) > maxDomainSegmentLength { 301 el = append(el, field.TooLong(fldPath, segments[0], maxDomainSegmentLength)) 302 } 303 // validate that segments[0] consists of valid DNS1123 labels separated by '.' 304 domainLabels := strings.Split(segments[0], ".") 305 for _, lbl := range domainLabels { 306 // use IsDNS1123Label as we want to ensure the max length of any single label in the domain 307 // is 63 characters 308 if errs := utilvalidation.IsDNS1123Label(lbl); len(errs) > 0 { 309 for _, err := range errs { 310 el = append(el, field.Invalid(fldPath, segments[0], fmt.Sprintf("validating label %q: %s", lbl, err))) 311 } 312 // if we encounter any errors whilst parsing the domain segment, break from 313 // validation as any further error messages will be duplicates, and non-distinguishable 314 // from each other, confusing users. 315 break 316 } 317 } 318 319 // validate that there is at least one '.' in segments[0] 320 if len(domainLabels) < 2 { 321 el = append(el, field.Invalid(fldPath, segments[0], "should be a domain with at least two segments separated by dots")) 322 } 323 324 // validate that segments[1] consists of valid DNS1123 subdomains separated by '.'. 325 pathLabels := strings.Split(segments[1], ".") 326 for _, lbl := range pathLabels { 327 // use IsDNS1123Subdomain because it enforces a length restriction of 253 characters 328 // which is required in order to fit a full resource name into a single 'label' 329 if errs := utilvalidation.IsDNS1123Subdomain(lbl); len(errs) > 0 { 330 for _, err := range errs { 331 el = append(el, field.Invalid(fldPath, segments[1], fmt.Sprintf("validating label %q: %s", lbl, err))) 332 } 333 // if we encounter any errors whilst parsing the path segment, break from 334 // validation as any further error messages will be duplicates, and non-distinguishable 335 // from each other, confusing users. 336 break 337 } 338 } 339 340 // ensure that segments[1] can accommodate a dns label + dns subdomain + '.' 341 maxPathSegmentLength := utilvalidation.DNS1123SubdomainMaxLength + utilvalidation.DNS1123LabelMaxLength + 1 342 maxSignerNameLength := maxDomainSegmentLength + maxPathSegmentLength + 1 343 if len(signerName) > maxSignerNameLength { 344 el = append(el, field.TooLong(fldPath, signerName, maxSignerNameLength)) 345 } 346 347 return el 348} 349 350func ValidateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList { 351 opts := getValidationOptions(version, newCSR, oldCSR) 352 return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts) 353} 354 355func ValidateCertificateSigningRequestStatusUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList { 356 opts := getValidationOptions(version, newCSR, oldCSR) 357 opts.allowSettingCertificate = true 358 return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts) 359} 360 361func ValidateCertificateSigningRequestApprovalUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList { 362 opts := getValidationOptions(version, newCSR, oldCSR) 363 opts.allowSettingApprovalConditions = true 364 return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts) 365} 366 367func validateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, opts certificateValidationOptions) field.ErrorList { 368 validationErrorList := validateCertificateSigningRequest(newCSR, opts) 369 metaUpdateErrorList := apivalidation.ValidateObjectMetaUpdate(&newCSR.ObjectMeta, &oldCSR.ObjectMeta, field.NewPath("metadata")) 370 371 // prevent removal of existing Approved/Denied/Failed conditions 372 for _, t := range []certificates.RequestConditionType{certificates.CertificateApproved, certificates.CertificateDenied, certificates.CertificateFailed} { 373 oldConditions := findConditions(oldCSR, t) 374 newConditions := findConditions(newCSR, t) 375 if len(newConditions) < len(oldConditions) { 376 validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not remove a condition of type %q", t))) 377 } 378 } 379 380 if !opts.allowSettingApprovalConditions { 381 // prevent addition/removal/modification of Approved/Denied conditions 382 for _, t := range []certificates.RequestConditionType{certificates.CertificateApproved, certificates.CertificateDenied} { 383 oldConditions := findConditions(oldCSR, t) 384 newConditions := findConditions(newCSR, t) 385 switch { 386 case len(newConditions) < len(oldConditions): 387 // removals are prevented above 388 case len(newConditions) > len(oldConditions): 389 validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not add a condition of type %q", t))) 390 case !apiequality.Semantic.DeepEqual(oldConditions, newConditions): 391 conditionDiff := diff.ObjectDiff(oldConditions, newConditions) 392 validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not modify a condition of type %q\n%v", t, conditionDiff))) 393 } 394 } 395 } 396 397 if !bytes.Equal(newCSR.Status.Certificate, oldCSR.Status.Certificate) { 398 if !opts.allowSettingCertificate { 399 validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "certificate"), "updates may not set certificate content")) 400 } else if !opts.allowResettingCertificate && len(oldCSR.Status.Certificate) > 0 { 401 validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "certificate"), "updates may not modify existing certificate content")) 402 } 403 } 404 405 return append(validationErrorList, metaUpdateErrorList...) 406} 407 408// findConditions returns all instances of conditions of the specified type 409func findConditions(csr *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) []certificates.CertificateSigningRequestCondition { 410 var retval []certificates.CertificateSigningRequestCondition 411 for i, c := range csr.Status.Conditions { 412 if c.Type == conditionType { 413 retval = append(retval, csr.Status.Conditions[i]) 414 } 415 } 416 return retval 417} 418 419// getValidationOptions returns the validation options to be 420// compatible with the specified version and existing CSR. 421// oldCSR may be nil if this is a create request. 422// validation options related to subresource-specific capabilities are set to false. 423func getValidationOptions(version schema.GroupVersion, newCSR, oldCSR *certificates.CertificateSigningRequest) certificateValidationOptions { 424 return certificateValidationOptions{ 425 allowResettingCertificate: allowResettingCertificate(version), 426 allowBothApprovedAndDenied: allowBothApprovedAndDenied(oldCSR), 427 allowLegacySignerName: allowLegacySignerName(version, oldCSR), 428 allowDuplicateConditionTypes: allowDuplicateConditionTypes(version, oldCSR), 429 allowEmptyConditionType: allowEmptyConditionType(version, oldCSR), 430 allowArbitraryCertificate: allowArbitraryCertificate(version, newCSR, oldCSR), 431 allowDuplicateUsages: allowDuplicateUsages(version, oldCSR), 432 allowUnknownUsages: allowUnknownUsages(version, oldCSR), 433 } 434} 435 436func allowResettingCertificate(version schema.GroupVersion) bool { 437 // compatibility with v1beta1 438 return version == certificatesv1beta1.SchemeGroupVersion 439} 440 441func allowBothApprovedAndDenied(oldCSR *certificates.CertificateSigningRequest) bool { 442 if oldCSR == nil { 443 return false 444 } 445 approved := false 446 denied := false 447 for _, c := range oldCSR.Status.Conditions { 448 if c.Type == certificates.CertificateApproved { 449 approved = true 450 } else if c.Type == certificates.CertificateDenied { 451 denied = true 452 } 453 } 454 // compatibility with existing data 455 return approved && denied 456} 457 458func allowLegacySignerName(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { 459 switch { 460 case version == certificatesv1beta1.SchemeGroupVersion: 461 return true // compatibility with v1beta1 462 case oldCSR != nil && oldCSR.Spec.SignerName == certificates.LegacyUnknownSignerName: 463 return true // compatibility with existing data 464 default: 465 return false 466 } 467} 468 469func allowDuplicateConditionTypes(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { 470 switch { 471 case version == certificatesv1beta1.SchemeGroupVersion: 472 return true // compatibility with v1beta1 473 case oldCSR != nil && hasDuplicateConditionTypes(oldCSR): 474 return true // compatibility with existing data 475 default: 476 return false 477 } 478} 479func hasDuplicateConditionTypes(csr *certificates.CertificateSigningRequest) bool { 480 seen := map[certificates.RequestConditionType]bool{} 481 for _, c := range csr.Status.Conditions { 482 if seen[c.Type] { 483 return true 484 } 485 seen[c.Type] = true 486 } 487 return false 488} 489 490func allowEmptyConditionType(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { 491 switch { 492 case version == certificatesv1beta1.SchemeGroupVersion: 493 return true // compatibility with v1beta1 494 case oldCSR != nil && hasEmptyConditionType(oldCSR): 495 return true // compatibility with existing data 496 default: 497 return false 498 } 499} 500func hasEmptyConditionType(csr *certificates.CertificateSigningRequest) bool { 501 for _, c := range csr.Status.Conditions { 502 if len(c.Type) == 0 { 503 return true 504 } 505 } 506 return false 507} 508 509func allowArbitraryCertificate(version schema.GroupVersion, newCSR, oldCSR *certificates.CertificateSigningRequest) bool { 510 switch { 511 case version == certificatesv1beta1.SchemeGroupVersion: 512 return true // compatibility with v1beta1 513 case newCSR != nil && oldCSR != nil && bytes.Equal(newCSR.Status.Certificate, oldCSR.Status.Certificate): 514 return true // tolerate updates that don't touch status.certificate 515 case oldCSR != nil && validateCertificate(oldCSR.Status.Certificate) != nil: 516 return true // compatibility with existing data 517 default: 518 return false 519 } 520} 521 522func allowUnknownUsages(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { 523 switch { 524 case version == certificatesv1beta1.SchemeGroupVersion: 525 return true // compatibility with v1beta1 526 case oldCSR != nil && hasUnknownUsage(oldCSR.Spec.Usages): 527 return true // compatibility with existing data 528 default: 529 return false 530 } 531} 532 533func hasUnknownUsage(usages []certificates.KeyUsage) bool { 534 for _, usage := range usages { 535 if !allValidUsages.Has(string(usage)) { 536 return true 537 } 538 } 539 return false 540} 541 542func allowDuplicateUsages(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { 543 switch { 544 case version == certificatesv1beta1.SchemeGroupVersion: 545 return true // compatibility with v1beta1 546 case oldCSR != nil && hasDuplicateUsage(oldCSR.Spec.Usages): 547 return true // compatibility with existing data 548 default: 549 return false 550 } 551} 552 553func hasDuplicateUsage(usages []certificates.KeyUsage) bool { 554 seen := make(map[certificates.KeyUsage]bool, len(usages)) 555 for _, usage := range usages { 556 if seen[usage] { 557 return true 558 } 559 seen[usage] = true 560 } 561 return false 562} 563