2019-01-12 04:58:27 +00:00
/ *
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 (
2020-08-10 17:43:49 +00:00
"bytes"
"crypto/x509"
"encoding/pem"
2019-01-12 04:58:27 +00:00
"fmt"
2020-03-26 21:07:15 +00:00
"strings"
2019-01-12 04:58:27 +00:00
2020-08-10 17:43:49 +00:00
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"
2020-03-26 21:07:15 +00:00
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/util/validation/field"
2020-08-10 17:43:49 +00:00
utilcert "k8s.io/client-go/util/cert"
2019-01-12 04:58:27 +00:00
"k8s.io/kubernetes/pkg/apis/certificates"
2020-08-10 17:43:49 +00:00
certificatesv1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
2019-01-12 04:58:27 +00:00
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
)
2020-08-10 17:43:49 +00:00
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
}
2019-01-12 04:58:27 +00:00
// 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 {
2020-08-10 17:43:49 +00:00
csr , err := certificates . ParseCSR ( obj . Spec . Request )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
// check that the signature is valid
err = csr . CheckSignature ( )
if err != nil {
return err
}
return nil
}
2020-08-10 17:43:49 +00:00
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
}
2019-01-12 04:58:27 +00:00
// We don't care what you call your certificate requests.
func ValidateCertificateRequestName ( name string , prefix bool ) [ ] string {
return nil
}
2020-08-10 17:43:49 +00:00
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 {
2019-01-12 04:58:27 +00:00
isNamespaced := false
allErrs := apivalidation . ValidateObjectMeta ( & csr . ObjectMeta , isNamespaced , ValidateCertificateRequestName , field . NewPath ( "metadata" ) )
specPath := field . NewPath ( "spec" )
2020-03-26 21:07:15 +00:00
err := validateCSR ( csr )
2019-01-12 04:58:27 +00:00
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" ) )
}
2020-08-10 17:43:49 +00:00
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
}
}
2019-01-12 04:58:27 +00:00
return allErrs
}
2020-03-26 21:07:15 +00:00
// 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
}
2020-08-10 17:43:49 +00:00
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 )
2019-01-12 04:58:27 +00:00
metaUpdateErrorList := apivalidation . ValidateObjectMetaUpdate ( & newCSR . ObjectMeta , & oldCSR . ObjectMeta , field . NewPath ( "metadata" ) )
2020-08-10 17:43:49 +00:00
// 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" ) )
}
}
2019-01-12 04:58:27 +00:00
return append ( validationErrorList , metaUpdateErrorList ... )
}
2020-08-10 17:43:49 +00:00
// 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
}