mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Merge pull request #90191 from liggitt/csr-status
CSR condition status, lastTransitionTime, versioned validation
This commit is contained in:
		
							
								
								
									
										10
									
								
								api/openapi-spec/swagger.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								api/openapi-spec/swagger.json
									
									
									
										generated
									
									
									
								
							@@ -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"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -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"))
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
`)
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								pkg/apis/certificates/zz_generated.deepcopy.go
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								pkg/apis/certificates/zz_generated.deepcopy.go
									
									
									
										generated
									
									
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)),
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
	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{}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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]))
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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{
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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:])
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -38,10 +38,12 @@ func (CertificateSigningRequest) SwaggerDoc() map[string]string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var map_CertificateSigningRequestCondition = map[string]string{
 | 
			
		||||
	"type":           "request approval state, currently Approved or Denied.",
 | 
			
		||||
	"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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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=="
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -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Ɨ
 | 
			
		||||
 
 | 
			
		||||
@@ -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=="
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -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Ɨ
 | 
			
		||||
@@ -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=="
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -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Ɨ
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
					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
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user