mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Merge pull request #90191 from liggitt/csr-status
CSR condition status, lastTransitionTime, versioned validation
This commit is contained in:
commit
5fb9e35e57
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"))
|
||||
}
|
||||
allErrs = append(allErrs, ValidateCertificateSigningRequestSignerName(specPath.Child("signerName"), csr.Spec.SignerName)...)
|
||||
if !opts.allowLegacySignerName && csr.Spec.SignerName == certificates.LegacyUnknownSignerName {
|
||||
allErrs = append(allErrs, field.Invalid(specPath.Child("signerName"), csr.Spec.SignerName, "the legacy signerName is not allowed via this API version"))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateCertificateSigningRequestSignerName(specPath.Child("signerName"), csr.Spec.SignerName)...)
|
||||
}
|
||||
allErrs = append(allErrs, validateConditions(field.NewPath("status", "conditions"), csr, opts)...)
|
||||
|
||||
if !opts.allowArbitraryCertificate {
|
||||
if err := validateCertificate(csr.Status.Certificate); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("status", "certificate"), "<certificate data>", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateConditions(fldPath *field.Path, csr *certificates.CertificateSigningRequest, opts certificateValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
seenTypes := map[certificates.RequestConditionType]bool{}
|
||||
hasApproved := false
|
||||
hasDenied := false
|
||||
|
||||
for i, c := range csr.Status.Conditions {
|
||||
|
||||
if !opts.allowEmptyConditionType {
|
||||
if len(c.Type) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("type"), ""))
|
||||
}
|
||||
}
|
||||
|
||||
allowedStatusValues := allStatusValues
|
||||
if trueConditionTypes.Has(string(c.Type)) {
|
||||
allowedStatusValues = trueStatusOnly
|
||||
}
|
||||
switch {
|
||||
case c.Status == "":
|
||||
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("status"), ""))
|
||||
case !allowedStatusValues.Has(string(c.Status)):
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Index(i).Child("status"), c.Status, trueConditionTypes.List()))
|
||||
}
|
||||
|
||||
if !opts.allowBothApprovedAndDenied {
|
||||
switch c.Type {
|
||||
case certificates.CertificateApproved:
|
||||
hasApproved = true
|
||||
if hasDenied {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), c.Type, "Approved and Denied conditions are mutually exclusive"))
|
||||
}
|
||||
case certificates.CertificateDenied:
|
||||
hasDenied = true
|
||||
if hasApproved {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), c.Type, "Approved and Denied conditions are mutually exclusive"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.allowDuplicateConditionTypes {
|
||||
if seenTypes[c.Type] {
|
||||
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), c.Type))
|
||||
}
|
||||
seenTypes[c.Type] = true
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
@ -140,8 +296,172 @@ func ValidateCertificateSigningRequestSignerName(fldPath *field.Path, signerName
|
||||
return el
|
||||
}
|
||||
|
||||
func ValidateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest) field.ErrorList {
|
||||
validationErrorList := ValidateCertificateSigningRequest(newCSR)
|
||||
func ValidateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList {
|
||||
opts := getValidationOptions(version, newCSR, oldCSR)
|
||||
return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts)
|
||||
}
|
||||
|
||||
func ValidateCertificateSigningRequestStatusUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList {
|
||||
opts := getValidationOptions(version, newCSR, oldCSR)
|
||||
opts.allowSettingCertificate = true
|
||||
return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts)
|
||||
}
|
||||
|
||||
func ValidateCertificateSigningRequestApprovalUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList {
|
||||
opts := getValidationOptions(version, newCSR, oldCSR)
|
||||
opts.allowSettingApprovalConditions = true
|
||||
return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts)
|
||||
}
|
||||
|
||||
func validateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, opts certificateValidationOptions) field.ErrorList {
|
||||
validationErrorList := validateCertificateSigningRequest(newCSR, opts)
|
||||
metaUpdateErrorList := apivalidation.ValidateObjectMetaUpdate(&newCSR.ObjectMeta, &oldCSR.ObjectMeta, field.NewPath("metadata"))
|
||||
|
||||
// prevent removal of existing Approved/Denied/Failed conditions
|
||||
for _, t := range []certificates.RequestConditionType{certificates.CertificateApproved, certificates.CertificateDenied, certificates.CertificateFailed} {
|
||||
oldConditions := findConditions(oldCSR, t)
|
||||
newConditions := findConditions(newCSR, t)
|
||||
if len(newConditions) < len(oldConditions) {
|
||||
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not remove a condition of type %q", t)))
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.allowSettingApprovalConditions {
|
||||
// prevent addition/removal/modification of Approved/Denied conditions
|
||||
for _, t := range []certificates.RequestConditionType{certificates.CertificateApproved, certificates.CertificateDenied} {
|
||||
oldConditions := findConditions(oldCSR, t)
|
||||
newConditions := findConditions(newCSR, t)
|
||||
switch {
|
||||
case len(newConditions) < len(oldConditions):
|
||||
// removals are prevented above
|
||||
case len(newConditions) > len(oldConditions):
|
||||
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not add a condition of type %q", t)))
|
||||
case !apiequality.Semantic.DeepEqual(oldConditions, newConditions):
|
||||
conditionDiff := diff.ObjectDiff(oldConditions, newConditions)
|
||||
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "conditions"), fmt.Sprintf("updates may not modify a condition of type %q\n%v", t, conditionDiff)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !bytes.Equal(newCSR.Status.Certificate, oldCSR.Status.Certificate) {
|
||||
if !opts.allowSettingCertificate {
|
||||
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "certificate"), "updates may not set certificate content"))
|
||||
} else if !opts.allowResettingCertificate && len(oldCSR.Status.Certificate) > 0 {
|
||||
validationErrorList = append(validationErrorList, field.Forbidden(field.NewPath("status", "certificate"), "updates may not modify existing certificate content"))
|
||||
}
|
||||
}
|
||||
|
||||
return append(validationErrorList, metaUpdateErrorList...)
|
||||
}
|
||||
|
||||
// findConditions returns all instances of conditions of the specified type
|
||||
func findConditions(csr *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) []certificates.CertificateSigningRequestCondition {
|
||||
var retval []certificates.CertificateSigningRequestCondition
|
||||
for i, c := range csr.Status.Conditions {
|
||||
if c.Type == conditionType {
|
||||
retval = append(retval, csr.Status.Conditions[i])
|
||||
}
|
||||
}
|
||||
return retval
|
||||
}
|
||||
|
||||
// getValidationOptions returns the validation options to be
|
||||
// compatible with the specified version and existing CSR.
|
||||
// oldCSR may be nil if this is a create request.
|
||||
// validation options related to subresource-specific capabilities are set to false.
|
||||
func getValidationOptions(version schema.GroupVersion, newCSR, oldCSR *certificates.CertificateSigningRequest) certificateValidationOptions {
|
||||
return certificateValidationOptions{
|
||||
allowResettingCertificate: allowResettingCertificate(version),
|
||||
allowBothApprovedAndDenied: allowBothApprovedAndDenied(oldCSR),
|
||||
allowLegacySignerName: allowLegacySignerName(version, oldCSR),
|
||||
allowDuplicateConditionTypes: allowDuplicateConditionTypes(version, oldCSR),
|
||||
allowEmptyConditionType: allowEmptyConditionType(version, oldCSR),
|
||||
allowArbitraryCertificate: allowArbitraryCertificate(version, newCSR, oldCSR),
|
||||
}
|
||||
}
|
||||
|
||||
func allowResettingCertificate(version schema.GroupVersion) bool {
|
||||
// compatibility with v1beta1
|
||||
return version == certificatesv1beta1.SchemeGroupVersion
|
||||
}
|
||||
|
||||
func allowBothApprovedAndDenied(oldCSR *certificates.CertificateSigningRequest) bool {
|
||||
if oldCSR == nil {
|
||||
return false
|
||||
}
|
||||
approved := false
|
||||
denied := false
|
||||
for _, c := range oldCSR.Status.Conditions {
|
||||
if c.Type == certificates.CertificateApproved {
|
||||
approved = true
|
||||
} else if c.Type == certificates.CertificateDenied {
|
||||
denied = true
|
||||
}
|
||||
}
|
||||
// compatibility with existing data
|
||||
return approved && denied
|
||||
}
|
||||
|
||||
func allowLegacySignerName(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool {
|
||||
switch {
|
||||
case version == certificatesv1beta1.SchemeGroupVersion:
|
||||
return true // compatibility with v1beta1
|
||||
case oldCSR != nil && oldCSR.Spec.SignerName == certificates.LegacyUnknownSignerName:
|
||||
return true // compatibility with existing data
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func allowDuplicateConditionTypes(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool {
|
||||
switch {
|
||||
case version == certificatesv1beta1.SchemeGroupVersion:
|
||||
return true // compatibility with v1beta1
|
||||
case oldCSR != nil && hasDuplicateConditionTypes(oldCSR):
|
||||
return true // compatibility with existing data
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
func hasDuplicateConditionTypes(csr *certificates.CertificateSigningRequest) bool {
|
||||
seen := map[certificates.RequestConditionType]bool{}
|
||||
for _, c := range csr.Status.Conditions {
|
||||
if seen[c.Type] {
|
||||
return true
|
||||
}
|
||||
seen[c.Type] = true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func allowEmptyConditionType(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool {
|
||||
switch {
|
||||
case version == certificatesv1beta1.SchemeGroupVersion:
|
||||
return true // compatibility with v1beta1
|
||||
case oldCSR != nil && hasEmptyConditionType(oldCSR):
|
||||
return true // compatibility with existing data
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
func hasEmptyConditionType(csr *certificates.CertificateSigningRequest) bool {
|
||||
for _, c := range csr.Status.Conditions {
|
||||
if len(c.Type) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func allowArbitraryCertificate(version schema.GroupVersion, newCSR, oldCSR *certificates.CertificateSigningRequest) bool {
|
||||
switch {
|
||||
case version == certificatesv1beta1.SchemeGroupVersion:
|
||||
return true // compatibility with v1beta1
|
||||
case newCSR != nil && oldCSR != nil && bytes.Equal(newCSR.Status.Certificate, oldCSR.Status.Certificate):
|
||||
return true // tolerate updates that don't touch status.certificate
|
||||
case oldCSR != nil && validateCertificate(oldCSR.Status.Certificate) != nil:
|
||||
return true // compatibility with existing data
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
newCSR.Status.Conditions = oldCSR.Status.Conditions
|
||||
|
||||
switch requestGroupVersion(ctx) {
|
||||
case certificatesv1beta1.SchemeGroupVersion:
|
||||
// Specifically preserve existing Approved/Denied conditions.
|
||||
// If we cannot (if the status update attempted to add/remove Approved/Denied conditions), revert to old conditions for backwards compatibility.
|
||||
if !preserveConditionInstances(newCSR, oldCSR, certificates.CertificateApproved) || !preserveConditionInstances(newCSR, oldCSR, certificates.CertificateDenied) {
|
||||
newCSR.Status.Conditions = oldCSR.Status.Conditions
|
||||
}
|
||||
default:
|
||||
// Specifically preserve existing Approved/Denied conditions.
|
||||
// Adding/removing Approved/Denied conditions will cause these to fail,
|
||||
// and the change in Approved/Denied conditions will produce a validation error
|
||||
preserveConditionInstances(newCSR, oldCSR, certificates.CertificateApproved)
|
||||
preserveConditionInstances(newCSR, oldCSR, certificates.CertificateDenied)
|
||||
}
|
||||
|
||||
populateConditionTimestamps(newCSR, oldCSR)
|
||||
}
|
||||
|
||||
// preserveConditionInstances copies instances of the the specified condition type from oldCSR to newCSR.
|
||||
// or returns false if the newCSR added or removed instances
|
||||
func preserveConditionInstances(newCSR, oldCSR *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) bool {
|
||||
oldIndices := findConditionIndices(oldCSR, conditionType)
|
||||
newIndices := findConditionIndices(newCSR, conditionType)
|
||||
if len(oldIndices) != len(newIndices) {
|
||||
// instances were added or removed, we cannot preserve the existing values
|
||||
return false
|
||||
}
|
||||
// preserve the old condition values
|
||||
for i, oldIndex := range oldIndices {
|
||||
newCSR.Status.Conditions[newIndices[i]] = oldCSR.Status.Conditions[oldIndex]
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// findConditionIndices returns the indices of instances of the specified condition type
|
||||
func findConditionIndices(csr *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) []int {
|
||||
var retval []int
|
||||
for i, c := range csr.Status.Conditions {
|
||||
if c.Type == conditionType {
|
||||
retval = append(retval, i)
|
||||
}
|
||||
}
|
||||
return retval
|
||||
}
|
||||
|
||||
// nowFunc allows overriding for unit tests
|
||||
var nowFunc = metav1.Now
|
||||
|
||||
// populateConditionTimestamps sets LastUpdateTime and LastTransitionTime in newCSR if missing
|
||||
func populateConditionTimestamps(newCSR, oldCSR *certificates.CertificateSigningRequest) {
|
||||
now := nowFunc()
|
||||
for i := range newCSR.Status.Conditions {
|
||||
if newCSR.Status.Conditions[i].LastUpdateTime.IsZero() {
|
||||
newCSR.Status.Conditions[i].LastUpdateTime = metav1.Now()
|
||||
newCSR.Status.Conditions[i].LastUpdateTime = now
|
||||
}
|
||||
|
||||
// preserve existing lastTransitionTime if the condition with this type/status already exists,
|
||||
// otherwise set to now.
|
||||
if newCSR.Status.Conditions[i].LastTransitionTime.IsZero() {
|
||||
lastTransition := now
|
||||
for _, oldCondition := range oldCSR.Status.Conditions {
|
||||
if oldCondition.Type == newCSR.Status.Conditions[i].Type &&
|
||||
oldCondition.Status == newCSR.Status.Conditions[i].Status &&
|
||||
!oldCondition.LastTransitionTime.IsZero() {
|
||||
lastTransition = oldCondition.LastTransitionTime
|
||||
break
|
||||
}
|
||||
}
|
||||
newCSR.Status.Conditions[i].LastTransitionTime = lastTransition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (csrStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
return validation.ValidateCertificateSigningRequestUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest))
|
||||
return validation.ValidateCertificateSigningRequestStatusUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest), requestGroupVersion(ctx))
|
||||
}
|
||||
|
||||
// Canonicalize normalizes the object after validation.
|
||||
@ -170,28 +236,22 @@ type csrApprovalStrategy struct {
|
||||
var ApprovalStrategy = csrApprovalStrategy{Strategy}
|
||||
|
||||
// PrepareForUpdate prepares the new certificate signing request by limiting
|
||||
// the data that is updated to only the conditions. Also, if there is no
|
||||
// existing LastUpdateTime on a condition, the current date/time will be set.
|
||||
// the data that is updated to only the conditions and populating condition timestamps
|
||||
func (csrApprovalStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||
newCSR := obj.(*certificates.CertificateSigningRequest)
|
||||
oldCSR := old.(*certificates.CertificateSigningRequest)
|
||||
|
||||
populateConditionTimestamps(newCSR, oldCSR)
|
||||
newConditions := newCSR.Status.Conditions
|
||||
|
||||
// Updating the approval should only update the conditions.
|
||||
newCSR.Spec = oldCSR.Spec
|
||||
oldCSR.Status.Conditions = newCSR.Status.Conditions
|
||||
for i := range newCSR.Status.Conditions {
|
||||
// The Conditions are an array of values, some of which may be
|
||||
// pre-existing and unaltered by this update, so a LastUpdateTime is
|
||||
// added only if one isn't already set.
|
||||
if newCSR.Status.Conditions[i].LastUpdateTime.IsZero() {
|
||||
newCSR.Status.Conditions[i].LastUpdateTime = metav1.Now()
|
||||
}
|
||||
}
|
||||
newCSR.Status = oldCSR.Status
|
||||
newCSR.Status.Conditions = newConditions
|
||||
}
|
||||
|
||||
func (csrApprovalStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
return validation.ValidateCertificateSigningRequestUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest))
|
||||
return validation.ValidateCertificateSigningRequestApprovalUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest), requestGroupVersion(ctx))
|
||||
}
|
||||
|
||||
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
||||
@ -211,3 +271,11 @@ func SelectableFields(obj *certificates.CertificateSigningRequest) fields.Set {
|
||||
}
|
||||
return generic.MergeFieldsSets(objectMetaFieldsSet, csrSpecificFieldsSet)
|
||||
}
|
||||
|
||||
// requestGroupVersion returns the group/version associated with the given context, or a zero-value group/version
|
||||
func requestGroupVersion(ctx context.Context) schema.GroupVersion {
|
||||
if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
|
||||
return schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
|
||||
}
|
||||
return schema.GroupVersion{}
|
||||
}
|
||||
|
@ -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.",
|
||||
"reason": "brief reason for the request state",
|
||||
"message": "human readable message with details about the request state",
|
||||
"lastUpdateTime": "timestamp for the last update to this condition",
|
||||
"type": "type of the condition. Known conditions include \"Approved\", \"Denied\", and \"Failed\".",
|
||||
"status": "Status of the condition, one of True, False, Unknown. Approved, Denied, and Failed conditions may not be \"False\" or \"Unknown\". Defaults to \"True\". If unset, should be treated as \"True\".",
|
||||
"reason": "brief reason for the request state",
|
||||
"message": "human readable message with details about the request state",
|
||||
"lastUpdateTime": "timestamp for the last update to this condition",
|
||||
"lastTransitionTime": "lastTransitionTime is the time the condition last transitioned from one status to another. If unset, when a new condition type is added or an existing condition's status is changed, the server defaults this to the current time.",
|
||||
}
|
||||
|
||||
func (CertificateSigningRequestCondition) SwaggerDoc() map[string]string {
|
||||
|
@ -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 {
|
||||
klog.V(2).Infof("certificate signing request %s is issued", csr.Name)
|
||||
return true, nil
|
||||
}
|
||||
klog.V(2).Infof("certificate signing request %s is approved, waiting to be issued", csr.Name)
|
||||
approved = true
|
||||
}
|
||||
}
|
||||
if approved {
|
||||
if len(csr.Status.Certificate) > 0 {
|
||||
klog.V(2).Infof("certificate signing request %s is issued", csr.Name)
|
||||
return true, nil
|
||||
}
|
||||
klog.V(2).Infof("certificate signing request %s is approved, waiting to be issued", csr.Name)
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
)
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user