Merge pull request #90191 from liggitt/csr-status

CSR condition status, lastTransitionTime, versioned validation
This commit is contained in:
Kubernetes Prow Robot 2020-06-01 23:34:15 -07:00 committed by GitHub
commit 5fb9e35e57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 2116 additions and 172 deletions

View File

@ -4347,6 +4347,10 @@
},
"io.k8s.api.certificates.v1beta1.CertificateSigningRequestCondition": {
"properties": {
"lastTransitionTime": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time",
"description": "lastTransitionTime is the time the condition last transitioned from one status to another. If unset, when a new condition type is added or an existing condition's status is changed, the server defaults this to the current time."
},
"lastUpdateTime": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time",
"description": "timestamp for the last update to this condition"
@ -4359,8 +4363,12 @@
"description": "brief reason for the request state",
"type": "string"
},
"status": {
"description": "Status of the condition, one of True, False, Unknown. Approved, Denied, and Failed conditions may not be \"False\" or \"Unknown\". Defaults to \"True\". If unset, should be treated as \"True\".",
"type": "string"
},
"type": {
"description": "request approval state, currently Approved or Denied.",
"description": "type of the condition. Known conditions include \"Approved\", \"Denied\", and \"Failed\".",
"type": "string"
}
},

View File

@ -16,6 +16,7 @@ go_library(
],
importpath = "k8s.io/kubernetes/pkg/apis/certificates",
deps = [
"//pkg/apis/core:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",

View File

@ -16,7 +16,10 @@ limitations under the License.
package certificates
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -72,6 +75,28 @@ type CertificateSigningRequestSpec struct {
Extra map[string]ExtraValue
}
// Built in signerName values that are honoured by kube-controller-manager.
// None of these usages are related to ServiceAccount token secrets
// `.data[ca.crt]` in any way.
const (
// Signs certificates that will be honored as client-certs by the
// kube-apiserver. Never auto-approved by kube-controller-manager.
KubeAPIServerClientSignerName = "kubernetes.io/kube-apiserver-client"
// Signs client certificates that will be honored as client-certs by the
// kube-apiserver for a kubelet.
// May be auto-approved by kube-controller-manager.
KubeAPIServerClientKubeletSignerName = "kubernetes.io/kube-apiserver-client-kubelet"
// Signs serving certificates that are honored as a valid kubelet serving
// certificate by the kube-apiserver, but has no other guarantees.
KubeletServingSignerName = "kubernetes.io/kubelet-serving"
// Has no guarantees for trust at all. Some distributions may honor these
// as client certs, but that behavior is not standard kubernetes behavior.
LegacyUnknownSignerName = "kubernetes.io/legacy-unknown"
)
// ExtraValue masks the value so protobuf can generate
type ExtraValue []string
@ -91,11 +116,17 @@ type RequestConditionType string
const (
CertificateApproved RequestConditionType = "Approved"
CertificateDenied RequestConditionType = "Denied"
CertificateFailed RequestConditionType = "Failed"
)
type CertificateSigningRequestCondition struct {
// request approval state, currently Approved or Denied.
// type of the condition. Known conditions include "Approved", "Denied", and "Failed".
Type RequestConditionType
// Status of the condition, one of True, False, Unknown.
// Approved, Denied, and Failed conditions may not be "False" or "Unknown".
// If unset, should be treated as "True".
// +optional
Status api.ConditionStatus
// brief reason for the request state
// +optional
Reason string
@ -105,6 +136,9 @@ type CertificateSigningRequestCondition struct {
// timestamp for the last update to this condition
// +optional
LastUpdateTime metav1.Time
// lastTransitionTime is the time the condition last transitioned from one status to another.
// +optional
LastTransitionTime metav1.Time
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -20,7 +20,9 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/apis/certificates/v1beta1",
deps = [
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/core:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",

View File

@ -18,10 +18,12 @@ package v1beta1
import (
"crypto/x509"
"fmt"
"reflect"
"strings"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
)
@ -41,6 +43,12 @@ func SetDefaults_CertificateSigningRequestSpec(obj *certificatesv1beta1.Certific
}
}
func SetDefaults_CertificateSigningRequestCondition(obj *certificatesv1beta1.CertificateSigningRequestCondition) {
if len(obj.Status) == 0 {
obj.Status = v1.ConditionTrue
}
}
// DefaultSignerNameFromSpec will determine the signerName that should be set
// by attempting to inspect the 'request' content and the spec options.
func DefaultSignerNameFromSpec(obj *certificatesv1beta1.CertificateSigningRequestSpec) string {
@ -59,18 +67,34 @@ func DefaultSignerNameFromSpec(obj *certificatesv1beta1.CertificateSigningReques
}
}
var (
organizationNotSystemNodesErr = fmt.Errorf("subject organization is not system:nodes")
commonNameNotSystemNode = fmt.Errorf("subject common name does not begin with system:node:")
dnsOrIPSANRequiredErr = fmt.Errorf("DNS or IP subjectAltName is required")
dnsSANNotAllowedErr = fmt.Errorf("DNS subjectAltNames are not allowed")
emailSANNotAllowedErr = fmt.Errorf("Email subjectAltNames are not allowed")
ipSANNotAllowedErr = fmt.Errorf("IP subjectAltNames are not allowed")
uriSANNotAllowedErr = fmt.Errorf("URI subjectAltNames are not allowed")
)
func IsKubeletServingCSR(req *x509.CertificateRequest, usages []certificatesv1beta1.KeyUsage) bool {
return ValidateKubeletServingCSR(req, usages) == nil
}
func ValidateKubeletServingCSR(req *x509.CertificateRequest, usages []certificatesv1beta1.KeyUsage) error {
if !reflect.DeepEqual([]string{"system:nodes"}, req.Subject.Organization) {
return false
return organizationNotSystemNodesErr
}
// at least one of dnsNames or ipAddresses must be specified
if len(req.DNSNames) == 0 && len(req.IPAddresses) == 0 {
return false
return dnsOrIPSANRequiredErr
}
if len(req.EmailAddresses) > 0 || len(req.URIs) > 0 {
return false
if len(req.EmailAddresses) > 0 {
return emailSANNotAllowedErr
}
if len(req.URIs) > 0 {
return uriSANNotAllowedErr
}
requiredUsages := []certificatesv1beta1.KeyUsage{
@ -79,27 +103,39 @@ func IsKubeletServingCSR(req *x509.CertificateRequest, usages []certificatesv1be
certificatesv1beta1.UsageServerAuth,
}
if !equalUnsorted(requiredUsages, usages) {
return false
return fmt.Errorf("usages did not match %v", requiredUsages)
}
if !strings.HasPrefix(req.Subject.CommonName, "system:node:") {
return false
return commonNameNotSystemNode
}
return true
return nil
}
func IsKubeletClientCSR(req *x509.CertificateRequest, usages []certificatesv1beta1.KeyUsage) bool {
return ValidateKubeletClientCSR(req, usages) == nil
}
func ValidateKubeletClientCSR(req *x509.CertificateRequest, usages []certificatesv1beta1.KeyUsage) error {
if !reflect.DeepEqual([]string{"system:nodes"}, req.Subject.Organization) {
return false
return organizationNotSystemNodesErr
}
if len(req.DNSNames) > 0 || len(req.EmailAddresses) > 0 || len(req.IPAddresses) > 0 || len(req.URIs) > 0 {
return false
if len(req.DNSNames) > 0 {
return dnsSANNotAllowedErr
}
if len(req.EmailAddresses) > 0 {
return emailSANNotAllowedErr
}
if len(req.IPAddresses) > 0 {
return ipSANNotAllowedErr
}
if len(req.URIs) > 0 {
return uriSANNotAllowedErr
}
if !strings.HasPrefix(req.Subject.CommonName, "system:node:") {
return false
return commonNameNotSystemNode
}
requiredUsages := []certificatesv1beta1.KeyUsage{
@ -108,10 +144,10 @@ func IsKubeletClientCSR(req *x509.CertificateRequest, usages []certificatesv1bet
certificatesv1beta1.UsageClientAuth,
}
if !equalUnsorted(requiredUsages, usages) {
return false
return fmt.Errorf("usages did not match %v", requiredUsages)
}
return true
return nil
}
// equalUnsorted compares two []string for equality of contents regardless of

View File

@ -24,10 +24,12 @@ import (
unsafe "unsafe"
v1beta1 "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
certificates "k8s.io/kubernetes/pkg/apis/certificates"
core "k8s.io/kubernetes/pkg/apis/core"
)
func init() {
@ -124,9 +126,11 @@ func Convert_certificates_CertificateSigningRequest_To_v1beta1_CertificateSignin
func autoConvert_v1beta1_CertificateSigningRequestCondition_To_certificates_CertificateSigningRequestCondition(in *v1beta1.CertificateSigningRequestCondition, out *certificates.CertificateSigningRequestCondition, s conversion.Scope) error {
out.Type = certificates.RequestConditionType(in.Type)
out.Status = core.ConditionStatus(in.Status)
out.Reason = in.Reason
out.Message = in.Message
out.LastUpdateTime = in.LastUpdateTime
out.LastTransitionTime = in.LastTransitionTime
return nil
}
@ -137,9 +141,11 @@ func Convert_v1beta1_CertificateSigningRequestCondition_To_certificates_Certific
func autoConvert_certificates_CertificateSigningRequestCondition_To_v1beta1_CertificateSigningRequestCondition(in *certificates.CertificateSigningRequestCondition, out *v1beta1.CertificateSigningRequestCondition, s conversion.Scope) error {
out.Type = v1beta1.RequestConditionType(in.Type)
out.Status = v1.ConditionStatus(in.Status)
out.Reason = in.Reason
out.Message = in.Message
out.LastUpdateTime = in.LastUpdateTime
out.LastTransitionTime = in.LastTransitionTime
return nil
}
@ -192,7 +198,7 @@ func Convert_certificates_CertificateSigningRequestList_To_v1beta1_CertificateSi
func autoConvert_v1beta1_CertificateSigningRequestSpec_To_certificates_CertificateSigningRequestSpec(in *v1beta1.CertificateSigningRequestSpec, out *certificates.CertificateSigningRequestSpec, s conversion.Scope) error {
out.Request = *(*[]byte)(unsafe.Pointer(&in.Request))
if err := v1.Convert_Pointer_string_To_string(&in.SignerName, &out.SignerName, s); err != nil {
if err := metav1.Convert_Pointer_string_To_string(&in.SignerName, &out.SignerName, s); err != nil {
return err
}
out.Usages = *(*[]certificates.KeyUsage)(unsafe.Pointer(&in.Usages))
@ -210,7 +216,7 @@ func Convert_v1beta1_CertificateSigningRequestSpec_To_certificates_CertificateSi
func autoConvert_certificates_CertificateSigningRequestSpec_To_v1beta1_CertificateSigningRequestSpec(in *certificates.CertificateSigningRequestSpec, out *v1beta1.CertificateSigningRequestSpec, s conversion.Scope) error {
out.Request = *(*[]byte)(unsafe.Pointer(&in.Request))
if err := v1.Convert_string_To_Pointer_string(&in.SignerName, &out.SignerName, s); err != nil {
if err := metav1.Convert_string_To_Pointer_string(&in.SignerName, &out.SignerName, s); err != nil {
return err
}
out.Usages = *(*[]v1beta1.KeyUsage)(unsafe.Pointer(&in.Usages))

View File

@ -40,6 +40,10 @@ func RegisterDefaults(scheme *runtime.Scheme) error {
func SetObjectDefaults_CertificateSigningRequest(in *v1beta1.CertificateSigningRequest) {
SetDefaults_CertificateSigningRequestSpec(&in.Spec)
for i := range in.Status.Conditions {
a := &in.Status.Conditions[i]
SetDefaults_CertificateSigningRequestCondition(a)
}
}
func SetObjectDefaults_CertificateSigningRequestList(in *v1beta1.CertificateSigningRequestList) {

View File

@ -12,9 +12,16 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/apis/certificates/validation",
deps = [
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/certificates/v1beta1:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
],
)
@ -37,7 +44,11 @@ go_test(
embed = [":go_default_library"],
deps = [
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/certificates/v1beta1:go_default_library",
"//pkg/apis/core:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)

View File

@ -17,16 +17,65 @@ 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
}
// 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.
@ -43,12 +92,55 @@ func validateCSR(obj *certificates.CertificateSigningRequest) error {
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 ValidateCertificateSigningRequest(csr *certificates.CertificateSigningRequest) field.ErrorList {
func ValidateCertificateSigningRequestCreate(csr *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList {
opts := getValidationOptions(version, csr, nil)
return validateCertificateSigningRequest(csr, opts)
}
func validateCertificateSigningRequest(csr *certificates.CertificateSigningRequest, opts certificateValidationOptions) field.ErrorList {
isNamespaced := false
allErrs := apivalidation.ValidateObjectMeta(&csr.ObjectMeta, isNamespaced, ValidateCertificateRequestName, field.NewPath("metadata"))
@ -60,7 +152,71 @@ func ValidateCertificateSigningRequest(csr *certificates.CertificateSigningReque
if len(csr.Spec.Usages) == 0 {
allErrs = append(allErrs, field.Required(specPath.Child("usages"), "usages must be provided"))
}
allErrs = append(allErrs, ValidateCertificateSigningRequestSignerName(specPath.Child("signerName"), csr.Spec.SignerName)...)
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, trueConditionTypes.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
}
@ -140,8 +296,172 @@ func ValidateCertificateSigningRequestSignerName(fldPath *field.Path, signerName
return el
}
func ValidateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest) field.ErrorList {
validationErrorList := ValidateCertificateSigningRequest(newCSR)
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),
}
}
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
}
}

View File

@ -28,9 +28,13 @@ import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/certificates"
capi "k8s.io/kubernetes/pkg/apis/certificates"
capiv1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
"k8s.io/kubernetes/pkg/apis/core"
)
var (
@ -39,7 +43,7 @@ var (
validUsages = []capi.KeyUsage{capi.UsageKeyEncipherment}
)
func TestValidateCertificateSigningRequest(t *testing.T) {
func TestValidateCertificateSigningRequestCreate(t *testing.T) {
specPath := field.NewPath("spec")
// maxLengthSignerName is a signerName that is of maximum length, utilising
// the max length specifications defined in validation.go.
@ -48,6 +52,7 @@ func TestValidateCertificateSigningRequest(t *testing.T) {
maxLengthSignerName := fmt.Sprintf("%s/%s.%s", maxLengthFQDN, repeatString("a", 63), repeatString("a", 253))
tests := map[string]struct {
csr capi.CertificateSigningRequest
gv schema.GroupVersion
errs field.ErrorList
}{
"CSR with empty request data should fail": {
@ -261,7 +266,7 @@ func TestValidateCertificateSigningRequest(t *testing.T) {
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
el := ValidateCertificateSigningRequest(&test.csr)
el := ValidateCertificateSigningRequestCreate(&test.csr, test.gv)
if !reflect.DeepEqual(el, test.errs) {
t.Errorf("returned and expected errors did not match - expected %v but got %v", test.errs.ToAggregate(), el.ToAggregate())
}
@ -306,3 +311,812 @@ func newCSRPEM(t *testing.T) []byte {
return p
}
func Test_getValidationOptions(t *testing.T) {
tests := []struct {
name string
version schema.GroupVersion
newCSR *certificates.CertificateSigningRequest
oldCSR *certificates.CertificateSigningRequest
want certificateValidationOptions
}{
{
name: "v1beta1 compatible create",
version: capiv1beta1.SchemeGroupVersion,
oldCSR: nil,
want: certificateValidationOptions{
allowResettingCertificate: true,
allowBothApprovedAndDenied: false,
allowLegacySignerName: true,
allowDuplicateConditionTypes: true,
allowEmptyConditionType: true,
allowArbitraryCertificate: true,
},
},
{
name: "v1 strict create",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: nil,
want: certificateValidationOptions{},
},
{
name: "v1beta1 compatible update",
version: capiv1beta1.SchemeGroupVersion,
oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateDenied}},
}},
want: certificateValidationOptions{
allowResettingCertificate: true,
allowBothApprovedAndDenied: true, // existing object has both approved and denied
allowLegacySignerName: true,
allowDuplicateConditionTypes: true,
allowEmptyConditionType: true,
allowArbitraryCertificate: true,
},
},
{
name: "v1 strict update",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{},
want: certificateValidationOptions{},
},
{
name: "v1 compatible update, approved+denied",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateDenied}},
}},
want: certificateValidationOptions{
allowBothApprovedAndDenied: true,
},
},
{
name: "v1 compatible update, legacy signerName",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{SignerName: capi.LegacyUnknownSignerName}},
want: certificateValidationOptions{
allowLegacySignerName: true,
},
},
{
name: "v1 compatible update, duplicate condition types",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateApproved}},
}},
want: certificateValidationOptions{
allowDuplicateConditionTypes: true,
},
},
{
name: "v1 compatible update, empty condition types",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{}},
}},
want: certificateValidationOptions{
allowEmptyConditionType: true,
},
},
{
name: "v1 compatible update, no diff to certificate",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Certificate: validCertificate,
}},
oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Certificate: validCertificate,
}},
want: certificateValidationOptions{
allowArbitraryCertificate: true,
},
},
{
name: "v1 compatible update, existing invalid certificate",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Certificate: []byte(`new - no PEM blocks`),
}},
oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Certificate: []byte(`old - no PEM blocks`),
}},
want: certificateValidationOptions{
allowArbitraryCertificate: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getValidationOptions(tt.version, tt.newCSR, tt.oldCSR); !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %#v\nwant %#v", got, tt.want)
}
})
}
}
func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
validUpdateMeta := validObjectMeta
validUpdateMeta.ResourceVersion = "1"
validUpdateMetaWithFinalizers := validUpdateMeta
validUpdateMetaWithFinalizers.Finalizers = []string{"foo"}
validSpec := capi.CertificateSigningRequestSpec{
Usages: validUsages,
Request: newCSRPEM(t),
SignerName: "example.com/something",
}
tests := []struct {
name string
newCSR *certificates.CertificateSigningRequest
oldCSR *certificates.CertificateSigningRequest
versionErrs map[string][]string
}{
{
name: "no-op",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
},
{
name: "finalizer change with invalid status",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
},
{
name: "add Approved condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not add a condition of type "Approved"`},
"v1beta1": {`status.conditions: Forbidden: updates may not add a condition of type "Approved"`},
},
},
{
name: "remove Approved condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`},
"v1beta1": {`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`},
},
},
{
name: "add Denied condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not add a condition of type "Denied"`},
"v1beta1": {`status.conditions: Forbidden: updates may not add a condition of type "Denied"`},
},
},
{
name: "remove Denied condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`},
"v1beta1": {`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`},
},
},
{
name: "add Failed condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{},
},
{
name: "remove Failed condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`},
"v1beta1": {`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`},
},
},
{
name: "set certificate",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Certificate: validCertificate,
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{
"v1": {`status.certificate: Forbidden: updates may not set certificate content`},
"v1beta1": {`status.certificate: Forbidden: updates may not set certificate content`},
},
},
}
for _, tt := range tests {
for _, version := range []string{"v1", "v1beta1"} {
t.Run(tt.name+"_"+version, func(t *testing.T) {
gotErrs := sets.NewString()
for _, err := range ValidateCertificateSigningRequestUpdate(tt.newCSR, tt.oldCSR, schema.GroupVersion{Group: certificates.GroupName, Version: version}) {
gotErrs.Insert(err.Error())
}
wantErrs := sets.NewString(tt.versionErrs[version]...)
for _, missing := range wantErrs.Difference(gotErrs).List() {
t.Errorf("missing expected error: %s", missing)
}
for _, unexpected := range gotErrs.Difference(wantErrs).List() {
t.Errorf("unexpected error: %s", unexpected)
}
})
}
}
}
func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
validUpdateMeta := validObjectMeta
validUpdateMeta.ResourceVersion = "1"
validUpdateMetaWithFinalizers := validUpdateMeta
validUpdateMetaWithFinalizers.Finalizers = []string{"foo"}
validSpec := capi.CertificateSigningRequestSpec{
Usages: validUsages,
Request: newCSRPEM(t),
SignerName: "example.com/something",
}
tests := []struct {
name string
newCSR *certificates.CertificateSigningRequest
oldCSR *certificates.CertificateSigningRequest
versionErrs map[string][]string
}{
{
name: "no-op",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
},
{
name: "finalizer change with invalid status",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
},
{
name: "add Approved condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not add a condition of type "Approved"`},
"v1beta1": {`status.conditions: Forbidden: updates may not add a condition of type "Approved"`},
},
},
{
name: "remove Approved condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`},
"v1beta1": {`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`},
},
},
{
name: "add Denied condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not add a condition of type "Denied"`},
"v1beta1": {`status.conditions: Forbidden: updates may not add a condition of type "Denied"`},
},
},
{
name: "remove Denied condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`},
"v1beta1": {`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`},
},
},
{
name: "add Failed condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{},
},
{
name: "remove Failed condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`},
"v1beta1": {`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`},
},
},
{
name: "set valid certificate",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Certificate: validCertificate,
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{},
},
{
name: "set invalid certificate",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Certificate: invalidCertificateNoPEM,
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{
"v1": {`status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`},
},
},
{
name: "reset certificate",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Certificate: invalidCertificateNonCertificatePEM,
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Certificate: invalidCertificateNoPEM,
}},
versionErrs: map[string][]string{
"v1": {`status.certificate: Forbidden: updates may not modify existing certificate content`},
},
},
}
for _, tt := range tests {
for _, version := range []string{"v1", "v1beta1"} {
t.Run(tt.name+"_"+version, func(t *testing.T) {
gotErrs := sets.NewString()
for _, err := range ValidateCertificateSigningRequestStatusUpdate(tt.newCSR, tt.oldCSR, schema.GroupVersion{Group: certificates.GroupName, Version: version}) {
gotErrs.Insert(err.Error())
}
wantErrs := sets.NewString(tt.versionErrs[version]...)
for _, missing := range wantErrs.Difference(gotErrs).List() {
t.Errorf("missing expected error: %s", missing)
}
for _, unexpected := range gotErrs.Difference(wantErrs).List() {
t.Errorf("unexpected error: %s", unexpected)
}
})
}
}
}
func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
validUpdateMeta := validObjectMeta
validUpdateMeta.ResourceVersion = "1"
validUpdateMetaWithFinalizers := validUpdateMeta
validUpdateMetaWithFinalizers.Finalizers = []string{"foo"}
validSpec := capi.CertificateSigningRequestSpec{
Usages: validUsages,
Request: newCSRPEM(t),
SignerName: "example.com/something",
}
tests := []struct {
name string
newCSR *certificates.CertificateSigningRequest
oldCSR *certificates.CertificateSigningRequest
versionErrs map[string][]string
}{
{
name: "no-op",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
},
{
name: "finalizer change with invalid certificate",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
},
{
name: "add Approved condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
},
{
name: "remove Approved condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`},
"v1beta1": {`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`},
},
},
{
name: "add Denied condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
},
{
name: "remove Denied condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`},
"v1beta1": {`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`},
},
},
{
name: "add Failed condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{},
},
{
name: "remove Failed condition",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}},
versionErrs: map[string][]string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`},
"v1beta1": {`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`},
},
},
{
name: "set certificate",
newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Certificate: validCertificate,
}},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{
"v1": {`status.certificate: Forbidden: updates may not set certificate content`},
"v1beta1": {`status.certificate: Forbidden: updates may not set certificate content`},
},
},
}
for _, tt := range tests {
for _, version := range []string{"v1", "v1beta1"} {
t.Run(tt.name+"_"+version, func(t *testing.T) {
gotErrs := sets.NewString()
for _, err := range ValidateCertificateSigningRequestApprovalUpdate(tt.newCSR, tt.oldCSR, schema.GroupVersion{Group: certificates.GroupName, Version: version}) {
gotErrs.Insert(err.Error())
}
wantErrs := sets.NewString(tt.versionErrs[version]...)
for _, missing := range wantErrs.Difference(gotErrs).List() {
t.Errorf("missing expected error: %s", missing)
}
for _, unexpected := range gotErrs.Difference(wantErrs).List() {
t.Errorf("unexpected error: %s", unexpected)
}
})
}
}
}
// Test_validateCertificateSigningRequestOptions verifies validation options are effective in tolerating specific aspects of CSRs
func Test_validateCertificateSigningRequestOptions(t *testing.T) {
validSpec := capi.CertificateSigningRequestSpec{
Usages: validUsages,
Request: newCSRPEM(t),
SignerName: "example.com/something",
}
tests := []struct {
// testcase name
name string
// csr being validated
csr *certificates.CertificateSigningRequest
// options that allow the csr to pass validation
lenientOpts certificateValidationOptions
// expected errors when validating strictly
strictErrs []string
}{
// valid strict cases
{
name: "no status",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec},
},
{
name: "approved condition",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
},
},
},
{
name: "denied condition",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
},
},
},
{
name: "failed condition",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
},
},
},
{
name: "approved+issued",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
Certificate: validCertificate,
},
},
},
// legacy signer
{
name: "legacy signer",
csr: &capi.CertificateSigningRequest{
ObjectMeta: validObjectMeta,
Spec: func() capi.CertificateSigningRequestSpec {
specCopy := validSpec
specCopy.SignerName = capi.LegacyUnknownSignerName
return specCopy
}(),
},
lenientOpts: certificateValidationOptions{allowLegacySignerName: true},
strictErrs: []string{`spec.signerName: Invalid value: "kubernetes.io/legacy-unknown": the legacy signerName is not allowed via this API version`},
},
// invalid condition cases
{
name: "empty condition type",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Status: core.ConditionTrue}},
},
},
lenientOpts: certificateValidationOptions{allowEmptyConditionType: true},
strictErrs: []string{`status.conditions[0].type: Required value`},
},
{
name: "approved and denied",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}, {Type: capi.CertificateDenied, Status: core.ConditionTrue}},
},
},
lenientOpts: certificateValidationOptions{allowBothApprovedAndDenied: true},
strictErrs: []string{`status.conditions[1].type: Invalid value: "Denied": Approved and Denied conditions are mutually exclusive`},
},
{
name: "duplicate condition",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}, {Type: capi.CertificateApproved, Status: core.ConditionTrue}},
},
},
lenientOpts: certificateValidationOptions{allowDuplicateConditionTypes: true},
strictErrs: []string{`status.conditions[1].type: Duplicate value: "Approved"`},
},
// invalid allowArbitraryCertificate cases
{
name: "status.certificate, no PEM",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
Certificate: invalidCertificateNoPEM,
},
},
lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`},
},
{
name: "status.certificate, non-CERTIFICATE PEM",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
Certificate: invalidCertificateNonCertificatePEM,
},
},
lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": only CERTIFICATE PEM blocks are allowed, found "CERTIFICATE1"`},
},
{
name: "status.certificate, PEM headers",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
Certificate: invalidCertificatePEMHeaders,
},
},
lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": no PEM block headers are permitted`},
},
{
name: "status.certificate, non-base64 PEM",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
Certificate: invalidCertificateNonBase64PEM,
},
},
lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`},
},
{
name: "status.certificate, empty PEM block",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
Certificate: invalidCertificateEmptyPEM,
},
},
lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": found CERTIFICATE PEM block containing 0 certificates`},
},
{
name: "status.certificate, non-ASN1 data",
csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
Certificate: invalidCertificateNonASN1Data,
},
},
lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
strictErrs: []string{`status.certificate: Invalid value: "<certificate data>": asn1: structure error: sequence tag mismatch`},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// make sure the lenient options validate with no errors
for _, err := range validateCertificateSigningRequest(tt.csr, tt.lenientOpts) {
t.Errorf("unexpected error with lenient options: %s", err.Error())
}
// make sure the strict options produce the expected errors
gotErrs := sets.NewString()
for _, err := range validateCertificateSigningRequest(tt.csr, certificateValidationOptions{}) {
gotErrs.Insert(err.Error())
}
wantErrs := sets.NewString(tt.strictErrs...)
for _, missing := range wantErrs.Difference(gotErrs).List() {
t.Errorf("missing expected strict error: %s", missing)
}
for _, unexpected := range gotErrs.Difference(wantErrs).List() {
t.Errorf("unexpected strict error: %s", unexpected)
}
})
}
}
var (
validCertificate = []byte(`
Leading non-PEM content
-----BEGIN CERTIFICATE-----
MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
-----END CERTIFICATE-----
Intermediate non-PEM content
-----BEGIN CERTIFICATE-----
MIIBqDCCAU6gAwIBAgIUfqZtjoFgczZ+oQZbEC/BDSS2J6wwCgYIKoZIzj0EAwIw
EjEQMA4GA1UEAxMHUm9vdC1DQTAgFw0xNjEwMTEwNTA2MDBaGA8yMTE2MDkxNzA1
MDYwMFowGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMFkwEwYHKoZIzj0CAQYI
KoZIzj0DAQcDQgAEyWHEMMCctJg8Xa5YWLqaCPbk3MjB+uvXac42JM9pj4k9jedD
kpUJRkWIPzgJI8Zk/3cSzluUTixP6JBSDKtwwaN4MHYwDgYDVR0PAQH/BAQDAgGm
MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FF+p0JcY31pz+mjNZnjv0Gum92vZMB8GA1UdIwQYMBaAFB7P6+i4/pfNjqZgJv/b
dgA7Fe4tMAoGCCqGSM49BAMCA0gAMEUCIQCTT1YWQZaAqfQ2oBxzOkJE2BqLFxhz
3smQlrZ5gCHddwIgcvT7puhYOzAgcvMn9+SZ1JOyZ7edODjshCVCRnuHK2c=
-----END CERTIFICATE-----
Trailing non-PEM content
`)
invalidCertificateNoPEM = []byte(`no PEM content`)
invalidCertificateNonCertificatePEM = []byte(`
Leading non-PEM content
-----BEGIN CERTIFICATE1-----
MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
-----END CERTIFICATE1-----
Trailing non-PEM content
`)
invalidCertificatePEMHeaders = []byte(`
Leading non-PEM content
-----BEGIN CERTIFICATE-----
Some-Header: Some-Value
MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
-----END CERTIFICATE-----
Trailing non-PEM content
`)
invalidCertificateNonBase64PEM = []byte(`
Leading non-PEM content
-----BEGIN CERTIFICATE-----
MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1d?????????
-----END CERTIFICATE-----
Trailing non-PEM content
`)
invalidCertificateEmptyPEM = []byte(`
Leading non-PEM content
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
Trailing non-PEM content
`)
// first character is invalid
invalidCertificateNonASN1Data = []byte(`
Leading non-PEM content
-----BEGIN CERTIFICATE-----
MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
MTYwOTE3MDUwNjAwWjAUNRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
-----END CERTIFICATE-----
Trailing non-PEM content
`)
)

View File

@ -56,6 +56,7 @@ func (in *CertificateSigningRequest) DeepCopyObject() runtime.Object {
func (in *CertificateSigningRequestCondition) DeepCopyInto(out *CertificateSigningRequestCondition) {
*out = *in
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}

View File

@ -16,6 +16,7 @@ go_library(
"//pkg/apis/certificates/v1beta1:go_default_library",
"//pkg/controller:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",

View File

@ -186,7 +186,7 @@ func (cc *CertificateController) syncFunc(key string) error {
return err
}
if csr.Status.Certificate != nil {
if len(csr.Status.Certificate) > 0 {
// no need to do anything because it already has a cert
return nil
}

View File

@ -16,7 +16,10 @@ limitations under the License.
package certificates
import certificates "k8s.io/api/certificates/v1beta1"
import (
certificates "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/api/core/v1"
)
// IsCertificateRequestApproved returns true if a certificate request has the
// "Approved" condition and no "Denied" conditions; false otherwise.
@ -25,6 +28,16 @@ func IsCertificateRequestApproved(csr *certificates.CertificateSigningRequest) b
return approved && !denied
}
// HasCondition returns true if the csr contains a condition of the specified type with a status that is set to True or is empty
func HasTrueCondition(csr *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) bool {
for _, c := range csr.Status.Conditions {
if c.Type == conditionType && (len(c.Status) == 0 || c.Status == v1.ConditionTrue) {
return true
}
}
return false
}
func GetCertApprovalCondition(status *certificates.CertificateSigningRequestStatus) (approved bool, denied bool) {
for _, c := range status.Conditions {
if c.Type == certificates.CertificateApproved {

View File

@ -47,6 +47,7 @@ const (
// cleaned up.
approvedExpiration = 1 * time.Hour
deniedExpiration = 1 * time.Hour
failedExpiration = 1 * time.Hour
pendingExpiration = 24 * time.Hour
)
@ -108,7 +109,7 @@ func (ccc *CSRCleanerController) handle(csr *capi.CertificateSigningRequest) err
if err != nil {
return err
}
if isIssuedPastDeadline(csr) || isDeniedPastDeadline(csr) || isPendingPastDeadline(csr) || isIssuedExpired {
if isIssuedPastDeadline(csr) || isDeniedPastDeadline(csr) || isFailedPastDeadline(csr) || isPendingPastDeadline(csr) || isIssuedExpired {
if err := ccc.csrClient.Delete(context.TODO(), csr.Name, metav1.DeleteOptions{}); err != nil {
return fmt.Errorf("unable to delete CSR %q: %v", csr.Name, err)
}
@ -158,6 +159,19 @@ func isDeniedPastDeadline(csr *capi.CertificateSigningRequest) bool {
return false
}
// isFailedPastDeadline checks if the certificate has a Failed status and the
// creation time of the CSR is passed the deadline that pending requests are
// maintained for.
func isFailedPastDeadline(csr *capi.CertificateSigningRequest) bool {
for _, c := range csr.Status.Conditions {
if c.Type == capi.CertificateFailed && isOlderThan(c.LastUpdateTime, deniedExpiration) {
klog.Infof("Cleaning CSR %q as it is more than %v old and failed.", csr.Name, deniedExpiration)
return true
}
}
return false
}
// isIssuedPastDeadline checks if the certificate has an Issued status and the
// creation time of the CSR is passed the deadline that issued requests are
// maintained for.
@ -180,13 +194,13 @@ func isOlderThan(t metav1.Time, d time.Duration) bool {
// 'Issued' status. Implicitly, if there is a certificate associated with the
// CSR, the CSR statuses that are visible via `kubectl` will include 'Issued'.
func isIssued(csr *capi.CertificateSigningRequest) bool {
return csr.Status.Certificate != nil
return len(csr.Status.Certificate) > 0
}
// isExpired checks if the CSR has a certificate and the date in the `NotAfter`
// field has gone by.
func isExpired(csr *capi.CertificateSigningRequest) (bool, error) {
if csr.Status.Certificate == nil {
if len(csr.Status.Certificate) == 0 {
return false, nil
}
block, _ := pem.Decode(csr.Status.Certificate)
@ -197,5 +211,8 @@ func isExpired(csr *capi.CertificateSigningRequest) (bool, error) {
if err != nil {
return false, fmt.Errorf("unable to parse certificate data: %v", err)
}
if len(certs) == 0 {
return false, fmt.Errorf("no certificates found")
}
return time.Now().After(certs[0].NotAfter), nil
}

View File

@ -124,6 +124,38 @@ func TestCleanerWithApprovedExpiredCSR(t *testing.T) {
},
[]string{"delete"},
},
{
"no delete failed not passed deadline",
metav1.NewTime(time.Now().Add(-1 * time.Minute)),
nil,
[]capi.CertificateSigningRequestCondition{
{
Type: capi.CertificateApproved,
LastUpdateTime: metav1.NewTime(time.Now().Add(-2 * time.Hour)),
},
{
Type: capi.CertificateFailed,
LastUpdateTime: metav1.NewTime(time.Now().Add(-50 * time.Minute)),
},
},
[]string{},
},
{
"delete failed passed deadline",
metav1.NewTime(time.Now().Add(-1 * time.Minute)),
nil,
[]capi.CertificateSigningRequestCondition{
{
Type: capi.CertificateApproved,
LastUpdateTime: metav1.NewTime(time.Now().Add(-2 * time.Hour)),
},
{
Type: capi.CertificateFailed,
LastUpdateTime: metav1.NewTime(time.Now().Add(-2 * time.Hour)),
},
},
[]string{"delete"},
},
{
"no delete pending not passed deadline",
metav1.NewTime(time.Now().Add(-5 * time.Hour)),

View File

@ -17,6 +17,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//pkg/apis/certificates/v1beta1:go_default_library",
"//pkg/controller/certificates:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/clock:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
@ -39,6 +40,7 @@ go_library(
"//pkg/controller/certificates:go_default_library",
"//pkg/controller/certificates/authority:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates:go_default_library",
"//staging/src/k8s.io/client-go/informers/certificates/v1beta1:go_default_library",

View File

@ -25,6 +25,7 @@ import (
"time"
capi "k8s.io/api/certificates/v1beta1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
certificatesinformers "k8s.io/client-go/informers/certificates/v1beta1"
@ -106,7 +107,7 @@ func (c *CSRSigningController) Run(workers int, stopCh <-chan struct{}) {
c.certificateController.Run(workers, stopCh)
}
type isRequestForSignerFunc func(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool
type isRequestForSignerFunc func(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error)
type signer struct {
caProvider *caProvider
@ -139,8 +140,8 @@ func newSigner(signerName, caFile, caKeyFile string, client clientset.Interface,
}
func (s *signer) handle(csr *capi.CertificateSigningRequest) error {
// Ignore unapproved requests
if !certificates.IsCertificateRequestApproved(csr) {
// Ignore unapproved or failed requests
if !certificates.IsCertificateRequestApproved(csr) || certificates.HasTrueCondition(csr, capi.CertificateFailed) {
return nil
}
@ -153,9 +154,21 @@ func (s *signer) handle(csr *capi.CertificateSigningRequest) error {
if err != nil {
return fmt.Errorf("unable to parse csr %q: %v", csr.Name, err)
}
if !s.isRequestForSignerFn(x509cr, csr.Spec.Usages, *csr.Spec.SignerName) {
// TODO: mark the CertificateRequest as being in a terminal state and
// communicate to the user why the request has been refused.
if recognized, err := s.isRequestForSignerFn(x509cr, csr.Spec.Usages, *csr.Spec.SignerName); err != nil {
csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
Type: capi.CertificateFailed,
Status: v1.ConditionTrue,
Reason: "SignerValidationFailure",
Message: err.Error(),
LastUpdateTime: metav1.Now(),
})
_, err = s.client.CertificatesV1beta1().CertificateSigningRequests().UpdateStatus(context.TODO(), csr, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("error adding failure condition for csr: %v", err)
}
return nil
} else if !recognized {
// Ignore requests for kubernetes.io signerNames we don't recognize
return nil
}
cert, err := s.sign(x509cr, csr.Spec.Usages)
@ -201,41 +214,41 @@ func getCSRVerificationFuncForSignerName(signerName string) (isRequestForSignerF
// TODO type this error so that a different reporting loop (one without a signing cert), can mark
// CSRs with unknown kube signers as terminal if we wish. This largely depends on how tightly we want to control
// our signerNames.
return nil, fmt.Errorf("unrecongized signerName: %q", signerName)
return nil, fmt.Errorf("unrecognized signerName: %q", signerName)
}
}
func isKubeletServing(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool {
func isKubeletServing(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) {
if signerName != capi.KubeletServingSignerName {
return false
return false, nil
}
return capihelper.IsKubeletServingCSR(req, usages)
return true, capihelper.ValidateKubeletServingCSR(req, usages)
}
func isKubeletClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool {
func isKubeletClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) {
if signerName != capi.KubeAPIServerClientKubeletSignerName {
return false
return false, nil
}
return capihelper.IsKubeletClientCSR(req, usages)
return true, capihelper.ValidateKubeletClientCSR(req, usages)
}
func isKubeAPIServerClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool {
func isKubeAPIServerClient(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) {
if signerName != capi.KubeAPIServerClientSignerName {
return false
return false, nil
}
return validAPIServerClientUsages(usages)
return true, validAPIServerClientUsages(usages)
}
func isLegacyUnknown(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) bool {
func isLegacyUnknown(req *x509.CertificateRequest, usages []capi.KeyUsage, signerName string) (bool, error) {
if signerName != capi.LegacyUnknownSignerName {
return false
return false, nil
}
// No restrictions are applied to the legacy-unknown signerName to
// maintain backward compatibility in v1beta1.
return true
return true, nil
}
func validAPIServerClientUsages(usages []capi.KeyUsage) bool {
func validAPIServerClientUsages(usages []capi.KeyUsage) error {
hasClientAuth := false
for _, u := range usages {
switch u {
@ -244,8 +257,11 @@ func validAPIServerClientUsages(usages []capi.KeyUsage) bool {
case capi.UsageClientAuth:
hasClientAuth = true
default:
return false
return fmt.Errorf("invalid usage for client certificate: %s", u)
}
}
return hasClientAuth
if !hasClientAuth {
return fmt.Errorf("missing required usage for client certificate: %s", capi.UsageClientAuth)
}
return nil
}

View File

@ -35,6 +35,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
testclient "k8s.io/client-go/testing"
"k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/controller/certificates"
capihelper "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
)
@ -114,6 +115,8 @@ func TestHandle(t *testing.T) {
usages []capi.KeyUsage
// whether the generated CSR should be marked as approved
approved bool
// whether the generated CSR should be marked as failed
failed bool
// the signerName to be set on the generated CSR
signerName string
// if true, expect an error to be returned
@ -149,10 +152,17 @@ func TestHandle(t *testing.T) {
usages: []capi.KeyUsage{capi.UsageServerAuth, capi.UsageClientAuth, capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
approved: true,
verify: func(t *testing.T, as []testclient.Action) {
if len(as) != 0 {
t.Errorf("expected no Update action but got %d", len(as))
if len(as) != 1 {
t.Errorf("expected one Update action but got %d", len(as))
return
}
csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
if len(csr.Status.Certificate) != 0 {
t.Errorf("expected no certificate to be issued")
}
if !certificates.HasTrueCondition(csr, capi.CertificateFailed) {
t.Errorf("expected Failed condition")
}
},
},
{
@ -207,6 +217,21 @@ func TestHandle(t *testing.T) {
}
},
},
{
name: "should do nothing if failed",
signerName: "kubernetes.io/kubelet-serving",
commonName: "system:node:testnode",
org: []string{"system:nodes"},
usages: []capi.KeyUsage{capi.UsageServerAuth, capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
dnsNames: []string{"example.com"},
approved: true,
failed: true,
verify: func(t *testing.T, as []testclient.Action) {
if len(as) != 0 {
t.Errorf("expected no action to be taken")
}
},
},
{
name: "should do nothing if an unrecognised signerName is used",
signerName: "kubernetes.io/not-recognised",
@ -267,7 +292,7 @@ func TestHandle(t *testing.T) {
// continue with rest of test
}
csr := makeTestCSR(csrBuilder{cn: c.commonName, signerName: c.signerName, approved: c.approved, usages: c.usages, org: c.org, dnsNames: c.dnsNames})
csr := makeTestCSR(csrBuilder{cn: c.commonName, signerName: c.signerName, approved: c.approved, failed: c.failed, usages: c.usages, org: c.org, dnsNames: c.dnsNames})
if err := s.handle(csr); err != nil && !c.err {
t.Errorf("unexpected err: %v", err)
}
@ -286,6 +311,7 @@ type csrBuilder struct {
org []string
signerName string
approved bool
failed bool
usages []capi.KeyUsage
}
@ -318,5 +344,10 @@ func makeTestCSR(b csrBuilder) *capi.CertificateSigningRequest {
Type: capi.CertificateApproved,
})
}
if b.failed {
csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
Type: capi.CertificateFailed,
})
}
return csr
}

View File

@ -174,6 +174,7 @@ func (c *fakeClient) Watch(_ context.Context, opts metav1.ListOptions) (watch.In
func (c *fakeClient) generateCSR() *certificates.CertificateSigningRequest {
var condition certificates.CertificateSigningRequestCondition
var certificateData []byte
if c.failureType == certificateSigningRequestDenied {
condition = certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateDenied,
@ -182,6 +183,7 @@ func (c *fakeClient) generateCSR() *certificates.CertificateSigningRequest {
condition = certificates.CertificateSigningRequestCondition{
Type: certificates.CertificateApproved,
}
certificateData = []byte(`issued certificate`)
}
csr := certificates.CertificateSigningRequest{
@ -192,7 +194,7 @@ func (c *fakeClient) generateCSR() *certificates.CertificateSigningRequest {
Conditions: []certificates.CertificateSigningRequestCondition{
condition,
},
Certificate: []byte{},
Certificate: certificateData,
},
}
return &csr

View File

@ -1781,10 +1781,7 @@ func printCertificateSigningRequest(obj *certificates.CertificateSigningRequest,
row := metav1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
status, err := extractCSRStatus(obj)
if err != nil {
return nil, err
}
status := extractCSRStatus(obj)
signerName := "<none>"
if obj.Spec.SignerName != "" {
signerName = obj.Spec.SignerName
@ -1793,16 +1790,16 @@ func printCertificateSigningRequest(obj *certificates.CertificateSigningRequest,
return []metav1.TableRow{row}, nil
}
func extractCSRStatus(csr *certificates.CertificateSigningRequest) (string, error) {
var approved, denied bool
func extractCSRStatus(csr *certificates.CertificateSigningRequest) string {
var approved, denied, failed bool
for _, c := range csr.Status.Conditions {
switch c.Type {
case certificates.CertificateApproved:
approved = true
case certificates.CertificateDenied:
denied = true
default:
return "", fmt.Errorf("unknown csr condition %q", c)
case certificates.CertificateFailed:
failed = true
}
}
var status string
@ -1814,10 +1811,13 @@ func extractCSRStatus(csr *certificates.CertificateSigningRequest) (string, erro
} else {
status += "Pending"
}
if failed {
status += ",Failed"
}
if len(csr.Status.Certificate) > 0 {
status += ",Issued"
}
return status, nil
return status
}
func printCertificateSigningRequestList(list *certificates.CertificateSigningRequestList, options printers.GenerateOptions) ([]metav1.TableRow, error) {

View File

@ -17,10 +17,12 @@ go_library(
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/certificates/validation:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
@ -34,6 +36,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//pkg/apis/certificates:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",

View File

@ -20,10 +20,12 @@ import (
"context"
"fmt"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/generic"
@ -94,7 +96,7 @@ func (csrStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
// Validate validates a new CSR. Validation must check for a correct signature.
func (csrStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
csr := obj.(*certificates.CertificateSigningRequest)
return validation.ValidateCertificateSigningRequest(csr)
return validation.ValidateCertificateSigningRequestCreate(csr, requestGroupVersion(ctx))
}
// Canonicalize normalizes the object after validation (which includes a signature check).
@ -104,7 +106,7 @@ func (csrStrategy) Canonicalize(obj runtime.Object) {}
func (csrStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
oldCSR := old.(*certificates.CertificateSigningRequest)
newCSR := obj.(*certificates.CertificateSigningRequest)
return validation.ValidateCertificateSigningRequestUpdate(newCSR, oldCSR)
return validation.ValidateCertificateSigningRequestUpdate(newCSR, oldCSR, requestGroupVersion(ctx))
}
// If AllowUnconditionalUpdate() is true and the object specified by
@ -142,20 +144,84 @@ func (csrStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.
newCSR := obj.(*certificates.CertificateSigningRequest)
oldCSR := old.(*certificates.CertificateSigningRequest)
// Updating the Status should only update the Status and not the spec
// or approval conditions. The intent is to separate the concerns of
// approval and certificate issuance.
// Updating /status should not modify spec
newCSR.Spec = oldCSR.Spec
newCSR.Status.Conditions = oldCSR.Status.Conditions
switch requestGroupVersion(ctx) {
case certificatesv1beta1.SchemeGroupVersion:
// Specifically preserve existing Approved/Denied conditions.
// If we cannot (if the status update attempted to add/remove Approved/Denied conditions), revert to old conditions for backwards compatibility.
if !preserveConditionInstances(newCSR, oldCSR, certificates.CertificateApproved) || !preserveConditionInstances(newCSR, oldCSR, certificates.CertificateDenied) {
newCSR.Status.Conditions = oldCSR.Status.Conditions
}
default:
// Specifically preserve existing Approved/Denied conditions.
// Adding/removing Approved/Denied conditions will cause these to fail,
// and the change in Approved/Denied conditions will produce a validation error
preserveConditionInstances(newCSR, oldCSR, certificates.CertificateApproved)
preserveConditionInstances(newCSR, oldCSR, certificates.CertificateDenied)
}
populateConditionTimestamps(newCSR, oldCSR)
}
// preserveConditionInstances copies instances of the the specified condition type from oldCSR to newCSR.
// or returns false if the newCSR added or removed instances
func preserveConditionInstances(newCSR, oldCSR *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) bool {
oldIndices := findConditionIndices(oldCSR, conditionType)
newIndices := findConditionIndices(newCSR, conditionType)
if len(oldIndices) != len(newIndices) {
// instances were added or removed, we cannot preserve the existing values
return false
}
// preserve the old condition values
for i, oldIndex := range oldIndices {
newCSR.Status.Conditions[newIndices[i]] = oldCSR.Status.Conditions[oldIndex]
}
return true
}
// findConditionIndices returns the indices of instances of the specified condition type
func findConditionIndices(csr *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) []int {
var retval []int
for i, c := range csr.Status.Conditions {
if c.Type == conditionType {
retval = append(retval, i)
}
}
return retval
}
// nowFunc allows overriding for unit tests
var nowFunc = metav1.Now
// populateConditionTimestamps sets LastUpdateTime and LastTransitionTime in newCSR if missing
func populateConditionTimestamps(newCSR, oldCSR *certificates.CertificateSigningRequest) {
now := nowFunc()
for i := range newCSR.Status.Conditions {
if newCSR.Status.Conditions[i].LastUpdateTime.IsZero() {
newCSR.Status.Conditions[i].LastUpdateTime = metav1.Now()
newCSR.Status.Conditions[i].LastUpdateTime = now
}
// preserve existing lastTransitionTime if the condition with this type/status already exists,
// otherwise set to now.
if newCSR.Status.Conditions[i].LastTransitionTime.IsZero() {
lastTransition := now
for _, oldCondition := range oldCSR.Status.Conditions {
if oldCondition.Type == newCSR.Status.Conditions[i].Type &&
oldCondition.Status == newCSR.Status.Conditions[i].Status &&
!oldCondition.LastTransitionTime.IsZero() {
lastTransition = oldCondition.LastTransitionTime
break
}
}
newCSR.Status.Conditions[i].LastTransitionTime = lastTransition
}
}
}
func (csrStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidateCertificateSigningRequestUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest))
return validation.ValidateCertificateSigningRequestStatusUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest), requestGroupVersion(ctx))
}
// Canonicalize normalizes the object after validation.
@ -170,28 +236,22 @@ type csrApprovalStrategy struct {
var ApprovalStrategy = csrApprovalStrategy{Strategy}
// PrepareForUpdate prepares the new certificate signing request by limiting
// the data that is updated to only the conditions. Also, if there is no
// existing LastUpdateTime on a condition, the current date/time will be set.
// the data that is updated to only the conditions and populating condition timestamps
func (csrApprovalStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newCSR := obj.(*certificates.CertificateSigningRequest)
oldCSR := old.(*certificates.CertificateSigningRequest)
populateConditionTimestamps(newCSR, oldCSR)
newConditions := newCSR.Status.Conditions
// Updating the approval should only update the conditions.
newCSR.Spec = oldCSR.Spec
oldCSR.Status.Conditions = newCSR.Status.Conditions
for i := range newCSR.Status.Conditions {
// The Conditions are an array of values, some of which may be
// pre-existing and unaltered by this update, so a LastUpdateTime is
// added only if one isn't already set.
if newCSR.Status.Conditions[i].LastUpdateTime.IsZero() {
newCSR.Status.Conditions[i].LastUpdateTime = metav1.Now()
}
}
newCSR.Status = oldCSR.Status
newCSR.Status.Conditions = newConditions
}
func (csrApprovalStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidateCertificateSigningRequestUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest))
return validation.ValidateCertificateSigningRequestApprovalUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest), requestGroupVersion(ctx))
}
// GetAttrs returns labels and fields of a given object for filtering purposes.
@ -211,3 +271,11 @@ func SelectableFields(obj *certificates.CertificateSigningRequest) fields.Set {
}
return generic.MergeFieldsSets(objectMetaFieldsSet, csrSpecificFieldsSet)
}
// requestGroupVersion returns the group/version associated with the given context, or a zero-value group/version
func requestGroupVersion(ctx context.Context) schema.GroupVersion {
if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
return schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
}
return schema.GroupVersion{}
}

View File

@ -20,7 +20,9 @@ import (
"context"
"reflect"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apiserver/pkg/authentication/user"
@ -121,3 +123,117 @@ func TestStrategyCreate(t *testing.T) {
}
}
}
func TestStatusUpdate(t *testing.T) {
now := metav1.Now()
later := metav1.NewTime(now.Add(time.Hour))
nowFunc = func() metav1.Time { return now }
defer func() {
nowFunc = metav1.Now
}()
tests := []struct {
name string
newObj *certapi.CertificateSigningRequest
oldObj *certapi.CertificateSigningRequest
expectedObjs map[string]*certapi.CertificateSigningRequest
}{
{
name: "no-op",
newObj: &certapi.CertificateSigningRequest{},
oldObj: &certapi.CertificateSigningRequest{},
expectedObjs: map[string]*certapi.CertificateSigningRequest{
"v1": {},
"v1beta1": {},
},
},
{
name: "adding failed condition to existing approved/denied conditions",
newObj: &certapi.CertificateSigningRequest{
Status: certapi.CertificateSigningRequestStatus{
Conditions: []certapi.CertificateSigningRequestCondition{
{Type: certapi.CertificateFailed},
{Type: certapi.CertificateDenied},
{Type: certapi.CertificateApproved},
{Type: certapi.CertificateDenied},
{Type: certapi.CertificateApproved},
},
},
},
oldObj: &certapi.CertificateSigningRequest{
Status: certapi.CertificateSigningRequestStatus{
Conditions: []certapi.CertificateSigningRequestCondition{
{Type: certapi.CertificateDenied, Reason: "because1"},
{Type: certapi.CertificateApproved, Reason: "because2"},
{Type: certapi.CertificateDenied, Reason: "because3", LastUpdateTime: later, LastTransitionTime: later},
{Type: certapi.CertificateApproved, Reason: "because4", LastUpdateTime: later, LastTransitionTime: later},
},
},
},
expectedObjs: map[string]*certapi.CertificateSigningRequest{
// preserve existing Approved/Denied conditions
"v1": {
Status: certapi.CertificateSigningRequestStatus{
Conditions: []certapi.CertificateSigningRequestCondition{
{Type: certapi.CertificateFailed, LastUpdateTime: now, LastTransitionTime: now},
{Type: certapi.CertificateDenied, LastUpdateTime: now, LastTransitionTime: later, Reason: "because1"},
{Type: certapi.CertificateApproved, LastUpdateTime: now, LastTransitionTime: later, Reason: "because2"},
{Type: certapi.CertificateDenied, LastUpdateTime: later, LastTransitionTime: later, Reason: "because3"},
{Type: certapi.CertificateApproved, LastUpdateTime: later, LastTransitionTime: later, Reason: "because4"},
},
},
},
// preserve existing Approved/Denied conditions
"v1beta1": {
Status: certapi.CertificateSigningRequestStatus{
Conditions: []certapi.CertificateSigningRequestCondition{
{Type: certapi.CertificateFailed, LastUpdateTime: now, LastTransitionTime: now},
{Type: certapi.CertificateDenied, LastUpdateTime: now, LastTransitionTime: later, Reason: "because1"},
{Type: certapi.CertificateApproved, LastUpdateTime: now, LastTransitionTime: later, Reason: "because2"},
{Type: certapi.CertificateDenied, LastUpdateTime: later, LastTransitionTime: later, Reason: "because3"},
{Type: certapi.CertificateApproved, LastUpdateTime: later, LastTransitionTime: later, Reason: "because4"},
},
},
},
},
},
{
name: "add approved condition",
newObj: &certapi.CertificateSigningRequest{
Status: certapi.CertificateSigningRequestStatus{
Conditions: []certapi.CertificateSigningRequestCondition{
{Type: certapi.CertificateApproved},
},
},
},
oldObj: &certapi.CertificateSigningRequest{},
expectedObjs: map[string]*certapi.CertificateSigningRequest{
// preserved submitted conditions if existing Approved/Denied conditions could not be copied over (will fail validation)
"v1": {
Status: certapi.CertificateSigningRequestStatus{
Conditions: []certapi.CertificateSigningRequestCondition{
{Type: certapi.CertificateApproved, LastUpdateTime: now, LastTransitionTime: now},
},
},
},
// reset conditions to existing conditions if Approved/Denied conditions could not be copied over
"v1beta1": {
Status: certapi.CertificateSigningRequestStatus{},
},
},
},
}
for _, tt := range tests {
for _, version := range []string{"v1", "v1beta1"} {
t.Run(tt.name+"_"+version, func(t *testing.T) {
ctx := genericapirequest.WithRequestInfo(context.TODO(), &genericapirequest.RequestInfo{APIGroup: "certificates.k8s.io", APIVersion: version})
obj := tt.newObj.DeepCopy()
StatusStrategy.PrepareForUpdate(ctx, obj, tt.oldObj.DeepCopy())
if !reflect.DeepEqual(obj, tt.expectedObjs[version]) {
t.Errorf("object diff: %s", diff.ObjectDiff(obj, tt.expectedObjs[version]))
}
})
}
}
}

View File

@ -8,6 +8,7 @@ go_library(
deps = [
"//pkg/apis/certificates:go_default_library",
"//plugin/pkg/admission/certificates:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",

View File

@ -24,10 +24,10 @@ import (
"k8s.io/klog/v2"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apiserver/pkg/admission"
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/authorization/authorizer"
api "k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/plugin/pkg/admission/certificates"
)
@ -73,10 +73,10 @@ func NewPlugin() *Plugin {
var csrGroupResource = api.Resource("certificatesigningrequests")
// Validate verifies that the requesting user has permission to approve
// Validate verifies that the requesting user has permission to sign
// CertificateSigningRequests for the specified signerName.
func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
// Ignore all calls to anything other than 'certificatesigningrequests/approval'.
// Ignore all calls to anything other than 'certificatesigningrequests/status'.
// Ignore all operations other than UPDATE.
if a.GetSubresource() != "status" ||
a.GetResource().GroupResource() != csrGroupResource {
@ -92,8 +92,8 @@ func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admissi
return admission.NewForbidden(a, fmt.Errorf("expected type CertificateSigningRequest, got: %T", a.GetObject()))
}
// only run if the status.certificate field has been changed
if reflect.DeepEqual(oldCSR.Status.Certificate, csr.Status.Certificate) {
// only run if the status.certificate or status.conditions field has been changed
if reflect.DeepEqual(oldCSR.Status.Certificate, csr.Status.Certificate) && apiequality.Semantic.DeepEqual(oldCSR.Status.Conditions, csr.Status.Conditions) {
return nil
}

View File

@ -48,7 +48,7 @@ func TestPlugin_Validate(t *testing.T) {
},
allowed: false,
},
"allowed if the 'certificate' field has not changed": {
"allowed if the 'certificate' and conditions field has not changed": {
attributes: &testAttributes{
resource: certificatesapi.Resource("certificatesigningrequests"),
subresource: "status",
@ -63,7 +63,7 @@ func TestPlugin_Validate(t *testing.T) {
allowed: true,
authzErr: errors.New("faked error"),
},
"deny request if authz lookup fails": {
"deny request if authz lookup fails on certificate change": {
allowedName: "abc.com/xyz",
attributes: &testAttributes{
resource: certificatesapi.Resource("certificatesigningrequests"),
@ -84,6 +84,27 @@ func TestPlugin_Validate(t *testing.T) {
authzErr: errors.New("test"),
allowed: false,
},
"deny request if authz lookup fails on condition change": {
allowedName: "abc.com/xyz",
attributes: &testAttributes{
resource: certificatesapi.Resource("certificatesigningrequests"),
subresource: "status",
oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
SignerName: "abc.com/xyz",
}},
obj: &certificatesapi.CertificateSigningRequest{
Spec: certificatesapi.CertificateSigningRequestSpec{
SignerName: "abc.com/xyz",
},
Status: certificatesapi.CertificateSigningRequestStatus{
Conditions: []certificatesapi.CertificateSigningRequestCondition{{Type: certificatesapi.CertificateFailed}},
},
},
operation: admission.Update,
},
authzErr: errors.New("test"),
allowed: false,
},
"allow request if user is authorized for specific signerName": {
allowedName: "abc.com/xyz",
attributes: &testAttributes{

View File

@ -19,6 +19,7 @@ go_library(
importmap = "k8s.io/kubernetes/vendor/k8s.io/api/certificates/v1beta1",
importpath = "k8s.io/api/certificates/v1beta1",
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",

View File

@ -27,6 +27,8 @@ import (
proto "github.com/gogo/protobuf/proto"
github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys"
k8s_io_api_core_v1 "k8s.io/api/core/v1"
math "math"
math_bits "math/bits"
reflect "reflect"
@ -227,59 +229,62 @@ func init() {
}
var fileDescriptor_09d156762b8218ef = []byte{
// 824 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0x4d, 0x6f, 0x1b, 0x45,
0x18, 0xf6, 0xfa, 0xdb, 0xe3, 0x90, 0x56, 0x23, 0x54, 0x2d, 0x91, 0xba, 0x1b, 0xad, 0x00, 0x85,
0x8f, 0xce, 0x92, 0x0a, 0x41, 0x94, 0x03, 0x82, 0x0d, 0x15, 0x44, 0xb4, 0x20, 0x4d, 0x1a, 0x0e,
0x08, 0x89, 0x8e, 0xd7, 0x6f, 0x37, 0x53, 0x77, 0x3f, 0xd8, 0x99, 0x35, 0xf8, 0xd6, 0x9f, 0xc0,
0x91, 0x0b, 0x12, 0x3f, 0x27, 0x1c, 0x90, 0x7a, 0xec, 0x01, 0x59, 0xc4, 0xdc, 0xf9, 0x01, 0x3d,
0xa1, 0x99, 0x1d, 0x7b, 0x8d, 0x23, 0xd7, 0x55, 0x73, 0xdb, 0xf7, 0x79, 0xdf, 0xe7, 0x79, 0x3f,
0x67, 0xd1, 0x97, 0xa3, 0x03, 0x41, 0x78, 0xea, 0x8f, 0x8a, 0x01, 0xe4, 0x09, 0x48, 0x10, 0xfe,
0x18, 0x92, 0x61, 0x9a, 0xfb, 0xc6, 0xc1, 0x32, 0xee, 0x87, 0x90, 0x4b, 0xfe, 0x90, 0x87, 0x4c,
0xbb, 0xf7, 0x07, 0x20, 0xd9, 0xbe, 0x1f, 0x41, 0x02, 0x39, 0x93, 0x30, 0x24, 0x59, 0x9e, 0xca,
0x14, 0xbb, 0x25, 0x81, 0xb0, 0x8c, 0x93, 0x65, 0x02, 0x31, 0x84, 0x9d, 0x5b, 0x11, 0x97, 0x67,
0xc5, 0x80, 0x84, 0x69, 0xec, 0x47, 0x69, 0x94, 0xfa, 0x9a, 0x37, 0x28, 0x1e, 0x6a, 0x4b, 0x1b,
0xfa, 0xab, 0xd4, 0xdb, 0xf9, 0xb0, 0x2a, 0x20, 0x66, 0xe1, 0x19, 0x4f, 0x20, 0x9f, 0xf8, 0xd9,
0x28, 0x52, 0x80, 0xf0, 0x63, 0x90, 0xcc, 0x1f, 0x5f, 0xaa, 0x62, 0xc7, 0x5f, 0xc7, 0xca, 0x8b,
0x44, 0xf2, 0x18, 0x2e, 0x11, 0x3e, 0xda, 0x44, 0x10, 0xe1, 0x19, 0xc4, 0x6c, 0x95, 0xe7, 0xfd,
0x51, 0x47, 0x6f, 0x1c, 0x55, 0x6d, 0x9e, 0xf0, 0x28, 0xe1, 0x49, 0x44, 0xe1, 0xc7, 0x02, 0x84,
0xc4, 0x0f, 0x50, 0x57, 0x55, 0x38, 0x64, 0x92, 0xd9, 0xd6, 0xae, 0xb5, 0xd7, 0xbf, 0xfd, 0x01,
0xa9, 0xe6, 0xb3, 0x48, 0x44, 0xb2, 0x51, 0xa4, 0x00, 0x41, 0x54, 0x34, 0x19, 0xef, 0x93, 0x6f,
0x06, 0x8f, 0x20, 0x94, 0xf7, 0x40, 0xb2, 0x00, 0x9f, 0x4f, 0xdd, 0xda, 0x6c, 0xea, 0xa2, 0x0a,
0xa3, 0x0b, 0x55, 0xfc, 0x00, 0x35, 0x45, 0x06, 0xa1, 0x5d, 0xd7, 0xea, 0x9f, 0x90, 0x0d, 0xd3,
0x27, 0x6b, 0x6b, 0x3d, 0xc9, 0x20, 0x0c, 0xb6, 0x4c, 0xae, 0xa6, 0xb2, 0xa8, 0x56, 0xc6, 0x67,
0xa8, 0x2d, 0x24, 0x93, 0x85, 0xb0, 0x1b, 0x3a, 0xc7, 0xa7, 0x57, 0xc8, 0xa1, 0x75, 0x82, 0x6d,
0x93, 0xa5, 0x5d, 0xda, 0xd4, 0xe8, 0x7b, 0xbf, 0xd5, 0x91, 0xb7, 0x96, 0x7b, 0x94, 0x26, 0x43,
0x2e, 0x79, 0x9a, 0xe0, 0x03, 0xd4, 0x94, 0x93, 0x0c, 0xf4, 0x40, 0x7b, 0xc1, 0x9b, 0xf3, 0x92,
0xef, 0x4f, 0x32, 0x78, 0x3e, 0x75, 0x5f, 0x5f, 0x8d, 0x57, 0x38, 0xd5, 0x0c, 0xfc, 0x36, 0x6a,
0xe7, 0xc0, 0x44, 0x9a, 0xe8, 0x71, 0xf5, 0xaa, 0x42, 0xa8, 0x46, 0xa9, 0xf1, 0xe2, 0x77, 0x50,
0x27, 0x06, 0x21, 0x58, 0x04, 0xba, 0xe7, 0x5e, 0x70, 0xcd, 0x04, 0x76, 0xee, 0x95, 0x30, 0x9d,
0xfb, 0xf1, 0x23, 0xb4, 0xfd, 0x98, 0x09, 0x79, 0x9a, 0x0d, 0x99, 0x84, 0xfb, 0x3c, 0x06, 0xbb,
0xa9, 0xa7, 0xf4, 0xee, 0xcb, 0xed, 0x59, 0x31, 0x82, 0x1b, 0x46, 0x7d, 0xfb, 0xee, 0xff, 0x94,
0xe8, 0x8a, 0xb2, 0x37, 0xb5, 0xd0, 0xcd, 0xb5, 0xf3, 0xb9, 0xcb, 0x85, 0xc4, 0xdf, 0x5f, 0xba,
0x37, 0xf2, 0x72, 0x75, 0x28, 0xb6, 0xbe, 0xb6, 0xeb, 0xa6, 0x96, 0xee, 0x1c, 0x59, 0xba, 0xb5,
0x1f, 0x50, 0x8b, 0x4b, 0x88, 0x85, 0x5d, 0xdf, 0x6d, 0xec, 0xf5, 0x6f, 0x1f, 0xbe, 0xfa, 0x21,
0x04, 0xaf, 0x99, 0x34, 0xad, 0x63, 0x25, 0x48, 0x4b, 0x5d, 0xef, 0xdf, 0xc6, 0x0b, 0x1a, 0x54,
0x27, 0x89, 0xdf, 0x42, 0x9d, 0xbc, 0x34, 0x75, 0x7f, 0x5b, 0x41, 0x5f, 0x6d, 0xc5, 0x44, 0xd0,
0xb9, 0x0f, 0x13, 0x84, 0x04, 0x8f, 0x12, 0xc8, 0xbf, 0x66, 0x31, 0xd8, 0x9d, 0x72, 0xd9, 0xea,
0x0d, 0x9d, 0x2c, 0x50, 0xba, 0x14, 0x81, 0x09, 0x6a, 0x17, 0x6a, 0x9d, 0xc2, 0x6e, 0xed, 0x36,
0xf6, 0x7a, 0xc1, 0x0d, 0x75, 0x14, 0xa7, 0x1a, 0x79, 0x3e, 0x75, 0xbb, 0x5f, 0xc1, 0x44, 0x1b,
0xd4, 0x44, 0xe1, 0xf7, 0x51, 0xb7, 0x10, 0x90, 0x27, 0x4a, 0xbd, 0x3c, 0xa5, 0xc5, 0xdc, 0x4e,
0x0d, 0x4e, 0x17, 0x11, 0xf8, 0x26, 0x6a, 0x14, 0x7c, 0x68, 0x4e, 0xa9, 0x6f, 0x02, 0x1b, 0xa7,
0xc7, 0x9f, 0x53, 0x85, 0x63, 0x0f, 0xb5, 0xa3, 0x3c, 0x2d, 0x32, 0x61, 0x37, 0x75, 0x72, 0xa4,
0x92, 0x7f, 0xa1, 0x11, 0x6a, 0x3c, 0x38, 0x41, 0x2d, 0xf8, 0x59, 0xe6, 0xcc, 0x6e, 0xeb, 0xd1,
0x1f, 0x5f, 0xed, 0x9d, 0x93, 0x3b, 0x4a, 0xeb, 0x4e, 0x22, 0xf3, 0x49, 0xb5, 0x09, 0x8d, 0xd1,
0x32, 0xcd, 0x0e, 0x20, 0x54, 0xc5, 0xe0, 0xeb, 0xa8, 0x31, 0x82, 0x49, 0xf9, 0xe0, 0xa8, 0xfa,
0xc4, 0x9f, 0xa1, 0xd6, 0x98, 0x3d, 0x2e, 0xc0, 0xfc, 0x77, 0xde, 0xdb, 0x58, 0x8f, 0x56, 0xfb,
0x56, 0x51, 0x68, 0xc9, 0x3c, 0xac, 0x1f, 0x58, 0xde, 0x9f, 0x16, 0x72, 0x37, 0xfc, 0x2d, 0xf0,
0x4f, 0x08, 0x85, 0xf3, 0xb7, 0x2c, 0x6c, 0x4b, 0xf7, 0x7f, 0xf4, 0xea, 0xfd, 0x2f, 0xfe, 0x0b,
0xd5, 0x8f, 0x75, 0x01, 0x09, 0xba, 0x94, 0x0a, 0xef, 0xa3, 0xfe, 0x92, 0xb4, 0xee, 0x74, 0x2b,
0xb8, 0x36, 0x9b, 0xba, 0xfd, 0x25, 0x71, 0xba, 0x1c, 0xe3, 0x7d, 0x6c, 0xc6, 0xa6, 0x1b, 0xc5,
0xee, 0xfc, 0xbd, 0x58, 0x7a, 0xaf, 0xbd, 0xd5, 0x7b, 0x3f, 0xec, 0xfe, 0xfa, 0xbb, 0x5b, 0x7b,
0xf2, 0xd7, 0x6e, 0x2d, 0xb8, 0x75, 0x7e, 0xe1, 0xd4, 0x9e, 0x5e, 0x38, 0xb5, 0x67, 0x17, 0x4e,
0xed, 0xc9, 0xcc, 0xb1, 0xce, 0x67, 0x8e, 0xf5, 0x74, 0xe6, 0x58, 0xcf, 0x66, 0x8e, 0xf5, 0xf7,
0xcc, 0xb1, 0x7e, 0xf9, 0xc7, 0xa9, 0x7d, 0xd7, 0x31, 0xdd, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff,
0x69, 0x8d, 0xc8, 0xd3, 0xaf, 0x07, 0x00, 0x00,
// 878 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x4b, 0x6f, 0x1c, 0x45,
0x10, 0xde, 0xf1, 0xbe, 0x7b, 0x8d, 0x13, 0xb5, 0x50, 0x34, 0xac, 0x94, 0x19, 0x6b, 0x04, 0xc8,
0x3c, 0xd2, 0x83, 0xa3, 0x08, 0x2c, 0x1f, 0x10, 0x8c, 0x89, 0xc0, 0xc2, 0x01, 0xa9, 0x6d, 0x73,
0x40, 0x48, 0xa4, 0x77, 0xb6, 0x32, 0xee, 0x6c, 0xe6, 0xc1, 0x74, 0xcf, 0xc2, 0xde, 0xf2, 0x13,
0x38, 0x72, 0xe4, 0xe7, 0x98, 0x03, 0x52, 0x8e, 0x39, 0xa0, 0x15, 0xde, 0xdc, 0xf9, 0x01, 0x3e,
0xa1, 0xee, 0xe9, 0x9d, 0x5d, 0xbf, 0x70, 0x48, 0x6e, 0xdb, 0x5f, 0xd7, 0xf7, 0x7d, 0x55, 0x35,
0xd5, 0xb5, 0xe8, 0xab, 0xd1, 0x96, 0x20, 0x3c, 0xf5, 0x47, 0xc5, 0x00, 0xf2, 0x04, 0x24, 0x08,
0x7f, 0x0c, 0xc9, 0x30, 0xcd, 0x7d, 0x73, 0xc1, 0x32, 0xee, 0x87, 0x90, 0x4b, 0xfe, 0x88, 0x87,
0x4c, 0x5f, 0x6f, 0x0e, 0x40, 0xb2, 0x4d, 0x3f, 0x82, 0x04, 0x72, 0x26, 0x61, 0x48, 0xb2, 0x3c,
0x95, 0x29, 0x76, 0x4b, 0x02, 0x61, 0x19, 0x27, 0xcb, 0x04, 0x62, 0x08, 0xfd, 0x3b, 0x11, 0x97,
0x47, 0xc5, 0x80, 0x84, 0x69, 0xec, 0x47, 0x69, 0x94, 0xfa, 0x9a, 0x37, 0x28, 0x1e, 0xe9, 0x93,
0x3e, 0xe8, 0x5f, 0xa5, 0x5e, 0xdf, 0x5b, 0x4e, 0x20, 0xcd, 0xc1, 0x1f, 0x5f, 0xf0, 0xec, 0xdf,
0x5b, 0xc4, 0xc4, 0x2c, 0x3c, 0xe2, 0x09, 0xe4, 0x13, 0x3f, 0x1b, 0x45, 0x0a, 0x10, 0x7e, 0x0c,
0x92, 0x5d, 0xc6, 0xf2, 0xaf, 0x62, 0xe5, 0x45, 0x22, 0x79, 0x0c, 0x17, 0x08, 0x1f, 0x5f, 0x47,
0x10, 0xe1, 0x11, 0xc4, 0xec, 0x3c, 0xcf, 0xfb, 0x63, 0x05, 0xbd, 0xb5, 0xb3, 0x68, 0xc5, 0x3e,
0x8f, 0x12, 0x9e, 0x44, 0x14, 0x7e, 0x2a, 0x40, 0x48, 0xfc, 0x10, 0x75, 0x54, 0x86, 0x43, 0x26,
0x99, 0x6d, 0xad, 0x5b, 0x1b, 0xbd, 0xbb, 0x1f, 0x91, 0x45, 0x0f, 0x2b, 0x23, 0x92, 0x8d, 0x22,
0x05, 0x08, 0xa2, 0xa2, 0xc9, 0x78, 0x93, 0x7c, 0x3b, 0x78, 0x0c, 0xa1, 0x7c, 0x00, 0x92, 0x05,
0xf8, 0x78, 0xea, 0xd6, 0x66, 0x53, 0x17, 0x2d, 0x30, 0x5a, 0xa9, 0xe2, 0x87, 0xa8, 0x21, 0x32,
0x08, 0xed, 0x15, 0xad, 0xfe, 0x29, 0xb9, 0xe6, 0x0b, 0x91, 0x2b, 0x73, 0xdd, 0xcf, 0x20, 0x0c,
0x56, 0x8d, 0x57, 0x43, 0x9d, 0xa8, 0x56, 0xc6, 0x47, 0xa8, 0x25, 0x24, 0x93, 0x85, 0xb0, 0xeb,
0xda, 0xe3, 0xb3, 0xd7, 0xf0, 0xd0, 0x3a, 0xc1, 0x9a, 0x71, 0x69, 0x95, 0x67, 0x6a, 0xf4, 0xbd,
0x17, 0x75, 0xe4, 0x5d, 0xc9, 0xdd, 0x49, 0x93, 0x21, 0x97, 0x3c, 0x4d, 0xf0, 0x16, 0x6a, 0xc8,
0x49, 0x06, 0xba, 0xa1, 0xdd, 0xe0, 0xed, 0x79, 0xca, 0x07, 0x93, 0x0c, 0x4e, 0xa7, 0xee, 0x9b,
0xe7, 0xe3, 0x15, 0x4e, 0x35, 0x03, 0xef, 0x55, 0xa5, 0xb4, 0x34, 0xf7, 0xde, 0xd9, 0x44, 0x4e,
0xa7, 0xee, 0x25, 0x13, 0x49, 0x2a, 0xa5, 0xb3, 0xe9, 0xe2, 0x77, 0x51, 0x2b, 0x07, 0x26, 0xd2,
0x44, 0x37, 0xbf, 0xbb, 0x28, 0x8b, 0x6a, 0x94, 0x9a, 0x5b, 0xfc, 0x1e, 0x6a, 0xc7, 0x20, 0x04,
0x8b, 0x40, 0x77, 0xb0, 0x1b, 0xdc, 0x30, 0x81, 0xed, 0x07, 0x25, 0x4c, 0xe7, 0xf7, 0xf8, 0x31,
0x5a, 0x7b, 0xc2, 0x84, 0x3c, 0xcc, 0x86, 0x4c, 0xc2, 0x01, 0x8f, 0xc1, 0x6e, 0xe8, 0x9e, 0xbf,
0xff, 0x72, 0x53, 0xa3, 0x18, 0xc1, 0x2d, 0xa3, 0xbe, 0xb6, 0x77, 0x46, 0x89, 0x9e, 0x53, 0xc6,
0x63, 0x84, 0x15, 0x72, 0x90, 0xb3, 0x44, 0x94, 0x8d, 0x52, 0x7e, 0xcd, 0xff, 0xed, 0xd7, 0x37,
0x7e, 0x78, 0xef, 0x82, 0x1a, 0xbd, 0xc4, 0xc1, 0x9b, 0x5a, 0xe8, 0xf6, 0x95, 0x5f, 0x79, 0x8f,
0x0b, 0x89, 0x7f, 0xb8, 0xf0, 0x6a, 0xc8, 0xcb, 0xe5, 0xa3, 0xd8, 0xfa, 0xcd, 0xdc, 0x34, 0x39,
0x75, 0xe6, 0xc8, 0xd2, 0x8b, 0xf9, 0x11, 0x35, 0xb9, 0x84, 0x58, 0xd8, 0x2b, 0xeb, 0xf5, 0x8d,
0xde, 0xdd, 0xed, 0x57, 0x1f, 0xe7, 0xe0, 0x0d, 0x63, 0xd3, 0xdc, 0x55, 0x82, 0xb4, 0xd4, 0xf5,
0xfe, 0xa9, 0xff, 0x47, 0x81, 0xea, 0x61, 0xe1, 0x77, 0x50, 0x3b, 0x2f, 0x8f, 0xba, 0xbe, 0xd5,
0xa0, 0xa7, 0xa6, 0xc1, 0x44, 0xd0, 0xf9, 0x1d, 0x26, 0x08, 0x09, 0x1e, 0x25, 0x90, 0x7f, 0xc3,
0x62, 0xb0, 0xdb, 0xe5, 0x90, 0xa9, 0x4d, 0xb0, 0x5f, 0xa1, 0x74, 0x29, 0x02, 0x13, 0xd4, 0x2a,
0xd4, 0x18, 0x09, 0xbb, 0xb9, 0x5e, 0xdf, 0xe8, 0x06, 0xb7, 0xd4, 0x30, 0x1e, 0x6a, 0xe4, 0x74,
0xea, 0x76, 0xbe, 0x86, 0x89, 0x3e, 0x50, 0x13, 0x85, 0x3f, 0x44, 0x9d, 0x42, 0x40, 0x9e, 0x28,
0xf5, 0x72, 0x84, 0xab, 0xbe, 0x1d, 0x1a, 0x9c, 0x56, 0x11, 0xf8, 0x36, 0xaa, 0x17, 0x7c, 0x68,
0x46, 0xb8, 0x67, 0x02, 0xeb, 0x87, 0xbb, 0x5f, 0x50, 0x85, 0x63, 0x0f, 0xb5, 0xa2, 0x3c, 0x2d,
0x32, 0x61, 0x37, 0xb4, 0x39, 0x52, 0xe6, 0x5f, 0x6a, 0x84, 0x9a, 0x1b, 0x9c, 0xa0, 0x26, 0xfc,
0x22, 0x73, 0x66, 0xb7, 0x74, 0xeb, 0x77, 0x5f, 0x6f, 0x5b, 0x91, 0xfb, 0x4a, 0xeb, 0x7e, 0x22,
0xf3, 0xc9, 0xe2, 0x4b, 0x68, 0x8c, 0x96, 0x36, 0x7d, 0x40, 0x68, 0x11, 0x83, 0x6f, 0xa2, 0xfa,
0x08, 0x26, 0xe5, 0xda, 0xa0, 0xea, 0x27, 0xfe, 0x1c, 0x35, 0xc7, 0xec, 0x49, 0x01, 0x66, 0x7b,
0x7e, 0x70, 0x6d, 0x3e, 0x5a, 0xed, 0x3b, 0x45, 0xa1, 0x25, 0x73, 0x7b, 0x65, 0xcb, 0xf2, 0xfe,
0xb4, 0x90, 0x7b, 0xcd, 0xce, 0xc3, 0x3f, 0x23, 0x14, 0xce, 0xf7, 0x88, 0xb0, 0x2d, 0x5d, 0xff,
0xce, 0xab, 0xd7, 0x5f, 0xed, 0xa4, 0xc5, 0xdf, 0x43, 0x05, 0x09, 0xba, 0x64, 0x85, 0x37, 0x51,
0x6f, 0x49, 0x5a, 0x57, 0xba, 0x1a, 0xdc, 0x98, 0x4d, 0xdd, 0xde, 0x92, 0x38, 0x5d, 0x8e, 0xf1,
0x3e, 0x31, 0x6d, 0xd3, 0x85, 0x62, 0x77, 0xfe, 0x5e, 0x2c, 0xfd, 0x5d, 0xbb, 0xe7, 0xe7, 0x7d,
0xbb, 0xf3, 0xdb, 0xef, 0x6e, 0xed, 0xe9, 0x5f, 0xeb, 0xb5, 0xe0, 0xce, 0xf1, 0x89, 0x53, 0x7b,
0x76, 0xe2, 0xd4, 0x9e, 0x9f, 0x38, 0xb5, 0xa7, 0x33, 0xc7, 0x3a, 0x9e, 0x39, 0xd6, 0xb3, 0x99,
0x63, 0x3d, 0x9f, 0x39, 0xd6, 0xdf, 0x33, 0xc7, 0xfa, 0xf5, 0x85, 0x53, 0xfb, 0xbe, 0x6d, 0xaa,
0xfb, 0x37, 0x00, 0x00, 0xff, 0xff, 0x21, 0x97, 0x54, 0xe9, 0x99, 0x08, 0x00, 0x00,
}
func (m *CertificateSigningRequest) Marshal() (dAtA []byte, err error) {
@ -355,6 +360,21 @@ func (m *CertificateSigningRequestCondition) MarshalToSizedBuffer(dAtA []byte) (
_ = i
var l int
_ = l
i -= len(m.Status)
copy(dAtA[i:], m.Status)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status)))
i--
dAtA[i] = 0x32
{
size, err := m.LastTransitionTime.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGenerated(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x2a
{
size, err := m.LastUpdateTime.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
@ -640,6 +660,10 @@ func (m *CertificateSigningRequestCondition) Size() (n int) {
n += 1 + l + sovGenerated(uint64(l))
l = m.LastUpdateTime.Size()
n += 1 + l + sovGenerated(uint64(l))
l = m.LastTransitionTime.Size()
n += 1 + l + sovGenerated(uint64(l))
l = len(m.Status)
n += 1 + l + sovGenerated(uint64(l))
return n
}
@ -763,6 +787,8 @@ func (this *CertificateSigningRequestCondition) String() string {
`Reason:` + fmt.Sprintf("%v", this.Reason) + `,`,
`Message:` + fmt.Sprintf("%v", this.Message) + `,`,
`LastUpdateTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastUpdateTime), "Time", "v1.Time", 1), `&`, ``, 1) + `,`,
`LastTransitionTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastTransitionTime), "Time", "v1.Time", 1), `&`, ``, 1) + `,`,
`Status:` + fmt.Sprintf("%v", this.Status) + `,`,
`}`,
}, "")
return s
@ -1143,6 +1169,71 @@ func (m *CertificateSigningRequestCondition) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field LastTransitionTime", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := m.LastTransitionTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Status = k8s_io_api_core_v1.ConditionStatus(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@ -21,6 +21,7 @@ syntax = 'proto2';
package k8s.io.api.certificates.v1beta1;
import "k8s.io/api/core/v1/generated.proto";
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
@ -43,9 +44,16 @@ message CertificateSigningRequest {
}
message CertificateSigningRequestCondition {
// request approval state, currently Approved or Denied.
// type of the condition. Known conditions include "Approved", "Denied", and "Failed".
optional string type = 1;
// Status of the condition, one of True, False, Unknown.
// Approved, Denied, and Failed conditions may not be "False" or "Unknown".
// Defaults to "True".
// If unset, should be treated as "True".
// +optional
optional string status = 6;
// brief reason for the request state
// +optional
optional string reason = 2;
@ -57,6 +65,12 @@ message CertificateSigningRequestCondition {
// timestamp for the last update to this condition
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastUpdateTime = 4;
// lastTransitionTime is the time the condition last transitioned from one status to another.
// If unset, when a new condition type is added or an existing condition's status is changed,
// the server defaults this to the current time.
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 5;
}
message CertificateSigningRequestList {

View File

@ -19,6 +19,7 @@ package v1beta1
import (
"fmt"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -134,11 +135,18 @@ type RequestConditionType string
const (
CertificateApproved RequestConditionType = "Approved"
CertificateDenied RequestConditionType = "Denied"
CertificateFailed RequestConditionType = "Failed"
)
type CertificateSigningRequestCondition struct {
// request approval state, currently Approved or Denied.
// type of the condition. Known conditions include "Approved", "Denied", and "Failed".
Type RequestConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=RequestConditionType"`
// Status of the condition, one of True, False, Unknown.
// Approved, Denied, and Failed conditions may not be "False" or "Unknown".
// Defaults to "True".
// If unset, should be treated as "True".
// +optional
Status v1.ConditionStatus `json:"status" protobuf:"bytes,6,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"`
// brief reason for the request state
// +optional
Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"`
@ -148,6 +156,11 @@ type CertificateSigningRequestCondition struct {
// timestamp for the last update to this condition
// +optional
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty" protobuf:"bytes,4,opt,name=lastUpdateTime"`
// lastTransitionTime is the time the condition last transitioned from one status to another.
// If unset, when a new condition type is added or an existing condition's status is changed,
// the server defaults this to the current time.
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,5,opt,name=lastTransitionTime"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -38,10 +38,12 @@ func (CertificateSigningRequest) SwaggerDoc() map[string]string {
}
var map_CertificateSigningRequestCondition = map[string]string{
"type": "request approval state, currently Approved or Denied.",
"reason": "brief reason for the request state",
"message": "human readable message with details about the request state",
"lastUpdateTime": "timestamp for the last update to this condition",
"type": "type of the condition. Known conditions include \"Approved\", \"Denied\", and \"Failed\".",
"status": "Status of the condition, one of True, False, Unknown. Approved, Denied, and Failed conditions may not be \"False\" or \"Unknown\". Defaults to \"True\". If unset, should be treated as \"True\".",
"reason": "brief reason for the request state",
"message": "human readable message with details about the request state",
"lastUpdateTime": "timestamp for the last update to this condition",
"lastTransitionTime": "lastTransitionTime is the time the condition last transitioned from one status to another. If unset, when a new condition type is added or an existing condition's status is changed, the server defaults this to the current time.",
}
func (CertificateSigningRequestCondition) SwaggerDoc() map[string]string {

View File

@ -56,6 +56,7 @@ func (in *CertificateSigningRequest) DeepCopyObject() runtime.Object {
func (in *CertificateSigningRequestCondition) DeepCopyInto(out *CertificateSigningRequestCondition) {
*out = *in
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}

View File

@ -61,11 +61,13 @@
"conditions": [
{
"type": "o,c鮽ort昍řČ扷5Ɨ",
"status": "ěĂ凗蓏Ŋ蛊ĉy緅縕",
"reason": "25",
"message": "26",
"lastUpdateTime": "2901-11-14T22:54:07Z"
"lastUpdateTime": "1985-03-23T14:10:57Z",
"lastTransitionTime": "2352-05-22T04:29:36Z"
}
],
"certificate": "9Q=="
"certificate": "cw=="
}
}

View File

@ -42,9 +42,11 @@ spec:
- J枊a
username: "20"
status:
certificate: 9Q==
certificate: cw==
conditions:
- lastUpdateTime: "2901-11-14T22:54:07Z"
- lastTransitionTime: "2352-05-22T04:29:36Z"
lastUpdateTime: "1985-03-23T14:10:57Z"
message: "26"
reason: "25"
status: ěĂ凗蓏Ŋ蛊ĉy緅縕
type: o,c鮽ort昍řČ扷5Ɨ

View File

@ -0,0 +1,72 @@
{
"kind": "CertificateSigningRequest",
"apiVersion": "certificates.k8s.io/v1beta1",
"metadata": {
"name": "2",
"generateName": "3",
"namespace": "4",
"selfLink": "5",
"uid": "7",
"resourceVersion": "11042405498087606203",
"generation": 8071137005907523419,
"creationTimestamp": null,
"deletionGracePeriodSeconds": -4955867275792137171,
"labels": {
"7": "8"
},
"annotations": {
"9": "10"
},
"ownerReferences": [
{
"apiVersion": "11",
"kind": "12",
"name": "13",
"uid": "Dz廔ȇ{sŊƏp",
"controller": false,
"blockOwnerDeletion": true
}
],
"finalizers": [
"14"
],
"clusterName": "15",
"managedFields": [
{
"manager": "16",
"operation": "鐊唊飙Ş-U圴÷a/ɔ}摁(湗Ć]",
"apiVersion": "17",
"fieldsType": "18"
}
]
},
"spec": {
"request": "OA==",
"usages": [
"J枊a"
],
"username": "19",
"uid": "20",
"groups": [
"21"
],
"extra": {
"22": [
"23"
]
}
},
"status": {
"conditions": [
{
"type": "o,c鮽ort昍řČ扷5Ɨ",
"status": "",
"reason": "24",
"message": "25",
"lastUpdateTime": "2901-11-14T22:54:07Z",
"lastTransitionTime": null
}
],
"certificate": "9Q=="
}
}

View File

@ -0,0 +1,51 @@
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
annotations:
"9": "10"
clusterName: "15"
creationTimestamp: null
deletionGracePeriodSeconds: -4955867275792137171
finalizers:
- "14"
generateName: "3"
generation: 8071137005907523419
labels:
"7": "8"
managedFields:
- apiVersion: "17"
fieldsType: "18"
manager: "16"
operation: 鐊唊飙Ş-U圴÷a/ɔ}摁(湗Ć]
name: "2"
namespace: "4"
ownerReferences:
- apiVersion: "11"
blockOwnerDeletion: true
controller: false
kind: "12"
name: "13"
uid: Dz廔ȇ{sŊƏp
resourceVersion: "11042405498087606203"
selfLink: "5"
uid: "7"
spec:
extra:
"22":
- "23"
groups:
- "21"
request: OA==
uid: "20"
usages:
- J枊a
username: "19"
status:
certificate: 9Q==
conditions:
- lastTransitionTime: null
lastUpdateTime: "2901-11-14T22:54:07Z"
message: "25"
reason: "24"
status: ""
type: o,c鮽ort昍řČ扷5Ɨ

View File

@ -0,0 +1,73 @@
{
"kind": "CertificateSigningRequest",
"apiVersion": "certificates.k8s.io/v1beta1",
"metadata": {
"name": "2",
"generateName": "3",
"namespace": "4",
"selfLink": "5",
"uid": "7",
"resourceVersion": "11042405498087606203",
"generation": 8071137005907523419,
"creationTimestamp": null,
"deletionGracePeriodSeconds": -4955867275792137171,
"labels": {
"7": "8"
},
"annotations": {
"9": "10"
},
"ownerReferences": [
{
"apiVersion": "11",
"kind": "12",
"name": "13",
"uid": "Dz廔ȇ{sŊƏp",
"controller": false,
"blockOwnerDeletion": true
}
],
"finalizers": [
"14"
],
"clusterName": "15",
"managedFields": [
{
"manager": "16",
"operation": "鐊唊飙Ş-U圴÷a/ɔ}摁(湗Ć]",
"apiVersion": "17",
"fieldsType": "18"
}
]
},
"spec": {
"request": "OA==",
"signerName": "19",
"usages": [
"J枊a"
],
"username": "20",
"uid": "21",
"groups": [
"22"
],
"extra": {
"23": [
"24"
]
}
},
"status": {
"conditions": [
{
"type": "o,c鮽ort昍řČ扷5Ɨ",
"status": "",
"reason": "25",
"message": "26",
"lastUpdateTime": "2901-11-14T22:54:07Z",
"lastTransitionTime": null
}
],
"certificate": "9Q=="
}
}

View File

@ -0,0 +1,52 @@
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
annotations:
"9": "10"
clusterName: "15"
creationTimestamp: null
deletionGracePeriodSeconds: -4955867275792137171
finalizers:
- "14"
generateName: "3"
generation: 8071137005907523419
labels:
"7": "8"
managedFields:
- apiVersion: "17"
fieldsType: "18"
manager: "16"
operation: 鐊唊飙Ş-U圴÷a/ɔ}摁(湗Ć]
name: "2"
namespace: "4"
ownerReferences:
- apiVersion: "11"
blockOwnerDeletion: true
controller: false
kind: "12"
name: "13"
uid: Dz廔ȇ{sŊƏp
resourceVersion: "11042405498087606203"
selfLink: "5"
uid: "7"
spec:
extra:
"23":
- "24"
groups:
- "22"
request: OA==
signerName: "19"
uid: "21"
usages:
- J枊a
username: "20"
status:
certificate: 9Q==
conditions:
- lastTransitionTime: null
lastUpdateTime: "2901-11-14T22:54:07Z"
message: "26"
reason: "25"
status: ""
type: o,c鮽ort昍řČ扷5Ɨ

View File

@ -374,6 +374,9 @@ func getCurrentCertificateOrBootstrap(
if err != nil {
return nil, false, fmt.Errorf("unable to parse certificate data: %v", err)
}
if len(certs) < 1 {
return nil, false, fmt.Errorf("no cert data found")
}
bootstrapCert.Leaf = certs[0]
if _, err := store.Update(bootstrapCertificatePEM, bootstrapKeyPEM); err != nil {

View File

@ -112,18 +112,25 @@ func WaitForCertificate(ctx context.Context, client certificatesclient.Certifica
if csr.UID != req.UID {
return false, fmt.Errorf("csr %q changed UIDs", csr.Name)
}
approved := false
for _, c := range csr.Status.Conditions {
if c.Type == certificates.CertificateDenied {
return false, fmt.Errorf("certificate signing request is not approved, reason: %v, message: %v", c.Reason, c.Message)
return false, fmt.Errorf("certificate signing request is denied, reason: %v, message: %v", c.Reason, c.Message)
}
if c.Type == certificates.CertificateFailed {
return false, fmt.Errorf("certificate signing request failed, reason: %v, message: %v", c.Reason, c.Message)
}
if c.Type == certificates.CertificateApproved {
if csr.Status.Certificate != nil {
klog.V(2).Infof("certificate signing request %s is issued", csr.Name)
return true, nil
}
klog.V(2).Infof("certificate signing request %s is approved, waiting to be issued", csr.Name)
approved = true
}
}
if approved {
if len(csr.Status.Certificate) > 0 {
klog.V(2).Infof("certificate signing request %s is issued", csr.Name)
return true, nil
}
klog.V(2).Infof("certificate signing request %s is approved, waiting to be issued", csr.Name)
}
return false, nil
},
)

View File

@ -3261,10 +3261,7 @@ func (p *CertificateSigningRequestDescriber) Describe(namespace, name string, de
if err != nil {
return "", fmt.Errorf("Error parsing CSR: %v", err)
}
status, err := extractCSRStatus(csr)
if err != nil {
return "", err
}
status := extractCSRStatus(csr)
var events *corev1.EventList
if describerSettings.ShowEvents {
@ -4843,16 +4840,16 @@ func formatEndpoints(endpoints *corev1.Endpoints, ports sets.String) string {
return ret
}
func extractCSRStatus(csr *certificatesv1beta1.CertificateSigningRequest) (string, error) {
var approved, denied bool
func extractCSRStatus(csr *certificatesv1beta1.CertificateSigningRequest) string {
var approved, denied, failed bool
for _, c := range csr.Status.Conditions {
switch c.Type {
case certificatesv1beta1.CertificateApproved:
approved = true
case certificatesv1beta1.CertificateDenied:
denied = true
default:
return "", fmt.Errorf("unknown csr condition %q", c)
case certificatesv1beta1.CertificateFailed:
failed = true
}
}
var status string
@ -4864,10 +4861,13 @@ func extractCSRStatus(csr *certificatesv1beta1.CertificateSigningRequest) (strin
} else {
status += "Pending"
}
if failed {
status += ",Failed"
}
if len(csr.Status.Certificate) > 0 {
status += ",Issued"
}
return status, nil
return status
}
// backendStringer behaves just like a string interface and converts the given backend to a string.