diff --git a/plugin/pkg/admission/certificates/ctbattest/admission.go b/plugin/pkg/admission/certificates/ctbattest/admission.go index 8b2dfea7661..77d0786ffb8 100644 --- a/plugin/pkg/admission/certificates/ctbattest/admission.go +++ b/plugin/pkg/admission/certificates/ctbattest/admission.go @@ -27,7 +27,9 @@ import ( "k8s.io/component-base/featuregate" "k8s.io/klog/v2" api "k8s.io/kubernetes/pkg/apis/certificates" + kapihelper "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/registry/rbac" "k8s.io/kubernetes/plugin/pkg/admission/certificates" ) @@ -109,6 +111,11 @@ func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, _ admissi return nil } + // Skip the attest check when the semantics of the bundle are unchanged to support storage migration and GC workflows + if a.GetOperation() == admission.Update && rbac.IsOnlyMutatingGCFields(a.GetObject(), a.GetOldObject(), kapihelper.Semantic) { + return nil + } + if !certificates.IsAuthorizedForSignerName(ctx, p.authz, a.GetUserInfo(), "attest", newBundle.Spec.SignerName) { klog.V(4).Infof("user not permitted to attest ClusterTrustBundle %q with signerName %q", newBundle.Name, newBundle.Spec.SignerName) return admission.NewForbidden(a, fmt.Errorf("user not permitted to attest for signerName %q", newBundle.Spec.SignerName)) diff --git a/plugin/pkg/admission/certificates/ctbattest/admission_test.go b/plugin/pkg/admission/certificates/ctbattest/admission_test.go index e8f844c3464..2afd9d4ce6c 100644 --- a/plugin/pkg/admission/certificates/ctbattest/admission_test.go +++ b/plugin/pkg/admission/certificates/ctbattest/admission_test.go @@ -22,6 +22,7 @@ import ( "fmt" "testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" @@ -206,6 +207,51 @@ func TestPluginValidate(t *testing.T) { }, allowed: false, }, + { + description: "should always allow no-op update", + clusterTrustBundleFeatureEnabled: true, + authzErr: errors.New("broken"), + attributes: &testAttributes{ + resource: certificatesapi.Resource("clustertrustbundles"), + oldObj: &certificatesapi.ClusterTrustBundle{ + Spec: certificatesapi.ClusterTrustBundleSpec{ + SignerName: "panda.com/foo", + }, + }, + obj: &certificatesapi.ClusterTrustBundle{ + Spec: certificatesapi.ClusterTrustBundleSpec{ + SignerName: "panda.com/foo", + }, + }, + operation: admission.Update, + }, + allowed: true, + }, + { + description: "should always allow finalizer update", + clusterTrustBundleFeatureEnabled: true, + authzErr: errors.New("broken"), + attributes: &testAttributes{ + resource: certificatesapi.Resource("clustertrustbundles"), + oldObj: &certificatesapi.ClusterTrustBundle{ + Spec: certificatesapi.ClusterTrustBundleSpec{ + SignerName: "panda.com/foo", + }, + }, + obj: &certificatesapi.ClusterTrustBundle{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "something"}, + }, + }, + Spec: certificatesapi.ClusterTrustBundleSpec{ + SignerName: "panda.com/foo", + }, + }, + operation: admission.Update, + }, + allowed: true, + }, } for _, tc := range tests {