Drop legacy validation logic for certificates API

This commit is contained in:
Jordan Liggitt 2021-08-09 12:37:34 -04:00
parent befffd1565
commit b1d344db44
4 changed files with 175 additions and 318 deletions

View File

@ -25,14 +25,12 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
utilvalidation "k8s.io/apimachinery/pkg/util/validation" utilvalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
utilcert "k8s.io/client-go/util/cert" utilcert "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/apis/certificates"
certificatesv1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
) )
@ -139,8 +137,8 @@ func ValidateCertificateRequestName(name string, prefix bool) []string {
return nil return nil
} }
func ValidateCertificateSigningRequestCreate(csr *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList { func ValidateCertificateSigningRequestCreate(csr *certificates.CertificateSigningRequest) field.ErrorList {
opts := getValidationOptions(version, csr, nil) opts := getValidationOptions(csr, nil)
return validateCertificateSigningRequest(csr, opts) return validateCertificateSigningRequest(csr, opts)
} }
@ -347,19 +345,19 @@ func ValidateCertificateSigningRequestSignerName(fldPath *field.Path, signerName
return el return el
} }
func ValidateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList { func ValidateCertificateSigningRequestUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest) field.ErrorList {
opts := getValidationOptions(version, newCSR, oldCSR) opts := getValidationOptions(newCSR, oldCSR)
return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts) return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts)
} }
func ValidateCertificateSigningRequestStatusUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList { func ValidateCertificateSigningRequestStatusUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest) field.ErrorList {
opts := getValidationOptions(version, newCSR, oldCSR) opts := getValidationOptions(newCSR, oldCSR)
opts.allowSettingCertificate = true opts.allowSettingCertificate = true
return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts) return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts)
} }
func ValidateCertificateSigningRequestApprovalUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest, version schema.GroupVersion) field.ErrorList { func ValidateCertificateSigningRequestApprovalUpdate(newCSR, oldCSR *certificates.CertificateSigningRequest) field.ErrorList {
opts := getValidationOptions(version, newCSR, oldCSR) opts := getValidationOptions(newCSR, oldCSR)
opts.allowSettingApprovalConditions = true opts.allowSettingApprovalConditions = true
return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts) return validateCertificateSigningRequestUpdate(newCSR, oldCSR, opts)
} }
@ -420,24 +418,19 @@ func findConditions(csr *certificates.CertificateSigningRequest, conditionType c
// compatible with the specified version and existing CSR. // compatible with the specified version and existing CSR.
// oldCSR may be nil if this is a create request. // oldCSR may be nil if this is a create request.
// validation options related to subresource-specific capabilities are set to false. // validation options related to subresource-specific capabilities are set to false.
func getValidationOptions(version schema.GroupVersion, newCSR, oldCSR *certificates.CertificateSigningRequest) certificateValidationOptions { func getValidationOptions(newCSR, oldCSR *certificates.CertificateSigningRequest) certificateValidationOptions {
return certificateValidationOptions{ return certificateValidationOptions{
allowResettingCertificate: allowResettingCertificate(version), allowResettingCertificate: false,
allowBothApprovedAndDenied: allowBothApprovedAndDenied(oldCSR), allowBothApprovedAndDenied: allowBothApprovedAndDenied(oldCSR),
allowLegacySignerName: allowLegacySignerName(version, oldCSR), allowLegacySignerName: allowLegacySignerName(oldCSR),
allowDuplicateConditionTypes: allowDuplicateConditionTypes(version, oldCSR), allowDuplicateConditionTypes: allowDuplicateConditionTypes(oldCSR),
allowEmptyConditionType: allowEmptyConditionType(version, oldCSR), allowEmptyConditionType: allowEmptyConditionType(oldCSR),
allowArbitraryCertificate: allowArbitraryCertificate(version, newCSR, oldCSR), allowArbitraryCertificate: allowArbitraryCertificate(newCSR, oldCSR),
allowDuplicateUsages: allowDuplicateUsages(version, oldCSR), allowDuplicateUsages: allowDuplicateUsages(oldCSR),
allowUnknownUsages: allowUnknownUsages(version, oldCSR), allowUnknownUsages: allowUnknownUsages(oldCSR),
} }
} }
func allowResettingCertificate(version schema.GroupVersion) bool {
// compatibility with v1beta1
return version == certificatesv1beta1.SchemeGroupVersion
}
func allowBothApprovedAndDenied(oldCSR *certificates.CertificateSigningRequest) bool { func allowBothApprovedAndDenied(oldCSR *certificates.CertificateSigningRequest) bool {
if oldCSR == nil { if oldCSR == nil {
return false return false
@ -455,10 +448,8 @@ func allowBothApprovedAndDenied(oldCSR *certificates.CertificateSigningRequest)
return approved && denied return approved && denied
} }
func allowLegacySignerName(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { func allowLegacySignerName(oldCSR *certificates.CertificateSigningRequest) bool {
switch { switch {
case version == certificatesv1beta1.SchemeGroupVersion:
return true // compatibility with v1beta1
case oldCSR != nil && oldCSR.Spec.SignerName == certificates.LegacyUnknownSignerName: case oldCSR != nil && oldCSR.Spec.SignerName == certificates.LegacyUnknownSignerName:
return true // compatibility with existing data return true // compatibility with existing data
default: default:
@ -466,10 +457,8 @@ func allowLegacySignerName(version schema.GroupVersion, oldCSR *certificates.Cer
} }
} }
func allowDuplicateConditionTypes(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { func allowDuplicateConditionTypes(oldCSR *certificates.CertificateSigningRequest) bool {
switch { switch {
case version == certificatesv1beta1.SchemeGroupVersion:
return true // compatibility with v1beta1
case oldCSR != nil && hasDuplicateConditionTypes(oldCSR): case oldCSR != nil && hasDuplicateConditionTypes(oldCSR):
return true // compatibility with existing data return true // compatibility with existing data
default: default:
@ -487,10 +476,8 @@ func hasDuplicateConditionTypes(csr *certificates.CertificateSigningRequest) boo
return false return false
} }
func allowEmptyConditionType(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { func allowEmptyConditionType(oldCSR *certificates.CertificateSigningRequest) bool {
switch { switch {
case version == certificatesv1beta1.SchemeGroupVersion:
return true // compatibility with v1beta1
case oldCSR != nil && hasEmptyConditionType(oldCSR): case oldCSR != nil && hasEmptyConditionType(oldCSR):
return true // compatibility with existing data return true // compatibility with existing data
default: default:
@ -506,10 +493,8 @@ func hasEmptyConditionType(csr *certificates.CertificateSigningRequest) bool {
return false return false
} }
func allowArbitraryCertificate(version schema.GroupVersion, newCSR, oldCSR *certificates.CertificateSigningRequest) bool { func allowArbitraryCertificate(newCSR, oldCSR *certificates.CertificateSigningRequest) bool {
switch { switch {
case version == certificatesv1beta1.SchemeGroupVersion:
return true // compatibility with v1beta1
case newCSR != nil && oldCSR != nil && bytes.Equal(newCSR.Status.Certificate, oldCSR.Status.Certificate): case newCSR != nil && oldCSR != nil && bytes.Equal(newCSR.Status.Certificate, oldCSR.Status.Certificate):
return true // tolerate updates that don't touch status.certificate return true // tolerate updates that don't touch status.certificate
case oldCSR != nil && validateCertificate(oldCSR.Status.Certificate) != nil: case oldCSR != nil && validateCertificate(oldCSR.Status.Certificate) != nil:
@ -519,10 +504,8 @@ func allowArbitraryCertificate(version schema.GroupVersion, newCSR, oldCSR *cert
} }
} }
func allowUnknownUsages(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { func allowUnknownUsages(oldCSR *certificates.CertificateSigningRequest) bool {
switch { switch {
case version == certificatesv1beta1.SchemeGroupVersion:
return true // compatibility with v1beta1
case oldCSR != nil && hasUnknownUsage(oldCSR.Spec.Usages): case oldCSR != nil && hasUnknownUsage(oldCSR.Spec.Usages):
return true // compatibility with existing data return true // compatibility with existing data
default: default:
@ -539,10 +522,8 @@ func hasUnknownUsage(usages []certificates.KeyUsage) bool {
return false return false
} }
func allowDuplicateUsages(version schema.GroupVersion, oldCSR *certificates.CertificateSigningRequest) bool { func allowDuplicateUsages(oldCSR *certificates.CertificateSigningRequest) bool {
switch { switch {
case version == certificatesv1beta1.SchemeGroupVersion:
return true // compatibility with v1beta1
case oldCSR != nil && hasDuplicateUsage(oldCSR.Spec.Usages): case oldCSR != nil && hasDuplicateUsage(oldCSR.Spec.Usages):
return true // compatibility with existing data return true // compatibility with existing data
default: default:

View File

@ -29,12 +29,10 @@ import (
"time" "time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/sets"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/client-go/util/certificate/csr" "k8s.io/client-go/util/certificate/csr"
capi "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" "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
) )
@ -54,7 +52,6 @@ func TestValidateCertificateSigningRequestCreate(t *testing.T) {
maxLengthSignerName := fmt.Sprintf("%s/%s.%s", maxLengthFQDN, repeatString("a", 63), repeatString("a", 253)) maxLengthSignerName := fmt.Sprintf("%s/%s.%s", maxLengthFQDN, repeatString("a", 63), repeatString("a", 253))
tests := map[string]struct { tests := map[string]struct {
csr capi.CertificateSigningRequest csr capi.CertificateSigningRequest
gv schema.GroupVersion
errs field.ErrorList errs field.ErrorList
}{ }{
"CSR with empty request data should fail": { "CSR with empty request data should fail": {
@ -342,19 +339,7 @@ func TestValidateCertificateSigningRequestCreate(t *testing.T) {
field.Required(specPath.Child("usages"), "usages must be provided"), field.Required(specPath.Child("usages"), "usages must be provided"),
}, },
}, },
"unknown and duplicate usages - v1beta1": { "unknown and duplicate usages": {
gv: schema.GroupVersion{Group: capi.SchemeGroupVersion.Group, Version: "v1beta1"},
csr: capi.CertificateSigningRequest{
ObjectMeta: validObjectMeta,
Spec: capi.CertificateSigningRequestSpec{
Usages: []capi.KeyUsage{"unknown", "unknown"},
Request: newCSRPEM(t),
SignerName: validSignerName,
},
},
},
"unknown and duplicate usages - v1": {
gv: schema.GroupVersion{Group: capi.SchemeGroupVersion.Group, Version: "v1"},
csr: capi.CertificateSigningRequest{ csr: capi.CertificateSigningRequest{
ObjectMeta: validObjectMeta, ObjectMeta: validObjectMeta,
Spec: capi.CertificateSigningRequestSpec{ Spec: capi.CertificateSigningRequestSpec{
@ -372,7 +357,7 @@ func TestValidateCertificateSigningRequestCreate(t *testing.T) {
} }
for name, test := range tests { for name, test := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
el := ValidateCertificateSigningRequestCreate(&test.csr, test.gv) el := ValidateCertificateSigningRequestCreate(&test.csr)
if !reflect.DeepEqual(el, test.errs) { if !reflect.DeepEqual(el, test.errs) {
t.Errorf("returned and expected errors did not match - expected\n%v\nbut got\n%v", test.errs.ToAggregate(), el.ToAggregate()) t.Errorf("returned and expected errors did not match - expected\n%v\nbut got\n%v", test.errs.ToAggregate(), el.ToAggregate())
} }
@ -421,58 +406,22 @@ func newCSRPEM(t *testing.T) []byte {
func Test_getValidationOptions(t *testing.T) { func Test_getValidationOptions(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
version schema.GroupVersion
newCSR *capi.CertificateSigningRequest newCSR *capi.CertificateSigningRequest
oldCSR *capi.CertificateSigningRequest oldCSR *capi.CertificateSigningRequest
want certificateValidationOptions want certificateValidationOptions
}{ }{
{ {
name: "v1beta1 compatible create", name: "strict create",
version: capiv1beta1.SchemeGroupVersion,
oldCSR: nil,
want: certificateValidationOptions{
allowResettingCertificate: true,
allowBothApprovedAndDenied: false,
allowLegacySignerName: true,
allowDuplicateConditionTypes: true,
allowEmptyConditionType: true,
allowArbitraryCertificate: true,
allowUnknownUsages: true,
allowDuplicateUsages: true,
},
},
{
name: "v1 strict create",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: nil, oldCSR: nil,
want: certificateValidationOptions{}, want: certificateValidationOptions{},
}, },
{ {
name: "v1beta1 compatible update", name: "strict 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,
allowUnknownUsages: true,
allowDuplicateUsages: true,
},
},
{
name: "v1 strict update",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{}, oldCSR: &capi.CertificateSigningRequest{},
want: certificateValidationOptions{}, want: certificateValidationOptions{},
}, },
{ {
name: "v1 compatible update, approved+denied", name: "compatible update, approved+denied",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateDenied}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateDenied}},
}}, }},
@ -481,16 +430,14 @@ func Test_getValidationOptions(t *testing.T) {
}, },
}, },
{ {
name: "v1 compatible update, legacy signerName", name: "compatible update, legacy signerName",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{SignerName: capi.LegacyUnknownSignerName}}, oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{SignerName: capi.LegacyUnknownSignerName}},
want: certificateValidationOptions{ want: certificateValidationOptions{
allowLegacySignerName: true, allowLegacySignerName: true,
}, },
}, },
{ {
name: "v1 compatible update, duplicate condition types", name: "compatible update, duplicate condition types",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateApproved}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateApproved}},
}}, }},
@ -499,8 +446,7 @@ func Test_getValidationOptions(t *testing.T) {
}, },
}, },
{ {
name: "v1 compatible update, empty condition types", name: "compatible update, empty condition types",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{}}, Conditions: []capi.CertificateSigningRequestCondition{{}},
}}, }},
@ -509,8 +455,7 @@ func Test_getValidationOptions(t *testing.T) {
}, },
}, },
{ {
name: "v1 compatible update, no diff to certificate", name: "compatible update, no diff to certificate",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Certificate: validCertificate, Certificate: validCertificate,
}}, }},
@ -522,8 +467,7 @@ func Test_getValidationOptions(t *testing.T) {
}, },
}, },
{ {
name: "v1 compatible update, existing invalid certificate", name: "compatible update, existing invalid certificate",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{ newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
Certificate: []byte(`new - no PEM blocks`), Certificate: []byte(`new - no PEM blocks`),
}}, }},
@ -535,16 +479,14 @@ func Test_getValidationOptions(t *testing.T) {
}, },
}, },
{ {
name: "v1 compatible update, existing unknown usages", name: "compatible update, existing unknown usages",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"unknown"}}}, oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"unknown"}}},
want: certificateValidationOptions{ want: certificateValidationOptions{
allowUnknownUsages: true, allowUnknownUsages: true,
}, },
}, },
{ {
name: "v1 compatible update, existing duplicate usages", name: "compatible update, existing duplicate usages",
version: schema.GroupVersion{Group: "certificates.k8s.io", Version: "v1"},
oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"any", "any"}}}, oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"any", "any"}}},
want: certificateValidationOptions{ want: certificateValidationOptions{
allowDuplicateUsages: true, allowDuplicateUsages: true,
@ -553,7 +495,7 @@ func Test_getValidationOptions(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := getValidationOptions(tt.version, tt.newCSR, tt.oldCSR); !reflect.DeepEqual(got, tt.want) { if got := getValidationOptions(tt.newCSR, tt.oldCSR); !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %#v\nwant %#v", got, tt.want) t.Errorf("got %#v\nwant %#v", got, tt.want)
} }
}) })
@ -577,7 +519,7 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
name string name string
newCSR *capi.CertificateSigningRequest newCSR *capi.CertificateSigningRequest
oldCSR *capi.CertificateSigningRequest oldCSR *capi.CertificateSigningRequest
versionErrs map[string][]string errs []string
}{ }{
{ {
name: "no-op", name: "no-op",
@ -595,9 +537,8 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not add a condition of type "Approved"`}, `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"`},
}, },
}, },
{ {
@ -606,9 +547,8 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`}, `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"`},
}, },
}, },
{ {
@ -617,9 +557,8 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not add a condition of type "Denied"`}, `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"`},
}, },
}, },
{ {
@ -628,9 +567,8 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`}, `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"`},
}, },
}, },
{ {
@ -639,7 +577,7 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{}, errs: []string{},
}, },
{ {
name: "remove Failed condition", name: "remove Failed condition",
@ -647,9 +585,8 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`}, `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"`},
}, },
}, },
{ {
@ -658,21 +595,19 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
Certificate: validCertificate, Certificate: validCertificate,
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.certificate: Forbidden: updates may not set certificate content`}, `status.certificate: Forbidden: updates may not set certificate content`,
"v1beta1": {`status.certificate: Forbidden: updates may not set certificate content`},
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
for _, version := range []string{"v1", "v1beta1"} { t.Run(tt.name, func(t *testing.T) {
t.Run(tt.name+"_"+version, func(t *testing.T) {
gotErrs := sets.NewString() gotErrs := sets.NewString()
for _, err := range ValidateCertificateSigningRequestUpdate(tt.newCSR, tt.oldCSR, schema.GroupVersion{Group: capi.GroupName, Version: version}) { for _, err := range ValidateCertificateSigningRequestUpdate(tt.newCSR, tt.oldCSR) {
gotErrs.Insert(err.Error()) gotErrs.Insert(err.Error())
} }
wantErrs := sets.NewString(tt.versionErrs[version]...) wantErrs := sets.NewString(tt.errs...)
for _, missing := range wantErrs.Difference(gotErrs).List() { for _, missing := range wantErrs.Difference(gotErrs).List() {
t.Errorf("missing expected error: %s", missing) t.Errorf("missing expected error: %s", missing)
} }
@ -681,7 +616,6 @@ func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
} }
}) })
} }
}
} }
func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) { func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
@ -701,7 +635,7 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
name string name string
newCSR *capi.CertificateSigningRequest newCSR *capi.CertificateSigningRequest
oldCSR *capi.CertificateSigningRequest oldCSR *capi.CertificateSigningRequest
versionErrs map[string][]string errs []string
}{ }{
{ {
name: "no-op", name: "no-op",
@ -732,9 +666,8 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not add a condition of type "Approved"`}, `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"`},
}, },
}, },
{ {
@ -743,9 +676,8 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`}, `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"`},
}, },
}, },
{ {
@ -754,9 +686,8 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not add a condition of type "Denied"`}, `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"`},
}, },
}, },
{ {
@ -765,9 +696,8 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`}, `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"`},
}, },
}, },
{ {
@ -776,7 +706,7 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{}, errs: []string{},
}, },
{ {
name: "remove Failed condition", name: "remove Failed condition",
@ -784,9 +714,8 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`}, `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"`},
}, },
}, },
{ {
@ -795,7 +724,7 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
Certificate: validCertificate, Certificate: validCertificate,
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{}, errs: []string{},
}, },
{ {
name: "set invalid certificate", name: "set invalid certificate",
@ -803,8 +732,8 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
Certificate: invalidCertificateNoPEM, Certificate: invalidCertificateNoPEM,
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`}, `status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`,
}, },
}, },
{ {
@ -815,20 +744,19 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Certificate: invalidCertificateNoPEM, Certificate: invalidCertificateNoPEM,
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.certificate: Forbidden: updates may not modify existing certificate content`}, `status.certificate: Forbidden: updates may not modify existing certificate content`,
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
for _, version := range []string{"v1", "v1beta1"} { t.Run(tt.name, func(t *testing.T) {
t.Run(tt.name+"_"+version, func(t *testing.T) {
gotErrs := sets.NewString() gotErrs := sets.NewString()
for _, err := range ValidateCertificateSigningRequestStatusUpdate(tt.newCSR, tt.oldCSR, schema.GroupVersion{Group: capi.GroupName, Version: version}) { for _, err := range ValidateCertificateSigningRequestStatusUpdate(tt.newCSR, tt.oldCSR) {
gotErrs.Insert(err.Error()) gotErrs.Insert(err.Error())
} }
wantErrs := sets.NewString(tt.versionErrs[version]...) wantErrs := sets.NewString(tt.errs...)
for _, missing := range wantErrs.Difference(gotErrs).List() { for _, missing := range wantErrs.Difference(gotErrs).List() {
t.Errorf("missing expected error: %s", missing) t.Errorf("missing expected error: %s", missing)
} }
@ -837,7 +765,6 @@ func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
} }
}) })
} }
}
} }
func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) { func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
@ -857,7 +784,7 @@ func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
name string name string
newCSR *capi.CertificateSigningRequest newCSR *capi.CertificateSigningRequest
oldCSR *capi.CertificateSigningRequest oldCSR *capi.CertificateSigningRequest
versionErrs map[string][]string errs []string
}{ }{
{ {
name: "no-op", name: "no-op",
@ -882,9 +809,8 @@ func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`}, `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"`},
}, },
}, },
{ {
@ -900,9 +826,8 @@ func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`}, `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"`},
}, },
}, },
{ {
@ -911,7 +836,7 @@ func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{}, errs: []string{},
}, },
{ {
name: "remove Failed condition", name: "remove Failed condition",
@ -919,9 +844,8 @@ func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{ oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}}, Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
}}, }},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`}, `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"`},
}, },
}, },
{ {
@ -930,21 +854,19 @@ func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
Certificate: validCertificate, Certificate: validCertificate,
}}, }},
oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec}, oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
versionErrs: map[string][]string{ errs: []string{
"v1": {`status.certificate: Forbidden: updates may not set certificate content`}, `status.certificate: Forbidden: updates may not set certificate content`,
"v1beta1": {`status.certificate: Forbidden: updates may not set certificate content`},
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
for _, version := range []string{"v1", "v1beta1"} { t.Run(tt.name, func(t *testing.T) {
t.Run(tt.name+"_"+version, func(t *testing.T) {
gotErrs := sets.NewString() gotErrs := sets.NewString()
for _, err := range ValidateCertificateSigningRequestApprovalUpdate(tt.newCSR, tt.oldCSR, schema.GroupVersion{Group: capi.GroupName, Version: version}) { for _, err := range ValidateCertificateSigningRequestApprovalUpdate(tt.newCSR, tt.oldCSR) {
gotErrs.Insert(err.Error()) gotErrs.Insert(err.Error())
} }
wantErrs := sets.NewString(tt.versionErrs[version]...) wantErrs := sets.NewString(tt.errs...)
for _, missing := range wantErrs.Difference(gotErrs).List() { for _, missing := range wantErrs.Difference(gotErrs).List() {
t.Errorf("missing expected error: %s", missing) t.Errorf("missing expected error: %s", missing)
} }
@ -953,7 +875,6 @@ func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
} }
}) })
} }
}
} }
// Test_validateCertificateSigningRequestOptions verifies validation options are effective in tolerating specific aspects of CSRs // Test_validateCertificateSigningRequestOptions verifies validation options are effective in tolerating specific aspects of CSRs

View File

@ -20,12 +20,10 @@ import (
"context" "context"
"fmt" "fmt"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/generic"
@ -120,7 +118,7 @@ func (csrStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
// Validate validates a new CSR. Validation must check for a correct signature. // Validate validates a new CSR. Validation must check for a correct signature.
func (csrStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { func (csrStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
csr := obj.(*certificates.CertificateSigningRequest) csr := obj.(*certificates.CertificateSigningRequest)
return validation.ValidateCertificateSigningRequestCreate(csr, requestGroupVersion(ctx)) return validation.ValidateCertificateSigningRequestCreate(csr)
} }
// WarningsOnCreate returns warnings for the creation of the given object. // WarningsOnCreate returns warnings for the creation of the given object.
@ -133,7 +131,7 @@ func (csrStrategy) Canonicalize(obj runtime.Object) {}
func (csrStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (csrStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
oldCSR := old.(*certificates.CertificateSigningRequest) oldCSR := old.(*certificates.CertificateSigningRequest)
newCSR := obj.(*certificates.CertificateSigningRequest) newCSR := obj.(*certificates.CertificateSigningRequest)
return validation.ValidateCertificateSigningRequestUpdate(newCSR, oldCSR, requestGroupVersion(ctx)) return validation.ValidateCertificateSigningRequestUpdate(newCSR, oldCSR)
} }
// WarningsOnUpdate returns warnings for the given update. // WarningsOnUpdate returns warnings for the given update.
@ -181,20 +179,11 @@ func (csrStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.
// Updating /status should not modify spec // Updating /status should not modify spec
newCSR.Spec = oldCSR.Spec newCSR.Spec = oldCSR.Spec
switch requestGroupVersion(ctx) {
case certificatesv1beta1.SchemeGroupVersion:
// Specifically preserve existing Approved/Denied conditions.
// If we cannot (if the status update attempted to add/remove Approved/Denied conditions), revert to old conditions for backwards compatibility.
if !preserveConditionInstances(newCSR, oldCSR, certificates.CertificateApproved) || !preserveConditionInstances(newCSR, oldCSR, certificates.CertificateDenied) {
newCSR.Status.Conditions = oldCSR.Status.Conditions
}
default:
// Specifically preserve existing Approved/Denied conditions. // Specifically preserve existing Approved/Denied conditions.
// Adding/removing Approved/Denied conditions will cause these to fail, // Adding/removing Approved/Denied conditions will cause these to fail,
// and the change in Approved/Denied conditions will produce a validation error // and the change in Approved/Denied conditions will produce a validation error
preserveConditionInstances(newCSR, oldCSR, certificates.CertificateApproved) preserveConditionInstances(newCSR, oldCSR, certificates.CertificateApproved)
preserveConditionInstances(newCSR, oldCSR, certificates.CertificateDenied) preserveConditionInstances(newCSR, oldCSR, certificates.CertificateDenied)
}
populateConditionTimestamps(newCSR, oldCSR) populateConditionTimestamps(newCSR, oldCSR)
} }
@ -255,7 +244,7 @@ func populateConditionTimestamps(newCSR, oldCSR *certificates.CertificateSigning
} }
func (csrStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (csrStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidateCertificateSigningRequestStatusUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest), requestGroupVersion(ctx)) return validation.ValidateCertificateSigningRequestStatusUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest))
} }
// WarningsOnUpdate returns warnings for the given update. // WarningsOnUpdate returns warnings for the given update.
@ -307,7 +296,7 @@ func (csrApprovalStrategy) PrepareForUpdate(ctx context.Context, obj, old runtim
} }
func (csrApprovalStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (csrApprovalStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return validation.ValidateCertificateSigningRequestApprovalUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest), requestGroupVersion(ctx)) return validation.ValidateCertificateSigningRequestApprovalUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest))
} }
// WarningsOnUpdate returns warnings for the given update. // WarningsOnUpdate returns warnings for the given update.
@ -332,11 +321,3 @@ func SelectableFields(obj *certificates.CertificateSigningRequest) fields.Set {
} }
return generic.MergeFieldsSets(objectMetaFieldsSet, csrSpecificFieldsSet) return generic.MergeFieldsSets(objectMetaFieldsSet, csrSpecificFieldsSet)
} }
// requestGroupVersion returns the group/version associated with the given context, or a zero-value group/version
func requestGroupVersion(ctx context.Context) schema.GroupVersion {
if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
return schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
}
return schema.GroupVersion{}
}

View File

@ -177,16 +177,13 @@ func TestStatusUpdate(t *testing.T) {
name string name string
newObj *certapi.CertificateSigningRequest newObj *certapi.CertificateSigningRequest
oldObj *certapi.CertificateSigningRequest oldObj *certapi.CertificateSigningRequest
expectedObjs map[string]*certapi.CertificateSigningRequest expectedObj *certapi.CertificateSigningRequest
}{ }{
{ {
name: "no-op", name: "no-op",
newObj: &certapi.CertificateSigningRequest{}, newObj: &certapi.CertificateSigningRequest{},
oldObj: &certapi.CertificateSigningRequest{}, oldObj: &certapi.CertificateSigningRequest{},
expectedObjs: map[string]*certapi.CertificateSigningRequest{ expectedObj: &certapi.CertificateSigningRequest{},
"v1": {},
"v1beta1": {},
},
}, },
{ {
name: "adding failed condition to existing approved/denied conditions", name: "adding failed condition to existing approved/denied conditions",
@ -211,9 +208,8 @@ func TestStatusUpdate(t *testing.T) {
}, },
}, },
}, },
expectedObjs: map[string]*certapi.CertificateSigningRequest{ expectedObj: &certapi.CertificateSigningRequest{
// preserve existing Approved/Denied conditions // preserve existing Approved/Denied conditions
"v1": {
Status: certapi.CertificateSigningRequestStatus{ Status: certapi.CertificateSigningRequestStatus{
Conditions: []certapi.CertificateSigningRequestCondition{ Conditions: []certapi.CertificateSigningRequestCondition{
{Type: certapi.CertificateFailed, LastUpdateTime: now, LastTransitionTime: now}, {Type: certapi.CertificateFailed, LastUpdateTime: now, LastTransitionTime: now},
@ -224,19 +220,6 @@ func TestStatusUpdate(t *testing.T) {
}, },
}, },
}, },
// 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", name: "add approved condition",
@ -248,33 +231,24 @@ func TestStatusUpdate(t *testing.T) {
}, },
}, },
oldObj: &certapi.CertificateSigningRequest{}, oldObj: &certapi.CertificateSigningRequest{},
expectedObjs: map[string]*certapi.CertificateSigningRequest{ expectedObj: &certapi.CertificateSigningRequest{
// preserved submitted conditions if existing Approved/Denied conditions could not be copied over (will fail validation) // preserved submitted conditions if existing Approved/Denied conditions could not be copied over (will fail validation)
"v1": {
Status: certapi.CertificateSigningRequestStatus{ Status: certapi.CertificateSigningRequestStatus{
Conditions: []certapi.CertificateSigningRequestCondition{ Conditions: []certapi.CertificateSigningRequestCondition{
{Type: certapi.CertificateApproved, LastUpdateTime: now, LastTransitionTime: now}, {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 _, tt := range tests {
for _, version := range []string{"v1", "v1beta1"} { t.Run(tt.name, func(t *testing.T) {
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() obj := tt.newObj.DeepCopy()
StatusStrategy.PrepareForUpdate(ctx, obj, tt.oldObj.DeepCopy()) StatusStrategy.PrepareForUpdate(context.TODO(), obj, tt.oldObj.DeepCopy())
if !reflect.DeepEqual(obj, tt.expectedObjs[version]) { if !reflect.DeepEqual(obj, tt.expectedObj) {
t.Errorf("object diff: %s", diff.ObjectDiff(obj, tt.expectedObjs[version])) t.Errorf("object diff: %s", diff.ObjectDiff(obj, tt.expectedObj))
} }
}) })
} }
}
} }