mirror of https://github.com/k3s-io/k3s
560 lines
21 KiB
Go
560 lines
21 KiB
Go
/*
|
|
Copyright 2016 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package validation
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"strings"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/diff"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
utilcert "k8s.io/client-go/util/cert"
|
|
"k8s.io/kubernetes/pkg/apis/certificates"
|
|
certificatesv1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
|
|
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
|
)
|
|
|
|
var (
|
|
// trueConditionTypes is the set of condition types which may only have a status of True if present
|
|
trueConditionTypes = sets.NewString(
|
|
string(certificates.CertificateApproved),
|
|
string(certificates.CertificateDenied),
|
|
string(certificates.CertificateFailed),
|
|
)
|
|
|
|
trueStatusOnly = sets.NewString(string(v1.ConditionTrue))
|
|
allStatusValues = sets.NewString(string(v1.ConditionTrue), string(v1.ConditionFalse), string(v1.ConditionUnknown))
|
|
)
|
|
|
|
type certificateValidationOptions struct {
|
|
// The following allow modifications only permitted via certain update paths
|
|
|
|
// allow populating/modifying Approved/Denied conditions
|
|
allowSettingApprovalConditions bool
|
|
// allow populating status.certificate
|
|
allowSettingCertificate bool
|
|
|
|
// allow Approved and Denied conditions to be exist.
|
|
// we tolerate this when the problem is already present in the persisted object for compatibility.
|
|
allowBothApprovedAndDenied bool
|
|
|
|
// The following are bad things we tolerate for compatibility reasons:
|
|
// * in requests made via the v1beta1 API
|
|
// * in update requests where the problem is already present in the persisted object
|
|
|
|
// allow modifying status.certificate on an update where the old object has a different certificate
|
|
allowResettingCertificate bool
|
|
// allow the legacy-unknown signerName
|
|
allowLegacySignerName bool
|
|
// allow conditions with duplicate types
|
|
allowDuplicateConditionTypes bool
|
|
// allow conditions with "" types
|
|
allowEmptyConditionType bool
|
|
// allow arbitrary content in status.certificate
|
|
allowArbitraryCertificate bool
|
|
// allow usages values outside the known set
|
|
allowUnknownUsages bool
|
|
// allow duplicate usages values
|
|
allowDuplicateUsages bool
|
|
}
|
|
|
|
// validateCSR validates the signature and formatting of a base64-wrapped,
|
|
// PEM-encoded PKCS#10 certificate signing request. If this is invalid, we must
|
|
// not accept the CSR for further processing.
|
|
func validateCSR(obj *certificates.CertificateSigningRequest) error {
|
|
csr, err := certificates.ParseCSR(obj.Spec.Request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check that the signature is valid
|
|
err = csr.CheckSignature()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateCertificate(pemData []byte) error {
|
|
if len(pemData) == 0 {
|
|
return nil
|
|
}
|
|
|
|
blocks := 0
|
|
for {
|
|
block, remainingData := pem.Decode(pemData)
|
|
if block == nil {
|
|
break
|
|
}
|
|
|
|
if block.Type != utilcert.CertificateBlockType {
|
|
return fmt.Errorf("only CERTIFICATE PEM blocks are allowed, found %q", block.Type)
|
|
}
|
|
if len(block.Headers) != 0 {
|
|
return fmt.Errorf("no PEM block headers are permitted")
|
|
}
|
|
blocks++
|
|
|
|
certs, err := x509.ParseCertificates(block.Bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(certs) == 0 {
|
|
return fmt.Errorf("found CERTIFICATE PEM block containing 0 certificates")
|
|
}
|
|
|
|
pemData = remainingData
|
|
}
|
|
|
|
if blocks == 0 {
|
|
return fmt.Errorf("must contain at least one CERTIFICATE PEM block")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// We don't care what you call your certificate requests.
|
|
func ValidateCertificateRequestName(name string, prefix bool) []string {
|
|
return nil
|
|
}
|
|
|
|
func ValidateCertificateSigningRequestCreate(csr *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList {
|
|
opts := getValidationOptions(version, csr, nil)
|
|
return validateCertificateSigningRequest(csr, opts)
|
|
}
|
|
|
|
var (
|
|
allValidUsages = sets.NewString(
|
|
string(certificates.UsageSigning),
|
|
string(certificates.UsageDigitalSignature),
|
|
string(certificates.UsageContentCommitment),
|
|
string(certificates.UsageKeyEncipherment),
|
|
string(certificates.UsageKeyAgreement),
|
|
string(certificates.UsageDataEncipherment),
|
|
string(certificates.UsageCertSign),
|
|
string(certificates.UsageCRLSign),
|
|
string(certificates.UsageEncipherOnly),
|
|
string(certificates.UsageDecipherOnly),
|
|
string(certificates.UsageAny),
|
|
string(certificates.UsageServerAuth),
|
|
string(certificates.UsageClientAuth),
|
|
string(certificates.UsageCodeSigning),
|
|
string(certificates.UsageEmailProtection),
|
|
string(certificates.UsageSMIME),
|
|
string(certificates.UsageIPsecEndSystem),
|
|
string(certificates.UsageIPsecTunnel),
|
|
string(certificates.UsageIPsecUser),
|
|
string(certificates.UsageTimestamping),
|
|
string(certificates.UsageOCSPSigning),
|
|
string(certificates.UsageMicrosoftSGC),
|
|
string(certificates.UsageNetscapeSGC),
|
|
)
|
|
)
|
|
|
|
func validateCertificateSigningRequest(csr *certificates.CertificateSigningRequest, opts certificateValidationOptions) field.ErrorList {
|
|
isNamespaced := false
|
|
allErrs := apivalidation.ValidateObjectMeta(&csr.ObjectMeta, isNamespaced, ValidateCertificateRequestName, field.NewPath("metadata"))
|
|
|
|
specPath := field.NewPath("spec")
|
|
err := validateCSR(csr)
|
|
if err != nil {
|
|
allErrs = append(allErrs, field.Invalid(specPath.Child("request"), csr.Spec.Request, fmt.Sprintf("%v", err)))
|
|
}
|
|
if len(csr.Spec.Usages) == 0 {
|
|
allErrs = append(allErrs, field.Required(specPath.Child("usages"), "usages must be provided"))
|
|
}
|
|
if !opts.allowUnknownUsages {
|
|
for i, usage := range csr.Spec.Usages {
|
|
if !allValidUsages.Has(string(usage)) {
|
|
allErrs = append(allErrs, field.NotSupported(specPath.Child("usages").Index(i), usage, allValidUsages.List()))
|
|
}
|
|
}
|
|
}
|
|
if !opts.allowDuplicateUsages {
|
|
seen := make(map[certificates.KeyUsage]bool, len(csr.Spec.Usages))
|
|
for i, usage := range csr.Spec.Usages {
|
|
if seen[usage] {
|
|
allErrs = append(allErrs, field.Duplicate(specPath.Child("usages").Index(i), usage))
|
|
}
|
|
seen[usage] = true
|
|
}
|
|
}
|
|
if !opts.allowLegacySignerName && csr.Spec.SignerName == certificates.LegacyUnknownSignerName {
|
|
allErrs = append(allErrs, field.Invalid(specPath.Child("signerName"), csr.Spec.SignerName, "the legacy signerName is not allowed via this API version"))
|
|
} else {
|
|
allErrs = append(allErrs, ValidateCertificateSigningRequestSignerName(specPath.Child("signerName"), csr.Spec.SignerName)...)
|
|
}
|
|
allErrs = append(allErrs, validateConditions(field.NewPath("status", "conditions"), csr, opts)...)
|
|
|
|
if !opts.allowArbitraryCertificate {
|
|
if err := validateCertificate(csr.Status.Certificate); err != nil {
|
|
allErrs = append(allErrs, field.Invalid(field.NewPath("status", "certificate"), "<certificate data>", err.Error()))
|
|
}
|
|
}
|
|
|
|
return allErrs
|
|
}
|
|
|
|
func validateConditions(fldPath *field.Path, csr *certificates.CertificateSigningRequest, opts certificateValidationOptions) field.ErrorList {
|
|
allErrs := field.ErrorList{}
|
|
|
|
seenTypes := map[certificates.RequestConditionType]bool{}
|
|
hasApproved := false
|
|
hasDenied := false
|
|
|
|
for i, c := range csr.Status.Conditions {
|
|
|
|
if !opts.allowEmptyConditionType {
|
|
if len(c.Type) == 0 {
|
|
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("type"), ""))
|
|
}
|
|
}
|
|
|
|
allowedStatusValues := allStatusValues
|
|
if trueConditionTypes.Has(string(c.Type)) {
|
|
allowedStatusValues = trueStatusOnly
|
|
}
|
|
switch {
|
|
case c.Status == "":
|
|
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("status"), ""))
|
|
case !allowedStatusValues.Has(string(c.Status)):
|
|
allErrs = append(allErrs, field.NotSupported(fldPath.Index(i).Child("status"), c.Status, allowedStatusValues.List()))
|
|
}
|
|
|
|
if !opts.allowBothApprovedAndDenied {
|
|
switch c.Type {
|
|
case certificates.CertificateApproved:
|
|
hasApproved = true
|
|
if hasDenied {
|
|
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), c.Type, "Approved and Denied conditions are mutually exclusive"))
|
|
}
|
|
case certificates.CertificateDenied:
|
|
hasDenied = true
|
|
if hasApproved {
|
|
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), c.Type, "Approved and Denied conditions are mutually exclusive"))
|
|
}
|
|
}
|
|
}
|
|
|
|
if !opts.allowDuplicateConditionTypes {
|
|
if seenTypes[c.Type] {
|
|
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), c.Type))
|
|
}
|
|
seenTypes[c.Type] = true
|
|
}
|
|
}
|
|
|
|
return allErrs
|
|
}
|
|
|
|
// ensure signerName is of the form domain.com/something and up to 571 characters.
|
|
// This length and format is specified to accommodate signerNames like:
|
|
// <fqdn>/<resource-namespace>.<resource-name>.
|
|
// The max length of a FQDN is 253 characters (DNS1123Subdomain max length)
|
|
// The max length of a namespace name is 63 characters (DNS1123Label max length)
|
|
// The max length of a resource name is 253 characters (DNS1123Subdomain max length)
|
|
// We then add an additional 2 characters to account for the one '.' and one '/'.
|
|
func ValidateCertificateSigningRequestSignerName(fldPath *field.Path, signerName string) field.ErrorList {
|
|
var el field.ErrorList
|
|
if len(signerName) == 0 {
|
|
el = append(el, field.Required(fldPath, "signerName must be provided"))
|
|
return el
|
|
}
|
|
|
|
segments := strings.Split(signerName, "/")
|
|
// validate that there is one '/' in the signerName.
|
|
// we do this after validating the domain segment to provide more info to the user.
|
|
if len(segments) != 2 {
|
|
el = append(el, field.Invalid(fldPath, signerName, "must be a fully qualified domain and path of the form 'example.com/signer-name'"))
|
|
// return early here as we should not continue attempting to validate a missing or malformed path segment
|
|
// (i.e. one containing multiple or zero `/`)
|
|
return el
|
|
}
|
|
|
|
// validate that segments[0] is less than 253 characters altogether
|
|
maxDomainSegmentLength := utilvalidation.DNS1123SubdomainMaxLength
|
|
if len(segments[0]) > maxDomainSegmentLength {
|
|
el = append(el, field.TooLong(fldPath, segments[0], maxDomainSegmentLength))
|
|
}
|
|
// validate that segments[0] consists of valid DNS1123 labels separated by '.'
|
|
domainLabels := strings.Split(segments[0], ".")
|
|
for _, lbl := range domainLabels {
|
|
// use IsDNS1123Label as we want to ensure the max length of any single label in the domain
|
|
// is 63 characters
|
|
if errs := utilvalidation.IsDNS1123Label(lbl); len(errs) > 0 {
|
|
for _, err := range errs {
|
|
el = append(el, field.Invalid(fldPath, segments[0], fmt.Sprintf("validating label %q: %s", lbl, err)))
|
|
}
|
|
// if we encounter any errors whilst parsing the domain segment, break from
|
|
// validation as any further error messages will be duplicates, and non-distinguishable
|
|
// from each other, confusing users.
|
|
break
|
|
}
|
|
}
|
|
|
|
// validate that there is at least one '.' in segments[0]
|
|
if len(domainLabels) < 2 {
|
|
el = append(el, field.Invalid(fldPath, segments[0], "should be a domain with at least two segments separated by dots"))
|
|
}
|
|
|
|
// validate that segments[1] consists of valid DNS1123 subdomains separated by '.'.
|
|
pathLabels := strings.Split(segments[1], ".")
|
|
for _, lbl := range pathLabels {
|
|
// use IsDNS1123Subdomain because it enforces a length restriction of 253 characters
|
|
// which is required in order to fit a full resource name into a single 'label'
|
|
if errs := utilvalidation.IsDNS1123Subdomain(lbl); len(errs) > 0 {
|
|
for _, err := range errs {
|
|
el = append(el, field.Invalid(fldPath, segments[1], fmt.Sprintf("validating label %q: %s", lbl, err)))
|
|
}
|
|
// if we encounter any errors whilst parsing the path segment, break from
|
|
// validation as any further error messages will be duplicates, and non-distinguishable
|
|
// from each other, confusing users.
|
|
break
|
|
}
|
|
}
|
|
|
|
// ensure that segments[1] can accommodate a dns label + dns subdomain + '.'
|
|
maxPathSegmentLength := utilvalidation.DNS1123SubdomainMaxLength + utilvalidation.DNS1123LabelMaxLength + 1
|
|
maxSignerNameLength := maxDomainSegmentLength + maxPathSegmentLength + 1
|
|
if len(signerName) > maxSignerNameLength {
|
|
el = append(el, field.TooLong(fldPath, signerName, maxSignerNameLength))
|
|
}
|
|
|
|
return el
|
|
}
|
|
|
|
func ValidateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList {
|
|
opts := getValidationOptions(version, newCSR, oldCSR)
|
|
return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts)
|
|
}
|
|
|
|
func ValidateCertificateSigningRequestStatusUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList {
|
|
opts := getValidationOptions(version, newCSR, oldCSR)
|
|
opts.allowSettingCertificate = true
|
|
return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts)
|
|
}
|
|
|
|
func ValidateCertificateSigningRequestApprovalUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList {
|
|
opts := getValidationOptions(version, newCSR, oldCSR)
|
|
opts.allowSettingApprovalConditions = true
|
|
return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts)
|
|
}
|
|
|
|
func validateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, opts certificateValidationOptions) field.ErrorList {
|
|
validationErrorList := validateCertificateSigningRequest(newCSR, opts)
|
|
metaUpdateErrorList := apivalidation.ValidateObjectMetaUpdate(&newCSR.ObjectMeta, &oldCSR.ObjectMeta, field.NewPath("metadata"))
|
|
|
|
// prevent removal of existing Approved/Denied/Failed conditions
|
|
for _, t := range []certificates.RequestConditionType{certificates.CertificateApproved, certificates.CertificateDenied, certificates.CertificateFailed} {
|
|
oldConditions := findConditions(oldCSR, t)
|
|
newConditions := findConditions(newCSR, t)
|
|
if len(newConditions) < len(oldConditions) {
|
|
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not remove a condition of type %q", t)))
|
|
}
|
|
}
|
|
|
|
if !opts.allowSettingApprovalConditions {
|
|
// prevent addition/removal/modification of Approved/Denied conditions
|
|
for _, t := range []certificates.RequestConditionType{certificates.CertificateApproved, certificates.CertificateDenied} {
|
|
oldConditions := findConditions(oldCSR, t)
|
|
newConditions := findConditions(newCSR, t)
|
|
switch {
|
|
case len(newConditions) < len(oldConditions):
|
|
// removals are prevented above
|
|
case len(newConditions) > len(oldConditions):
|
|
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not add a condition of type %q", t)))
|
|
case !apiequality.Semantic.DeepEqual(oldConditions, newConditions):
|
|
conditionDiff := diff.ObjectDiff(oldConditions, newConditions)
|
|
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not modify a condition of type %q\n%v", t, conditionDiff)))
|
|
}
|
|
}
|
|
}
|
|
|
|
if !bytes.Equal(newCSR.Status.Certificate, oldCSR.Status.Certificate) {
|
|
if !opts.allowSettingCertificate {
|
|
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "certificate"), "updates may not set certificate content"))
|
|
} else if !opts.allowResettingCertificate && len(oldCSR.Status.Certificate) > 0 {
|
|
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "certificate"), "updates may not modify existing certificate content"))
|
|
}
|
|
}
|
|
|
|
return append(validationErrorList, metaUpdateErrorList...)
|
|
}
|
|
|
|
// findConditions returns all instances of conditions of the specified type
|
|
func findConditions(csr *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) []certificates.CertificateSigningRequestCondition {
|
|
var retval []certificates.CertificateSigningRequestCondition
|
|
for i, c := range csr.Status.Conditions {
|
|
if c.Type == conditionType {
|
|
retval = append(retval, csr.Status.Conditions[i])
|
|
}
|
|
}
|
|
return retval
|
|
}
|
|
|
|
// getValidationOptions returns the validation options to be
|
|
// compatible with the specified version and existing CSR.
|
|
// oldCSR may be nil if this is a create request.
|
|
// validation options related to subresource-specific capabilities are set to false.
|
|
func getValidationOptions(version schema.GroupVersion, newCSR, oldCSR *certificates.CertificateSigningRequest) certificateValidationOptions {
|
|
return certificateValidationOptions{
|
|
allowResettingCertificate: allowResettingCertificate(version),
|
|
allowBothApprovedAndDenied: allowBothApprovedAndDenied(oldCSR),
|
|
allowLegacySignerName: allowLegacySignerName(version, oldCSR),
|
|
allowDuplicateConditionTypes: allowDuplicateConditionTypes(version, oldCSR),
|
|
allowEmptyConditionType: allowEmptyConditionType(version, oldCSR),
|
|
allowArbitraryCertificate: allowArbitraryCertificate(version, newCSR, oldCSR),
|
|
allowDuplicateUsages: allowDuplicateUsages(version, oldCSR),
|
|
allowUnknownUsages: allowUnknownUsages(version, oldCSR),
|
|
}
|
|
}
|
|
|
|
func allowResettingCertificate(version schema.GroupVersion) bool {
|
|
// compatibility with v1beta1
|
|
return version == certificatesv1beta1.SchemeGroupVersion
|
|
}
|
|
|
|
func allowBothApprovedAndDenied(oldCSR *certificates.CertificateSigningRequest) bool {
|
|
if oldCSR == nil {
|
|
return false
|
|
}
|
|
approved := false
|
|
denied := false
|
|
for _, c := range oldCSR.Status.Conditions {
|
|
if c.Type == certificates.CertificateApproved {
|
|
approved = true
|
|
} else if c.Type == certificates.CertificateDenied {
|
|
denied = true
|
|
}
|
|
}
|
|
// compatibility with existing data
|
|
return approved && denied
|
|
}
|
|
|
|
func allowLegacySignerName(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool {
|
|
switch {
|
|
case version == certificatesv1beta1.SchemeGroupVersion:
|
|
return true // compatibility with v1beta1
|
|
case oldCSR != nil && oldCSR.Spec.SignerName == certificates.LegacyUnknownSignerName:
|
|
return true // compatibility with existing data
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func allowDuplicateConditionTypes(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool {
|
|
switch {
|
|
case version == certificatesv1beta1.SchemeGroupVersion:
|
|
return true // compatibility with v1beta1
|
|
case oldCSR != nil && hasDuplicateConditionTypes(oldCSR):
|
|
return true // compatibility with existing data
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
func hasDuplicateConditionTypes(csr *certificates.CertificateSigningRequest) bool {
|
|
seen := map[certificates.RequestConditionType]bool{}
|
|
for _, c := range csr.Status.Conditions {
|
|
if seen[c.Type] {
|
|
return true
|
|
}
|
|
seen[c.Type] = true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func allowEmptyConditionType(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool {
|
|
switch {
|
|
case version == certificatesv1beta1.SchemeGroupVersion:
|
|
return true // compatibility with v1beta1
|
|
case oldCSR != nil && hasEmptyConditionType(oldCSR):
|
|
return true // compatibility with existing data
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
func hasEmptyConditionType(csr *certificates.CertificateSigningRequest) bool {
|
|
for _, c := range csr.Status.Conditions {
|
|
if len(c.Type) == 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func allowArbitraryCertificate(version schema.GroupVersion, newCSR, oldCSR *certificates.CertificateSigningRequest) bool {
|
|
switch {
|
|
case version == certificatesv1beta1.SchemeGroupVersion:
|
|
return true // compatibility with v1beta1
|
|
case newCSR != nil && oldCSR != nil && bytes.Equal(newCSR.Status.Certificate, oldCSR.Status.Certificate):
|
|
return true // tolerate updates that don't touch status.certificate
|
|
case oldCSR != nil && validateCertificate(oldCSR.Status.Certificate) != nil:
|
|
return true // compatibility with existing data
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func allowUnknownUsages(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool {
|
|
switch {
|
|
case version == certificatesv1beta1.SchemeGroupVersion:
|
|
return true // compatibility with v1beta1
|
|
case oldCSR != nil && hasUnknownUsage(oldCSR.Spec.Usages):
|
|
return true // compatibility with existing data
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func hasUnknownUsage(usages []certificates.KeyUsage) bool {
|
|
for _, usage := range usages {
|
|
if !allValidUsages.Has(string(usage)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func allowDuplicateUsages(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool {
|
|
switch {
|
|
case version == certificatesv1beta1.SchemeGroupVersion:
|
|
return true // compatibility with v1beta1
|
|
case oldCSR != nil && hasDuplicateUsage(oldCSR.Spec.Usages):
|
|
return true // compatibility with existing data
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func hasDuplicateUsage(usages []certificates.KeyUsage) bool {
|
|
seen := make(map[certificates.KeyUsage]bool, len(usages))
|
|
for _, usage := range usages {
|
|
if seen[usage] {
|
|
return true
|
|
}
|
|
seen[usage] = true
|
|
}
|
|
return false
|
|
}
|