VYPR
Critical severityNVD Advisory· Published May 19, 2025· Updated Feb 6, 2026

Bypassing project secret validation can lead to privilege escalation

CVE-2025-47283

Description

Gardener implements the automated management and operation of Kubernetes clusters as a service. A security vulnerability was discovered in Gardener prior to versions 1.116.4, 1.117.5, 1.118.2, and 1.119.0 that could allow a user with administrative privileges for a Gardener project to obtain control over the seed cluster(s) where their shoot clusters are managed. This CVE affects all Gardener installations no matter of the public cloud provider(s) used for the seed clusters/shoot clusters. gardener/gardener (gardenlet) is the affected component. Versions 1.116.4, 1.117.5, 1.118.2, and 1.119.0 fix the issue.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A privilege escalation in Gardener allows project admins to gain control over seed clusters due to insufficient validation in admission webhooks.

Vulnerability

Description A security vulnerability in Gardener (prior to versions 1.116.4, 1.117.5, 1.118.2, and 1.119.0) allows a user with administrative privileges for a Gardener project to obtain control over the seed cluster(s) where their shoot clusters are managed [1][2]. The root cause is insufficient validation in extension admission webhooks, specifically related to WorkloadIdentity and CredentialsBinding objects, which could be exploited to escalate privileges from project-level access to seed-level access [3][4].

Exploitation

To exploit this vulnerability, an attacker must have administrative privileges over a Gardener project (e.g., as a project admin). By crafting malicious WorkloadIdentity or CredentialsBinding resources, the attacker can bypass the intended security checks and obtain credentials or permissions that grant access to the underlying seed cluster [3][4]. The attack does not require any specific cloud provider—it affects all Gardener installations regardless of the infrastructure used for seed or shoot clusters [1][2].

Impact

Successful exploitation allows the attacker to take control of the seed cluster, which manages multiple shoot clusters. This can lead to a full compromise of all shoot clusters running on that seed, including potential data breaches, service disruption, or lateral movement within the environment [1][2].

Mitigation

The vulnerability is fixed in Gardener versions 1.116.4, 1.117.5, 1.118.2, and 1.119.0 [1][2]. Administrators are strongly advised to upgrade immediately. No workarounds are documented; the fix involves hardening the admission webhooks to properly validate WorkloadIdentity and CredentialsBinding objects [3][4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/gardener/gardenerGo
< 1.116.41.116.4
github.com/gardener/gardenerGo
>= 1.117.0, < 1.117.51.117.5
github.com/gardener/gardenerGo
>= 1.118.0, < 1.118.21.118.2

Affected products

4

Patches

4
b89cf2cd5067

[release-v1.118] Ensure extension admission webhooks validated `WorkloadIdentity`s and `Secret`s when used (#12075)

https://github.com/gardener/gardenerGardener Prow RobotMay 13, 2025via ghsa
55 files changed · +1700 493
  • charts/gardener/controlplane/charts/application/templates/mutatingwebhook-admission-controller.yaml+29 1 modified
    @@ -3,5 +3,33 @@ apiVersion: admissionregistration.k8s.io/v1
     kind: MutatingWebhookConfiguration
     metadata:
       name: gardener-admission-controller
    -webhooks: []
    +webhooks:
    +- name: sync-provider-secret-labels.gardener.cloud
    +  admissionReviewVersions: ["v1", "v1beta1"]
    +  timeoutSeconds: 10
    +  rules:
    +  - apiGroups:
    +    - ""
    +    apiVersions:
    +    - v1
    +    operations:
    +    - CREATE
    +    - UPDATE
    +    resources:
    +    - secrets
    +  failurePolicy: Fail
    +  namespaceSelector:
    +    matchExpressions:
    +    - {key: gardener.cloud/role, operator: In, values: [project]}
    +  clientConfig:
    +    {{- if .Values.global.deployment.virtualGarden.enabled }}
    +    url: https://gardener-admission-controller.garden/webhooks/sync-provider-secret-labels
    +    {{- else }}
    +    service:
    +      namespace: garden
    +      name: gardener-admission-controller
    +      path: /webhooks/sync-provider-secret-labels
    +    {{- end }}
    +    caBundle: {{ required ".Values.global.admission.config.server.webhooks.tls.caBundle is required" (b64enc .Values.global.admission.config.server.webhooks.tls.caBundle) }}
    +  sideEffects: None
     {{- end }}
    
  • cmd/gardener-extension-admission-local/app/app.go+4 2 modified
    @@ -26,8 +26,9 @@ import (
     	extensionscmdcontroller "github.com/gardener/gardener/extensions/pkg/controller/cmd"
     	"github.com/gardener/gardener/extensions/pkg/util"
     	extensionscmdwebhook "github.com/gardener/gardener/extensions/pkg/webhook/cmd"
    -	"github.com/gardener/gardener/pkg/apis/core/install"
    +	gardencoreinstall "github.com/gardener/gardener/pkg/apis/core/install"
     	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
    +	securityinstall "github.com/gardener/gardener/pkg/apis/security/install"
     	gardenerhealthz "github.com/gardener/gardener/pkg/healthz"
     	admissioncmd "github.com/gardener/gardener/pkg/provider-local/admission/cmd"
     	localinstall "github.com/gardener/gardener/pkg/provider-local/apis/local/install"
    @@ -123,7 +124,8 @@ func NewAdmissionCommand(ctx context.Context) *cobra.Command {
     				return fmt.Errorf("could not instantiate manager: %w", err)
     			}
     
    -			install.Install(mgr.GetScheme())
    +			gardencoreinstall.Install(mgr.GetScheme())
    +			securityinstall.Install(mgr.GetScheme())
     
     			if err := localinstall.AddToScheme(mgr.GetScheme()); err != nil {
     				return fmt.Errorf("could not update manager scheme: %w", err)
    
  • docs/concepts/apiserver-admission-plugins.md+17 6 modified
    @@ -74,14 +74,27 @@ _(enabled by default)_
     
     This admission controller reacts on `CREATE` and `UPDATE` operations for `BackupBucket`s, `BackupEntry`s, `CloudProfile`s, `NamespacedCloudProfile`s, `Seed`s, `SecretBinding`s, `CredentialsBinding`s, `WorkloadIdentity`s and `Shoot`s. For all the various extension types in the specifications of these objects, it adds a corresponding label in the resource. This would allow extension admission webhooks to filter out the resources they are responsible for and ignore all others. This label is of the form `<extension-type>.extensions.gardener.cloud/<extension-name> : "true"`. For example, an extension label for provider extension type `aws`, looks like `provider.extensions.gardener.cloud/aws : "true"`.
     
    +## `FinalizerRemoval`
    +
    +_(enabled by default)_
    +
    +This admission controller reacts on `UPDATE` operations for `CredentialsBinding`s, `SecretBinding`s, `Shoot`s. 
    +It ensures that the finalizers of these resources are not removed by users, as long as the affected resource is still in use.
    +For `CredentialsBinding`s and `SecretBinding`s this means, that the `gardener` finalizer can only be removed if the binding is not referenced by any `Shoot`.
    +In case of `Shoot`s, the `gardener` finalizer can only be removed if the last operation of the `Shoot` indicates a successful deletion. 
    +
     ## `ProjectValidator`
     
     _(enabled by default)_
     
    -This admission controller reacts on `CREATE` operations for `Project`s.
    +This admission controller reacts on `CREATE` and `UPDATE` operations for `Project`s.
     It prevents creating `Project`s with a non-empty `.spec.namespace` if the value in `.spec.namespace` does not start with `garden-`.
     
    -⚠️ This admission plugin will be removed in a future release and its business logic will be incorporated into the static validation of the `gardener-apiserver`.
    +In addition, the project specification is initialized during creation:
    +- `.spec.createdBy` is set to the user creating the project.
    +- `.spec.owner` defaults to the value of `.spec.createdBy` if it is not specified.
    +
    +During subsequent updates, it ensures that the project owner is included in the `.spec.members` list.
     
     ## `ResourceQuota`
     
    @@ -96,11 +109,9 @@ _(enabled by default)_
     
     This admission controller reacts on `CREATE` and `UPDATE` operations for `CloudProfile`s, `Project`s, `SecretBinding`s, `Seed`s, and `Shoot`s.
     Generally, it checks whether referred resources stated in the specifications of these objects exist in the system (e.g., if a referenced `Secret` exists).
    -However, it also has some special behaviours for certain resources:
     
    +However, it also has some special behaviours for certain resources:
     * `CloudProfile`s: It rejects removing Kubernetes or machine image versions if there is at least one `Shoot` that refers to them.
    -* `Project`s: It sets the `.spec.createdBy` field for newly created `Project` resources, and defaults the `.spec.owner` field in case it is empty (to the same value of `.spec.createdBy`).
    -* `Shoot`s: It sets the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.
     
     ## `SeedValidator`
     
    @@ -187,7 +198,7 @@ _(enabled by default)_
     This admission controller reacts on `CREATE`, `UPDATE` and `DELETE` operations for `Shoot`s.
     It validates certain configurations in the specification against the referred `CloudProfile` (e.g., machine images, machine types, used Kubernetes version, ...).
     Generally, it performs validations that cannot be handled by the static API validation due to their dynamic nature (e.g., when something needs to be checked against referred resources).
    -Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version).
    +Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version) and setting the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.
     
     ## `ShootManagedSeed`
     
    
  • pkg/admissioncontroller/webhook/add.go+8 0 modified
    @@ -18,6 +18,7 @@ import (
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/internaldomainsecret"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/kubeconfigsecret"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/namespacedeletion"
    +	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/providersecretlabels"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/resourcesize"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/seedrestriction"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref"
    @@ -65,6 +66,13 @@ func AddToManager(
     		return fmt.Errorf("failed adding %s webhook handler: %w", namespacedeletion.HandlerName, err)
     	}
     
    +	if err := (&providersecretlabels.Handler{
    +		Logger: mgr.GetLogger().WithName("webhook").WithName(providersecretlabels.HandlerName),
    +		Client: mgr.GetClient(),
    +	}).AddToManager(mgr); err != nil {
    +		return fmt.Errorf("failed adding %s webhook handler: %w", providersecretlabels.HandlerName, err)
    +	}
    +
     	if err := (&resourcesize.Handler{
     		Logger: mgr.GetLogger().WithName("webhook").WithName(resourcesize.HandlerName),
     		Config: cfg.Server.ResourceAdmissionConfiguration,
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/add.go+28 0 added
    @@ -0,0 +1,28 @@
    +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels
    +
    +import (
    +	corev1 "k8s.io/api/core/v1"
    +	"sigs.k8s.io/controller-runtime/pkg/manager"
    +	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    +)
    +
    +const (
    +	// HandlerName is the name of this admission webhook handler.
    +	HandlerName = "sync-provider-secret-labels"
    +	// WebhookPath is the HTTP handler path for this admission webhook handler.
    +	WebhookPath = "/webhooks/sync-provider-secret-labels"
    +)
    +
    +// AddToManager adds Handler to the given manager.
    +func (h *Handler) AddToManager(mgr manager.Manager) error {
    +	webhook := admission.
    +		WithCustomDefaulter(mgr.GetScheme(), &corev1.Secret{}, h).
    +		WithRecoverPanic(true)
    +
    +	mgr.GetWebhookServer().Register(WebhookPath, webhook)
    +	return nil
    +}
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/handler.go+99 0 added
    @@ -0,0 +1,99 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels
    +
    +import (
    +	"context"
    +	"fmt"
    +	"strings"
    +
    +	"github.com/go-logr/logr"
    +	corev1 "k8s.io/api/core/v1"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/runtime"
    +	"k8s.io/apimachinery/pkg/util/sets"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
    +	v1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +)
    +
    +// Handler syncs the provider labels on Secrets referenced in SecretBindings or CredentialsBindings.
    +type Handler struct {
    +	Logger logr.Logger
    +	Client client.Client
    +}
    +
    +// Default syncs the provider labels.
    +func (h *Handler) Default(ctx context.Context, obj runtime.Object) error {
    +	secret, ok := obj.(*corev1.Secret)
    +	if !ok {
    +		return fmt.Errorf("expected secret but got %T", obj)
    +	}
    +
    +	typesFromSecretBindings, err := h.fetchProviderTypesFromSecretBindings(ctx, secret)
    +	if err != nil {
    +		return fmt.Errorf("failed fetching provider types from SecretBindings: %w", err)
    +	}
    +
    +	typesFromCredentialsBindings, err := h.fetchProviderTypesFromCredentialsBindings(ctx, secret)
    +	if err != nil {
    +		return fmt.Errorf("failed fetching provider types from CredentialsBindings: %w", err)
    +	}
    +
    +	if typesFromSecretBindings.Len()+typesFromCredentialsBindings.Len() > 0 {
    +		maintainLabels(secret, typesFromSecretBindings.Union(typesFromCredentialsBindings).UnsortedList()...)
    +	}
    +
    +	return nil
    +}
    +
    +func (h *Handler) fetchProviderTypesFromSecretBindings(ctx context.Context, secret *corev1.Secret) (sets.Set[string], error) {
    +	secretBindingList := &gardencorev1beta1.SecretBindingList{}
    +	if err := h.Client.List(ctx, secretBindingList); err != nil {
    +		return nil, fmt.Errorf("failed to list SecretBindings: %w", err)
    +	}
    +
    +	providerTypes := sets.New[string]()
    +	for _, secretBinding := range secretBindingList.Items {
    +		if secretBinding.SecretRef.Name == secret.Name &&
    +			secretBinding.SecretRef.Namespace == secret.Namespace {
    +			providerTypes.Insert(v1beta1helper.GetSecretBindingTypes(&secretBinding)...)
    +		}
    +	}
    +	return providerTypes, nil
    +}
    +
    +func (h *Handler) fetchProviderTypesFromCredentialsBindings(ctx context.Context, secret *corev1.Secret) (sets.Set[string], error) {
    +	credentialsBindingList := &securityv1alpha1.CredentialsBindingList{}
    +	if err := h.Client.List(ctx, credentialsBindingList); err != nil {
    +		return nil, fmt.Errorf("failed to list CredentialsBindings: %w", err)
    +	}
    +
    +	providerTypes := sets.New[string]()
    +	for _, credentialsBinding := range credentialsBindingList.Items {
    +		if credentialsBinding.CredentialsRef.APIVersion == corev1.SchemeGroupVersion.String() &&
    +			credentialsBinding.CredentialsRef.Kind == "Secret" &&
    +			credentialsBinding.CredentialsRef.Name == secret.Name &&
    +			credentialsBinding.CredentialsRef.Namespace == secret.Namespace {
    +			providerTypes.Insert(credentialsBinding.Provider.Type)
    +		}
    +	}
    +	return providerTypes, nil
    +}
    +
    +func maintainLabels(secret *corev1.Secret, providerTypes ...string) {
    +	for k := range secret.Labels {
    +		if strings.HasPrefix(k, v1beta1constants.LabelShootProviderPrefix) {
    +			delete(secret.Labels, k)
    +		}
    +	}
    +
    +	for _, providerType := range providerTypes {
    +		metav1.SetMetaDataLabel(&secret.ObjectMeta, v1beta1constants.LabelShootProviderPrefix+providerType, "true")
    +	}
    +}
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/handler_test.go+138 0 added
    @@ -0,0 +1,138 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels_test
    +
    +import (
    +	"context"
    +
    +	"github.com/go-logr/logr"
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	corev1 "k8s.io/api/core/v1"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    +	logzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
    +
    +	. "github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/providersecretlabels"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +	"github.com/gardener/gardener/pkg/client/kubernetes"
    +	"github.com/gardener/gardener/pkg/logger"
    +)
    +
    +var _ = Describe("handler", func() {
    +	var (
    +		ctx context.Context
    +
    +		log        logr.Logger
    +		fakeClient client.Client
    +		handler    *Handler
    +
    +		namespace            string
    +		provider1, provider2 string
    +		secret               *corev1.Secret
    +		secretBinding        *gardencorev1beta1.SecretBinding
    +		credentialsBinding   *securityv1alpha1.CredentialsBinding
    +	)
    +
    +	BeforeEach(func() {
    +		ctx = context.Background()
    +		log = logger.MustNewZapLogger(logger.DebugLevel, logger.FormatJSON, logzap.WriteTo(GinkgoWriter))
    +
    +		fakeClient = fakeclient.NewClientBuilder().WithScheme(kubernetes.GardenScheme).Build()
    +		handler = &Handler{
    +			Logger: log,
    +			Client: fakeClient,
    +		}
    +
    +		namespace = "test"
    +		secret = &corev1.Secret{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-secret",
    +				Namespace: namespace,
    +			},
    +		}
    +
    +		provider1 = "provider1"
    +		provider2 = "provider2"
    +
    +		secretBinding = &gardencorev1beta1.SecretBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-secret-binding",
    +				Namespace: namespace,
    +			},
    +			SecretRef: corev1.SecretReference{
    +				Name:      "test-secret",
    +				Namespace: namespace,
    +			},
    +			Provider: &gardencorev1beta1.SecretBindingProvider{
    +				Type: provider1,
    +			},
    +		}
    +
    +		credentialsBinding = &securityv1alpha1.CredentialsBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-credentials-binding",
    +				Namespace: "another-namespace",
    +			},
    +			CredentialsRef: corev1.ObjectReference{
    +				APIVersion: corev1.SchemeGroupVersion.String(),
    +				Kind:       "Secret",
    +				Name:       "test-secret",
    +				Namespace:  namespace,
    +			},
    +			Provider: securityv1alpha1.CredentialsBindingProvider{
    +				Type: provider2,
    +			},
    +		}
    +	})
    +
    +	It("should set the provider label based on the available credential and secret bindings", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +	})
    +
    +	It("should remove undesired provider type", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +		secret.Labels = map[string]string{
    +			"provider.shoot.gardener.cloud/provider1": "true",
    +			"provider.shoot.gardener.cloud/provider2": "true",
    +			"provider.shoot.gardener.cloud/provider3": "true",
    +		}
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +		Expect(secret.Labels).NotTo(HaveKey("provider.shoot.gardener.cloud/provider3"))
    +	})
    +
    +	It("should add the missing provider and delete the wrong one", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +		secret.Labels = map[string]string{
    +			"provider.shoot.gardener.cloud/provider1": "true",
    +			"provider.shoot.gardener.cloud/provider3": "true",
    +		}
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +		Expect(secret.Labels).NotTo(HaveKey("provider.shoot.gardener.cloud/provider3"))
    +	})
    +
    +	It("should not add provider labels when secret is unreferenced", func() {
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +		Expect(secret.Labels).To(BeEmpty())
    +	})
    +})
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/providersecretlabels_suite_test.go+17 0 added
    @@ -0,0 +1,17 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +)
    +
    +func TestProviderSecretLabels(t *testing.T) {
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "AdmissionController Webhook Admission ProviderSecretLabels Suite")
    +}
    
  • pkg/apis/core/helper/secretbinding.go+3 0 modified
    @@ -12,5 +12,8 @@ import (
     
     // GetSecretBindingTypes returns the SecretBinding provider types.
     func GetSecretBindingTypes(secretBinding *core.SecretBinding) []string {
    +	if secretBinding.Provider == nil {
    +		return []string{}
    +	}
     	return strings.Split(secretBinding.Provider.Type, ",")
     }
    
  • pkg/apis/core/helper/secretbinding_test.go+1 0 modified
    @@ -19,6 +19,7 @@ var _ = Describe("Helper", func() {
     			Expect(actual).To(Equal(expected))
     		},
     
    +		Entry("with nil provider type", &core.SecretBinding{Provider: nil}, []string{}),
     		Entry("with single-value provider type", &core.SecretBinding{Provider: &core.SecretBindingProvider{Type: "foo"}}, []string{"foo"}),
     		Entry("with multi-value provider type", &core.SecretBinding{Provider: &core.SecretBindingProvider{Type: "foo,bar,baz"}}, []string{"foo", "bar", "baz"}),
     	)
    
  • pkg/apis/core/v1beta1/helper/secretbinding.go+3 0 modified
    @@ -44,5 +44,8 @@ func AddTypeToSecretBinding(secretBinding *gardencorev1beta1.SecretBinding, prov
     
     // GetSecretBindingTypes returns the SecretBinding provider types.
     func GetSecretBindingTypes(secretBinding *gardencorev1beta1.SecretBinding) []string {
    +	if secretBinding.Provider == nil {
    +		return []string{}
    +	}
     	return strings.Split(secretBinding.Provider.Type, ",")
     }
    
  • pkg/apis/core/v1beta1/helper/secretbinding_test.go+1 0 modified
    @@ -45,6 +45,7 @@ var _ = Describe("Helper", func() {
     			Expect(actual).To(Equal(expected))
     		},
     
    +		Entry("with nil provider type", &gardencorev1beta1.SecretBinding{Provider: nil}, []string{}),
     		Entry("with single-value provider type", &gardencorev1beta1.SecretBinding{Provider: &gardencorev1beta1.SecretBindingProvider{Type: "foo"}}, []string{"foo"}),
     		Entry("with multi-value provider type", &gardencorev1beta1.SecretBinding{Provider: &gardencorev1beta1.SecretBindingProvider{Type: "foo,bar,baz"}}, []string{"foo", "bar", "baz"}),
     	)
    
  • pkg/apiserver/plugins.go+2 0 modified
    @@ -14,6 +14,7 @@ import (
     	"github.com/gardener/gardener/plugin/pkg/global/deletionconfirmation"
     	"github.com/gardener/gardener/plugin/pkg/global/extensionlabels"
     	"github.com/gardener/gardener/plugin/pkg/global/extensionvalidation"
    +	"github.com/gardener/gardener/plugin/pkg/global/finalizerremoval"
     	"github.com/gardener/gardener/plugin/pkg/global/resourcereferencemanager"
     	managedseedshoot "github.com/gardener/gardener/plugin/pkg/managedseed/shoot"
     	managedseedvalidator "github.com/gardener/gardener/plugin/pkg/managedseed/validator"
    @@ -39,6 +40,7 @@ import (
     func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
     	resourcereferencemanager.Register(plugins)
     	deletionconfirmation.Register(plugins)
    +	finalizerremoval.Register(plugins)
     	extensionvalidation.Register(plugins)
     	extensionlabels.Register(plugins)
     	shoottolerationrestriction.Register(plugins)
    
  • pkg/apiserver/registry/core/backupbucket/backupbucket_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBackupBucket(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core BackupBucket Suite")
    +	RunSpecs(t, "APIServer Registry Core BackupBucket Suite")
     }
    
  • pkg/apiserver/registry/core/backupentry/backupentry_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBackupEntry(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core BackupEntry Suite")
    +	RunSpecs(t, "APIServer Registry Core BackupEntry Suite")
     }
    
  • pkg/apiserver/registry/core/cloudprofile/cloudprofile_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestCloudProfile(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core CloudProfile Suite")
    +	RunSpecs(t, "APIServer Registry Core CloudProfile Suite")
     }
    
  • pkg/apiserver/registry/core/controllerinstallation/strategy_test.go+1 1 modified
    @@ -20,7 +20,7 @@ import (
     
     func TestControllerInstallation(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry ControllerInstallation Suite")
    +	RunSpecs(t, "APIServer Registry ControllerInstallation Suite")
     }
     
     var _ = Describe("ToSelectableFields", func() {
    
  • pkg/apiserver/registry/core/namespacedcloudprofile/namespacedcloudprofile_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestNamespacedCloudProfile(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core NamespacedCloudProfile Suite")
    +	RunSpecs(t, "APIServer Registry Core NamespacedCloudProfile Suite")
     }
    
  • pkg/apiserver/registry/core/project/project_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestProject(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Project Suite")
    +	RunSpecs(t, "APIServer Registry Core Project Suite")
     }
    
  • pkg/apiserver/registry/core/secretbinding/secretbinding_suite_test.go+1 1 modified
    @@ -16,5 +16,5 @@ import (
     func TestSecretBinding(t *testing.T) {
     	features.RegisterFeatureGates()
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core SecretBinding Suite")
    +	RunSpecs(t, "APIServer Registry Core SecretBinding Suite")
     }
    
  • pkg/apiserver/registry/core/secretbinding/strategy.go+6 1 modified
    @@ -28,7 +28,12 @@ func (secretBindingStrategy) NamespaceScoped() bool {
     	return true
     }
     
    -func (secretBindingStrategy) PrepareForCreate(_ context.Context, _ runtime.Object) {
    +func (s secretBindingStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) {
    +	binding := obj.(*core.SecretBinding)
    +
    +	if binding.GetName() == "" {
    +		binding.SetName(s.GenerateName(binding.GetGenerateName()))
    +	}
     }
     
     func (secretBindingStrategy) Validate(_ context.Context, obj runtime.Object) field.ErrorList {
    
  • pkg/apiserver/registry/core/secretbinding/strategy_test.go+29 0 modified
    @@ -34,6 +34,35 @@ var _ = Describe("Strategy", func() {
     		}
     	})
     
    +	Describe("#PrepareForCreate", func() {
    +		It("should set the name if not set", func() {
    +			secretBinding.SetName("")
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetName()).NotTo(BeEmpty())
    +		})
    +
    +		It("should set name with generateName as prefix", func() {
    +			genName := "prefix-"
    +			secretBinding.GenerateName = genName
    +			secretBinding.Name = ""
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetGenerateName()).To(Equal(genName))
    +			Expect(secretBinding.GetName()).To(HavePrefix(genName))
    +		})
    +
    +		It("should not overwrite already set name", func() {
    +			secretBinding.SetName("bar")
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetName()).To(Equal("bar"))
    +		})
    +	})
    +
     	Describe("#Validate", func() {
     		It("should forbid creating SecretBinding when provider is nil or empty", func() {
     			secretBinding.Provider = nil
    
  • pkg/apiserver/registry/core/seed/seed_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestSeed(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Seed Suite")
    +	RunSpecs(t, "APIServer Registry Core Seed Suite")
     }
    
  • pkg/apiserver/registry/core/shoot/shoot_suite_test.go+1 1 modified
    @@ -16,5 +16,5 @@ import (
     func TestShoot(t *testing.T) {
     	features.RegisterFeatureGates()
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Shoot Suite")
    +	RunSpecs(t, "APIServer Registry Core Shoot Suite")
     }
    
  • pkg/apiserver/registry/core/shoot/storage/storage_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestStorage(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Shoot Storage Suite")
    +	RunSpecs(t, "APIServer Registry Core Shoot Storage Suite")
     }
    
  • pkg/apiserver/registry/operations/bastion/bastion_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBastion(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Operations Bastion Suite")
    +	RunSpecs(t, "APIServer Registry Operations Bastion Suite")
     }
    
  • pkg/apiserver/registry/security/credentialsbinding/credentialsbinding_suite_test.go+20 0 added
    @@ -0,0 +1,20 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package credentialsbinding_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +
    +	"github.com/gardener/gardener/pkg/apiserver/features"
    +)
    +
    +func TestCredentialsBinding(t *testing.T) {
    +	features.RegisterFeatureGates()
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "APIServer Registry Security CredentialsBinding Suite")
    +}
    
  • pkg/apiserver/registry/security/credentialsbinding/strategy.go+5 1 modified
    @@ -28,8 +28,12 @@ func (credentialsBindingStrategy) NamespaceScoped() bool {
     	return true
     }
     
    -func (credentialsBindingStrategy) PrepareForCreate(_ context.Context, _ runtime.Object) {
    +func (c credentialsBindingStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) {
    +	credentialsbinding := obj.(*security.CredentialsBinding)
     
    +	if credentialsbinding.GetName() == "" {
    +		credentialsbinding.SetName(c.GenerateName(credentialsbinding.GetGenerateName()))
    +	}
     }
     
     func (credentialsBindingStrategy) PrepareForUpdate(_ context.Context, _, _ runtime.Object) {
    
  • pkg/apiserver/registry/security/credentialsbinding/strategy_test.go+58 0 added
    @@ -0,0 +1,58 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package credentialsbinding_test
    +
    +import (
    +	"context"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	credentialsbindingregistry "github.com/gardener/gardener/pkg/apiserver/registry/security/credentialsbinding"
    +)
    +
    +var _ = Describe("Strategy", func() {
    +	var credentialsBinding *security.CredentialsBinding
    +
    +	BeforeEach(func() {
    +		credentialsBinding = &security.CredentialsBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "profile",
    +				Namespace: "garden",
    +			},
    +		}
    +	})
    +
    +	Describe("#PrepareForCreate", func() {
    +		It("should set the name if not set", func() {
    +			credentialsBinding.SetName("")
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetName()).NotTo(BeEmpty())
    +		})
    +
    +		It("should set name with generateName as prefix", func() {
    +			genName := "prefix-"
    +			credentialsBinding.GenerateName = genName
    +			credentialsBinding.Name = ""
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetGenerateName()).To(Equal(genName))
    +			Expect(credentialsBinding.GetName()).To(HavePrefix(genName))
    +		})
    +
    +		It("should not overwrite already set name", func() {
    +			credentialsBinding.SetName("bar")
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetName()).To(Equal("bar"))
    +		})
    +	})
    +})
    
  • pkg/apiserver/registry/security/workloadidentity/storage/storage_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestStorage(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Security WorkloadIdentity Storage Suite")
    +	RunSpecs(t, "APIServer Registry Security WorkloadIdentity Storage Suite")
     }
    
  • pkg/apiserver/registry/security/workloadidentity/workloadidentity_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestWorkloadIdentity(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Security WorkloadIdentity Suite")
    +	RunSpecs(t, "APIServer Registry Security WorkloadIdentity Suite")
     }
    
  • pkg/apiserver/registry/seedmanagement/managedseed/managedseed_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestManagedSeed(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry SeedManagement ManagedSeed Suite")
    +	RunSpecs(t, "APIServer Registry SeedManagement ManagedSeed Suite")
     }
    
  • pkg/apiserver/registry/seedmanagement/managedseedset/managedseedset_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestManagedSeedSet(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry SeedManagement ManagedSeedSet Suite")
    +	RunSpecs(t, "APIServer Registry SeedManagement ManagedSeedSet Suite")
     }
    
  • pkg/component/gardener/admissioncontroller/admission_controller.go+1 0 modified
    @@ -131,6 +131,7 @@ func (a *gardenerAdmissionController) Deploy(ctx context.Context) error {
     		a.clusterRole(),
     		a.clusterRoleBinding(virtualGardenAccessSecret.ServiceAccountName),
     		a.validatingWebhookConfiguration(caSecret),
    +		a.mutatingWebhookConfiguration(caSecret),
     	)
     	if err != nil {
     		return err
    
  • pkg/component/gardener/admissioncontroller/admission_controller_test.go+40 0 modified
    @@ -502,6 +502,7 @@ func verifyExpectations(ctx context.Context, fakeClient client.Client, consistOf
     		clusterRole(),
     		clusterRoleBinding(),
     		validatingWebhookConfiguration(namespace, caGardener.Data["bundle.crt"], testValues),
    +		mutatingWebhookConfiguration(namespace, caGardener.Data["bundle.crt"]),
     	))
     
     	virtualManagedResourceSecret := &corev1.Secret{
    @@ -1312,3 +1313,42 @@ func validatingWebhookConfiguration(namespace string, caBundle []byte, testValue
     
     	return webhookConfig
     }
    +
    +func mutatingWebhookConfiguration(namespace string, caBundle []byte) *admissionregistrationv1.MutatingWebhookConfiguration {
    +	var (
    +		failurePolicyFail = admissionregistrationv1.Fail
    +		sideEffectsNone   = admissionregistrationv1.SideEffectClassNone
    +	)
    +
    +	return &admissionregistrationv1.MutatingWebhookConfiguration{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name: "gardener-admission-controller",
    +		},
    +		Webhooks: []admissionregistrationv1.MutatingWebhook{
    +			{
    +				Name:                    "sync-provider-secret-labels.gardener.cloud",
    +				AdmissionReviewVersions: []string{"v1", "v1beta1"},
    +				TimeoutSeconds:          ptr.To[int32](10),
    +				Rules: []admissionregistrationv1.RuleWithOperations{{
    +					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
    +					Rule: admissionregistrationv1.Rule{
    +						APIGroups:   []string{""},
    +						APIVersions: []string{"v1"},
    +						Resources:   []string{"secrets"},
    +					},
    +				}},
    +				FailurePolicy: &failurePolicyFail,
    +				NamespaceSelector: &metav1.LabelSelector{
    +					MatchLabels: map[string]string{
    +						"gardener.cloud/role": "project",
    +					},
    +				},
    +				ClientConfig: admissionregistrationv1.WebhookClientConfig{
    +					URL:      ptr.To("https://gardener-admission-controller." + namespace + "/webhooks/sync-provider-secret-labels"),
    +					CABundle: caBundle,
    +				},
    +				SideEffects: &sideEffectsNone,
    +			},
    +		},
    +	}
    +}
    
  • pkg/component/gardener/admissioncontroller/webhooks.go+41 0 modified
    @@ -375,6 +375,47 @@ func (a *gardenerAdmissionController) validatingWebhookConfiguration(caSecret *c
     	return validatingWebhook
     }
     
    +func (a *gardenerAdmissionController) mutatingWebhookConfiguration(caSecret *corev1.Secret) *admissionregistrationv1.MutatingWebhookConfiguration {
    +	var (
    +		failurePolicyFail = admissionregistrationv1.Fail
    +		sideEffectsNone   = admissionregistrationv1.SideEffectClassNone
    +
    +		caBundle = caSecret.Data[secrets.DataKeyCertificateBundle]
    +	)
    +
    +	return &admissionregistrationv1.MutatingWebhookConfiguration{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name: DeploymentName,
    +		},
    +		Webhooks: []admissionregistrationv1.MutatingWebhook{
    +			{
    +				Name:                    "sync-provider-secret-labels.gardener.cloud",
    +				AdmissionReviewVersions: []string{"v1", "v1beta1"},
    +				TimeoutSeconds:          ptr.To[int32](10),
    +				Rules: []admissionregistrationv1.RuleWithOperations{{
    +					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
    +					Rule: admissionregistrationv1.Rule{
    +						APIGroups:   []string{corev1.GroupName},
    +						APIVersions: []string{"v1"},
    +						Resources:   []string{"secrets"},
    +					},
    +				}},
    +				FailurePolicy: &failurePolicyFail,
    +				NamespaceSelector: &metav1.LabelSelector{
    +					MatchLabels: map[string]string{
    +						v1beta1constants.GardenRole: v1beta1constants.GardenRoleProject,
    +					},
    +				},
    +				ClientConfig: admissionregistrationv1.WebhookClientConfig{
    +					URL:      buildClientConfigURL("/webhooks/sync-provider-secret-labels", a.namespace),
    +					CABundle: caBundle,
    +				},
    +				SideEffects: &sideEffectsNone,
    +			},
    +		},
    +	}
    +}
    +
     func buildWebhookConfigRulesForResourceSize(config *admissioncontrollerconfigv1alpha1.ResourceAdmissionConfiguration) []admissionregistrationv1.RuleWithOperations {
     	if config == nil || len(config.Limits) == 0 {
     		return nil
    
  • pkg/controllermanager/controller/secretbinding/reconciler.go+9 11 modified
    @@ -143,17 +143,15 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
     		}
     	}
     
    -	if secretBinding.Provider != nil {
    -		types := v1beta1helper.GetSecretBindingTypes(secretBinding)
    -		for _, t := range types {
    -			labelKey := v1beta1constants.LabelShootProviderPrefix + t
    -
    -			if !metav1.HasLabel(secret.ObjectMeta, labelKey) {
    -				patch := client.MergeFrom(secret.DeepCopy())
    -				metav1.SetMetaDataLabel(&secret.ObjectMeta, labelKey, "true")
    -				if err := r.Client.Patch(ctx, secret, patch); err != nil {
    -					return reconcile.Result{}, fmt.Errorf("failed to add provider type label to Secret referenced in SecretBinding: %w", err)
    -				}
    +	types := v1beta1helper.GetSecretBindingTypes(secretBinding)
    +	for _, t := range types {
    +		labelKey := v1beta1constants.LabelShootProviderPrefix + t
    +
    +		if !metav1.HasLabel(secret.ObjectMeta, labelKey) {
    +			patch := client.MergeFrom(secret.DeepCopy())
    +			metav1.SetMetaDataLabel(&secret.ObjectMeta, labelKey, "true")
    +			if err := r.Client.Patch(ctx, secret, patch); err != nil {
    +				return reconcile.Result{}, fmt.Errorf("failed to add provider type label to Secret referenced in SecretBinding: %w", err)
     			}
     		}
     	}
    
  • pkg/provider-local/admission/cmd/options.go+1 0 modified
    @@ -14,6 +14,7 @@ import (
     func GardenWebhookSwitchOptions() *extensionscmdwebhook.SwitchOptions {
     	return extensionscmdwebhook.NewSwitchOptions(
     		extensionscmdwebhook.Switch(validator.Name, validator.New),
    +		extensionscmdwebhook.Switch(validator.SecretsValidatorName, validator.NewSecretsWebhook),
     		extensionscmdwebhook.Switch(mutator.Name, mutator.New),
     	)
     }
    
  • pkg/provider-local/admission/validator/secret.go+48 0 added
    @@ -0,0 +1,48 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package validator
    +
    +import (
    +	"context"
    +	"fmt"
    +
    +	corev1 "k8s.io/api/core/v1"
    +	apiequality "k8s.io/apimachinery/pkg/api/equality"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
    +)
    +
    +type secretValidator struct{}
    +
    +// NewSecretValidator returns a new instance of a secret validator.
    +func NewSecretValidator() extensionswebhook.Validator {
    +	return &secretValidator{}
    +}
    +
    +// Validate checks whether the data is empty.
    +func (s *secretValidator) Validate(_ context.Context, newObj, oldObj client.Object) error {
    +	secret, ok := newObj.(*corev1.Secret)
    +	if !ok {
    +		return fmt.Errorf("wrong object type %T", newObj)
    +	}
    +
    +	if oldObj != nil {
    +		oldSecret, ok := oldObj.(*corev1.Secret)
    +		if !ok {
    +			return fmt.Errorf("wrong object type %T for old object", oldObj)
    +		}
    +
    +		if apiequality.Semantic.DeepEqual(secret.Data, oldSecret.Data) {
    +			return nil
    +		}
    +	}
    +
    +	if len(secret.Data) != 0 {
    +		return fmt.Errorf("secret data should be empty")
    +	}
    +
    +	return nil
    +}
    
  • pkg/provider-local/admission/validator/webhook.go+24 1 modified
    @@ -5,18 +5,22 @@
     package validator
     
     import (
    +	corev1 "k8s.io/api/core/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"sigs.k8s.io/controller-runtime/pkg/log"
     	"sigs.k8s.io/controller-runtime/pkg/manager"
     
     	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
     	"github.com/gardener/gardener/pkg/apis/core"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
     	"github.com/gardener/gardener/pkg/provider-local/local"
     )
     
     const (
     	// Name is a name for a validation webhook.
     	Name = "validator"
    +	// SecretsValidatorName is the name of the secrets validator.
    +	SecretsValidatorName = "secrets." + Name
     )
     
     var logger = log.Log.WithName("local-validator-webhook")
    @@ -31,10 +35,29 @@ func New(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
     		Path:     "/webhooks/validate",
     		Validators: map[extensionswebhook.Validator][]extensionswebhook.Type{
     			NewNamespacedCloudProfileValidator(mgr): {{Obj: &core.NamespacedCloudProfile{}}},
    +			NewWorkloadIdentityValidator():          {{Obj: &securityv1alpha1.WorkloadIdentity{}}},
     		},
     		Target: extensionswebhook.TargetSeed,
     		ObjectSelector: &metav1.LabelSelector{
    -			MatchLabels: map[string]string{"provider.extensions.gardener.cloud/local": "true"},
    +			MatchLabels: map[string]string{"provider.extensions.gardener.cloud/" + local.Type: "true"},
    +		},
    +	})
    +}
    +
    +// NewSecretsWebhook creates a new validation webhook for Secrets.
    +func NewSecretsWebhook(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
    +	logger.Info("Setting up webhook", "name", SecretsValidatorName)
    +
    +	return extensionswebhook.New(mgr, extensionswebhook.Args{
    +		Provider: local.Type,
    +		Name:     SecretsValidatorName,
    +		Path:     "/webhooks/validate/secrets",
    +		Validators: map[extensionswebhook.Validator][]extensionswebhook.Type{
    +			NewSecretValidator(): {{Obj: &corev1.Secret{}}},
    +		},
    +		Target: extensionswebhook.TargetSeed,
    +		ObjectSelector: &metav1.LabelSelector{
    +			MatchLabels: map[string]string{"provider.shoot.gardener.cloud/" + local.Type: "true"},
     		},
     	})
     }
    
  • pkg/provider-local/admission/validator/workloadidentity.go+38 0 added
    @@ -0,0 +1,38 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package validator
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +)
    +
    +type workloadIdentityValidator struct {
    +}
    +
    +// NewWorkloadIdentityValidator returns a new instance of a WorkloadIdentity validator.
    +func NewWorkloadIdentityValidator() extensionswebhook.Validator {
    +	return &workloadIdentityValidator{}
    +}
    +
    +// Validate checks whether the provider config is empty.
    +func (wi *workloadIdentityValidator) Validate(_ context.Context, newObj, _ client.Object) error {
    +	workloadIdentity, ok := newObj.(*securityv1alpha1.WorkloadIdentity)
    +	if !ok {
    +		return fmt.Errorf("wrong object type %T", newObj)
    +	}
    +
    +	if workloadIdentity.Spec.TargetSystem.ProviderConfig != nil {
    +		return errors.New("target system provider config must be empty")
    +	}
    +
    +	return nil
    +}
    
  • plugin/pkg/global/extensionlabels/admission.go+3 5 modified
    @@ -242,11 +242,9 @@ func addMetaDataLabelsSeed(seed *core.Seed) {
     }
     
     func addMetaDataLabelsSecretBinding(secretBinding *core.SecretBinding) {
    -	if secretBinding.Provider != nil {
    -		types := gardencorehelper.GetSecretBindingTypes(secretBinding)
    -		for _, t := range types {
    -			metav1.SetMetaDataLabel(&secretBinding.ObjectMeta, v1beta1constants.LabelExtensionProviderTypePrefix+t, "true")
    -		}
    +	types := gardencorehelper.GetSecretBindingTypes(secretBinding)
    +	for _, t := range types {
    +		metav1.SetMetaDataLabel(&secretBinding.ObjectMeta, v1beta1constants.LabelExtensionProviderTypePrefix+t, "true")
     	}
     }
     
    
  • plugin/pkg/global/finalizerremoval/admission.go+196 0 added
    @@ -0,0 +1,196 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +	"io"
    +	"slices"
    +
    +	apierrors "k8s.io/apimachinery/pkg/api/errors"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/labels"
    +	"k8s.io/apimachinery/pkg/util/sets"
    +	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/utils/ptr"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	"github.com/gardener/gardener/pkg/apis/core"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	admissioninitializer "github.com/gardener/gardener/pkg/apiserver/admission/initializer"
    +	gardencoreinformers "github.com/gardener/gardener/pkg/client/core/informers/externalversions"
    +	gardencorev1beta1listers "github.com/gardener/gardener/pkg/client/core/listers/core/v1beta1"
    +	plugin "github.com/gardener/gardener/plugin/pkg"
    +)
    +
    +// Register registers a plugin.
    +func Register(plugins *admission.Plugins) {
    +	plugins.Register(plugin.PluginNameFinalizerRemoval, func(_ io.Reader) (admission.Interface, error) {
    +		return New()
    +	})
    +}
    +
    +// FinalizerRemoval contains listers and admission handler.
    +type FinalizerRemoval struct {
    +	*admission.Handler
    +	shootLister gardencorev1beta1listers.ShootLister
    +	readyFunc   admission.ReadyFunc
    +}
    +
    +var (
    +	_ = admissioninitializer.WantsCoreInformerFactory(&FinalizerRemoval{})
    +
    +	readyFuncs []admission.ReadyFunc
    +)
    +
    +// New creates a new FinalizerRemoval admission plugin.
    +func New() (*FinalizerRemoval, error) {
    +	return &FinalizerRemoval{
    +		Handler: admission.NewHandler(admission.Update),
    +	}, nil
    +}
    +
    +// AssignReadyFunc assigns the ready function to the admission handler.
    +func (f *FinalizerRemoval) AssignReadyFunc(fn admission.ReadyFunc) {
    +	f.readyFunc = fn
    +	f.SetReadyFunc(fn)
    +}
    +
    +// SetCoreInformerFactory gets Lister from SharedInformerFactory.
    +func (f *FinalizerRemoval) SetCoreInformerFactory(g gardencoreinformers.SharedInformerFactory) {
    +	shootInformer := g.Core().V1beta1().Shoots()
    +	f.shootLister = shootInformer.Lister()
    +
    +	readyFuncs = append(readyFuncs,
    +		shootInformer.Informer().HasSynced,
    +	)
    +}
    +
    +// ValidateInitialization checks whether the plugin was correctly initialized.
    +func (f *FinalizerRemoval) ValidateInitialization() error {
    +	if f.shootLister == nil {
    +		return errors.New("missing shoot lister")
    +	}
    +	return nil
    +}
    +
    +// Admit ensures that finalizers from objects can only be removed if they are not needed anymore.
    +func (f *FinalizerRemoval) Admit(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +	// Wait until the caches have been synced
    +	if f.readyFunc == nil {
    +		f.AssignReadyFunc(func() bool {
    +			for _, readyFunc := range readyFuncs {
    +				if !readyFunc() {
    +					return false
    +				}
    +			}
    +			return true
    +		})
    +	}
    +	if !f.WaitForReady() {
    +		return admission.NewForbidden(a, errors.New("not yet ready to handle request"))
    +	}
    +
    +	var (
    +		err            error
    +		newObj, oldObj client.Object
    +	)
    +
    +	oldObj, ok := a.GetOldObject().(client.Object)
    +	if !ok {
    +		return nil
    +	}
    +
    +	newObj, ok = a.GetObject().(client.Object)
    +	if !ok {
    +		return nil
    +	}
    +
    +	switch a.GetKind().GroupKind() {
    +	case core.Kind("SecretBinding"):
    +		binding, ok := a.GetObject().(*core.SecretBinding)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into SecretBinding object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the SecretBinding is not used by any shoot.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) {
    +			inUse, err := f.isUsedByShoot(binding.Namespace, func(shoot *gardencorev1beta1.Shoot) bool {
    +				return ptr.Deref(shoot.Spec.SecretBindingName, "") == binding.Name
    +			})
    +			if err != nil {
    +				return apierrors.NewInternalError(fmt.Errorf("error checking if secret binding is in use: %w", err))
    +			}
    +			if inUse {
    +				return admission.NewForbidden(a, fmt.Errorf("finalizer must not be removed - secret binding %s/%s is still in use by at least one shoot", binding.Namespace, binding.Name))
    +			}
    +		}
    +	case security.Kind("CredentialsBinding"):
    +		binding, ok := a.GetObject().(*security.CredentialsBinding)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into CredentialsBinding object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the CredentialsBinding is not used by any shoot.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) {
    +			inUse, err := f.isUsedByShoot(binding.Namespace, func(shoot *gardencorev1beta1.Shoot) bool {
    +				return ptr.Deref(shoot.Spec.CredentialsBindingName, "") == binding.Name
    +			})
    +			if err != nil {
    +				return apierrors.NewInternalError(fmt.Errorf("error checking if credentials binding is in use: %w", err))
    +			}
    +			if inUse {
    +				return admission.NewForbidden(a, fmt.Errorf("finalizer must not be removed - credentials binding %s/%s is still in use by at least one shoot", binding.Namespace, binding.Name))
    +			}
    +		}
    +	case core.Kind("Shoot"):
    +		shoot, ok := a.GetObject().(*core.Shoot)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into Shoot object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the Shoot deletion has completed successfully.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) && !shootDeletionSucceeded(shoot) {
    +			return admission.NewForbidden(a, fmt.Errorf("finalizer %q cannot be removed because shoot deletion has not completed successfully yet", core.GardenerName))
    +		}
    +	}
    +
    +	if err != nil {
    +		return admission.NewForbidden(a, err)
    +	}
    +	return nil
    +}
    +
    +func (f *FinalizerRemoval) isUsedByShoot(namespace string, inUse func(*gardencorev1beta1.Shoot) bool) (bool, error) {
    +	shoots, err := f.shootLister.Shoots(namespace).List(labels.Everything())
    +	if err != nil {
    +		return false, fmt.Errorf("error retrieving shoots: %w", err)
    +	}
    +
    +	return slices.ContainsFunc(shoots, inUse), nil
    +}
    +
    +func shootDeletionSucceeded(shoot *core.Shoot) bool {
    +	if len(shoot.Status.TechnicalID) == 0 || shoot.Status.LastOperation == nil {
    +		return true
    +	}
    +
    +	lastOperation := shoot.Status.LastOperation
    +	return lastOperation.Type == core.LastOperationTypeDelete &&
    +		lastOperation.State == core.LastOperationStateSucceeded &&
    +		lastOperation.Progress == 100
    +}
    +
    +func isFinalizerRemoved(old, new metav1.Object, finalizerName string) bool {
    +	var (
    +		oldFinalizers = sets.New(old.GetFinalizers()...)
    +		newFinalizer  = sets.New(new.GetFinalizers()...)
    +	)
    +
    +	return oldFinalizers.Has(finalizerName) && !newFinalizer.Has(finalizerName)
    +}
    
  • plugin/pkg/global/finalizerremoval/admission_test.go+216 0 added
    @@ -0,0 +1,216 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval_test
    +
    +import (
    +	"context"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/utils/ptr"
    +
    +	"github.com/gardener/gardener/pkg/apis/core"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	gardencoreinformers "github.com/gardener/gardener/pkg/client/core/informers/externalversions"
    +	. "github.com/gardener/gardener/plugin/pkg/global/finalizerremoval"
    +)
    +
    +var _ = Describe("finalizerremoval", func() {
    +	Describe("#Admit", func() {
    +		var (
    +			ctx                       context.Context
    +			admissionHandler          *FinalizerRemoval
    +			gardenCoreInformerFactory gardencoreinformers.SharedInformerFactory
    +
    +			finalizers []string
    +
    +			namespace              = "default"
    +			secretBindingName      = "binding-1"
    +			credentialsBindingName = "credentials-binding-1"
    +			shootName              = "shoot-1"
    +
    +			shoot *gardencorev1beta1.Shoot
    +		)
    +
    +		BeforeEach(func() {
    +			ctx = context.Background()
    +			admissionHandler, _ = New()
    +			admissionHandler.AssignReadyFunc(func() bool { return true })
    +
    +			finalizers = []string{core.GardenerName}
    +
    +			shoot = &gardencorev1beta1.Shoot{
    +				ObjectMeta: metav1.ObjectMeta{
    +					Name:      shootName,
    +					Namespace: namespace,
    +				},
    +				Spec: gardencorev1beta1.ShootSpec{
    +					CredentialsBindingName: ptr.To(credentialsBindingName),
    +					SecretBindingName:      ptr.To(secretBindingName),
    +				},
    +			}
    +
    +			gardenCoreInformerFactory = gardencoreinformers.NewSharedInformerFactory(nil, 0)
    +			admissionHandler.SetCoreInformerFactory(gardenCoreInformerFactory)
    +		})
    +
    +		Context("SecretBinding", func() {
    +			var coreSecretBinding *core.SecretBinding
    +
    +			BeforeEach(func() {
    +				coreSecretBinding = &core.SecretBinding{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Name:       secretBindingName,
    +						Namespace:  namespace,
    +						Finalizers: finalizers,
    +					},
    +				}
    +			})
    +
    +			It("should admit the removal because object is not used by any shoot", func() {
    +				attrs := admission.NewAttributesRecord(&core.SecretBinding{}, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should admit the removal because finalizer is irrelevant", func() {
    +				newSecretBinding := coreSecretBinding.DeepCopy()
    +				coreSecretBinding.Finalizers = append(coreSecretBinding.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newSecretBinding, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should reject the removal because object is not used by any shoot", func() {
    +				newSecretBinding := coreSecretBinding.DeepCopy()
    +				newSecretBinding.Finalizers = nil
    +
    +				secondShoot := shoot.DeepCopy()
    +				secondShoot.Name = shootName + "-2"
    +				secondShoot.Spec.SecretBindingName = ptr.To(secretBindingName + "-2")
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shoot)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(secondShoot)).To(Succeed())
    +
    +				attrs := admission.NewAttributesRecord(newSecretBinding, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("finalizer must not be removed")))
    +			})
    +		})
    +
    +		Context("CredentialsBinding", func() {
    +			var coreCredentialsBinding *security.CredentialsBinding
    +
    +			BeforeEach(func() {
    +				coreCredentialsBinding = &security.CredentialsBinding{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Name:       credentialsBindingName,
    +						Namespace:  namespace,
    +						Finalizers: finalizers,
    +					},
    +				}
    +			})
    +
    +			It("should admit the removal because object is not used by any shoot", func() {
    +				attrs := admission.NewAttributesRecord(&security.CredentialsBinding{}, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should admit the removal because finalizer is irrelevant", func() {
    +				newCredentialsBinding := coreCredentialsBinding.DeepCopy()
    +				coreCredentialsBinding.Finalizers = append(coreCredentialsBinding.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newCredentialsBinding, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should reject the removal because object is not used by any shoot", func() {
    +				newCredentialsBinding := coreCredentialsBinding.DeepCopy()
    +				newCredentialsBinding.Finalizers = nil
    +
    +				secondShoot := shoot.DeepCopy()
    +				secondShoot.Name = shootName + "-2"
    +				secondShoot.Spec.CredentialsBindingName = ptr.To(secretBindingName + "-2")
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shoot)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(secondShoot)).To(Succeed())
    +
    +				attrs := admission.NewAttributesRecord(newCredentialsBinding, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("finalizer must not be removed")))
    +			})
    +		})
    +
    +		Context("shoot", func() {
    +			var coreShoot *core.Shoot
    +
    +			BeforeEach(func() {
    +				coreShoot = &core.Shoot{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Finalizers: finalizers,
    +					},
    +					Status: core.ShootStatus{
    +						TechnicalID: "some-id",
    +						LastOperation: &core.LastOperation{
    +							Type:     core.LastOperationTypeReconcile,
    +							State:    core.LastOperationStateSucceeded,
    +							Progress: 100,
    +						},
    +					},
    +				}
    +			})
    +
    +			It("should allow the removal because finalizer is irrelevant", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				coreShoot.Finalizers = append(coreShoot.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, security.Kind("Shoot").WithVersion("version"), "", coreShoot.Name, security.Resource("Shoot").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should admit the removal if the shoot deletion succeeded ", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.LastOperation.Type = core.LastOperationTypeDelete
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should reject the removal if the shoot has not yet been deleted successfully", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("shoot deletion has not completed successfully yet")))
    +			})
    +
    +			It("should admit the removal if the shoot has not yet a last operation", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.LastOperation = nil
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should admit the removal if the shoot has not yet a technical id", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.TechnicalID = ""
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +		})
    +	})
    +})
    
  • plugin/pkg/global/finalizerremoval/finalizerremoval_suite_test.go+17 0 added
    @@ -0,0 +1,17 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +)
    +
    +func TestFinalizerRemoval(t *testing.T) {
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "AdmissionPlugin Global FinalizerRemoval Suite")
    +}
    
  • plugin/pkg/global/resourcereferencemanager/admission.go+91 72 modified
    @@ -10,13 +10,13 @@ import (
     	"fmt"
     	"io"
     	"reflect"
    +	"slices"
     	"strings"
     	"sync"
     	"time"
     
     	"github.com/hashicorp/go-multierror"
     	corev1 "k8s.io/api/core/v1"
    -	rbacv1 "k8s.io/api/rbac/v1"
     	apiequality "k8s.io/apimachinery/pkg/api/equality"
     	apierrors "k8s.io/apimachinery/pkg/api/errors"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    @@ -32,6 +32,7 @@ import (
     	kubeinformers "k8s.io/client-go/informers"
     	"k8s.io/client-go/kubernetes"
     	kubecorev1listers "k8s.io/client-go/listers/core/v1"
    +	"k8s.io/utils/ptr"
     	"sigs.k8s.io/controller-runtime/pkg/client"
     
     	"github.com/gardener/gardener/pkg/apis/core"
    @@ -287,8 +288,8 @@ func (r *ReferenceManager) ValidateInitialization() error {
     	return nil
     }
     
    -// Admit ensures that referenced resources do actually exist.
    -func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +// Validate ensures that referenced resources do actually exist.
    +func (r *ReferenceManager) Validate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
     	// Wait until the caches have been synced
     	if r.readyFunc == nil {
     		r.AssignReadyFunc(func() bool {
    @@ -350,14 +351,6 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     
     		switch a.GetOperation() {
     		case admission.Create:
    -			// Add createdBy annotation to Shoot
    -			annotations := shoot.Annotations
    -			if annotations == nil {
    -				annotations = map[string]string{}
    -			}
    -			annotations[v1beta1constants.GardenCreatedBy] = a.GetUserInfo().GetName()
    -			shoot.Annotations = annotations
    -
     			oldShoot = &core.Shoot{}
     		case admission.Update:
     			// skip verification if spec wasn't changed
    @@ -408,31 +401,9 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     		if utils.SkipVerification(operation, project.ObjectMeta) {
     			return nil
     		}
    -		// Set createdBy field in Project
    +
     		switch a.GetOperation() {
     		case admission.Create:
    -			project.Spec.CreatedBy = &rbacv1.Subject{
    -				APIGroup: "rbac.authorization.k8s.io",
    -				Kind:     rbacv1.UserKind,
    -				Name:     a.GetUserInfo().GetName(),
    -			}
    -
    -			if project.Spec.Owner == nil {
    -				owner := project.Spec.CreatedBy
    -
    -			outer:
    -				for _, member := range project.Spec.Members {
    -					for _, role := range member.Roles {
    -						if role == core.ProjectMemberOwner {
    -							owner = member.Subject.DeepCopy()
    -							break outer
    -						}
    -					}
    -				}
    -
    -				project.Spec.Owner = owner
    -			}
    -
     			err = r.ensureProjectNamespace(project)
     		case admission.Update:
     			oldProject, ok := a.GetOldObject().(*core.Project)
    @@ -444,24 +415,6 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     			}
     		}
     
    -		if project.Spec.Owner != nil {
    -			ownerIsMember := false
    -			for _, member := range project.Spec.Members {
    -				if member.Subject == *project.Spec.Owner {
    -					ownerIsMember = true
    -				}
    -			}
    -			if !ownerIsMember {
    -				project.Spec.Members = append(project.Spec.Members, core.ProjectMember{
    -					Subject: *project.Spec.Owner,
    -					Roles: []string{
    -						core.ProjectMemberAdmin,
    -						core.ProjectMemberOwner,
    -					},
    -				})
    -			}
    -		}
    -
     	case core.Kind("BackupBucket"):
     		if operation == admission.Delete {
     			// The "delete endpoint" handler of the k8s.io/apiserver library calls the admission controllers
    @@ -788,7 +741,10 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		credentialsNamespace  string
     		credentialsName       string
     		credentialsKind       string
    +		providerTypes         []string
    +		credentialsReferenced func(shoot *gardencorev1beta1.Shoot) bool
     	)
    +
     	switch attributes.GetKind().GroupKind() {
     	case core.Kind("SecretBinding"):
     		b, ok := binding.(*core.SecretBinding)
    @@ -802,6 +758,11 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		credentialsNamespace = b.SecretRef.Namespace
     		credentialsName = b.SecretRef.Name
     		credentialsKind = "Secret"
    +		providerTypes = helper.GetSecretBindingTypes(b)
    +		credentialsReferenced = func(shoot *gardencorev1beta1.Shoot) bool {
    +			return ptr.Deref(shoot.Spec.SecretBindingName, "") == b.Name
    +		}
    +
     	case security.Kind("CredentialsBinding"):
     		b, ok := binding.(*security.CredentialsBinding)
     		if !ok {
    @@ -823,9 +784,30 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		}
     		credentialsNamespace = b.CredentialsRef.Namespace
     		credentialsName = b.CredentialsRef.Name
    +		providerTypes = []string{b.Provider.Type}
    +		credentialsReferenced = func(shoot *gardencorev1beta1.Shoot) bool {
    +			return ptr.Deref(shoot.Spec.CredentialsBindingName, "") == b.Name
    +		}
    +
     	default:
     		return fmt.Errorf("%s is neither of kind SecretBinding nor CredentialsBinding", attributes.GetKind().GroupKind())
     	}
    +
    +	shoots, err := r.shootLister.Shoots(attributes.GetNamespace()).List(labels.Everything())
    +	if err != nil {
    +		return fmt.Errorf("failed listing shoots: %w", err)
    +	}
    +
    +	for _, shoot := range shoots {
    +		if !credentialsReferenced(shoot) {
    +			continue
    +		}
    +
    +		if !slices.Contains(providerTypes, shoot.Spec.Provider.Type) {
    +			return fmt.Errorf("%s is referenced by shoot %q, but provider types (%+v) do not match with the shoot provider type %q", attributes.GetKind().Kind, shoot.Name, providerTypes, shoot.Spec.Provider.Type)
    +		}
    +	}
    +
     	readAttributes := authorizer.AttributesRecord{
     		User:            attributes.GetUserInfo(),
     		Verb:            "get",
    @@ -847,10 +829,20 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		if err := r.lookupSecret(ctx, credentialsNamespace, credentialsName); err != nil {
     			return err
     		}
    +		if err := r.sanityCheckProviderSecret(ctx, credentialsNamespace, credentialsName, providerTypes); err != nil {
    +			return err
    +		}
    +
     	case "WorkloadIdentity":
    -		if err := r.lookupWorkloadIdentity(ctx, credentialsNamespace, credentialsName); err != nil {
    +		workloadIdentity, err := r.lookupWorkloadIdentity(ctx, credentialsNamespace, credentialsName)
    +		if err != nil {
     			return err
     		}
    +
    +		if !slices.Contains(providerTypes, workloadIdentity.Spec.TargetSystem.Type) {
    +			return fmt.Errorf("CredentialsBinding provider type (%+v) does not match with WorkloadIdentity provider type %s", providerTypes, workloadIdentity.Spec.TargetSystem.Type)
    +		}
    +
     	default:
     		return fmt.Errorf("unknown credentials kind: %s", credentialsKind)
     	}
    @@ -1175,42 +1167,36 @@ func validateShootWorkersForRemovedMachineImageVersions(channel chan error, shoo
     
     type getFn func(context.Context, string, string) (runtime.Object, error)
     
    -func lookupResource(ctx context.Context, namespace, name string, get getFn, fallbackGet getFn) error {
    +func lookupResource(ctx context.Context, namespace, name string, get getFn, fallbackGet getFn) (runtime.Object, error) {
     	// First try to detect the resource in the cache.
    -	var err error
    -
    -	_, err = get(ctx, namespace, name)
    +	obj, err := get(ctx, namespace, name)
     	if err == nil {
    -		return nil
    +		return obj, nil
     	}
     	if !apierrors.IsNotFound(err) {
    -		return err
    +		return nil, err
     	}
     
     	// Second try to detect the resource in the cache after the first try failed.
     	// Give the cache time to observe the resource before rejecting a create.
     	// This helps when creating a resource and immediately creating a binding referencing it.
     	time.Sleep(MissingResourceWait)
    -	_, err = get(ctx, namespace, name)
    +	obj, err = get(ctx, namespace, name)
     
     	switch {
     	case apierrors.IsNotFound(err):
     		// no-op
     	case err != nil:
    -		return err
    +		return nil, err
     	default:
    -		return nil
    +		return obj, nil
     	}
     
     	// Third try to detect the secret, now by doing a live lookup instead of relying on the cache.
    -	if _, err := fallbackGet(ctx, namespace, name); err != nil {
    -		return err
    -	}
    -
    -	return nil
    +	return fallbackGet(ctx, namespace, name)
     }
     
    -func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace, name string) error {
    +func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace, name string) (*securityv1alpha1.WorkloadIdentity, error) {
     	workloadIdentityFromLister := func(_ context.Context, namespace, name string) (runtime.Object, error) {
     		return r.workloadIdentityLister.WorkloadIdentities(namespace).Get(name)
     	}
    @@ -1219,7 +1205,11 @@ func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace
     		return r.gardenSecurityClient.SecurityV1alpha1().WorkloadIdentities(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, workloadIdentityFromLister, workloadIdentityFromClient)
    +	obj, err := lookupResource(ctx, namespace, name, workloadIdentityFromLister, workloadIdentityFromClient)
    +	if err != nil {
    +		return nil, err
    +	}
    +	return obj.(*securityv1alpha1.WorkloadIdentity), nil
     }
     
     func (r *ReferenceManager) lookupSecret(ctx context.Context, namespace, name string) error {
    @@ -1231,7 +1221,8 @@ func (r *ReferenceManager) lookupSecret(ctx context.Context, namespace, name str
     		return r.kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, secretFromLister, secretFromClient)
    +	_, err := lookupResource(ctx, namespace, name, secretFromLister, secretFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) lookupConfigMap(ctx context.Context, namespace, name string) error {
    @@ -1243,7 +1234,8 @@ func (r *ReferenceManager) lookupConfigMap(ctx context.Context, namespace, name
     		return r.kubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, configMapFromLister, configMapFromClient)
    +	_, err := lookupResource(ctx, namespace, name, configMapFromLister, configMapFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) lookupControllerDeployment(ctx context.Context, name string) error {
    @@ -1255,7 +1247,8 @@ func (r *ReferenceManager) lookupControllerDeployment(ctx context.Context, name
     		return r.gardenCoreClient.CoreV1beta1().ControllerDeployments().Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, "", name, deploymentFromLister, deploymentFromClient)
    +	_, err := lookupResource(ctx, "", name, deploymentFromLister, deploymentFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) getAPIResource(groupVersion, kind string) (*metav1.APIResource, error) {
    @@ -1350,3 +1343,29 @@ func (r *ReferenceManager) ensureResourceReferences(ctx context.Context, attribu
     	}
     	return nil
     }
    +
    +func (r *ReferenceManager) sanityCheckProviderSecret(ctx context.Context, namespace, name string, providerTypes []string) error {
    +	secret, err := r.kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
    +	if err != nil {
    +		return err
    +	}
    +
    +	for _, providerType := range providerTypes {
    +		dummySecret := &corev1.Secret{
    +			ObjectMeta: metav1.ObjectMeta{
    +				GenerateName: name,
    +				Namespace:    namespace,
    +				Annotations:  secret.Annotations,
    +				Labels:       gardenerutils.MergeStringMaps(secret.Labels, map[string]string{v1beta1constants.LabelShootProviderPrefix + providerType: "true"}),
    +			},
    +			Type: secret.Type,
    +			Data: secret.Data,
    +		}
    +
    +		if _, err := r.kubeClient.CoreV1().Secrets(dummySecret.Namespace).Create(ctx, dummySecret, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}); err != nil {
    +			return fmt.Errorf("%s provider secret sanity check failed: %w", providerType, err)
    +		}
    +	}
    +
    +	return nil
    +}
    
  • plugin/pkg/global/resourcereferencemanager/admission_test.go+221 230 modified
    @@ -16,7 +16,6 @@ import (
     	. "github.com/onsi/gomega/gstruct"
     	autoscalingv1 "k8s.io/api/autoscaling/v1"
     	corev1 "k8s.io/api/core/v1"
    -	rbacv1 "k8s.io/api/rbac/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/runtime"
     	"k8s.io/apiserver/pkg/admission"
    @@ -30,7 +29,6 @@ import (
     
     	"github.com/gardener/gardener/pkg/apis/core"
     	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    -	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
     	"github.com/gardener/gardener/pkg/apis/security"
     	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
     	"github.com/gardener/gardener/pkg/apis/seedmanagement"
    @@ -178,6 +176,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: &core.SecretBindingProvider{
    +					Type: "test",
    +				},
     			}
     			secretBinding = gardencorev1beta1.SecretBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -195,6 +196,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: &gardencorev1beta1.SecretBindingProvider{
    +					Type: "test",
    +				},
     			}
     			securityCredentialsBindingRefSecret = security.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -213,6 +217,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: security.CredentialsBindingProvider{
    +					Type: "test",
    +				},
     			}
     			credentialsBindingRefSecret = securityv1alpha1.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -231,6 +238,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: securityv1alpha1.CredentialsBindingProvider{
    +					Type: "test",
    +				},
     			}
     			securityCredentialsBindingRefWorkloadIdentity = security.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -244,6 +254,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					Name:       workloadIdentityName,
     					Namespace:  namespace,
     				},
    +				Provider: security.CredentialsBindingProvider{Type: "wiprovider"},
     				Quotas: []corev1.ObjectReference{
     					{
     						Name:      quotaName,
    @@ -451,18 +462,20 @@ var _ = Describe("resourcereferencemanager", func() {
     
     			err = gardencorev1beta1.Convert_core_Project_To_v1beta1_Project(&coreProject, &project, nil)
     			Expect(err).To(Succeed())
    +
    +			workloadIdentity.Spec.TargetSystem = securityv1alpha1.TargetSystem{Type: "wiprovider"}
     		})
     
     		It("should return nil because the resource is not BackupBucket and operation is delete", func() {
     			attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
     
    -			err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +			err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     			Expect(err).NotTo(HaveOccurred())
     
     			attrs = admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), "", controllerRegistration.Name, core.Resource("shoots").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
     
    -			err = admissionHandler.Admit(context.TODO(), attrs, nil)
    +			err = admissionHandler.Validate(context.TODO(), attrs, nil)
     
     			Expect(err).NotTo(HaveOccurred())
     		})
    @@ -474,7 +487,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -487,7 +500,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -499,7 +512,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					return true, nil, errors.New("nope, out of luck")
     				})
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     				Expect(err.Error()).To(ContainSubstring("nope, out of luck"))
    @@ -514,7 +527,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -533,11 +546,36 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, nil
    +				})
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the sanity check fails", func() {
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, &corev1.Secret{
    +						ObjectMeta: metav1.ObjectMeta{
    +							Namespace: secret.Namespace,
    +							Name:      secret.Name,
    +						},
    +					}, nil
    +				})
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, fmt.Errorf("sanity check failed")
    +				})
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("test provider secret sanity check failed: sanity check failed")))
    +			})
    +
     			It("should reject because the referenced secret does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -547,7 +585,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -559,7 +597,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -570,7 +608,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -582,7 +620,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -617,7 +655,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -652,10 +690,23 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    +
    +			It("should reject because provider types do not match with shoot type", func() {
    +				coreSecretBinding.Provider.Type = "another-provider"
    +				coreSecretBinding.Quotas = nil
    +				shoot.Spec.Provider.Type = "local"
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(&shoot)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(`SecretBinding is referenced by shoot "shoot-1", but provider types ([another-provider]) do not match with the shoot provider type "local"`)))
    +			})
     		})
     
     		Context("tests for CredentialsBinding objects referencing Secret", func() {
    @@ -666,7 +717,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -685,11 +736,36 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, nil
    +				})
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the sanity check fails", func() {
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, &corev1.Secret{
    +						ObjectMeta: metav1.ObjectMeta{
    +							Namespace: secret.Namespace,
    +							Name:      secret.Name,
    +						},
    +					}, nil
    +				})
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, fmt.Errorf("sanity check failed")
    +				})
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("test provider secret sanity check failed: sanity check failed")))
    +			})
    +
     			It("should reject because the referenced secret does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -699,7 +775,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -711,7 +787,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -722,7 +798,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -734,7 +810,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -769,7 +845,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -804,10 +880,23 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    +
    +			It("should reject because provider types do not match with shoot type", func() {
    +				securityCredentialsBindingRefSecret.Provider.Type = "another-provider"
    +				securityCredentialsBindingRefSecret.Quotas = nil
    +				shoot.Spec.Provider.Type = "local"
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(&shoot)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(`CredentialsBinding is referenced by shoot "shoot-1", but provider types ([another-provider]) do not match with the shoot provider type "local"`)))
    +			})
     		})
     
     		Context("tests for CredentialsBinding objects referencing WorkloadIdentity", func() {
    @@ -818,30 +907,38 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
     			It("should accept because all referenced objects have been found (workloadidentity looked up live)", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				gardenSecurityClient.AddReactor("get", "workloadidentities", func(_ testing.Action) (bool, runtime.Object, error) {
    -					return true, &securityv1alpha1.WorkloadIdentity{
    -						ObjectMeta: metav1.ObjectMeta{
    -							Namespace: workloadIdentity.Namespace,
    -							Name:      workloadIdentity.Name,
    -						},
    -					}, nil
    +					return true, &workloadIdentity, nil
     				})
     
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the provider type does not match in WorkloadIdentity and CredentialsBinding", func() {
    +				workloadIdentity.Spec.TargetSystem.Type = "foo"
    +				Expect(gardenSecurityInformerFactory.Security().V1alpha1().WorkloadIdentities().Informer().GetStore().Add(&workloadIdentity)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
    +
    +				Expect(err).To(MatchError(ContainSubstring("does not match with WorkloadIdentity provider type")))
    +			})
    +
     			It("should reject because the referenced workload identity does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				gardenSecurityClient.AddReactor("get", "workloadidentities", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -851,7 +948,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -863,7 +960,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -874,7 +971,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -886,7 +983,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -914,14 +1011,15 @@ var _ = Describe("resourcereferencemanager", func() {
     				quotaRefList = append(quotaRefList, quota2Ref)
     				securityCredentialsBindingRefWorkloadIdentity.Quotas = quotaRefList
     
    +				Expect(gardenSecurityInformerFactory.Security().V1alpha1().WorkloadIdentities().Informer().GetStore().Add(&workloadIdentity)).To(Succeed())
     				Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota2)).To(Succeed())
     
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -975,31 +1073,13 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
     		})
     
     		Context("tests for Shoot objects", func() {
    -			It("should add the created-by annotation", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(gardenSecurityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBindingRefSecret)).To(Succeed())
    -				Expect(kubeInformerFactory.Core().V1().ConfigMaps().Informer().GetStore().Add(&configMap)).To(Succeed())
    -
    -				user := &user.DefaultInfo{Name: allowedUser}
    -				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    -
    -				Expect(coreShoot.Annotations).NotTo(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, user.Name))
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreShoot.Annotations).To(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, user.Name))
    -			})
    -
     			It("should accept because all referenced objects have been found", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    @@ -1010,7 +1090,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1026,7 +1106,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreShoot.Status.TechnicalID = "should-never-change"
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1035,7 +1115,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				It("should reject because the referenced cloud profile does not exist (create)", func() {
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1046,7 +1126,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1061,7 +1141,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1078,7 +1158,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1091,7 +1171,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1103,7 +1183,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1115,7 +1195,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1130,7 +1210,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1150,7 +1230,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				It("should reject because the referenced exposure class does not exists", func() {
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     					Expect(err).To(HaveOccurred())
     				})
     
    @@ -1164,7 +1244,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					Expect(gardenCoreInformerFactory.Core().V1beta1().ExposureClasses().Informer().GetStore().Add(&exposureClass)).To(Succeed())
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     					Expect(err).To(HaveOccurred())
     				})
     			})
    @@ -1176,7 +1256,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1191,7 +1271,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("shoots.core.gardener.cloud \"shoot-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1218,7 +1298,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("shoots.core.gardener.cloud \"shoot-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1234,7 +1314,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1251,7 +1331,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError(ContainSubstring("failed to resolve resource reference")))
     			})
    @@ -1273,7 +1353,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
     				})
     
     				It("should reject because the referenced "+description+" does not exist (update)", func() {
    @@ -1293,7 +1373,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
     				})
     
     				It("should pass because the referenced "+description+" does not exist but shoot has deletion timestamp", func() {
    @@ -1316,7 +1396,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     				})
     
     				It("should pass because the referenced "+description+" exists", func() {
    @@ -1335,7 +1415,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     				})
     			}
     
    @@ -1409,7 +1489,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSeed, oldSeed, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("seeds.core.gardener.cloud \"seed-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1420,7 +1500,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSeed, oldSeed, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1429,7 +1509,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSeed, nil, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError(ContainSubstring("failed to resolve resource reference")))
     			})
    @@ -1439,7 +1519,7 @@ var _ = Describe("resourcereferencemanager", func() {
     			It("should reject if the referred Seed is not found", func() {
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: seed.core.gardener.cloud %q not found", coreBackupBucket.Name, seed.Name)))
    @@ -1453,7 +1533,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("secret not found")))
    @@ -1472,7 +1552,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1483,7 +1563,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1498,7 +1578,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1517,7 +1597,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", backupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: cannot delete BackupBucket because BackupEntries are still referencing it, backupEntryNames: %s/%s,%s/%s", backupBucket.Name, backupEntry.Namespace, backupEntry.Name, backupEntry2.Namespace, backupEntry2.Name)))
    @@ -1533,7 +1613,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1561,7 +1641,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", "", core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: cannot delete BackupBucket because BackupEntries are still referencing it, backupEntryNames: %s/%s,%s/%s", backupBucket2.Name, backupEntry.Namespace, backupEntry.Name, backupEntry2.Namespace, backupEntry2.Name)))
    @@ -1590,7 +1670,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", "", core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1600,7 +1680,7 @@ var _ = Describe("resourcereferencemanager", func() {
     			It("should reject if the referred Seed is not found", func() {
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupEntries.core.gardener.cloud %q is forbidden: seed.core.gardener.cloud %q not found", coreBackupEntry.Name, seed.Name)))
    @@ -1610,7 +1690,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupEntries.core.gardener.cloud %q is forbidden: backupbucket.core.gardener.cloud %q not found", coreBackupEntry.Name, coreBackupBucket.Name)))
    @@ -1621,102 +1701,13 @@ var _ = Describe("resourcereferencemanager", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().BackupBuckets().Informer().GetStore().Add(&backupBucket)).To(Succeed())
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     		})
     
     		Context("tests for Project objects", func() {
    -			It("should set the created-by field", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should set the owner field (member with owner role found)", func() {
    -				projectCopy := project.DeepCopy()
    -				coreProjectCopy := coreProject.DeepCopy()
    -				ownerMember := &rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     "owner",
    -				}
    -				projectCopy.Name = "foo"
    -				projectCopy.Spec.Members = []gardencorev1beta1.ProjectMember{
    -					{
    -						Subject: *ownerMember,
    -						Roles:   []string{core.ProjectMemberOwner},
    -					},
    -				}
    -				coreProjectCopy.Name = "foo"
    -				coreProjectCopy.Spec.Members = []core.ProjectMember{
    -					{
    -						Subject: *ownerMember,
    -						Roles:   []string{core.ProjectMemberOwner},
    -					},
    -				}
    -
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(projectCopy)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(coreProjectCopy, nil, core.Kind("Project").WithVersion("version"), coreProjectCopy.Namespace, coreProjectCopy.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProjectCopy.Spec.Owner).To(Equal(ownerMember))
    -				Expect(coreProjectCopy.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should set the owner field", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.Owner).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should add the owner to members", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.Members).To(ContainElement(Equal(core.ProjectMember{
    -					Subject: rbacv1.Subject{
    -						APIGroup: "rbac.authorization.k8s.io",
    -						Kind:     rbacv1.UserKind,
    -						Name:     defaultUserName,
    -					},
    -					Roles: []string{
    -						core.ProjectMemberAdmin,
    -						core.ProjectMemberOwner,
    -					},
    -				})))
    -			})
    -
     			It("should allow specifying a namespace which is not in use (create)", func() {
     				project.Spec.Namespace = ptr.To("garden-foo")
     				projectCopy := project.DeepCopy()
    @@ -1727,7 +1718,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1745,7 +1736,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, coreProjectOld, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1757,7 +1748,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1771,7 +1762,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{
     					"ErrStatus": MatchFields(IgnoreExtras, Fields{
    @@ -1793,7 +1784,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, &coreProjectOld, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{
     					"ErrStatus": MatchFields(IgnoreExtras, Fields{
    @@ -1835,7 +1826,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfile, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1856,7 +1847,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1876,7 +1867,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     				Expect(err.Error()).To(ContainSubstring("1.24.1"))
    @@ -1904,7 +1895,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(And(
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(And(
     					ContainSubstring("unable to delete Kubernetes version"),
     					ContainSubstring("1.24.1"),
     					ContainSubstring("still in use by NamespacedCloudProfile"),
    @@ -1935,7 +1926,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(And(
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(And(
     					ContainSubstring("unable to delete Kubernetes version"),
     					ContainSubstring("1.24.1"),
     					ContainSubstring("still in use by shoot '/shoot-Two'"),
    @@ -1964,7 +1955,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     			})
     
     			It("should accept removal of kubernetes versions that are used by shoots using another unrelated NamespacedCloudProfile of same name", func() {
    @@ -1987,7 +1978,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     			})
     
     			It("should accept removal of kubernetes version that is still in use by a shoot that is being deleted", func() {
    @@ -2009,7 +2000,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2105,7 +2096,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfile, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2148,7 +2139,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2192,7 +2183,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.Upd
    ... [truncated]
    
  • plugin/pkg/plugins.go+4 0 modified
    @@ -27,6 +27,8 @@ const (
     	PluginNameExtensionLabels = "ExtensionLabels"
     	// PluginNameExtensionValidator is the name of the ExtensionValidator admission plugin.
     	PluginNameExtensionValidator = "ExtensionValidator"
    +	// PluginNameFinalizerRemoval is the name of the FinalizerRemoval admission plugin.
    +	PluginNameFinalizerRemoval = "FinalizerRemoval"
     	// PluginNameResourceReferenceManager is the name of the ResourceReferenceManager admission plugin.
     	PluginNameResourceReferenceManager = "ResourceReferenceManager"
     	// PluginNameManagedSeedShoot is the name of the ManagedSeedShoot admission plugin.
    @@ -88,6 +90,7 @@ func AllPluginNames() []string {
     		PluginNameNamespacedCloudProfileValidator,   // NamespacedCloudProfileValidator
     		PluginNameProjectValidator,                  // ProjectValidator
     		PluginNameDeletionConfirmation,              // DeletionConfirmation
    +		PluginNameFinalizerRemoval,                  // FinalizerRemoval
     		PluginNameOpenIDConnectPreset,               // OpenIDConnectPreset
     		PluginNameClusterOpenIDConnectPreset,        // ClusterOpenIDConnectPreset
     		PluginNameCustomVerbAuthorizer,              // CustomVerbAuthorizer
    @@ -131,6 +134,7 @@ func DefaultOnPlugins() sets.Set[string] {
     		PluginNameNamespacedCloudProfileValidator, // NamespacedCloudProfileValidator
     		PluginNameProjectValidator,                // ProjectValidator
     		PluginNameDeletionConfirmation,            // DeletionConfirmation
    +		PluginNameFinalizerRemoval,                // FinalizerRemoval
     		PluginNameOpenIDConnectPreset,             // OpenIDConnectPreset
     		PluginNameClusterOpenIDConnectPreset,      // ClusterOpenIDConnectPreset
     		PluginNameCustomVerbAuthorizer,            // CustomVerbAuthorizer
    
  • plugin/pkg/project/validator/admission.go+59 4 modified
    @@ -8,15 +8,18 @@ import (
     	"context"
     	"fmt"
     	"io"
    +	"slices"
     	"strings"
     
    +	rbacv1 "k8s.io/api/rbac/v1"
     	apierrors "k8s.io/apimachinery/pkg/api/errors"
     	"k8s.io/apiserver/pkg/admission"
     
     	gardencore "github.com/gardener/gardener/pkg/apis/core"
     	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
     	gardenerutils "github.com/gardener/gardener/pkg/utils/gardener"
     	plugin "github.com/gardener/gardener/plugin/pkg"
    +	"github.com/gardener/gardener/plugin/pkg/utils"
     )
     
     // Register registers a plugin.
    @@ -33,13 +36,13 @@ type handler struct {
     // New creates a new handler admission plugin.
     func New() (*handler, error) {
     	return &handler{
    -		Handler: admission.NewHandler(admission.Create),
    +		Handler: admission.NewHandler(admission.Create, admission.Update),
     	}, nil
     }
     
    -var _ admission.ValidationInterface = &handler{}
    +var _ admission.MutationInterface = &handler{}
     
    -func (v *handler) Validate(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +func (v *handler) Admit(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
     	// Ignore all kinds other than Project
     	if a.GetKind().GroupKind() != gardencore.Kind("Project") {
     		return nil
    @@ -56,10 +59,62 @@ func (v *handler) Validate(_ context.Context, a admission.Attributes, _ admissio
     		return apierrors.NewBadRequest("could not convert object to Project")
     	}
     
    -	// TODO: Remove this admission plugin in favor of static validation in a future release, see https://github.com/gardener/gardener/pull/4228.
    +	// TODO: Remove this check in favor of static validation in a future release, see https://github.com/gardener/gardener/pull/4228.
     	if project.Spec.Namespace != nil && *project.Spec.Namespace != v1beta1constants.GardenNamespace && !strings.HasPrefix(*project.Spec.Namespace, gardenerutils.ProjectNamespacePrefix) {
     		return admission.NewForbidden(a, fmt.Errorf(".spec.namespace must start with %s", gardenerutils.ProjectNamespacePrefix))
     	}
     
    +	if utils.SkipVerification(a.GetOperation(), project.ObjectMeta) {
    +		return nil
    +	}
    +
    +	if a.GetOperation() == admission.Create {
    +		ensureProjectOwner(project, a.GetUserInfo().GetName())
    +	}
    +
    +	ensureOwnerIsMember(project)
    +
     	return nil
     }
    +
    +func ensureProjectOwner(project *gardencore.Project, userName string) {
    +	// Set createdBy field in Project
    +	project.Spec.CreatedBy = &rbacv1.Subject{
    +		APIGroup: "rbac.authorization.k8s.io",
    +		Kind:     rbacv1.UserKind,
    +		Name:     userName,
    +	}
    +
    +	if project.Spec.Owner == nil {
    +		project.Spec.Owner = func() *rbacv1.Subject {
    +			for _, member := range project.Spec.Members {
    +				for _, role := range member.Roles {
    +					if role == gardencore.ProjectMemberOwner {
    +						return member.Subject.DeepCopy()
    +					}
    +				}
    +			}
    +			return project.Spec.CreatedBy
    +		}()
    +	}
    +}
    +
    +func ensureOwnerIsMember(project *gardencore.Project) {
    +	if project.Spec.Owner == nil {
    +		return
    +	}
    +
    +	ownerIsMember := slices.ContainsFunc(project.Spec.Members, func(member gardencore.ProjectMember) bool {
    +		return member.Subject == *project.Spec.Owner
    +	})
    +
    +	if !ownerIsMember {
    +		project.Spec.Members = append(project.Spec.Members, gardencore.ProjectMember{
    +			Subject: *project.Spec.Owner,
    +			Roles: []string{
    +				gardencore.ProjectMemberAdmin,
    +				gardencore.ProjectMemberOwner,
    +			},
    +		})
    +	}
    +}
    
  • plugin/pkg/project/validator/admission_test.go+136 28 modified
    @@ -9,8 +9,10 @@ import (
     
     	. "github.com/onsi/ginkgo/v2"
     	. "github.com/onsi/gomega"
    +	rbacv1 "k8s.io/api/rbac/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/apiserver/pkg/authentication/user"
     	"k8s.io/utils/ptr"
     
     	"github.com/gardener/gardener/pkg/apis/core"
    @@ -23,7 +25,8 @@ var _ = Describe("Admission", func() {
     		var (
     			err              error
     			project          core.Project
    -			admissionHandler admission.ValidationInterface
    +			admissionHandler admission.MutationInterface
    +			attrs            admission.Attributes
     
     			namespaceName = "garden-my-project"
     			projectName   = "my-project"
    @@ -33,43 +36,148 @@ var _ = Describe("Admission", func() {
     					Namespace: namespaceName,
     				},
     			}
    +
    +			userInfo user.Info
     		)
     
     		BeforeEach(func() {
     			admissionHandler, err = New()
     			Expect(err).NotTo(HaveOccurred())
     
     			project = projectBase
    -		})
    -
    -		It("should allow creating the project (namespace nil)", func() {
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
     
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    +			userInfo = &user.DefaultInfo{Name: "foo"}
     		})
     
    -		It("should allow creating the project(namespace non-nil)", func() {
    -			project.Spec.Namespace = &namespaceName
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    +		When("project is created", func() {
    +			BeforeEach(func() {
    +				attrs = admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +			})
    +
    +			It("should allow creating the project (namespace nil)", func() {
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should allow creating the project(namespace non-nil)", func() {
    +				project.Spec.Namespace = &namespaceName
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should allow creating the project (namespace is 'garden')", func() {
    +				project.Spec.Namespace = ptr.To(v1beta1constants.GardenNamespace)
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should prevent creating the project because namespace prefix is missing", func() {
    +				project.Spec.Namespace = ptr.To("foo")
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(".spec.namespace must start with garden-")))
    +			})
    +
    +			It("should maintain createdBy and project owner", func() {
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     userInfo.GetName(),
    +				}))
    +
    +				Expect(project.Spec.Owner).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     userInfo.GetName(),
    +				}))
    +
    +				Expect(project.Spec.Members).To(ConsistOf(core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     userInfo.GetName(),
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}))
    +			})
    +
    +			It("should not overwrite project owner", func() {
    +				project.Spec.Owner = &rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     "bar",
    +				}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Owner).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     "bar",
    +				}))
    +			})
     		})
     
    -		It("should allow creating the project (namespace is 'garden')", func() {
    -			project.Spec.Namespace = ptr.To(v1beta1constants.GardenNamespace)
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    -		})
    -
    -		It("should prevent creating the project because namespace prefix is missing", func() {
    -			project.Spec.Namespace = ptr.To("foo")
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(".spec.namespace must start with garden-")))
    +		When("project is updated", func() {
    +			BeforeEach(func() {
    +				attrs = admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, userInfo)
    +			})
    +
    +			It("should add project owner to members", func() {
    +				projectOwner := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "foo",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}
    +
    +				projectMemberBar := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "bar",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberViewer,
    +					},
    +				}
    +
    +				project.Spec.Owner = &projectOwner.Subject
    +				project.Spec.Members = []core.ProjectMember{projectMemberBar}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Members).To(ConsistOf(projectMemberBar, projectOwner))
    +			})
    +
    +			It("should not re-add owner as member", func() {
    +				projectOwner := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "foo",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}
    +
    +				project.Spec.Owner = &projectOwner.Subject
    +				project.Spec.Members = []core.ProjectMember{projectOwner}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Members).To(ConsistOf(projectOwner))
    +			})
     		})
     	})
     
    @@ -85,11 +193,11 @@ var _ = Describe("Admission", func() {
     	})
     
     	Describe("#New", func() {
    -		It("should only handle CREATE operations", func() {
    +		It("should handle CREATE and UPDATE operations", func() {
     			dr, err := New()
     			Expect(err).ToNot(HaveOccurred())
     			Expect(dr.Handles(admission.Create)).To(BeTrue())
    -			Expect(dr.Handles(admission.Update)).To(BeFalse())
    +			Expect(dr.Handles(admission.Update)).To(BeTrue())
     			Expect(dr.Handles(admission.Connect)).To(BeFalse())
     			Expect(dr.Handles(admission.Delete)).To(BeFalse())
     		})
    
  • plugin/pkg/shoot/validator/admission.go+17 19 modified
    @@ -253,15 +253,19 @@ func (v *ValidateShoot) Admit(ctx context.Context, a admission.Attributes, _ adm
     		}
     	}
     
    +	if a.GetOperation() == admission.Create {
    +		addCreatedByAnnotation(shoot, a.GetUserInfo().GetName())
    +
    +		if len(ptr.Deref(shoot.Spec.CloudProfileName, "")) > 0 && shoot.Spec.CloudProfile != nil {
    +			return fmt.Errorf("new shoot can only specify either cloudProfileName or cloudProfile reference")
    +		}
    +	}
    +
     	cloudProfileSpec, err := admissionutils.GetCloudProfileSpec(v.cloudProfileLister, v.namespacedCloudProfileLister, shoot)
     	if err != nil {
     		return apierrors.NewInternalError(fmt.Errorf("could not find referenced cloud profile: %+v", err.Error()))
     	}
     
    -	if a.GetOperation() == admission.Create && len(ptr.Deref(shoot.Spec.CloudProfileName, "")) > 0 && shoot.Spec.CloudProfile != nil {
    -		return fmt.Errorf("new shoot can only specify either cloudProfileName or cloudProfile reference")
    -	}
    -
     	if err := admissionutils.ValidateCloudProfileChanges(v.cloudProfileLister, v.namespacedCloudProfileLister, shoot, oldShoot); err != nil {
     		return err
     	}
    @@ -608,21 +612,6 @@ func (c *validationContext) validateDeletion(a admission.Attributes) error {
     		}
     	}
     
    -	// Allow removal of `gardener` finalizer only if the Shoot deletion has completed successfully
    -	if len(c.shoot.Status.TechnicalID) > 0 && c.shoot.Status.LastOperation != nil {
    -		oldFinalizers := sets.New(c.oldShoot.Finalizers...)
    -		newFinalizers := sets.New(c.shoot.Finalizers...)
    -
    -		if oldFinalizers.Has(core.GardenerName) && !newFinalizers.Has(core.GardenerName) {
    -			lastOperation := c.shoot.Status.LastOperation
    -			deletionSucceeded := lastOperation.Type == core.LastOperationTypeDelete && lastOperation.State == core.LastOperationStateSucceeded && lastOperation.Progress == 100
    -
    -			if !deletionSucceeded {
    -				return admission.NewForbidden(a, fmt.Errorf("finalizer %q cannot be removed because shoot deletion has not completed successfully yet", core.GardenerName))
    -			}
    -		}
    -	}
    -
     	return nil
     }
     
    @@ -2162,3 +2151,12 @@ func validateMaxNodesTotal(workers []core.Worker, maxNodesTotal int32) field.Err
     
     	return allErrs
     }
    +
    +func addCreatedByAnnotation(shoot *core.Shoot, userName string) {
    +	annotations := shoot.Annotations
    +	if annotations == nil {
    +		annotations = map[string]string{}
    +	}
    +	annotations[v1beta1constants.GardenCreatedBy] = userName
    +	shoot.Annotations = annotations
    +}
    
  • plugin/pkg/shoot/validator/admission_test.go+50 96 modified
    @@ -18,7 +18,6 @@ import (
     	"k8s.io/apimachinery/pkg/api/resource"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/runtime"
    -	"k8s.io/apimachinery/pkg/util/sets"
     	"k8s.io/apiserver/pkg/admission"
     	"k8s.io/apiserver/pkg/authentication/user"
     	"k8s.io/apiserver/pkg/authorization/authorizer"
    @@ -423,106 +422,61 @@ var _ = Describe("validator", func() {
     			})
     		})
     
    -		Context("shoot with generate name", func() {
    +		Context("shoot creation", func() {
     			BeforeEach(func() {
    -				shoot.ObjectMeta = metav1.ObjectMeta{
    -					GenerateName: "demo-",
    -					Namespace:    namespaceName,
    -				}
    -			})
    -
    -			It("should admit Shoot resources", func() {
     				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    -
    -				authorizeAttributes.Name = shoot.Name
    -
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
     			})
     
    -			It("should reject Shoot resources with not fulfilling the length constraints", func() {
    -				tooLongName := "too-long-namespace"
    -				project.ObjectMeta = metav1.ObjectMeta{
    -					Name: tooLongName,
    -				}
    -				shoot.ObjectMeta = metav1.ObjectMeta{
    -					GenerateName: "too-long-name",
    -					Namespace:    namespaceName,
    -				}
    -
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    -
    -				authorizeAttributes.Name = shoot.Name
    +			Context("with generate name", func() {
    +				BeforeEach(func() {
    +					shoot.ObjectMeta = metav1.ObjectMeta{
    +						GenerateName: "demo-",
    +						Namespace:    namespaceName,
    +					}
    +				})
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    +				It("should admit Shoot resources", func() {
    +					authorizeAttributes.Name = shoot.Name
     
    -				Expect(err).To(BeInvalidError())
    -				Expect(err.Error()).To(ContainSubstring("name must not exceed"))
    -			})
    -		})
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +					err := admissionHandler.Admit(ctx, attrs, nil)
     
    -		Context("finalizer removal checks", func() {
    -			var (
    -				oldShoot *core.Shoot
    -			)
    +					Expect(err).NotTo(HaveOccurred())
    +				})
     
    -			BeforeEach(func() {
    -				shoot = *shootBase.DeepCopy()
    +				It("should reject Shoot resources with not fulfilling the length constraints", func() {
    +					tooLongName := "too-long-namespace"
    +					project.ObjectMeta = metav1.ObjectMeta{
    +						Name: tooLongName,
    +					}
    +					shoot.ObjectMeta = metav1.ObjectMeta{
    +						GenerateName: "too-long-name",
    +						Namespace:    namespaceName,
    +					}
     
    -				shoot.Status.TechnicalID = "some-id"
    -				shoot.Status.LastOperation = &core.LastOperation{
    -					Type:     core.LastOperationTypeReconcile,
    -					State:    core.LastOperationStateSucceeded,
    -					Progress: 100,
    -				}
    +					Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
     
    -				// set old shoot for update and add gardener finalizer to it
    -				oldShoot = shoot.DeepCopy()
    -				finalizers := sets.New(oldShoot.GetFinalizers()...)
    -				finalizers.Insert(core.GardenerName)
    -				oldShoot.SetFinalizers(finalizers.UnsortedList())
    -			})
    +					authorizeAttributes.Name = shoot.Name
     
    -			It("should reject removing the gardener finalizer if the shoot has not yet been deleted successfully", func() {
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +					err := admissionHandler.Admit(ctx, attrs, nil)
     
    -				attrs := admission.NewAttributesRecord(&shoot, oldShoot, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -				Expect(err).To(HaveOccurred())
    -				Expect(err.Error()).To(ContainSubstring("shoot deletion has not completed successfully yet"))
    +					Expect(err).To(BeInvalidError())
    +					Expect(err.Error()).To(ContainSubstring("name must not exceed"))
    +				})
     			})
     
    -			It("should admit removing the gardener finalizer if the shoot deletion succeeded ", func() {
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    +			It("should add the created-by annotation", func() {
    +				Expect(shoot.Annotations).NotTo(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, userInfo.Name))
     
    -				shoot.Status.LastOperation = &core.LastOperation{
    -					Type:     core.LastOperationTypeDelete,
    -					State:    core.LastOperationStateSucceeded,
    -					Progress: 100,
    -				}
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
     
    -				attrs := admission.NewAttributesRecord(&shoot, oldShoot, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -				Expect(err).ToNot(HaveOccurred())
    +				Expect(shoot.Annotations).To(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, userInfo.Name))
     			})
     		})
     
    @@ -1706,7 +1660,7 @@ var _ = Describe("validator", func() {
     				shoot.Spec.SeedName = nil
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
    @@ -1719,7 +1673,7 @@ var _ = Describe("validator", func() {
     
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
    @@ -1735,7 +1689,7 @@ var _ = Describe("validator", func() {
     
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
    @@ -2013,23 +1967,23 @@ var _ = Describe("validator", func() {
     						})
     
     						It("should allow scheduling non-HA shoot", func() {
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'node'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeNode}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should reject scheduling HA shoot with failure tolerance type 'zone'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeZone}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(BeForbiddenError())
     						})
     					})
    @@ -2040,23 +1994,23 @@ var _ = Describe("validator", func() {
     						})
     
     						It("should allow scheduling non-HA shoot", func() {
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'node'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeNode}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'zone'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeZone}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     					})
    @@ -2270,7 +2224,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).NotTo(HaveOccurred())
    @@ -2290,7 +2244,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2320,7 +2274,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2351,7 +2305,7 @@ var _ = Describe("validator", func() {
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     						Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2383,7 +2337,7 @@ var _ = Describe("validator", func() {
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     						Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).NotTo(HaveOccurred())
    @@ -2408,7 +2362,7 @@ var _ = Describe("validator", func() {
     					Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     					Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     					err := admissionHandler.Admit(ctx, attrs, nil)
     
     					Expect(err).To(errorMatcher)
    
  • skaffold-operator.yaml+2 0 modified
    @@ -606,6 +606,7 @@ build:
                 - plugin/pkg/global/deletionconfirmation
                 - plugin/pkg/global/extensionlabels
                 - plugin/pkg/global/extensionvalidation
    +            - plugin/pkg/global/finalizerremoval
                 - plugin/pkg/global/resourcereferencemanager
                 - plugin/pkg/managedseed/shoot
                 - plugin/pkg/managedseed/validator
    @@ -865,6 +866,7 @@ build:
                 - pkg/admissioncontroller/webhook/admission/internaldomainsecret
                 - pkg/admissioncontroller/webhook/admission/kubeconfigsecret
                 - pkg/admissioncontroller/webhook/admission/namespacedeletion
    +            - pkg/admissioncontroller/webhook/admission/providersecretlabels
                 - pkg/admissioncontroller/webhook/admission/resourcesize
                 - pkg/admissioncontroller/webhook/admission/seedrestriction
                 - pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref
    
  • skaffold.yaml+2 0 modified
    @@ -203,6 +203,7 @@ build:
                 - plugin/pkg/global/deletionconfirmation
                 - plugin/pkg/global/extensionlabels
                 - plugin/pkg/global/extensionvalidation
    +            - plugin/pkg/global/finalizerremoval
                 - plugin/pkg/global/resourcereferencemanager
                 - plugin/pkg/managedseed/shoot
                 - plugin/pkg/managedseed/validator
    @@ -462,6 +463,7 @@ build:
                 - pkg/admissioncontroller/webhook/admission/internaldomainsecret
                 - pkg/admissioncontroller/webhook/admission/kubeconfigsecret
                 - pkg/admissioncontroller/webhook/admission/namespacedeletion
    +            - pkg/admissioncontroller/webhook/admission/providersecretlabels
                 - pkg/admissioncontroller/webhook/admission/resourcesize
                 - pkg/admissioncontroller/webhook/admission/seedrestriction
                 - pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref
    
  • test/integration/envtest/environment_test.go+1 1 modified
    @@ -44,6 +44,6 @@ var _ = Describe("GardenerTestEnvironment", func() {
     
     	It("should be able to manipulate resource from security.gardener.cloud/v1alpha1", func() {
     		credentialsBinding := &securityv1alpha1.CredentialsBinding{ObjectMeta: metav1.ObjectMeta{GenerateName: "test-", Namespace: testNamespace.Name}}
    -		Expect(testClient.Create(ctx, credentialsBinding)).To(MatchError(ContainSubstring("credentialsbindings.security.gardener.cloud \"test-\" is forbidden")))
    +		Expect(testClient.Create(ctx, credentialsBinding)).To(MatchError(MatchRegexp("CredentialsBinding.security.gardener.cloud \"test-.+\" is invalid")))
     	})
     })
    
cf4e9887d839

[release-v1.117] Ensure extension admission webhooks validated `WorkloadIdentity`s and `Secret`s when used (#12076)

https://github.com/gardener/gardenerGardener Prow RobotMay 13, 2025via ghsa
55 files changed · +1700 493
  • charts/gardener/controlplane/charts/application/templates/mutatingwebhook-admission-controller.yaml+29 1 modified
    @@ -3,5 +3,33 @@ apiVersion: admissionregistration.k8s.io/v1
     kind: MutatingWebhookConfiguration
     metadata:
       name: gardener-admission-controller
    -webhooks: []
    +webhooks:
    +- name: sync-provider-secret-labels.gardener.cloud
    +  admissionReviewVersions: ["v1", "v1beta1"]
    +  timeoutSeconds: 10
    +  rules:
    +  - apiGroups:
    +    - ""
    +    apiVersions:
    +    - v1
    +    operations:
    +    - CREATE
    +    - UPDATE
    +    resources:
    +    - secrets
    +  failurePolicy: Fail
    +  namespaceSelector:
    +    matchExpressions:
    +    - {key: gardener.cloud/role, operator: In, values: [project]}
    +  clientConfig:
    +    {{- if .Values.global.deployment.virtualGarden.enabled }}
    +    url: https://gardener-admission-controller.garden/webhooks/sync-provider-secret-labels
    +    {{- else }}
    +    service:
    +      namespace: garden
    +      name: gardener-admission-controller
    +      path: /webhooks/sync-provider-secret-labels
    +    {{- end }}
    +    caBundle: {{ required ".Values.global.admission.config.server.webhooks.tls.caBundle is required" (b64enc .Values.global.admission.config.server.webhooks.tls.caBundle) }}
    +  sideEffects: None
     {{- end }}
    
  • cmd/gardener-extension-admission-local/app/app.go+4 2 modified
    @@ -25,8 +25,9 @@ import (
     	extensionscmdcontroller "github.com/gardener/gardener/extensions/pkg/controller/cmd"
     	"github.com/gardener/gardener/extensions/pkg/util"
     	extensionscmdwebhook "github.com/gardener/gardener/extensions/pkg/webhook/cmd"
    -	"github.com/gardener/gardener/pkg/apis/core/install"
    +	gardencoreinstall "github.com/gardener/gardener/pkg/apis/core/install"
     	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
    +	securityinstall "github.com/gardener/gardener/pkg/apis/security/install"
     	gardenerhealthz "github.com/gardener/gardener/pkg/healthz"
     	admissioncmd "github.com/gardener/gardener/pkg/provider-local/admission/cmd"
     	localinstall "github.com/gardener/gardener/pkg/provider-local/apis/local/install"
    @@ -122,7 +123,8 @@ func NewAdmissionCommand(ctx context.Context) *cobra.Command {
     				return fmt.Errorf("could not instantiate manager: %w", err)
     			}
     
    -			install.Install(mgr.GetScheme())
    +			gardencoreinstall.Install(mgr.GetScheme())
    +			securityinstall.Install(mgr.GetScheme())
     
     			if err := localinstall.AddToScheme(mgr.GetScheme()); err != nil {
     				return fmt.Errorf("could not update manager scheme: %w", err)
    
  • docs/concepts/apiserver-admission-plugins.md+17 6 modified
    @@ -74,14 +74,27 @@ _(enabled by default)_
     
     This admission controller reacts on `CREATE` and `UPDATE` operations for `BackupBucket`s, `BackupEntry`s, `CloudProfile`s, `NamespacedCloudProfile`s, `Seed`s, `SecretBinding`s, `CredentialsBinding`s, `WorkloadIdentity`s and `Shoot`s. For all the various extension types in the specifications of these objects, it adds a corresponding label in the resource. This would allow extension admission webhooks to filter out the resources they are responsible for and ignore all others. This label is of the form `<extension-type>.extensions.gardener.cloud/<extension-name> : "true"`. For example, an extension label for provider extension type `aws`, looks like `provider.extensions.gardener.cloud/aws : "true"`.
     
    +## `FinalizerRemoval`
    +
    +_(enabled by default)_
    +
    +This admission controller reacts on `UPDATE` operations for `CredentialsBinding`s, `SecretBinding`s, `Shoot`s. 
    +It ensures that the finalizers of these resources are not removed by users, as long as the affected resource is still in use.
    +For `CredentialsBinding`s and `SecretBinding`s this means, that the `gardener` finalizer can only be removed if the binding is not referenced by any `Shoot`.
    +In case of `Shoot`s, the `gardener` finalizer can only be removed if the last operation of the `Shoot` indicates a successful deletion. 
    +
     ## `ProjectValidator`
     
     _(enabled by default)_
     
    -This admission controller reacts on `CREATE` operations for `Project`s.
    +This admission controller reacts on `CREATE` and `UPDATE` operations for `Project`s.
     It prevents creating `Project`s with a non-empty `.spec.namespace` if the value in `.spec.namespace` does not start with `garden-`.
     
    -⚠️ This admission plugin will be removed in a future release and its business logic will be incorporated into the static validation of the `gardener-apiserver`.
    +In addition, the project specification is initialized during creation:
    +- `.spec.createdBy` is set to the user creating the project.
    +- `.spec.owner` defaults to the value of `.spec.createdBy` if it is not specified.
    +
    +During subsequent updates, it ensures that the project owner is included in the `.spec.members` list.
     
     ## `ResourceQuota`
     
    @@ -96,11 +109,9 @@ _(enabled by default)_
     
     This admission controller reacts on `CREATE` and `UPDATE` operations for `CloudProfile`s, `Project`s, `SecretBinding`s, `Seed`s, and `Shoot`s.
     Generally, it checks whether referred resources stated in the specifications of these objects exist in the system (e.g., if a referenced `Secret` exists).
    -However, it also has some special behaviours for certain resources:
     
    +However, it also has some special behaviours for certain resources:
     * `CloudProfile`s: It rejects removing Kubernetes or machine image versions if there is at least one `Shoot` that refers to them.
    -* `Project`s: It sets the `.spec.createdBy` field for newly created `Project` resources, and defaults the `.spec.owner` field in case it is empty (to the same value of `.spec.createdBy`).
    -* `Shoot`s: It sets the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.
     
     ## `SeedValidator`
     
    @@ -185,7 +196,7 @@ _(enabled by default)_
     This admission controller reacts on `CREATE`, `UPDATE` and `DELETE` operations for `Shoot`s.
     It validates certain configurations in the specification against the referred `CloudProfile` (e.g., machine images, machine types, used Kubernetes version, ...).
     Generally, it performs validations that cannot be handled by the static API validation due to their dynamic nature (e.g., when something needs to be checked against referred resources).
    -Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version).
    +Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version) and setting the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.
     
     ## `ShootManagedSeed`
     
    
  • pkg/admissioncontroller/webhook/add.go+8 0 modified
    @@ -18,6 +18,7 @@ import (
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/internaldomainsecret"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/kubeconfigsecret"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/namespacedeletion"
    +	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/providersecretlabels"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/resourcesize"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/seedrestriction"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref"
    @@ -65,6 +66,13 @@ func AddToManager(
     		return fmt.Errorf("failed adding %s webhook handler: %w", namespacedeletion.HandlerName, err)
     	}
     
    +	if err := (&providersecretlabels.Handler{
    +		Logger: mgr.GetLogger().WithName("webhook").WithName(providersecretlabels.HandlerName),
    +		Client: mgr.GetClient(),
    +	}).AddToManager(mgr); err != nil {
    +		return fmt.Errorf("failed adding %s webhook handler: %w", providersecretlabels.HandlerName, err)
    +	}
    +
     	if err := (&resourcesize.Handler{
     		Logger: mgr.GetLogger().WithName("webhook").WithName(resourcesize.HandlerName),
     		Config: cfg.Server.ResourceAdmissionConfiguration,
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/add.go+28 0 added
    @@ -0,0 +1,28 @@
    +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels
    +
    +import (
    +	corev1 "k8s.io/api/core/v1"
    +	"sigs.k8s.io/controller-runtime/pkg/manager"
    +	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    +)
    +
    +const (
    +	// HandlerName is the name of this admission webhook handler.
    +	HandlerName = "sync-provider-secret-labels"
    +	// WebhookPath is the HTTP handler path for this admission webhook handler.
    +	WebhookPath = "/webhooks/sync-provider-secret-labels"
    +)
    +
    +// AddToManager adds Handler to the given manager.
    +func (h *Handler) AddToManager(mgr manager.Manager) error {
    +	webhook := admission.
    +		WithCustomDefaulter(mgr.GetScheme(), &corev1.Secret{}, h).
    +		WithRecoverPanic(true)
    +
    +	mgr.GetWebhookServer().Register(WebhookPath, webhook)
    +	return nil
    +}
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/handler.go+99 0 added
    @@ -0,0 +1,99 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels
    +
    +import (
    +	"context"
    +	"fmt"
    +	"strings"
    +
    +	"github.com/go-logr/logr"
    +	corev1 "k8s.io/api/core/v1"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/runtime"
    +	"k8s.io/apimachinery/pkg/util/sets"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
    +	v1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +)
    +
    +// Handler syncs the provider labels on Secrets referenced in SecretBindings or CredentialsBindings.
    +type Handler struct {
    +	Logger logr.Logger
    +	Client client.Client
    +}
    +
    +// Default syncs the provider labels.
    +func (h *Handler) Default(ctx context.Context, obj runtime.Object) error {
    +	secret, ok := obj.(*corev1.Secret)
    +	if !ok {
    +		return fmt.Errorf("expected secret but got %T", obj)
    +	}
    +
    +	typesFromSecretBindings, err := h.fetchProviderTypesFromSecretBindings(ctx, secret)
    +	if err != nil {
    +		return fmt.Errorf("failed fetching provider types from SecretBindings: %w", err)
    +	}
    +
    +	typesFromCredentialsBindings, err := h.fetchProviderTypesFromCredentialsBindings(ctx, secret)
    +	if err != nil {
    +		return fmt.Errorf("failed fetching provider types from CredentialsBindings: %w", err)
    +	}
    +
    +	if typesFromSecretBindings.Len()+typesFromCredentialsBindings.Len() > 0 {
    +		maintainLabels(secret, typesFromSecretBindings.Union(typesFromCredentialsBindings).UnsortedList()...)
    +	}
    +
    +	return nil
    +}
    +
    +func (h *Handler) fetchProviderTypesFromSecretBindings(ctx context.Context, secret *corev1.Secret) (sets.Set[string], error) {
    +	secretBindingList := &gardencorev1beta1.SecretBindingList{}
    +	if err := h.Client.List(ctx, secretBindingList); err != nil {
    +		return nil, fmt.Errorf("failed to list SecretBindings: %w", err)
    +	}
    +
    +	providerTypes := sets.New[string]()
    +	for _, secretBinding := range secretBindingList.Items {
    +		if secretBinding.SecretRef.Name == secret.Name &&
    +			secretBinding.SecretRef.Namespace == secret.Namespace {
    +			providerTypes.Insert(v1beta1helper.GetSecretBindingTypes(&secretBinding)...)
    +		}
    +	}
    +	return providerTypes, nil
    +}
    +
    +func (h *Handler) fetchProviderTypesFromCredentialsBindings(ctx context.Context, secret *corev1.Secret) (sets.Set[string], error) {
    +	credentialsBindingList := &securityv1alpha1.CredentialsBindingList{}
    +	if err := h.Client.List(ctx, credentialsBindingList); err != nil {
    +		return nil, fmt.Errorf("failed to list CredentialsBindings: %w", err)
    +	}
    +
    +	providerTypes := sets.New[string]()
    +	for _, credentialsBinding := range credentialsBindingList.Items {
    +		if credentialsBinding.CredentialsRef.APIVersion == corev1.SchemeGroupVersion.String() &&
    +			credentialsBinding.CredentialsRef.Kind == "Secret" &&
    +			credentialsBinding.CredentialsRef.Name == secret.Name &&
    +			credentialsBinding.CredentialsRef.Namespace == secret.Namespace {
    +			providerTypes.Insert(credentialsBinding.Provider.Type)
    +		}
    +	}
    +	return providerTypes, nil
    +}
    +
    +func maintainLabels(secret *corev1.Secret, providerTypes ...string) {
    +	for k := range secret.Labels {
    +		if strings.HasPrefix(k, v1beta1constants.LabelShootProviderPrefix) {
    +			delete(secret.Labels, k)
    +		}
    +	}
    +
    +	for _, providerType := range providerTypes {
    +		metav1.SetMetaDataLabel(&secret.ObjectMeta, v1beta1constants.LabelShootProviderPrefix+providerType, "true")
    +	}
    +}
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/handler_test.go+138 0 added
    @@ -0,0 +1,138 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels_test
    +
    +import (
    +	"context"
    +
    +	"github.com/go-logr/logr"
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	corev1 "k8s.io/api/core/v1"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    +	logzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
    +
    +	. "github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/providersecretlabels"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +	"github.com/gardener/gardener/pkg/client/kubernetes"
    +	"github.com/gardener/gardener/pkg/logger"
    +)
    +
    +var _ = Describe("handler", func() {
    +	var (
    +		ctx context.Context
    +
    +		log        logr.Logger
    +		fakeClient client.Client
    +		handler    *Handler
    +
    +		namespace            string
    +		provider1, provider2 string
    +		secret               *corev1.Secret
    +		secretBinding        *gardencorev1beta1.SecretBinding
    +		credentialsBinding   *securityv1alpha1.CredentialsBinding
    +	)
    +
    +	BeforeEach(func() {
    +		ctx = context.Background()
    +		log = logger.MustNewZapLogger(logger.DebugLevel, logger.FormatJSON, logzap.WriteTo(GinkgoWriter))
    +
    +		fakeClient = fakeclient.NewClientBuilder().WithScheme(kubernetes.GardenScheme).Build()
    +		handler = &Handler{
    +			Logger: log,
    +			Client: fakeClient,
    +		}
    +
    +		namespace = "test"
    +		secret = &corev1.Secret{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-secret",
    +				Namespace: namespace,
    +			},
    +		}
    +
    +		provider1 = "provider1"
    +		provider2 = "provider2"
    +
    +		secretBinding = &gardencorev1beta1.SecretBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-secret-binding",
    +				Namespace: namespace,
    +			},
    +			SecretRef: corev1.SecretReference{
    +				Name:      "test-secret",
    +				Namespace: namespace,
    +			},
    +			Provider: &gardencorev1beta1.SecretBindingProvider{
    +				Type: provider1,
    +			},
    +		}
    +
    +		credentialsBinding = &securityv1alpha1.CredentialsBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-credentials-binding",
    +				Namespace: "another-namespace",
    +			},
    +			CredentialsRef: corev1.ObjectReference{
    +				APIVersion: corev1.SchemeGroupVersion.String(),
    +				Kind:       "Secret",
    +				Name:       "test-secret",
    +				Namespace:  namespace,
    +			},
    +			Provider: securityv1alpha1.CredentialsBindingProvider{
    +				Type: provider2,
    +			},
    +		}
    +	})
    +
    +	It("should set the provider label based on the available credential and secret bindings", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +	})
    +
    +	It("should remove undesired provider type", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +		secret.Labels = map[string]string{
    +			"provider.shoot.gardener.cloud/provider1": "true",
    +			"provider.shoot.gardener.cloud/provider2": "true",
    +			"provider.shoot.gardener.cloud/provider3": "true",
    +		}
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +		Expect(secret.Labels).NotTo(HaveKey("provider.shoot.gardener.cloud/provider3"))
    +	})
    +
    +	It("should add the missing provider and delete the wrong one", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +		secret.Labels = map[string]string{
    +			"provider.shoot.gardener.cloud/provider1": "true",
    +			"provider.shoot.gardener.cloud/provider3": "true",
    +		}
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +		Expect(secret.Labels).NotTo(HaveKey("provider.shoot.gardener.cloud/provider3"))
    +	})
    +
    +	It("should not add provider labels when secret is unreferenced", func() {
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +		Expect(secret.Labels).To(BeEmpty())
    +	})
    +})
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/providersecretlabels_suite_test.go+17 0 added
    @@ -0,0 +1,17 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +)
    +
    +func TestProviderSecretLabels(t *testing.T) {
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "AdmissionController Webhook Admission ProviderSecretLabels Suite")
    +}
    
  • pkg/apis/core/helper/secretbinding.go+3 0 modified
    @@ -12,5 +12,8 @@ import (
     
     // GetSecretBindingTypes returns the SecretBinding provider types.
     func GetSecretBindingTypes(secretBinding *core.SecretBinding) []string {
    +	if secretBinding.Provider == nil {
    +		return []string{}
    +	}
     	return strings.Split(secretBinding.Provider.Type, ",")
     }
    
  • pkg/apis/core/helper/secretbinding_test.go+1 0 modified
    @@ -19,6 +19,7 @@ var _ = Describe("Helper", func() {
     			Expect(actual).To(Equal(expected))
     		},
     
    +		Entry("with nil provider type", &core.SecretBinding{Provider: nil}, []string{}),
     		Entry("with single-value provider type", &core.SecretBinding{Provider: &core.SecretBindingProvider{Type: "foo"}}, []string{"foo"}),
     		Entry("with multi-value provider type", &core.SecretBinding{Provider: &core.SecretBindingProvider{Type: "foo,bar,baz"}}, []string{"foo", "bar", "baz"}),
     	)
    
  • pkg/apis/core/v1beta1/helper/secretbinding.go+3 0 modified
    @@ -44,5 +44,8 @@ func AddTypeToSecretBinding(secretBinding *gardencorev1beta1.SecretBinding, prov
     
     // GetSecretBindingTypes returns the SecretBinding provider types.
     func GetSecretBindingTypes(secretBinding *gardencorev1beta1.SecretBinding) []string {
    +	if secretBinding.Provider == nil {
    +		return []string{}
    +	}
     	return strings.Split(secretBinding.Provider.Type, ",")
     }
    
  • pkg/apis/core/v1beta1/helper/secretbinding_test.go+1 0 modified
    @@ -45,6 +45,7 @@ var _ = Describe("Helper", func() {
     			Expect(actual).To(Equal(expected))
     		},
     
    +		Entry("with nil provider type", &gardencorev1beta1.SecretBinding{Provider: nil}, []string{}),
     		Entry("with single-value provider type", &gardencorev1beta1.SecretBinding{Provider: &gardencorev1beta1.SecretBindingProvider{Type: "foo"}}, []string{"foo"}),
     		Entry("with multi-value provider type", &gardencorev1beta1.SecretBinding{Provider: &gardencorev1beta1.SecretBindingProvider{Type: "foo,bar,baz"}}, []string{"foo", "bar", "baz"}),
     	)
    
  • pkg/apiserver/plugins.go+2 0 modified
    @@ -14,6 +14,7 @@ import (
     	"github.com/gardener/gardener/plugin/pkg/global/deletionconfirmation"
     	"github.com/gardener/gardener/plugin/pkg/global/extensionlabels"
     	"github.com/gardener/gardener/plugin/pkg/global/extensionvalidation"
    +	"github.com/gardener/gardener/plugin/pkg/global/finalizerremoval"
     	"github.com/gardener/gardener/plugin/pkg/global/resourcereferencemanager"
     	managedseedshoot "github.com/gardener/gardener/plugin/pkg/managedseed/shoot"
     	managedseedvalidator "github.com/gardener/gardener/plugin/pkg/managedseed/validator"
    @@ -39,6 +40,7 @@ import (
     func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
     	resourcereferencemanager.Register(plugins)
     	deletionconfirmation.Register(plugins)
    +	finalizerremoval.Register(plugins)
     	extensionvalidation.Register(plugins)
     	extensionlabels.Register(plugins)
     	shoottolerationrestriction.Register(plugins)
    
  • pkg/apiserver/registry/core/backupbucket/backupbucket_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBackupBucket(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core BackupBucket Suite")
    +	RunSpecs(t, "APIServer Registry Core BackupBucket Suite")
     }
    
  • pkg/apiserver/registry/core/backupentry/backupentry_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBackupEntry(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core BackupEntry Suite")
    +	RunSpecs(t, "APIServer Registry Core BackupEntry Suite")
     }
    
  • pkg/apiserver/registry/core/cloudprofile/cloudprofile_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestCloudProfile(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core CloudProfile Suite")
    +	RunSpecs(t, "APIServer Registry Core CloudProfile Suite")
     }
    
  • pkg/apiserver/registry/core/controllerinstallation/strategy_test.go+1 1 modified
    @@ -20,7 +20,7 @@ import (
     
     func TestControllerInstallation(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry ControllerInstallation Suite")
    +	RunSpecs(t, "APIServer Registry ControllerInstallation Suite")
     }
     
     var _ = Describe("ToSelectableFields", func() {
    
  • pkg/apiserver/registry/core/namespacedcloudprofile/namespacedcloudprofile_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestNamespacedCloudProfile(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core NamespacedCloudProfile Suite")
    +	RunSpecs(t, "APIServer Registry Core NamespacedCloudProfile Suite")
     }
    
  • pkg/apiserver/registry/core/project/project_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestProject(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Project Suite")
    +	RunSpecs(t, "APIServer Registry Core Project Suite")
     }
    
  • pkg/apiserver/registry/core/secretbinding/secretbinding_suite_test.go+1 1 modified
    @@ -16,5 +16,5 @@ import (
     func TestSecretBinding(t *testing.T) {
     	features.RegisterFeatureGates()
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core SecretBinding Suite")
    +	RunSpecs(t, "APIServer Registry Core SecretBinding Suite")
     }
    
  • pkg/apiserver/registry/core/secretbinding/strategy.go+6 1 modified
    @@ -28,7 +28,12 @@ func (secretBindingStrategy) NamespaceScoped() bool {
     	return true
     }
     
    -func (secretBindingStrategy) PrepareForCreate(_ context.Context, _ runtime.Object) {
    +func (s secretBindingStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) {
    +	binding := obj.(*core.SecretBinding)
    +
    +	if binding.GetName() == "" {
    +		binding.SetName(s.GenerateName(binding.GetGenerateName()))
    +	}
     }
     
     func (secretBindingStrategy) Validate(_ context.Context, obj runtime.Object) field.ErrorList {
    
  • pkg/apiserver/registry/core/secretbinding/strategy_test.go+29 0 modified
    @@ -34,6 +34,35 @@ var _ = Describe("Strategy", func() {
     		}
     	})
     
    +	Describe("#PrepareForCreate", func() {
    +		It("should set the name if not set", func() {
    +			secretBinding.SetName("")
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetName()).NotTo(BeEmpty())
    +		})
    +
    +		It("should set name with generateName as prefix", func() {
    +			genName := "prefix-"
    +			secretBinding.GenerateName = genName
    +			secretBinding.Name = ""
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetGenerateName()).To(Equal(genName))
    +			Expect(secretBinding.GetName()).To(HavePrefix(genName))
    +		})
    +
    +		It("should not overwrite already set name", func() {
    +			secretBinding.SetName("bar")
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetName()).To(Equal("bar"))
    +		})
    +	})
    +
     	Describe("#Validate", func() {
     		It("should forbid creating SecretBinding when provider is nil or empty", func() {
     			secretBinding.Provider = nil
    
  • pkg/apiserver/registry/core/seed/seed_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestSeed(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Seed Suite")
    +	RunSpecs(t, "APIServer Registry Core Seed Suite")
     }
    
  • pkg/apiserver/registry/core/shoot/shoot_suite_test.go+1 1 modified
    @@ -16,5 +16,5 @@ import (
     func TestShoot(t *testing.T) {
     	features.RegisterFeatureGates()
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Shoot Suite")
    +	RunSpecs(t, "APIServer Registry Core Shoot Suite")
     }
    
  • pkg/apiserver/registry/core/shoot/storage/storage_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestStorage(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Shoot Storage Suite")
    +	RunSpecs(t, "APIServer Registry Core Shoot Storage Suite")
     }
    
  • pkg/apiserver/registry/operations/bastion/bastion_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBastion(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Operations Bastion Suite")
    +	RunSpecs(t, "APIServer Registry Operations Bastion Suite")
     }
    
  • pkg/apiserver/registry/security/credentialsbinding/credentialsbinding_suite_test.go+20 0 added
    @@ -0,0 +1,20 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package credentialsbinding_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +
    +	"github.com/gardener/gardener/pkg/apiserver/features"
    +)
    +
    +func TestCredentialsBinding(t *testing.T) {
    +	features.RegisterFeatureGates()
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "APIServer Registry Security CredentialsBinding Suite")
    +}
    
  • pkg/apiserver/registry/security/credentialsbinding/strategy.go+5 1 modified
    @@ -28,8 +28,12 @@ func (credentialsBindingStrategy) NamespaceScoped() bool {
     	return true
     }
     
    -func (credentialsBindingStrategy) PrepareForCreate(_ context.Context, _ runtime.Object) {
    +func (c credentialsBindingStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) {
    +	credentialsbinding := obj.(*security.CredentialsBinding)
     
    +	if credentialsbinding.GetName() == "" {
    +		credentialsbinding.SetName(c.GenerateName(credentialsbinding.GetGenerateName()))
    +	}
     }
     
     func (credentialsBindingStrategy) PrepareForUpdate(_ context.Context, _, _ runtime.Object) {
    
  • pkg/apiserver/registry/security/credentialsbinding/strategy_test.go+58 0 added
    @@ -0,0 +1,58 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package credentialsbinding_test
    +
    +import (
    +	"context"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	credentialsbindingregistry "github.com/gardener/gardener/pkg/apiserver/registry/security/credentialsbinding"
    +)
    +
    +var _ = Describe("Strategy", func() {
    +	var credentialsBinding *security.CredentialsBinding
    +
    +	BeforeEach(func() {
    +		credentialsBinding = &security.CredentialsBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "profile",
    +				Namespace: "garden",
    +			},
    +		}
    +	})
    +
    +	Describe("#PrepareForCreate", func() {
    +		It("should set the name if not set", func() {
    +			credentialsBinding.SetName("")
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetName()).NotTo(BeEmpty())
    +		})
    +
    +		It("should set name with generateName as prefix", func() {
    +			genName := "prefix-"
    +			credentialsBinding.GenerateName = genName
    +			credentialsBinding.Name = ""
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetGenerateName()).To(Equal(genName))
    +			Expect(credentialsBinding.GetName()).To(HavePrefix(genName))
    +		})
    +
    +		It("should not overwrite already set name", func() {
    +			credentialsBinding.SetName("bar")
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetName()).To(Equal("bar"))
    +		})
    +	})
    +})
    
  • pkg/apiserver/registry/security/workloadidentity/storage/storage_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestStorage(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Security WorkloadIdentity Storage Suite")
    +	RunSpecs(t, "APIServer Registry Security WorkloadIdentity Storage Suite")
     }
    
  • pkg/apiserver/registry/security/workloadidentity/workloadidentity_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestWorkloadIdentity(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Security WorkloadIdentity Suite")
    +	RunSpecs(t, "APIServer Registry Security WorkloadIdentity Suite")
     }
    
  • pkg/apiserver/registry/seedmanagement/managedseed/managedseed_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestManagedSeed(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry SeedManagement ManagedSeed Suite")
    +	RunSpecs(t, "APIServer Registry SeedManagement ManagedSeed Suite")
     }
    
  • pkg/apiserver/registry/seedmanagement/managedseedset/managedseedset_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestManagedSeedSet(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry SeedManagement ManagedSeedSet Suite")
    +	RunSpecs(t, "APIServer Registry SeedManagement ManagedSeedSet Suite")
     }
    
  • pkg/component/gardener/admissioncontroller/admission_controller.go+1 0 modified
    @@ -131,6 +131,7 @@ func (a *gardenerAdmissionController) Deploy(ctx context.Context) error {
     		a.clusterRole(),
     		a.clusterRoleBinding(virtualGardenAccessSecret.ServiceAccountName),
     		a.validatingWebhookConfiguration(caSecret),
    +		a.mutatingWebhookConfiguration(caSecret),
     	)
     	if err != nil {
     		return err
    
  • pkg/component/gardener/admissioncontroller/admission_controller_test.go+40 0 modified
    @@ -502,6 +502,7 @@ func verifyExpectations(ctx context.Context, fakeClient client.Client, consistOf
     		clusterRole(),
     		clusterRoleBinding(),
     		validatingWebhookConfiguration(namespace, caGardener.Data["bundle.crt"], testValues),
    +		mutatingWebhookConfiguration(namespace, caGardener.Data["bundle.crt"]),
     	))
     
     	virtualManagedResourceSecret := &corev1.Secret{
    @@ -1312,3 +1313,42 @@ func validatingWebhookConfiguration(namespace string, caBundle []byte, testValue
     
     	return webhookConfig
     }
    +
    +func mutatingWebhookConfiguration(namespace string, caBundle []byte) *admissionregistrationv1.MutatingWebhookConfiguration {
    +	var (
    +		failurePolicyFail = admissionregistrationv1.Fail
    +		sideEffectsNone   = admissionregistrationv1.SideEffectClassNone
    +	)
    +
    +	return &admissionregistrationv1.MutatingWebhookConfiguration{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name: "gardener-admission-controller",
    +		},
    +		Webhooks: []admissionregistrationv1.MutatingWebhook{
    +			{
    +				Name:                    "sync-provider-secret-labels.gardener.cloud",
    +				AdmissionReviewVersions: []string{"v1", "v1beta1"},
    +				TimeoutSeconds:          ptr.To[int32](10),
    +				Rules: []admissionregistrationv1.RuleWithOperations{{
    +					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
    +					Rule: admissionregistrationv1.Rule{
    +						APIGroups:   []string{""},
    +						APIVersions: []string{"v1"},
    +						Resources:   []string{"secrets"},
    +					},
    +				}},
    +				FailurePolicy: &failurePolicyFail,
    +				NamespaceSelector: &metav1.LabelSelector{
    +					MatchLabels: map[string]string{
    +						"gardener.cloud/role": "project",
    +					},
    +				},
    +				ClientConfig: admissionregistrationv1.WebhookClientConfig{
    +					URL:      ptr.To("https://gardener-admission-controller." + namespace + "/webhooks/sync-provider-secret-labels"),
    +					CABundle: caBundle,
    +				},
    +				SideEffects: &sideEffectsNone,
    +			},
    +		},
    +	}
    +}
    
  • pkg/component/gardener/admissioncontroller/webhooks.go+41 0 modified
    @@ -375,6 +375,47 @@ func (a *gardenerAdmissionController) validatingWebhookConfiguration(caSecret *c
     	return validatingWebhook
     }
     
    +func (a *gardenerAdmissionController) mutatingWebhookConfiguration(caSecret *corev1.Secret) *admissionregistrationv1.MutatingWebhookConfiguration {
    +	var (
    +		failurePolicyFail = admissionregistrationv1.Fail
    +		sideEffectsNone   = admissionregistrationv1.SideEffectClassNone
    +
    +		caBundle = caSecret.Data[secrets.DataKeyCertificateBundle]
    +	)
    +
    +	return &admissionregistrationv1.MutatingWebhookConfiguration{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name: DeploymentName,
    +		},
    +		Webhooks: []admissionregistrationv1.MutatingWebhook{
    +			{
    +				Name:                    "sync-provider-secret-labels.gardener.cloud",
    +				AdmissionReviewVersions: []string{"v1", "v1beta1"},
    +				TimeoutSeconds:          ptr.To[int32](10),
    +				Rules: []admissionregistrationv1.RuleWithOperations{{
    +					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
    +					Rule: admissionregistrationv1.Rule{
    +						APIGroups:   []string{corev1.GroupName},
    +						APIVersions: []string{"v1"},
    +						Resources:   []string{"secrets"},
    +					},
    +				}},
    +				FailurePolicy: &failurePolicyFail,
    +				NamespaceSelector: &metav1.LabelSelector{
    +					MatchLabels: map[string]string{
    +						v1beta1constants.GardenRole: v1beta1constants.GardenRoleProject,
    +					},
    +				},
    +				ClientConfig: admissionregistrationv1.WebhookClientConfig{
    +					URL:      buildClientConfigURL("/webhooks/sync-provider-secret-labels", a.namespace),
    +					CABundle: caBundle,
    +				},
    +				SideEffects: &sideEffectsNone,
    +			},
    +		},
    +	}
    +}
    +
     func buildWebhookConfigRulesForResourceSize(config *admissioncontrollerconfigv1alpha1.ResourceAdmissionConfiguration) []admissionregistrationv1.RuleWithOperations {
     	if config == nil || len(config.Limits) == 0 {
     		return nil
    
  • pkg/controllermanager/controller/secretbinding/reconciler.go+9 11 modified
    @@ -143,17 +143,15 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
     		}
     	}
     
    -	if secretBinding.Provider != nil {
    -		types := v1beta1helper.GetSecretBindingTypes(secretBinding)
    -		for _, t := range types {
    -			labelKey := v1beta1constants.LabelShootProviderPrefix + t
    -
    -			if !metav1.HasLabel(secret.ObjectMeta, labelKey) {
    -				patch := client.MergeFrom(secret.DeepCopy())
    -				metav1.SetMetaDataLabel(&secret.ObjectMeta, labelKey, "true")
    -				if err := r.Client.Patch(ctx, secret, patch); err != nil {
    -					return reconcile.Result{}, fmt.Errorf("failed to add provider type label to Secret referenced in SecretBinding: %w", err)
    -				}
    +	types := v1beta1helper.GetSecretBindingTypes(secretBinding)
    +	for _, t := range types {
    +		labelKey := v1beta1constants.LabelShootProviderPrefix + t
    +
    +		if !metav1.HasLabel(secret.ObjectMeta, labelKey) {
    +			patch := client.MergeFrom(secret.DeepCopy())
    +			metav1.SetMetaDataLabel(&secret.ObjectMeta, labelKey, "true")
    +			if err := r.Client.Patch(ctx, secret, patch); err != nil {
    +				return reconcile.Result{}, fmt.Errorf("failed to add provider type label to Secret referenced in SecretBinding: %w", err)
     			}
     		}
     	}
    
  • pkg/provider-local/admission/cmd/options.go+1 0 modified
    @@ -14,6 +14,7 @@ import (
     func GardenWebhookSwitchOptions() *extensionscmdwebhook.SwitchOptions {
     	return extensionscmdwebhook.NewSwitchOptions(
     		extensionscmdwebhook.Switch(validator.Name, validator.New),
    +		extensionscmdwebhook.Switch(validator.SecretsValidatorName, validator.NewSecretsWebhook),
     		extensionscmdwebhook.Switch(mutator.Name, mutator.New),
     	)
     }
    
  • pkg/provider-local/admission/validator/secret.go+48 0 added
    @@ -0,0 +1,48 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package validator
    +
    +import (
    +	"context"
    +	"fmt"
    +
    +	corev1 "k8s.io/api/core/v1"
    +	apiequality "k8s.io/apimachinery/pkg/api/equality"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
    +)
    +
    +type secretValidator struct{}
    +
    +// NewSecretValidator returns a new instance of a secret validator.
    +func NewSecretValidator() extensionswebhook.Validator {
    +	return &secretValidator{}
    +}
    +
    +// Validate checks whether the data is empty.
    +func (s *secretValidator) Validate(_ context.Context, newObj, oldObj client.Object) error {
    +	secret, ok := newObj.(*corev1.Secret)
    +	if !ok {
    +		return fmt.Errorf("wrong object type %T", newObj)
    +	}
    +
    +	if oldObj != nil {
    +		oldSecret, ok := oldObj.(*corev1.Secret)
    +		if !ok {
    +			return fmt.Errorf("wrong object type %T for old object", oldObj)
    +		}
    +
    +		if apiequality.Semantic.DeepEqual(secret.Data, oldSecret.Data) {
    +			return nil
    +		}
    +	}
    +
    +	if len(secret.Data) != 0 {
    +		return fmt.Errorf("secret data should be empty")
    +	}
    +
    +	return nil
    +}
    
  • pkg/provider-local/admission/validator/webhook.go+24 1 modified
    @@ -5,18 +5,22 @@
     package validator
     
     import (
    +	corev1 "k8s.io/api/core/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"sigs.k8s.io/controller-runtime/pkg/log"
     	"sigs.k8s.io/controller-runtime/pkg/manager"
     
     	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
     	"github.com/gardener/gardener/pkg/apis/core"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
     	"github.com/gardener/gardener/pkg/provider-local/local"
     )
     
     const (
     	// Name is a name for a validation webhook.
     	Name = "validator"
    +	// SecretsValidatorName is the name of the secrets validator.
    +	SecretsValidatorName = "secrets." + Name
     )
     
     var logger = log.Log.WithName("local-validator-webhook")
    @@ -31,10 +35,29 @@ func New(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
     		Path:     "/webhooks/validate",
     		Validators: map[extensionswebhook.Validator][]extensionswebhook.Type{
     			NewNamespacedCloudProfileValidator(mgr): {{Obj: &core.NamespacedCloudProfile{}}},
    +			NewWorkloadIdentityValidator():          {{Obj: &securityv1alpha1.WorkloadIdentity{}}},
     		},
     		Target: extensionswebhook.TargetSeed,
     		ObjectSelector: &metav1.LabelSelector{
    -			MatchLabels: map[string]string{"provider.extensions.gardener.cloud/local": "true"},
    +			MatchLabels: map[string]string{"provider.extensions.gardener.cloud/" + local.Type: "true"},
    +		},
    +	})
    +}
    +
    +// NewSecretsWebhook creates a new validation webhook for Secrets.
    +func NewSecretsWebhook(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
    +	logger.Info("Setting up webhook", "name", SecretsValidatorName)
    +
    +	return extensionswebhook.New(mgr, extensionswebhook.Args{
    +		Provider: local.Type,
    +		Name:     SecretsValidatorName,
    +		Path:     "/webhooks/validate/secrets",
    +		Validators: map[extensionswebhook.Validator][]extensionswebhook.Type{
    +			NewSecretValidator(): {{Obj: &corev1.Secret{}}},
    +		},
    +		Target: extensionswebhook.TargetSeed,
    +		ObjectSelector: &metav1.LabelSelector{
    +			MatchLabels: map[string]string{"provider.shoot.gardener.cloud/" + local.Type: "true"},
     		},
     	})
     }
    
  • pkg/provider-local/admission/validator/workloadidentity.go+38 0 added
    @@ -0,0 +1,38 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package validator
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +)
    +
    +type workloadIdentityValidator struct {
    +}
    +
    +// NewWorkloadIdentityValidator returns a new instance of a WorkloadIdentity validator.
    +func NewWorkloadIdentityValidator() extensionswebhook.Validator {
    +	return &workloadIdentityValidator{}
    +}
    +
    +// Validate checks whether the provider config is empty.
    +func (wi *workloadIdentityValidator) Validate(_ context.Context, newObj, _ client.Object) error {
    +	workloadIdentity, ok := newObj.(*securityv1alpha1.WorkloadIdentity)
    +	if !ok {
    +		return fmt.Errorf("wrong object type %T", newObj)
    +	}
    +
    +	if workloadIdentity.Spec.TargetSystem.ProviderConfig != nil {
    +		return errors.New("target system provider config must be empty")
    +	}
    +
    +	return nil
    +}
    
  • plugin/pkg/global/extensionlabels/admission.go+3 5 modified
    @@ -242,11 +242,9 @@ func addMetaDataLabelsSeed(seed *core.Seed) {
     }
     
     func addMetaDataLabelsSecretBinding(secretBinding *core.SecretBinding) {
    -	if secretBinding.Provider != nil {
    -		types := gardencorehelper.GetSecretBindingTypes(secretBinding)
    -		for _, t := range types {
    -			metav1.SetMetaDataLabel(&secretBinding.ObjectMeta, v1beta1constants.LabelExtensionProviderTypePrefix+t, "true")
    -		}
    +	types := gardencorehelper.GetSecretBindingTypes(secretBinding)
    +	for _, t := range types {
    +		metav1.SetMetaDataLabel(&secretBinding.ObjectMeta, v1beta1constants.LabelExtensionProviderTypePrefix+t, "true")
     	}
     }
     
    
  • plugin/pkg/global/finalizerremoval/admission.go+196 0 added
    @@ -0,0 +1,196 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +	"io"
    +	"slices"
    +
    +	apierrors "k8s.io/apimachinery/pkg/api/errors"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/labels"
    +	"k8s.io/apimachinery/pkg/util/sets"
    +	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/utils/ptr"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	"github.com/gardener/gardener/pkg/apis/core"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	admissioninitializer "github.com/gardener/gardener/pkg/apiserver/admission/initializer"
    +	gardencoreinformers "github.com/gardener/gardener/pkg/client/core/informers/externalversions"
    +	gardencorev1beta1listers "github.com/gardener/gardener/pkg/client/core/listers/core/v1beta1"
    +	plugin "github.com/gardener/gardener/plugin/pkg"
    +)
    +
    +// Register registers a plugin.
    +func Register(plugins *admission.Plugins) {
    +	plugins.Register(plugin.PluginNameFinalizerRemoval, func(_ io.Reader) (admission.Interface, error) {
    +		return New()
    +	})
    +}
    +
    +// FinalizerRemoval contains listers and admission handler.
    +type FinalizerRemoval struct {
    +	*admission.Handler
    +	shootLister gardencorev1beta1listers.ShootLister
    +	readyFunc   admission.ReadyFunc
    +}
    +
    +var (
    +	_ = admissioninitializer.WantsCoreInformerFactory(&FinalizerRemoval{})
    +
    +	readyFuncs []admission.ReadyFunc
    +)
    +
    +// New creates a new FinalizerRemoval admission plugin.
    +func New() (*FinalizerRemoval, error) {
    +	return &FinalizerRemoval{
    +		Handler: admission.NewHandler(admission.Update),
    +	}, nil
    +}
    +
    +// AssignReadyFunc assigns the ready function to the admission handler.
    +func (f *FinalizerRemoval) AssignReadyFunc(fn admission.ReadyFunc) {
    +	f.readyFunc = fn
    +	f.SetReadyFunc(fn)
    +}
    +
    +// SetCoreInformerFactory gets Lister from SharedInformerFactory.
    +func (f *FinalizerRemoval) SetCoreInformerFactory(g gardencoreinformers.SharedInformerFactory) {
    +	shootInformer := g.Core().V1beta1().Shoots()
    +	f.shootLister = shootInformer.Lister()
    +
    +	readyFuncs = append(readyFuncs,
    +		shootInformer.Informer().HasSynced,
    +	)
    +}
    +
    +// ValidateInitialization checks whether the plugin was correctly initialized.
    +func (f *FinalizerRemoval) ValidateInitialization() error {
    +	if f.shootLister == nil {
    +		return errors.New("missing shoot lister")
    +	}
    +	return nil
    +}
    +
    +// Admit ensures that finalizers from objects can only be removed if they are not needed anymore.
    +func (f *FinalizerRemoval) Admit(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +	// Wait until the caches have been synced
    +	if f.readyFunc == nil {
    +		f.AssignReadyFunc(func() bool {
    +			for _, readyFunc := range readyFuncs {
    +				if !readyFunc() {
    +					return false
    +				}
    +			}
    +			return true
    +		})
    +	}
    +	if !f.WaitForReady() {
    +		return admission.NewForbidden(a, errors.New("not yet ready to handle request"))
    +	}
    +
    +	var (
    +		err            error
    +		newObj, oldObj client.Object
    +	)
    +
    +	oldObj, ok := a.GetOldObject().(client.Object)
    +	if !ok {
    +		return nil
    +	}
    +
    +	newObj, ok = a.GetObject().(client.Object)
    +	if !ok {
    +		return nil
    +	}
    +
    +	switch a.GetKind().GroupKind() {
    +	case core.Kind("SecretBinding"):
    +		binding, ok := a.GetObject().(*core.SecretBinding)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into SecretBinding object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the SecretBinding is not used by any shoot.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) {
    +			inUse, err := f.isUsedByShoot(binding.Namespace, func(shoot *gardencorev1beta1.Shoot) bool {
    +				return ptr.Deref(shoot.Spec.SecretBindingName, "") == binding.Name
    +			})
    +			if err != nil {
    +				return apierrors.NewInternalError(fmt.Errorf("error checking if secret binding is in use: %w", err))
    +			}
    +			if inUse {
    +				return admission.NewForbidden(a, fmt.Errorf("finalizer must not be removed - secret binding %s/%s is still in use by at least one shoot", binding.Namespace, binding.Name))
    +			}
    +		}
    +	case security.Kind("CredentialsBinding"):
    +		binding, ok := a.GetObject().(*security.CredentialsBinding)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into CredentialsBinding object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the CredentialsBinding is not used by any shoot.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) {
    +			inUse, err := f.isUsedByShoot(binding.Namespace, func(shoot *gardencorev1beta1.Shoot) bool {
    +				return ptr.Deref(shoot.Spec.CredentialsBindingName, "") == binding.Name
    +			})
    +			if err != nil {
    +				return apierrors.NewInternalError(fmt.Errorf("error checking if credentials binding is in use: %w", err))
    +			}
    +			if inUse {
    +				return admission.NewForbidden(a, fmt.Errorf("finalizer must not be removed - credentials binding %s/%s is still in use by at least one shoot", binding.Namespace, binding.Name))
    +			}
    +		}
    +	case core.Kind("Shoot"):
    +		shoot, ok := a.GetObject().(*core.Shoot)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into Shoot object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the Shoot deletion has completed successfully.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) && !shootDeletionSucceeded(shoot) {
    +			return admission.NewForbidden(a, fmt.Errorf("finalizer %q cannot be removed because shoot deletion has not completed successfully yet", core.GardenerName))
    +		}
    +	}
    +
    +	if err != nil {
    +		return admission.NewForbidden(a, err)
    +	}
    +	return nil
    +}
    +
    +func (f *FinalizerRemoval) isUsedByShoot(namespace string, inUse func(*gardencorev1beta1.Shoot) bool) (bool, error) {
    +	shoots, err := f.shootLister.Shoots(namespace).List(labels.Everything())
    +	if err != nil {
    +		return false, fmt.Errorf("error retrieving shoots: %w", err)
    +	}
    +
    +	return slices.ContainsFunc(shoots, inUse), nil
    +}
    +
    +func shootDeletionSucceeded(shoot *core.Shoot) bool {
    +	if len(shoot.Status.TechnicalID) == 0 || shoot.Status.LastOperation == nil {
    +		return true
    +	}
    +
    +	lastOperation := shoot.Status.LastOperation
    +	return lastOperation.Type == core.LastOperationTypeDelete &&
    +		lastOperation.State == core.LastOperationStateSucceeded &&
    +		lastOperation.Progress == 100
    +}
    +
    +func isFinalizerRemoved(old, new metav1.Object, finalizerName string) bool {
    +	var (
    +		oldFinalizers = sets.New(old.GetFinalizers()...)
    +		newFinalizer  = sets.New(new.GetFinalizers()...)
    +	)
    +
    +	return oldFinalizers.Has(finalizerName) && !newFinalizer.Has(finalizerName)
    +}
    
  • plugin/pkg/global/finalizerremoval/admission_test.go+216 0 added
    @@ -0,0 +1,216 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval_test
    +
    +import (
    +	"context"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/utils/ptr"
    +
    +	"github.com/gardener/gardener/pkg/apis/core"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	gardencoreinformers "github.com/gardener/gardener/pkg/client/core/informers/externalversions"
    +	. "github.com/gardener/gardener/plugin/pkg/global/finalizerremoval"
    +)
    +
    +var _ = Describe("finalizerremoval", func() {
    +	Describe("#Admit", func() {
    +		var (
    +			ctx                       context.Context
    +			admissionHandler          *FinalizerRemoval
    +			gardenCoreInformerFactory gardencoreinformers.SharedInformerFactory
    +
    +			finalizers []string
    +
    +			namespace              = "default"
    +			secretBindingName      = "binding-1"
    +			credentialsBindingName = "credentials-binding-1"
    +			shootName              = "shoot-1"
    +
    +			shoot *gardencorev1beta1.Shoot
    +		)
    +
    +		BeforeEach(func() {
    +			ctx = context.Background()
    +			admissionHandler, _ = New()
    +			admissionHandler.AssignReadyFunc(func() bool { return true })
    +
    +			finalizers = []string{core.GardenerName}
    +
    +			shoot = &gardencorev1beta1.Shoot{
    +				ObjectMeta: metav1.ObjectMeta{
    +					Name:      shootName,
    +					Namespace: namespace,
    +				},
    +				Spec: gardencorev1beta1.ShootSpec{
    +					CredentialsBindingName: ptr.To(credentialsBindingName),
    +					SecretBindingName:      ptr.To(secretBindingName),
    +				},
    +			}
    +
    +			gardenCoreInformerFactory = gardencoreinformers.NewSharedInformerFactory(nil, 0)
    +			admissionHandler.SetCoreInformerFactory(gardenCoreInformerFactory)
    +		})
    +
    +		Context("SecretBinding", func() {
    +			var coreSecretBinding *core.SecretBinding
    +
    +			BeforeEach(func() {
    +				coreSecretBinding = &core.SecretBinding{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Name:       secretBindingName,
    +						Namespace:  namespace,
    +						Finalizers: finalizers,
    +					},
    +				}
    +			})
    +
    +			It("should admit the removal because object is not used by any shoot", func() {
    +				attrs := admission.NewAttributesRecord(&core.SecretBinding{}, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should admit the removal because finalizer is irrelevant", func() {
    +				newSecretBinding := coreSecretBinding.DeepCopy()
    +				coreSecretBinding.Finalizers = append(coreSecretBinding.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newSecretBinding, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should reject the removal because object is not used by any shoot", func() {
    +				newSecretBinding := coreSecretBinding.DeepCopy()
    +				newSecretBinding.Finalizers = nil
    +
    +				secondShoot := shoot.DeepCopy()
    +				secondShoot.Name = shootName + "-2"
    +				secondShoot.Spec.SecretBindingName = ptr.To(secretBindingName + "-2")
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shoot)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(secondShoot)).To(Succeed())
    +
    +				attrs := admission.NewAttributesRecord(newSecretBinding, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("finalizer must not be removed")))
    +			})
    +		})
    +
    +		Context("CredentialsBinding", func() {
    +			var coreCredentialsBinding *security.CredentialsBinding
    +
    +			BeforeEach(func() {
    +				coreCredentialsBinding = &security.CredentialsBinding{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Name:       credentialsBindingName,
    +						Namespace:  namespace,
    +						Finalizers: finalizers,
    +					},
    +				}
    +			})
    +
    +			It("should admit the removal because object is not used by any shoot", func() {
    +				attrs := admission.NewAttributesRecord(&security.CredentialsBinding{}, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should admit the removal because finalizer is irrelevant", func() {
    +				newCredentialsBinding := coreCredentialsBinding.DeepCopy()
    +				coreCredentialsBinding.Finalizers = append(coreCredentialsBinding.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newCredentialsBinding, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should reject the removal because object is not used by any shoot", func() {
    +				newCredentialsBinding := coreCredentialsBinding.DeepCopy()
    +				newCredentialsBinding.Finalizers = nil
    +
    +				secondShoot := shoot.DeepCopy()
    +				secondShoot.Name = shootName + "-2"
    +				secondShoot.Spec.CredentialsBindingName = ptr.To(secretBindingName + "-2")
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shoot)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(secondShoot)).To(Succeed())
    +
    +				attrs := admission.NewAttributesRecord(newCredentialsBinding, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("finalizer must not be removed")))
    +			})
    +		})
    +
    +		Context("shoot", func() {
    +			var coreShoot *core.Shoot
    +
    +			BeforeEach(func() {
    +				coreShoot = &core.Shoot{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Finalizers: finalizers,
    +					},
    +					Status: core.ShootStatus{
    +						TechnicalID: "some-id",
    +						LastOperation: &core.LastOperation{
    +							Type:     core.LastOperationTypeReconcile,
    +							State:    core.LastOperationStateSucceeded,
    +							Progress: 100,
    +						},
    +					},
    +				}
    +			})
    +
    +			It("should allow the removal because finalizer is irrelevant", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				coreShoot.Finalizers = append(coreShoot.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, security.Kind("Shoot").WithVersion("version"), "", coreShoot.Name, security.Resource("Shoot").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should admit the removal if the shoot deletion succeeded ", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.LastOperation.Type = core.LastOperationTypeDelete
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should reject the removal if the shoot has not yet been deleted successfully", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("shoot deletion has not completed successfully yet")))
    +			})
    +
    +			It("should admit the removal if the shoot has not yet a last operation", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.LastOperation = nil
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should admit the removal if the shoot has not yet a technical id", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.TechnicalID = ""
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +		})
    +	})
    +})
    
  • plugin/pkg/global/finalizerremoval/finalizerremoval_suite_test.go+17 0 added
    @@ -0,0 +1,17 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +)
    +
    +func TestFinalizerRemoval(t *testing.T) {
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "AdmissionPlugin Global FinalizerRemoval Suite")
    +}
    
  • plugin/pkg/global/resourcereferencemanager/admission.go+91 72 modified
    @@ -10,13 +10,13 @@ import (
     	"fmt"
     	"io"
     	"reflect"
    +	"slices"
     	"strings"
     	"sync"
     	"time"
     
     	"github.com/hashicorp/go-multierror"
     	corev1 "k8s.io/api/core/v1"
    -	rbacv1 "k8s.io/api/rbac/v1"
     	apiequality "k8s.io/apimachinery/pkg/api/equality"
     	apierrors "k8s.io/apimachinery/pkg/api/errors"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    @@ -32,6 +32,7 @@ import (
     	kubeinformers "k8s.io/client-go/informers"
     	"k8s.io/client-go/kubernetes"
     	kubecorev1listers "k8s.io/client-go/listers/core/v1"
    +	"k8s.io/utils/ptr"
     	"sigs.k8s.io/controller-runtime/pkg/client"
     
     	"github.com/gardener/gardener/pkg/apis/core"
    @@ -287,8 +288,8 @@ func (r *ReferenceManager) ValidateInitialization() error {
     	return nil
     }
     
    -// Admit ensures that referenced resources do actually exist.
    -func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +// Validate ensures that referenced resources do actually exist.
    +func (r *ReferenceManager) Validate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
     	// Wait until the caches have been synced
     	if r.readyFunc == nil {
     		r.AssignReadyFunc(func() bool {
    @@ -350,14 +351,6 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     
     		switch a.GetOperation() {
     		case admission.Create:
    -			// Add createdBy annotation to Shoot
    -			annotations := shoot.Annotations
    -			if annotations == nil {
    -				annotations = map[string]string{}
    -			}
    -			annotations[v1beta1constants.GardenCreatedBy] = a.GetUserInfo().GetName()
    -			shoot.Annotations = annotations
    -
     			oldShoot = &core.Shoot{}
     		case admission.Update:
     			// skip verification if spec wasn't changed
    @@ -408,31 +401,9 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     		if utils.SkipVerification(operation, project.ObjectMeta) {
     			return nil
     		}
    -		// Set createdBy field in Project
    +
     		switch a.GetOperation() {
     		case admission.Create:
    -			project.Spec.CreatedBy = &rbacv1.Subject{
    -				APIGroup: "rbac.authorization.k8s.io",
    -				Kind:     rbacv1.UserKind,
    -				Name:     a.GetUserInfo().GetName(),
    -			}
    -
    -			if project.Spec.Owner == nil {
    -				owner := project.Spec.CreatedBy
    -
    -			outer:
    -				for _, member := range project.Spec.Members {
    -					for _, role := range member.Roles {
    -						if role == core.ProjectMemberOwner {
    -							owner = member.Subject.DeepCopy()
    -							break outer
    -						}
    -					}
    -				}
    -
    -				project.Spec.Owner = owner
    -			}
    -
     			err = r.ensureProjectNamespace(project)
     		case admission.Update:
     			oldProject, ok := a.GetOldObject().(*core.Project)
    @@ -444,24 +415,6 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     			}
     		}
     
    -		if project.Spec.Owner != nil {
    -			ownerIsMember := false
    -			for _, member := range project.Spec.Members {
    -				if member.Subject == *project.Spec.Owner {
    -					ownerIsMember = true
    -				}
    -			}
    -			if !ownerIsMember {
    -				project.Spec.Members = append(project.Spec.Members, core.ProjectMember{
    -					Subject: *project.Spec.Owner,
    -					Roles: []string{
    -						core.ProjectMemberAdmin,
    -						core.ProjectMemberOwner,
    -					},
    -				})
    -			}
    -		}
    -
     	case core.Kind("BackupBucket"):
     		if operation == admission.Delete {
     			// The "delete endpoint" handler of the k8s.io/apiserver library calls the admission controllers
    @@ -788,7 +741,10 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		credentialsNamespace  string
     		credentialsName       string
     		credentialsKind       string
    +		providerTypes         []string
    +		credentialsReferenced func(shoot *gardencorev1beta1.Shoot) bool
     	)
    +
     	switch attributes.GetKind().GroupKind() {
     	case core.Kind("SecretBinding"):
     		b, ok := binding.(*core.SecretBinding)
    @@ -802,6 +758,11 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		credentialsNamespace = b.SecretRef.Namespace
     		credentialsName = b.SecretRef.Name
     		credentialsKind = "Secret"
    +		providerTypes = helper.GetSecretBindingTypes(b)
    +		credentialsReferenced = func(shoot *gardencorev1beta1.Shoot) bool {
    +			return ptr.Deref(shoot.Spec.SecretBindingName, "") == b.Name
    +		}
    +
     	case security.Kind("CredentialsBinding"):
     		b, ok := binding.(*security.CredentialsBinding)
     		if !ok {
    @@ -823,9 +784,30 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		}
     		credentialsNamespace = b.CredentialsRef.Namespace
     		credentialsName = b.CredentialsRef.Name
    +		providerTypes = []string{b.Provider.Type}
    +		credentialsReferenced = func(shoot *gardencorev1beta1.Shoot) bool {
    +			return ptr.Deref(shoot.Spec.CredentialsBindingName, "") == b.Name
    +		}
    +
     	default:
     		return fmt.Errorf("%s is neither of kind SecretBinding nor CredentialsBinding", attributes.GetKind().GroupKind())
     	}
    +
    +	shoots, err := r.shootLister.Shoots(attributes.GetNamespace()).List(labels.Everything())
    +	if err != nil {
    +		return fmt.Errorf("failed listing shoots: %w", err)
    +	}
    +
    +	for _, shoot := range shoots {
    +		if !credentialsReferenced(shoot) {
    +			continue
    +		}
    +
    +		if !slices.Contains(providerTypes, shoot.Spec.Provider.Type) {
    +			return fmt.Errorf("%s is referenced by shoot %q, but provider types (%+v) do not match with the shoot provider type %q", attributes.GetKind().Kind, shoot.Name, providerTypes, shoot.Spec.Provider.Type)
    +		}
    +	}
    +
     	readAttributes := authorizer.AttributesRecord{
     		User:            attributes.GetUserInfo(),
     		Verb:            "get",
    @@ -847,10 +829,20 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		if err := r.lookupSecret(ctx, credentialsNamespace, credentialsName); err != nil {
     			return err
     		}
    +		if err := r.sanityCheckProviderSecret(ctx, credentialsNamespace, credentialsName, providerTypes); err != nil {
    +			return err
    +		}
    +
     	case "WorkloadIdentity":
    -		if err := r.lookupWorkloadIdentity(ctx, credentialsNamespace, credentialsName); err != nil {
    +		workloadIdentity, err := r.lookupWorkloadIdentity(ctx, credentialsNamespace, credentialsName)
    +		if err != nil {
     			return err
     		}
    +
    +		if !slices.Contains(providerTypes, workloadIdentity.Spec.TargetSystem.Type) {
    +			return fmt.Errorf("CredentialsBinding provider type (%+v) does not match with WorkloadIdentity provider type %s", providerTypes, workloadIdentity.Spec.TargetSystem.Type)
    +		}
    +
     	default:
     		return fmt.Errorf("unknown credentials kind: %s", credentialsKind)
     	}
    @@ -1175,42 +1167,36 @@ func validateShootWorkersForRemovedMachineImageVersions(channel chan error, shoo
     
     type getFn func(context.Context, string, string) (runtime.Object, error)
     
    -func lookupResource(ctx context.Context, namespace, name string, get getFn, fallbackGet getFn) error {
    +func lookupResource(ctx context.Context, namespace, name string, get getFn, fallbackGet getFn) (runtime.Object, error) {
     	// First try to detect the resource in the cache.
    -	var err error
    -
    -	_, err = get(ctx, namespace, name)
    +	obj, err := get(ctx, namespace, name)
     	if err == nil {
    -		return nil
    +		return obj, nil
     	}
     	if !apierrors.IsNotFound(err) {
    -		return err
    +		return nil, err
     	}
     
     	// Second try to detect the resource in the cache after the first try failed.
     	// Give the cache time to observe the resource before rejecting a create.
     	// This helps when creating a resource and immediately creating a binding referencing it.
     	time.Sleep(MissingResourceWait)
    -	_, err = get(ctx, namespace, name)
    +	obj, err = get(ctx, namespace, name)
     
     	switch {
     	case apierrors.IsNotFound(err):
     		// no-op
     	case err != nil:
    -		return err
    +		return nil, err
     	default:
    -		return nil
    +		return obj, nil
     	}
     
     	// Third try to detect the secret, now by doing a live lookup instead of relying on the cache.
    -	if _, err := fallbackGet(ctx, namespace, name); err != nil {
    -		return err
    -	}
    -
    -	return nil
    +	return fallbackGet(ctx, namespace, name)
     }
     
    -func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace, name string) error {
    +func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace, name string) (*securityv1alpha1.WorkloadIdentity, error) {
     	workloadIdentityFromLister := func(_ context.Context, namespace, name string) (runtime.Object, error) {
     		return r.workloadIdentityLister.WorkloadIdentities(namespace).Get(name)
     	}
    @@ -1219,7 +1205,11 @@ func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace
     		return r.gardenSecurityClient.SecurityV1alpha1().WorkloadIdentities(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, workloadIdentityFromLister, workloadIdentityFromClient)
    +	obj, err := lookupResource(ctx, namespace, name, workloadIdentityFromLister, workloadIdentityFromClient)
    +	if err != nil {
    +		return nil, err
    +	}
    +	return obj.(*securityv1alpha1.WorkloadIdentity), nil
     }
     
     func (r *ReferenceManager) lookupSecret(ctx context.Context, namespace, name string) error {
    @@ -1231,7 +1221,8 @@ func (r *ReferenceManager) lookupSecret(ctx context.Context, namespace, name str
     		return r.kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, secretFromLister, secretFromClient)
    +	_, err := lookupResource(ctx, namespace, name, secretFromLister, secretFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) lookupConfigMap(ctx context.Context, namespace, name string) error {
    @@ -1243,7 +1234,8 @@ func (r *ReferenceManager) lookupConfigMap(ctx context.Context, namespace, name
     		return r.kubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, configMapFromLister, configMapFromClient)
    +	_, err := lookupResource(ctx, namespace, name, configMapFromLister, configMapFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) lookupControllerDeployment(ctx context.Context, name string) error {
    @@ -1255,7 +1247,8 @@ func (r *ReferenceManager) lookupControllerDeployment(ctx context.Context, name
     		return r.gardenCoreClient.CoreV1beta1().ControllerDeployments().Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, "", name, deploymentFromLister, deploymentFromClient)
    +	_, err := lookupResource(ctx, "", name, deploymentFromLister, deploymentFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) getAPIResource(groupVersion, kind string) (*metav1.APIResource, error) {
    @@ -1350,3 +1343,29 @@ func (r *ReferenceManager) ensureResourceReferences(ctx context.Context, attribu
     	}
     	return nil
     }
    +
    +func (r *ReferenceManager) sanityCheckProviderSecret(ctx context.Context, namespace, name string, providerTypes []string) error {
    +	secret, err := r.kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
    +	if err != nil {
    +		return err
    +	}
    +
    +	for _, providerType := range providerTypes {
    +		dummySecret := &corev1.Secret{
    +			ObjectMeta: metav1.ObjectMeta{
    +				GenerateName: name,
    +				Namespace:    namespace,
    +				Annotations:  secret.Annotations,
    +				Labels:       gardenerutils.MergeStringMaps(secret.Labels, map[string]string{v1beta1constants.LabelShootProviderPrefix + providerType: "true"}),
    +			},
    +			Type: secret.Type,
    +			Data: secret.Data,
    +		}
    +
    +		if _, err := r.kubeClient.CoreV1().Secrets(dummySecret.Namespace).Create(ctx, dummySecret, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}); err != nil {
    +			return fmt.Errorf("%s provider secret sanity check failed: %w", providerType, err)
    +		}
    +	}
    +
    +	return nil
    +}
    
  • plugin/pkg/global/resourcereferencemanager/admission_test.go+221 230 modified
    @@ -16,7 +16,6 @@ import (
     	. "github.com/onsi/gomega/gstruct"
     	autoscalingv1 "k8s.io/api/autoscaling/v1"
     	corev1 "k8s.io/api/core/v1"
    -	rbacv1 "k8s.io/api/rbac/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/runtime"
     	"k8s.io/apiserver/pkg/admission"
    @@ -30,7 +29,6 @@ import (
     
     	"github.com/gardener/gardener/pkg/apis/core"
     	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    -	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
     	"github.com/gardener/gardener/pkg/apis/security"
     	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
     	"github.com/gardener/gardener/pkg/apis/seedmanagement"
    @@ -178,6 +176,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: &core.SecretBindingProvider{
    +					Type: "test",
    +				},
     			}
     			secretBinding = gardencorev1beta1.SecretBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -195,6 +196,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: &gardencorev1beta1.SecretBindingProvider{
    +					Type: "test",
    +				},
     			}
     			securityCredentialsBindingRefSecret = security.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -213,6 +217,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: security.CredentialsBindingProvider{
    +					Type: "test",
    +				},
     			}
     			credentialsBindingRefSecret = securityv1alpha1.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -231,6 +238,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: securityv1alpha1.CredentialsBindingProvider{
    +					Type: "test",
    +				},
     			}
     			securityCredentialsBindingRefWorkloadIdentity = security.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -244,6 +254,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					Name:       workloadIdentityName,
     					Namespace:  namespace,
     				},
    +				Provider: security.CredentialsBindingProvider{Type: "wiprovider"},
     				Quotas: []corev1.ObjectReference{
     					{
     						Name:      quotaName,
    @@ -451,18 +462,20 @@ var _ = Describe("resourcereferencemanager", func() {
     
     			err = gardencorev1beta1.Convert_core_Project_To_v1beta1_Project(&coreProject, &project, nil)
     			Expect(err).To(Succeed())
    +
    +			workloadIdentity.Spec.TargetSystem = securityv1alpha1.TargetSystem{Type: "wiprovider"}
     		})
     
     		It("should return nil because the resource is not BackupBucket and operation is delete", func() {
     			attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
     
    -			err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +			err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     			Expect(err).NotTo(HaveOccurred())
     
     			attrs = admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), "", controllerRegistration.Name, core.Resource("shoots").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
     
    -			err = admissionHandler.Admit(context.TODO(), attrs, nil)
    +			err = admissionHandler.Validate(context.TODO(), attrs, nil)
     
     			Expect(err).NotTo(HaveOccurred())
     		})
    @@ -474,7 +487,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -487,7 +500,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -499,7 +512,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					return true, nil, errors.New("nope, out of luck")
     				})
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     				Expect(err.Error()).To(ContainSubstring("nope, out of luck"))
    @@ -514,7 +527,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -533,11 +546,36 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, nil
    +				})
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the sanity check fails", func() {
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, &corev1.Secret{
    +						ObjectMeta: metav1.ObjectMeta{
    +							Namespace: secret.Namespace,
    +							Name:      secret.Name,
    +						},
    +					}, nil
    +				})
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, fmt.Errorf("sanity check failed")
    +				})
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("test provider secret sanity check failed: sanity check failed")))
    +			})
    +
     			It("should reject because the referenced secret does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -547,7 +585,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -559,7 +597,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -570,7 +608,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -582,7 +620,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -617,7 +655,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -652,10 +690,23 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    +
    +			It("should reject because provider types do not match with shoot type", func() {
    +				coreSecretBinding.Provider.Type = "another-provider"
    +				coreSecretBinding.Quotas = nil
    +				shoot.Spec.Provider.Type = "local"
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(&shoot)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(`SecretBinding is referenced by shoot "shoot-1", but provider types ([another-provider]) do not match with the shoot provider type "local"`)))
    +			})
     		})
     
     		Context("tests for CredentialsBinding objects referencing Secret", func() {
    @@ -666,7 +717,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -685,11 +736,36 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, nil
    +				})
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the sanity check fails", func() {
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, &corev1.Secret{
    +						ObjectMeta: metav1.ObjectMeta{
    +							Namespace: secret.Namespace,
    +							Name:      secret.Name,
    +						},
    +					}, nil
    +				})
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, fmt.Errorf("sanity check failed")
    +				})
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("test provider secret sanity check failed: sanity check failed")))
    +			})
    +
     			It("should reject because the referenced secret does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -699,7 +775,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -711,7 +787,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -722,7 +798,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -734,7 +810,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -769,7 +845,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -804,10 +880,23 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    +
    +			It("should reject because provider types do not match with shoot type", func() {
    +				securityCredentialsBindingRefSecret.Provider.Type = "another-provider"
    +				securityCredentialsBindingRefSecret.Quotas = nil
    +				shoot.Spec.Provider.Type = "local"
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(&shoot)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(`CredentialsBinding is referenced by shoot "shoot-1", but provider types ([another-provider]) do not match with the shoot provider type "local"`)))
    +			})
     		})
     
     		Context("tests for CredentialsBinding objects referencing WorkloadIdentity", func() {
    @@ -818,30 +907,38 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
     			It("should accept because all referenced objects have been found (workloadidentity looked up live)", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				gardenSecurityClient.AddReactor("get", "workloadidentities", func(_ testing.Action) (bool, runtime.Object, error) {
    -					return true, &securityv1alpha1.WorkloadIdentity{
    -						ObjectMeta: metav1.ObjectMeta{
    -							Namespace: workloadIdentity.Namespace,
    -							Name:      workloadIdentity.Name,
    -						},
    -					}, nil
    +					return true, &workloadIdentity, nil
     				})
     
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the provider type does not match in WorkloadIdentity and CredentialsBinding", func() {
    +				workloadIdentity.Spec.TargetSystem.Type = "foo"
    +				Expect(gardenSecurityInformerFactory.Security().V1alpha1().WorkloadIdentities().Informer().GetStore().Add(&workloadIdentity)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
    +
    +				Expect(err).To(MatchError(ContainSubstring("does not match with WorkloadIdentity provider type")))
    +			})
    +
     			It("should reject because the referenced workload identity does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				gardenSecurityClient.AddReactor("get", "workloadidentities", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -851,7 +948,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -863,7 +960,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -874,7 +971,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -886,7 +983,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -914,14 +1011,15 @@ var _ = Describe("resourcereferencemanager", func() {
     				quotaRefList = append(quotaRefList, quota2Ref)
     				securityCredentialsBindingRefWorkloadIdentity.Quotas = quotaRefList
     
    +				Expect(gardenSecurityInformerFactory.Security().V1alpha1().WorkloadIdentities().Informer().GetStore().Add(&workloadIdentity)).To(Succeed())
     				Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota2)).To(Succeed())
     
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -975,31 +1073,13 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
     		})
     
     		Context("tests for Shoot objects", func() {
    -			It("should add the created-by annotation", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(gardenSecurityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBindingRefSecret)).To(Succeed())
    -				Expect(kubeInformerFactory.Core().V1().ConfigMaps().Informer().GetStore().Add(&configMap)).To(Succeed())
    -
    -				user := &user.DefaultInfo{Name: allowedUser}
    -				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    -
    -				Expect(coreShoot.Annotations).NotTo(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, user.Name))
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreShoot.Annotations).To(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, user.Name))
    -			})
    -
     			It("should accept because all referenced objects have been found", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    @@ -1010,7 +1090,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1026,7 +1106,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreShoot.Status.TechnicalID = "should-never-change"
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1035,7 +1115,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				It("should reject because the referenced cloud profile does not exist (create)", func() {
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1046,7 +1126,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1061,7 +1141,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1078,7 +1158,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1091,7 +1171,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1103,7 +1183,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1115,7 +1195,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1130,7 +1210,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1150,7 +1230,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				It("should reject because the referenced exposure class does not exists", func() {
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     					Expect(err).To(HaveOccurred())
     				})
     
    @@ -1164,7 +1244,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					Expect(gardenCoreInformerFactory.Core().V1beta1().ExposureClasses().Informer().GetStore().Add(&exposureClass)).To(Succeed())
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     					Expect(err).To(HaveOccurred())
     				})
     			})
    @@ -1176,7 +1256,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1191,7 +1271,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("shoots.core.gardener.cloud \"shoot-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1218,7 +1298,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("shoots.core.gardener.cloud \"shoot-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1234,7 +1314,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1251,7 +1331,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError(ContainSubstring("failed to resolve resource reference")))
     			})
    @@ -1273,7 +1353,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
     				})
     
     				It("should reject because the referenced "+description+" does not exist (update)", func() {
    @@ -1293,7 +1373,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
     				})
     
     				It("should pass because the referenced "+description+" does not exist but shoot has deletion timestamp", func() {
    @@ -1316,7 +1396,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     				})
     
     				It("should pass because the referenced "+description+" exists", func() {
    @@ -1335,7 +1415,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     				})
     			}
     
    @@ -1409,7 +1489,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSeed, oldSeed, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("seeds.core.gardener.cloud \"seed-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1420,7 +1500,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSeed, oldSeed, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1429,7 +1509,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSeed, nil, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError(ContainSubstring("failed to resolve resource reference")))
     			})
    @@ -1439,7 +1519,7 @@ var _ = Describe("resourcereferencemanager", func() {
     			It("should reject if the referred Seed is not found", func() {
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: seed.core.gardener.cloud %q not found", coreBackupBucket.Name, seed.Name)))
    @@ -1453,7 +1533,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("secret not found")))
    @@ -1472,7 +1552,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1483,7 +1563,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1498,7 +1578,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1517,7 +1597,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", backupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: cannot delete BackupBucket because BackupEntries are still referencing it, backupEntryNames: %s/%s,%s/%s", backupBucket.Name, backupEntry.Namespace, backupEntry.Name, backupEntry2.Namespace, backupEntry2.Name)))
    @@ -1533,7 +1613,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1561,7 +1641,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", "", core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: cannot delete BackupBucket because BackupEntries are still referencing it, backupEntryNames: %s/%s,%s/%s", backupBucket2.Name, backupEntry.Namespace, backupEntry.Name, backupEntry2.Namespace, backupEntry2.Name)))
    @@ -1590,7 +1670,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", "", core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1600,7 +1680,7 @@ var _ = Describe("resourcereferencemanager", func() {
     			It("should reject if the referred Seed is not found", func() {
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupEntries.core.gardener.cloud %q is forbidden: seed.core.gardener.cloud %q not found", coreBackupEntry.Name, seed.Name)))
    @@ -1610,7 +1690,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupEntries.core.gardener.cloud %q is forbidden: backupbucket.core.gardener.cloud %q not found", coreBackupEntry.Name, coreBackupBucket.Name)))
    @@ -1621,102 +1701,13 @@ var _ = Describe("resourcereferencemanager", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().BackupBuckets().Informer().GetStore().Add(&backupBucket)).To(Succeed())
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     		})
     
     		Context("tests for Project objects", func() {
    -			It("should set the created-by field", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should set the owner field (member with owner role found)", func() {
    -				projectCopy := project.DeepCopy()
    -				coreProjectCopy := coreProject.DeepCopy()
    -				ownerMember := &rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     "owner",
    -				}
    -				projectCopy.Name = "foo"
    -				projectCopy.Spec.Members = []gardencorev1beta1.ProjectMember{
    -					{
    -						Subject: *ownerMember,
    -						Roles:   []string{core.ProjectMemberOwner},
    -					},
    -				}
    -				coreProjectCopy.Name = "foo"
    -				coreProjectCopy.Spec.Members = []core.ProjectMember{
    -					{
    -						Subject: *ownerMember,
    -						Roles:   []string{core.ProjectMemberOwner},
    -					},
    -				}
    -
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(projectCopy)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(coreProjectCopy, nil, core.Kind("Project").WithVersion("version"), coreProjectCopy.Namespace, coreProjectCopy.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProjectCopy.Spec.Owner).To(Equal(ownerMember))
    -				Expect(coreProjectCopy.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should set the owner field", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.Owner).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should add the owner to members", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.Members).To(ContainElement(Equal(core.ProjectMember{
    -					Subject: rbacv1.Subject{
    -						APIGroup: "rbac.authorization.k8s.io",
    -						Kind:     rbacv1.UserKind,
    -						Name:     defaultUserName,
    -					},
    -					Roles: []string{
    -						core.ProjectMemberAdmin,
    -						core.ProjectMemberOwner,
    -					},
    -				})))
    -			})
    -
     			It("should allow specifying a namespace which is not in use (create)", func() {
     				project.Spec.Namespace = ptr.To("garden-foo")
     				projectCopy := project.DeepCopy()
    @@ -1727,7 +1718,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1745,7 +1736,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, coreProjectOld, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1757,7 +1748,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1771,7 +1762,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{
     					"ErrStatus": MatchFields(IgnoreExtras, Fields{
    @@ -1793,7 +1784,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, &coreProjectOld, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{
     					"ErrStatus": MatchFields(IgnoreExtras, Fields{
    @@ -1835,7 +1826,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfile, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1856,7 +1847,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1876,7 +1867,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     				Expect(err.Error()).To(ContainSubstring("1.24.1"))
    @@ -1904,7 +1895,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(And(
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(And(
     					ContainSubstring("unable to delete Kubernetes version"),
     					ContainSubstring("1.24.1"),
     					ContainSubstring("still in use by NamespacedCloudProfile"),
    @@ -1935,7 +1926,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(And(
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(And(
     					ContainSubstring("unable to delete Kubernetes version"),
     					ContainSubstring("1.24.1"),
     					ContainSubstring("still in use by shoot '/shoot-Two'"),
    @@ -1964,7 +1955,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     			})
     
     			It("should accept removal of kubernetes versions that are used by shoots using another unrelated NamespacedCloudProfile of same name", func() {
    @@ -1987,7 +1978,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     			})
     
     			It("should accept removal of kubernetes version that is still in use by a shoot that is being deleted", func() {
    @@ -2009,7 +2000,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2105,7 +2096,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfile, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2148,7 +2139,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2192,7 +2183,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.Upd
    ... [truncated]
    
  • plugin/pkg/plugins.go+4 0 modified
    @@ -27,6 +27,8 @@ const (
     	PluginNameExtensionLabels = "ExtensionLabels"
     	// PluginNameExtensionValidator is the name of the ExtensionValidator admission plugin.
     	PluginNameExtensionValidator = "ExtensionValidator"
    +	// PluginNameFinalizerRemoval is the name of the FinalizerRemoval admission plugin.
    +	PluginNameFinalizerRemoval = "FinalizerRemoval"
     	// PluginNameResourceReferenceManager is the name of the ResourceReferenceManager admission plugin.
     	PluginNameResourceReferenceManager = "ResourceReferenceManager"
     	// PluginNameManagedSeedShoot is the name of the ManagedSeedShoot admission plugin.
    @@ -88,6 +90,7 @@ func AllPluginNames() []string {
     		PluginNameNamespacedCloudProfileValidator,   // NamespacedCloudProfileValidator
     		PluginNameProjectValidator,                  // ProjectValidator
     		PluginNameDeletionConfirmation,              // DeletionConfirmation
    +		PluginNameFinalizerRemoval,                  // FinalizerRemoval
     		PluginNameOpenIDConnectPreset,               // OpenIDConnectPreset
     		PluginNameClusterOpenIDConnectPreset,        // ClusterOpenIDConnectPreset
     		PluginNameCustomVerbAuthorizer,              // CustomVerbAuthorizer
    @@ -131,6 +134,7 @@ func DefaultOnPlugins() sets.Set[string] {
     		PluginNameNamespacedCloudProfileValidator, // NamespacedCloudProfileValidator
     		PluginNameProjectValidator,                // ProjectValidator
     		PluginNameDeletionConfirmation,            // DeletionConfirmation
    +		PluginNameFinalizerRemoval,                // FinalizerRemoval
     		PluginNameOpenIDConnectPreset,             // OpenIDConnectPreset
     		PluginNameClusterOpenIDConnectPreset,      // ClusterOpenIDConnectPreset
     		PluginNameCustomVerbAuthorizer,            // CustomVerbAuthorizer
    
  • plugin/pkg/project/validator/admission.go+59 4 modified
    @@ -8,15 +8,18 @@ import (
     	"context"
     	"fmt"
     	"io"
    +	"slices"
     	"strings"
     
    +	rbacv1 "k8s.io/api/rbac/v1"
     	apierrors "k8s.io/apimachinery/pkg/api/errors"
     	"k8s.io/apiserver/pkg/admission"
     
     	gardencore "github.com/gardener/gardener/pkg/apis/core"
     	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
     	gardenerutils "github.com/gardener/gardener/pkg/utils/gardener"
     	plugin "github.com/gardener/gardener/plugin/pkg"
    +	"github.com/gardener/gardener/plugin/pkg/utils"
     )
     
     // Register registers a plugin.
    @@ -33,13 +36,13 @@ type handler struct {
     // New creates a new handler admission plugin.
     func New() (*handler, error) {
     	return &handler{
    -		Handler: admission.NewHandler(admission.Create),
    +		Handler: admission.NewHandler(admission.Create, admission.Update),
     	}, nil
     }
     
    -var _ admission.ValidationInterface = &handler{}
    +var _ admission.MutationInterface = &handler{}
     
    -func (v *handler) Validate(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +func (v *handler) Admit(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
     	// Ignore all kinds other than Project
     	if a.GetKind().GroupKind() != gardencore.Kind("Project") {
     		return nil
    @@ -56,10 +59,62 @@ func (v *handler) Validate(_ context.Context, a admission.Attributes, _ admissio
     		return apierrors.NewBadRequest("could not convert object to Project")
     	}
     
    -	// TODO: Remove this admission plugin in favor of static validation in a future release, see https://github.com/gardener/gardener/pull/4228.
    +	// TODO: Remove this check in favor of static validation in a future release, see https://github.com/gardener/gardener/pull/4228.
     	if project.Spec.Namespace != nil && *project.Spec.Namespace != v1beta1constants.GardenNamespace && !strings.HasPrefix(*project.Spec.Namespace, gardenerutils.ProjectNamespacePrefix) {
     		return admission.NewForbidden(a, fmt.Errorf(".spec.namespace must start with %s", gardenerutils.ProjectNamespacePrefix))
     	}
     
    +	if utils.SkipVerification(a.GetOperation(), project.ObjectMeta) {
    +		return nil
    +	}
    +
    +	if a.GetOperation() == admission.Create {
    +		ensureProjectOwner(project, a.GetUserInfo().GetName())
    +	}
    +
    +	ensureOwnerIsMember(project)
    +
     	return nil
     }
    +
    +func ensureProjectOwner(project *gardencore.Project, userName string) {
    +	// Set createdBy field in Project
    +	project.Spec.CreatedBy = &rbacv1.Subject{
    +		APIGroup: "rbac.authorization.k8s.io",
    +		Kind:     rbacv1.UserKind,
    +		Name:     userName,
    +	}
    +
    +	if project.Spec.Owner == nil {
    +		project.Spec.Owner = func() *rbacv1.Subject {
    +			for _, member := range project.Spec.Members {
    +				for _, role := range member.Roles {
    +					if role == gardencore.ProjectMemberOwner {
    +						return member.Subject.DeepCopy()
    +					}
    +				}
    +			}
    +			return project.Spec.CreatedBy
    +		}()
    +	}
    +}
    +
    +func ensureOwnerIsMember(project *gardencore.Project) {
    +	if project.Spec.Owner == nil {
    +		return
    +	}
    +
    +	ownerIsMember := slices.ContainsFunc(project.Spec.Members, func(member gardencore.ProjectMember) bool {
    +		return member.Subject == *project.Spec.Owner
    +	})
    +
    +	if !ownerIsMember {
    +		project.Spec.Members = append(project.Spec.Members, gardencore.ProjectMember{
    +			Subject: *project.Spec.Owner,
    +			Roles: []string{
    +				gardencore.ProjectMemberAdmin,
    +				gardencore.ProjectMemberOwner,
    +			},
    +		})
    +	}
    +}
    
  • plugin/pkg/project/validator/admission_test.go+136 28 modified
    @@ -9,8 +9,10 @@ import (
     
     	. "github.com/onsi/ginkgo/v2"
     	. "github.com/onsi/gomega"
    +	rbacv1 "k8s.io/api/rbac/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/apiserver/pkg/authentication/user"
     	"k8s.io/utils/ptr"
     
     	"github.com/gardener/gardener/pkg/apis/core"
    @@ -23,7 +25,8 @@ var _ = Describe("Admission", func() {
     		var (
     			err              error
     			project          core.Project
    -			admissionHandler admission.ValidationInterface
    +			admissionHandler admission.MutationInterface
    +			attrs            admission.Attributes
     
     			namespaceName = "garden-my-project"
     			projectName   = "my-project"
    @@ -33,43 +36,148 @@ var _ = Describe("Admission", func() {
     					Namespace: namespaceName,
     				},
     			}
    +
    +			userInfo user.Info
     		)
     
     		BeforeEach(func() {
     			admissionHandler, err = New()
     			Expect(err).NotTo(HaveOccurred())
     
     			project = projectBase
    -		})
    -
    -		It("should allow creating the project (namespace nil)", func() {
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
     
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    +			userInfo = &user.DefaultInfo{Name: "foo"}
     		})
     
    -		It("should allow creating the project(namespace non-nil)", func() {
    -			project.Spec.Namespace = &namespaceName
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    +		When("project is created", func() {
    +			BeforeEach(func() {
    +				attrs = admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +			})
    +
    +			It("should allow creating the project (namespace nil)", func() {
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should allow creating the project(namespace non-nil)", func() {
    +				project.Spec.Namespace = &namespaceName
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should allow creating the project (namespace is 'garden')", func() {
    +				project.Spec.Namespace = ptr.To(v1beta1constants.GardenNamespace)
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should prevent creating the project because namespace prefix is missing", func() {
    +				project.Spec.Namespace = ptr.To("foo")
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(".spec.namespace must start with garden-")))
    +			})
    +
    +			It("should maintain createdBy and project owner", func() {
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     userInfo.GetName(),
    +				}))
    +
    +				Expect(project.Spec.Owner).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     userInfo.GetName(),
    +				}))
    +
    +				Expect(project.Spec.Members).To(ConsistOf(core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     userInfo.GetName(),
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}))
    +			})
    +
    +			It("should not overwrite project owner", func() {
    +				project.Spec.Owner = &rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     "bar",
    +				}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Owner).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     "bar",
    +				}))
    +			})
     		})
     
    -		It("should allow creating the project (namespace is 'garden')", func() {
    -			project.Spec.Namespace = ptr.To(v1beta1constants.GardenNamespace)
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    -		})
    -
    -		It("should prevent creating the project because namespace prefix is missing", func() {
    -			project.Spec.Namespace = ptr.To("foo")
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(".spec.namespace must start with garden-")))
    +		When("project is updated", func() {
    +			BeforeEach(func() {
    +				attrs = admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, userInfo)
    +			})
    +
    +			It("should add project owner to members", func() {
    +				projectOwner := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "foo",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}
    +
    +				projectMemberBar := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "bar",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberViewer,
    +					},
    +				}
    +
    +				project.Spec.Owner = &projectOwner.Subject
    +				project.Spec.Members = []core.ProjectMember{projectMemberBar}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Members).To(ConsistOf(projectMemberBar, projectOwner))
    +			})
    +
    +			It("should not re-add owner as member", func() {
    +				projectOwner := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "foo",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}
    +
    +				project.Spec.Owner = &projectOwner.Subject
    +				project.Spec.Members = []core.ProjectMember{projectOwner}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Members).To(ConsistOf(projectOwner))
    +			})
     		})
     	})
     
    @@ -85,11 +193,11 @@ var _ = Describe("Admission", func() {
     	})
     
     	Describe("#New", func() {
    -		It("should only handle CREATE operations", func() {
    +		It("should handle CREATE and UPDATE operations", func() {
     			dr, err := New()
     			Expect(err).ToNot(HaveOccurred())
     			Expect(dr.Handles(admission.Create)).To(BeTrue())
    -			Expect(dr.Handles(admission.Update)).To(BeFalse())
    +			Expect(dr.Handles(admission.Update)).To(BeTrue())
     			Expect(dr.Handles(admission.Connect)).To(BeFalse())
     			Expect(dr.Handles(admission.Delete)).To(BeFalse())
     		})
    
  • plugin/pkg/shoot/validator/admission.go+17 19 modified
    @@ -252,15 +252,19 @@ func (v *ValidateShoot) Admit(ctx context.Context, a admission.Attributes, _ adm
     		}
     	}
     
    +	if a.GetOperation() == admission.Create {
    +		addCreatedByAnnotation(shoot, a.GetUserInfo().GetName())
    +
    +		if len(ptr.Deref(shoot.Spec.CloudProfileName, "")) > 0 && shoot.Spec.CloudProfile != nil {
    +			return fmt.Errorf("new shoot can only specify either cloudProfileName or cloudProfile reference")
    +		}
    +	}
    +
     	cloudProfileSpec, err := admissionutils.GetCloudProfileSpec(v.cloudProfileLister, v.namespacedCloudProfileLister, shoot)
     	if err != nil {
     		return apierrors.NewInternalError(fmt.Errorf("could not find referenced cloud profile: %+v", err.Error()))
     	}
     
    -	if a.GetOperation() == admission.Create && len(ptr.Deref(shoot.Spec.CloudProfileName, "")) > 0 && shoot.Spec.CloudProfile != nil {
    -		return fmt.Errorf("new shoot can only specify either cloudProfileName or cloudProfile reference")
    -	}
    -
     	if err := admissionutils.ValidateCloudProfileChanges(v.cloudProfileLister, v.namespacedCloudProfileLister, shoot, oldShoot); err != nil {
     		return err
     	}
    @@ -609,21 +613,6 @@ func (c *validationContext) validateDeletion(a admission.Attributes) error {
     		}
     	}
     
    -	// Allow removal of `gardener` finalizer only if the Shoot deletion has completed successfully
    -	if len(c.shoot.Status.TechnicalID) > 0 && c.shoot.Status.LastOperation != nil {
    -		oldFinalizers := sets.New(c.oldShoot.Finalizers...)
    -		newFinalizers := sets.New(c.shoot.Finalizers...)
    -
    -		if oldFinalizers.Has(core.GardenerName) && !newFinalizers.Has(core.GardenerName) {
    -			lastOperation := c.shoot.Status.LastOperation
    -			deletionSucceeded := lastOperation.Type == core.LastOperationTypeDelete && lastOperation.State == core.LastOperationStateSucceeded && lastOperation.Progress == 100
    -
    -			if !deletionSucceeded {
    -				return admission.NewForbidden(a, fmt.Errorf("finalizer %q cannot be removed because shoot deletion has not completed successfully yet", core.GardenerName))
    -			}
    -		}
    -	}
    -
     	return nil
     }
     
    @@ -2172,3 +2161,12 @@ func validateMaxNodesTotal(workers []core.Worker, maxNodesTotal int32) field.Err
     
     	return allErrs
     }
    +
    +func addCreatedByAnnotation(shoot *core.Shoot, userName string) {
    +	annotations := shoot.Annotations
    +	if annotations == nil {
    +		annotations = map[string]string{}
    +	}
    +	annotations[v1beta1constants.GardenCreatedBy] = userName
    +	shoot.Annotations = annotations
    +}
    
  • plugin/pkg/shoot/validator/admission_test.go+50 96 modified
    @@ -18,7 +18,6 @@ import (
     	"k8s.io/apimachinery/pkg/api/resource"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/runtime"
    -	"k8s.io/apimachinery/pkg/util/sets"
     	"k8s.io/apiserver/pkg/admission"
     	"k8s.io/apiserver/pkg/authentication/user"
     	"k8s.io/apiserver/pkg/authorization/authorizer"
    @@ -423,106 +422,61 @@ var _ = Describe("validator", func() {
     			})
     		})
     
    -		Context("shoot with generate name", func() {
    +		Context("shoot creation", func() {
     			BeforeEach(func() {
    -				shoot.ObjectMeta = metav1.ObjectMeta{
    -					GenerateName: "demo-",
    -					Namespace:    namespaceName,
    -				}
    -			})
    -
    -			It("should admit Shoot resources", func() {
     				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    -
    -				authorizeAttributes.Name = shoot.Name
    -
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
     			})
     
    -			It("should reject Shoot resources with not fulfilling the length constraints", func() {
    -				tooLongName := "too-long-namespace"
    -				project.ObjectMeta = metav1.ObjectMeta{
    -					Name: tooLongName,
    -				}
    -				shoot.ObjectMeta = metav1.ObjectMeta{
    -					GenerateName: "too-long-name",
    -					Namespace:    namespaceName,
    -				}
    -
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    -
    -				authorizeAttributes.Name = shoot.Name
    +			Context("with generate name", func() {
    +				BeforeEach(func() {
    +					shoot.ObjectMeta = metav1.ObjectMeta{
    +						GenerateName: "demo-",
    +						Namespace:    namespaceName,
    +					}
    +				})
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    +				It("should admit Shoot resources", func() {
    +					authorizeAttributes.Name = shoot.Name
     
    -				Expect(err).To(BeInvalidError())
    -				Expect(err.Error()).To(ContainSubstring("name must not exceed"))
    -			})
    -		})
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +					err := admissionHandler.Admit(ctx, attrs, nil)
     
    -		Context("finalizer removal checks", func() {
    -			var (
    -				oldShoot *core.Shoot
    -			)
    +					Expect(err).NotTo(HaveOccurred())
    +				})
     
    -			BeforeEach(func() {
    -				shoot = *shootBase.DeepCopy()
    +				It("should reject Shoot resources with not fulfilling the length constraints", func() {
    +					tooLongName := "too-long-namespace"
    +					project.ObjectMeta = metav1.ObjectMeta{
    +						Name: tooLongName,
    +					}
    +					shoot.ObjectMeta = metav1.ObjectMeta{
    +						GenerateName: "too-long-name",
    +						Namespace:    namespaceName,
    +					}
     
    -				shoot.Status.TechnicalID = "some-id"
    -				shoot.Status.LastOperation = &core.LastOperation{
    -					Type:     core.LastOperationTypeReconcile,
    -					State:    core.LastOperationStateSucceeded,
    -					Progress: 100,
    -				}
    +					Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
     
    -				// set old shoot for update and add gardener finalizer to it
    -				oldShoot = shoot.DeepCopy()
    -				finalizers := sets.New(oldShoot.GetFinalizers()...)
    -				finalizers.Insert(core.GardenerName)
    -				oldShoot.SetFinalizers(finalizers.UnsortedList())
    -			})
    +					authorizeAttributes.Name = shoot.Name
     
    -			It("should reject removing the gardener finalizer if the shoot has not yet been deleted successfully", func() {
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +					err := admissionHandler.Admit(ctx, attrs, nil)
     
    -				attrs := admission.NewAttributesRecord(&shoot, oldShoot, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -				Expect(err).To(HaveOccurred())
    -				Expect(err.Error()).To(ContainSubstring("shoot deletion has not completed successfully yet"))
    +					Expect(err).To(BeInvalidError())
    +					Expect(err.Error()).To(ContainSubstring("name must not exceed"))
    +				})
     			})
     
    -			It("should admit removing the gardener finalizer if the shoot deletion succeeded ", func() {
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    +			It("should add the created-by annotation", func() {
    +				Expect(shoot.Annotations).NotTo(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, userInfo.Name))
     
    -				shoot.Status.LastOperation = &core.LastOperation{
    -					Type:     core.LastOperationTypeDelete,
    -					State:    core.LastOperationStateSucceeded,
    -					Progress: 100,
    -				}
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
     
    -				attrs := admission.NewAttributesRecord(&shoot, oldShoot, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -				Expect(err).ToNot(HaveOccurred())
    +				Expect(shoot.Annotations).To(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, userInfo.Name))
     			})
     		})
     
    @@ -1706,7 +1660,7 @@ var _ = Describe("validator", func() {
     				shoot.Spec.SeedName = nil
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
    @@ -1719,7 +1673,7 @@ var _ = Describe("validator", func() {
     
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
    @@ -1735,7 +1689,7 @@ var _ = Describe("validator", func() {
     
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
    @@ -2013,23 +1967,23 @@ var _ = Describe("validator", func() {
     						})
     
     						It("should allow scheduling non-HA shoot", func() {
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'node'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeNode}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should reject scheduling HA shoot with failure tolerance type 'zone'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeZone}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(BeForbiddenError())
     						})
     					})
    @@ -2040,23 +1994,23 @@ var _ = Describe("validator", func() {
     						})
     
     						It("should allow scheduling non-HA shoot", func() {
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'node'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeNode}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'zone'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeZone}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     					})
    @@ -2270,7 +2224,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).NotTo(HaveOccurred())
    @@ -2290,7 +2244,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2320,7 +2274,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2351,7 +2305,7 @@ var _ = Describe("validator", func() {
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     						Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2383,7 +2337,7 @@ var _ = Describe("validator", func() {
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     						Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).NotTo(HaveOccurred())
    @@ -2408,7 +2362,7 @@ var _ = Describe("validator", func() {
     					Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     					Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     					err := admissionHandler.Admit(ctx, attrs, nil)
     
     					Expect(err).To(errorMatcher)
    
  • skaffold-operator.yaml+2 0 modified
    @@ -605,6 +605,7 @@ build:
                 - plugin/pkg/global/deletionconfirmation
                 - plugin/pkg/global/extensionlabels
                 - plugin/pkg/global/extensionvalidation
    +            - plugin/pkg/global/finalizerremoval
                 - plugin/pkg/global/resourcereferencemanager
                 - plugin/pkg/managedseed/shoot
                 - plugin/pkg/managedseed/validator
    @@ -852,6 +853,7 @@ build:
                 - pkg/admissioncontroller/webhook/admission/internaldomainsecret
                 - pkg/admissioncontroller/webhook/admission/kubeconfigsecret
                 - pkg/admissioncontroller/webhook/admission/namespacedeletion
    +            - pkg/admissioncontroller/webhook/admission/providersecretlabels
                 - pkg/admissioncontroller/webhook/admission/resourcesize
                 - pkg/admissioncontroller/webhook/admission/seedrestriction
                 - pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref
    
  • skaffold.yaml+2 0 modified
    @@ -202,6 +202,7 @@ build:
                 - plugin/pkg/global/deletionconfirmation
                 - plugin/pkg/global/extensionlabels
                 - plugin/pkg/global/extensionvalidation
    +            - plugin/pkg/global/finalizerremoval
                 - plugin/pkg/global/resourcereferencemanager
                 - plugin/pkg/managedseed/shoot
                 - plugin/pkg/managedseed/validator
    @@ -449,6 +450,7 @@ build:
                 - pkg/admissioncontroller/webhook/admission/internaldomainsecret
                 - pkg/admissioncontroller/webhook/admission/kubeconfigsecret
                 - pkg/admissioncontroller/webhook/admission/namespacedeletion
    +            - pkg/admissioncontroller/webhook/admission/providersecretlabels
                 - pkg/admissioncontroller/webhook/admission/resourcesize
                 - pkg/admissioncontroller/webhook/admission/seedrestriction
                 - pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref
    
  • test/integration/envtest/environment_test.go+1 1 modified
    @@ -44,6 +44,6 @@ var _ = Describe("GardenerTestEnvironment", func() {
     
     	It("should be able to manipulate resource from security.gardener.cloud/v1alpha1", func() {
     		credentialsBinding := &securityv1alpha1.CredentialsBinding{ObjectMeta: metav1.ObjectMeta{GenerateName: "test-", Namespace: testNamespace.Name}}
    -		Expect(testClient.Create(ctx, credentialsBinding)).To(MatchError(ContainSubstring("credentialsbindings.security.gardener.cloud \"test-\" is forbidden")))
    +		Expect(testClient.Create(ctx, credentialsBinding)).To(MatchError(MatchRegexp("CredentialsBinding.security.gardener.cloud \"test-.+\" is invalid")))
     	})
     })
    
924b1575aae0

[release-v1.116] Ensure extension admission webhooks validated `WorkloadIdentity`s and `Secret`s when used (#12077)

https://github.com/gardener/gardenerGardener Prow RobotMay 13, 2025via ghsa
55 files changed · +1700 493
  • charts/gardener/controlplane/charts/application/templates/mutatingwebhook-admission-controller.yaml+29 1 modified
    @@ -3,5 +3,33 @@ apiVersion: admissionregistration.k8s.io/v1
     kind: MutatingWebhookConfiguration
     metadata:
       name: gardener-admission-controller
    -webhooks: []
    +webhooks:
    +- name: sync-provider-secret-labels.gardener.cloud
    +  admissionReviewVersions: ["v1", "v1beta1"]
    +  timeoutSeconds: 10
    +  rules:
    +  - apiGroups:
    +    - ""
    +    apiVersions:
    +    - v1
    +    operations:
    +    - CREATE
    +    - UPDATE
    +    resources:
    +    - secrets
    +  failurePolicy: Fail
    +  namespaceSelector:
    +    matchExpressions:
    +    - {key: gardener.cloud/role, operator: In, values: [project]}
    +  clientConfig:
    +    {{- if .Values.global.deployment.virtualGarden.enabled }}
    +    url: https://gardener-admission-controller.garden/webhooks/sync-provider-secret-labels
    +    {{- else }}
    +    service:
    +      namespace: garden
    +      name: gardener-admission-controller
    +      path: /webhooks/sync-provider-secret-labels
    +    {{- end }}
    +    caBundle: {{ required ".Values.global.admission.config.server.webhooks.tls.caBundle is required" (b64enc .Values.global.admission.config.server.webhooks.tls.caBundle) }}
    +  sideEffects: None
     {{- end }}
    
  • cmd/gardener-extension-admission-local/app/app.go+4 2 modified
    @@ -25,8 +25,9 @@ import (
     	extensionscmdcontroller "github.com/gardener/gardener/extensions/pkg/controller/cmd"
     	"github.com/gardener/gardener/extensions/pkg/util"
     	extensionscmdwebhook "github.com/gardener/gardener/extensions/pkg/webhook/cmd"
    -	"github.com/gardener/gardener/pkg/apis/core/install"
    +	gardencoreinstall "github.com/gardener/gardener/pkg/apis/core/install"
     	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
    +	securityinstall "github.com/gardener/gardener/pkg/apis/security/install"
     	gardenerhealthz "github.com/gardener/gardener/pkg/healthz"
     	admissioncmd "github.com/gardener/gardener/pkg/provider-local/admission/cmd"
     	localinstall "github.com/gardener/gardener/pkg/provider-local/apis/local/install"
    @@ -122,7 +123,8 @@ func NewAdmissionCommand(ctx context.Context) *cobra.Command {
     				return fmt.Errorf("could not instantiate manager: %w", err)
     			}
     
    -			install.Install(mgr.GetScheme())
    +			gardencoreinstall.Install(mgr.GetScheme())
    +			securityinstall.Install(mgr.GetScheme())
     
     			if err := localinstall.AddToScheme(mgr.GetScheme()); err != nil {
     				return fmt.Errorf("could not update manager scheme: %w", err)
    
  • docs/concepts/apiserver-admission-plugins.md+17 6 modified
    @@ -74,14 +74,27 @@ _(enabled by default)_
     
     This admission controller reacts on `CREATE` and `UPDATE` operations for `BackupBucket`s, `BackupEntry`s, `CloudProfile`s, `NamespacedCloudProfile`s, `Seed`s, `SecretBinding`s, `CredentialsBinding`s, `WorkloadIdentity`s and `Shoot`s. For all the various extension types in the specifications of these objects, it adds a corresponding label in the resource. This would allow extension admission webhooks to filter out the resources they are responsible for and ignore all others. This label is of the form `<extension-type>.extensions.gardener.cloud/<extension-name> : "true"`. For example, an extension label for provider extension type `aws`, looks like `provider.extensions.gardener.cloud/aws : "true"`.
     
    +## `FinalizerRemoval`
    +
    +_(enabled by default)_
    +
    +This admission controller reacts on `UPDATE` operations for `CredentialsBinding`s, `SecretBinding`s, `Shoot`s. 
    +It ensures that the finalizers of these resources are not removed by users, as long as the affected resource is still in use.
    +For `CredentialsBinding`s and `SecretBinding`s this means, that the `gardener` finalizer can only be removed if the binding is not referenced by any `Shoot`.
    +In case of `Shoot`s, the `gardener` finalizer can only be removed if the last operation of the `Shoot` indicates a successful deletion. 
    +
     ## `ProjectValidator`
     
     _(enabled by default)_
     
    -This admission controller reacts on `CREATE` operations for `Project`s.
    +This admission controller reacts on `CREATE` and `UPDATE` operations for `Project`s.
     It prevents creating `Project`s with a non-empty `.spec.namespace` if the value in `.spec.namespace` does not start with `garden-`.
     
    -⚠️ This admission plugin will be removed in a future release and its business logic will be incorporated into the static validation of the `gardener-apiserver`.
    +In addition, the project specification is initialized during creation:
    +- `.spec.createdBy` is set to the user creating the project.
    +- `.spec.owner` defaults to the value of `.spec.createdBy` if it is not specified.
    +
    +During subsequent updates, it ensures that the project owner is included in the `.spec.members` list.
     
     ## `ResourceQuota`
     
    @@ -96,11 +109,9 @@ _(enabled by default)_
     
     This admission controller reacts on `CREATE` and `UPDATE` operations for `CloudProfile`s, `Project`s, `SecretBinding`s, `Seed`s, and `Shoot`s.
     Generally, it checks whether referred resources stated in the specifications of these objects exist in the system (e.g., if a referenced `Secret` exists).
    -However, it also has some special behaviours for certain resources:
     
    +However, it also has some special behaviours for certain resources:
     * `CloudProfile`s: It rejects removing Kubernetes or machine image versions if there is at least one `Shoot` that refers to them.
    -* `Project`s: It sets the `.spec.createdBy` field for newly created `Project` resources, and defaults the `.spec.owner` field in case it is empty (to the same value of `.spec.createdBy`).
    -* `Shoot`s: It sets the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.
     
     ## `SeedValidator`
     
    @@ -185,7 +196,7 @@ _(enabled by default)_
     This admission controller reacts on `CREATE`, `UPDATE` and `DELETE` operations for `Shoot`s.
     It validates certain configurations in the specification against the referred `CloudProfile` (e.g., machine images, machine types, used Kubernetes version, ...).
     Generally, it performs validations that cannot be handled by the static API validation due to their dynamic nature (e.g., when something needs to be checked against referred resources).
    -Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version).
    +Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version) and setting the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.
     
     ## `ShootManagedSeed`
     
    
  • pkg/admissioncontroller/webhook/add.go+8 0 modified
    @@ -18,6 +18,7 @@ import (
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/internaldomainsecret"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/kubeconfigsecret"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/namespacedeletion"
    +	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/providersecretlabels"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/resourcesize"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/seedrestriction"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref"
    @@ -65,6 +66,13 @@ func AddToManager(
     		return fmt.Errorf("failed adding %s webhook handler: %w", namespacedeletion.HandlerName, err)
     	}
     
    +	if err := (&providersecretlabels.Handler{
    +		Logger: mgr.GetLogger().WithName("webhook").WithName(providersecretlabels.HandlerName),
    +		Client: mgr.GetClient(),
    +	}).AddToManager(mgr); err != nil {
    +		return fmt.Errorf("failed adding %s webhook handler: %w", providersecretlabels.HandlerName, err)
    +	}
    +
     	if err := (&resourcesize.Handler{
     		Logger: mgr.GetLogger().WithName("webhook").WithName(resourcesize.HandlerName),
     		Config: cfg.Server.ResourceAdmissionConfiguration,
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/add.go+28 0 added
    @@ -0,0 +1,28 @@
    +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels
    +
    +import (
    +	corev1 "k8s.io/api/core/v1"
    +	"sigs.k8s.io/controller-runtime/pkg/manager"
    +	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    +)
    +
    +const (
    +	// HandlerName is the name of this admission webhook handler.
    +	HandlerName = "sync-provider-secret-labels"
    +	// WebhookPath is the HTTP handler path for this admission webhook handler.
    +	WebhookPath = "/webhooks/sync-provider-secret-labels"
    +)
    +
    +// AddToManager adds Handler to the given manager.
    +func (h *Handler) AddToManager(mgr manager.Manager) error {
    +	webhook := admission.
    +		WithCustomDefaulter(mgr.GetScheme(), &corev1.Secret{}, h).
    +		WithRecoverPanic(true)
    +
    +	mgr.GetWebhookServer().Register(WebhookPath, webhook)
    +	return nil
    +}
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/handler.go+99 0 added
    @@ -0,0 +1,99 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels
    +
    +import (
    +	"context"
    +	"fmt"
    +	"strings"
    +
    +	"github.com/go-logr/logr"
    +	corev1 "k8s.io/api/core/v1"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/runtime"
    +	"k8s.io/apimachinery/pkg/util/sets"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
    +	v1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +)
    +
    +// Handler syncs the provider labels on Secrets referenced in SecretBindings or CredentialsBindings.
    +type Handler struct {
    +	Logger logr.Logger
    +	Client client.Client
    +}
    +
    +// Default syncs the provider labels.
    +func (h *Handler) Default(ctx context.Context, obj runtime.Object) error {
    +	secret, ok := obj.(*corev1.Secret)
    +	if !ok {
    +		return fmt.Errorf("expected secret but got %T", obj)
    +	}
    +
    +	typesFromSecretBindings, err := h.fetchProviderTypesFromSecretBindings(ctx, secret)
    +	if err != nil {
    +		return fmt.Errorf("failed fetching provider types from SecretBindings: %w", err)
    +	}
    +
    +	typesFromCredentialsBindings, err := h.fetchProviderTypesFromCredentialsBindings(ctx, secret)
    +	if err != nil {
    +		return fmt.Errorf("failed fetching provider types from CredentialsBindings: %w", err)
    +	}
    +
    +	if typesFromSecretBindings.Len()+typesFromCredentialsBindings.Len() > 0 {
    +		maintainLabels(secret, typesFromSecretBindings.Union(typesFromCredentialsBindings).UnsortedList()...)
    +	}
    +
    +	return nil
    +}
    +
    +func (h *Handler) fetchProviderTypesFromSecretBindings(ctx context.Context, secret *corev1.Secret) (sets.Set[string], error) {
    +	secretBindingList := &gardencorev1beta1.SecretBindingList{}
    +	if err := h.Client.List(ctx, secretBindingList); err != nil {
    +		return nil, fmt.Errorf("failed to list SecretBindings: %w", err)
    +	}
    +
    +	providerTypes := sets.New[string]()
    +	for _, secretBinding := range secretBindingList.Items {
    +		if secretBinding.SecretRef.Name == secret.Name &&
    +			secretBinding.SecretRef.Namespace == secret.Namespace {
    +			providerTypes.Insert(v1beta1helper.GetSecretBindingTypes(&secretBinding)...)
    +		}
    +	}
    +	return providerTypes, nil
    +}
    +
    +func (h *Handler) fetchProviderTypesFromCredentialsBindings(ctx context.Context, secret *corev1.Secret) (sets.Set[string], error) {
    +	credentialsBindingList := &securityv1alpha1.CredentialsBindingList{}
    +	if err := h.Client.List(ctx, credentialsBindingList); err != nil {
    +		return nil, fmt.Errorf("failed to list CredentialsBindings: %w", err)
    +	}
    +
    +	providerTypes := sets.New[string]()
    +	for _, credentialsBinding := range credentialsBindingList.Items {
    +		if credentialsBinding.CredentialsRef.APIVersion == corev1.SchemeGroupVersion.String() &&
    +			credentialsBinding.CredentialsRef.Kind == "Secret" &&
    +			credentialsBinding.CredentialsRef.Name == secret.Name &&
    +			credentialsBinding.CredentialsRef.Namespace == secret.Namespace {
    +			providerTypes.Insert(credentialsBinding.Provider.Type)
    +		}
    +	}
    +	return providerTypes, nil
    +}
    +
    +func maintainLabels(secret *corev1.Secret, providerTypes ...string) {
    +	for k := range secret.Labels {
    +		if strings.HasPrefix(k, v1beta1constants.LabelShootProviderPrefix) {
    +			delete(secret.Labels, k)
    +		}
    +	}
    +
    +	for _, providerType := range providerTypes {
    +		metav1.SetMetaDataLabel(&secret.ObjectMeta, v1beta1constants.LabelShootProviderPrefix+providerType, "true")
    +	}
    +}
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/handler_test.go+138 0 added
    @@ -0,0 +1,138 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels_test
    +
    +import (
    +	"context"
    +
    +	"github.com/go-logr/logr"
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	corev1 "k8s.io/api/core/v1"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    +	logzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
    +
    +	. "github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/providersecretlabels"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +	"github.com/gardener/gardener/pkg/client/kubernetes"
    +	"github.com/gardener/gardener/pkg/logger"
    +)
    +
    +var _ = Describe("handler", func() {
    +	var (
    +		ctx context.Context
    +
    +		log        logr.Logger
    +		fakeClient client.Client
    +		handler    *Handler
    +
    +		namespace            string
    +		provider1, provider2 string
    +		secret               *corev1.Secret
    +		secretBinding        *gardencorev1beta1.SecretBinding
    +		credentialsBinding   *securityv1alpha1.CredentialsBinding
    +	)
    +
    +	BeforeEach(func() {
    +		ctx = context.Background()
    +		log = logger.MustNewZapLogger(logger.DebugLevel, logger.FormatJSON, logzap.WriteTo(GinkgoWriter))
    +
    +		fakeClient = fakeclient.NewClientBuilder().WithScheme(kubernetes.GardenScheme).Build()
    +		handler = &Handler{
    +			Logger: log,
    +			Client: fakeClient,
    +		}
    +
    +		namespace = "test"
    +		secret = &corev1.Secret{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-secret",
    +				Namespace: namespace,
    +			},
    +		}
    +
    +		provider1 = "provider1"
    +		provider2 = "provider2"
    +
    +		secretBinding = &gardencorev1beta1.SecretBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-secret-binding",
    +				Namespace: namespace,
    +			},
    +			SecretRef: corev1.SecretReference{
    +				Name:      "test-secret",
    +				Namespace: namespace,
    +			},
    +			Provider: &gardencorev1beta1.SecretBindingProvider{
    +				Type: provider1,
    +			},
    +		}
    +
    +		credentialsBinding = &securityv1alpha1.CredentialsBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-credentials-binding",
    +				Namespace: "another-namespace",
    +			},
    +			CredentialsRef: corev1.ObjectReference{
    +				APIVersion: corev1.SchemeGroupVersion.String(),
    +				Kind:       "Secret",
    +				Name:       "test-secret",
    +				Namespace:  namespace,
    +			},
    +			Provider: securityv1alpha1.CredentialsBindingProvider{
    +				Type: provider2,
    +			},
    +		}
    +	})
    +
    +	It("should set the provider label based on the available credential and secret bindings", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +	})
    +
    +	It("should remove undesired provider type", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +		secret.Labels = map[string]string{
    +			"provider.shoot.gardener.cloud/provider1": "true",
    +			"provider.shoot.gardener.cloud/provider2": "true",
    +			"provider.shoot.gardener.cloud/provider3": "true",
    +		}
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +		Expect(secret.Labels).NotTo(HaveKey("provider.shoot.gardener.cloud/provider3"))
    +	})
    +
    +	It("should add the missing provider and delete the wrong one", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +		secret.Labels = map[string]string{
    +			"provider.shoot.gardener.cloud/provider1": "true",
    +			"provider.shoot.gardener.cloud/provider3": "true",
    +		}
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +		Expect(secret.Labels).NotTo(HaveKey("provider.shoot.gardener.cloud/provider3"))
    +	})
    +
    +	It("should not add provider labels when secret is unreferenced", func() {
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +		Expect(secret.Labels).To(BeEmpty())
    +	})
    +})
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/providersecretlabels_suite_test.go+17 0 added
    @@ -0,0 +1,17 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +)
    +
    +func TestProviderSecretLabels(t *testing.T) {
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "AdmissionController Webhook Admission ProviderSecretLabels Suite")
    +}
    
  • pkg/apis/core/helper/secretbinding.go+3 0 modified
    @@ -12,5 +12,8 @@ import (
     
     // GetSecretBindingTypes returns the SecretBinding provider types.
     func GetSecretBindingTypes(secretBinding *core.SecretBinding) []string {
    +	if secretBinding.Provider == nil {
    +		return []string{}
    +	}
     	return strings.Split(secretBinding.Provider.Type, ",")
     }
    
  • pkg/apis/core/helper/secretbinding_test.go+1 0 modified
    @@ -19,6 +19,7 @@ var _ = Describe("Helper", func() {
     			Expect(actual).To(Equal(expected))
     		},
     
    +		Entry("with nil provider type", &core.SecretBinding{Provider: nil}, []string{}),
     		Entry("with single-value provider type", &core.SecretBinding{Provider: &core.SecretBindingProvider{Type: "foo"}}, []string{"foo"}),
     		Entry("with multi-value provider type", &core.SecretBinding{Provider: &core.SecretBindingProvider{Type: "foo,bar,baz"}}, []string{"foo", "bar", "baz"}),
     	)
    
  • pkg/apis/core/v1beta1/helper/secretbinding.go+3 0 modified
    @@ -44,5 +44,8 @@ func AddTypeToSecretBinding(secretBinding *gardencorev1beta1.SecretBinding, prov
     
     // GetSecretBindingTypes returns the SecretBinding provider types.
     func GetSecretBindingTypes(secretBinding *gardencorev1beta1.SecretBinding) []string {
    +	if secretBinding.Provider == nil {
    +		return []string{}
    +	}
     	return strings.Split(secretBinding.Provider.Type, ",")
     }
    
  • pkg/apis/core/v1beta1/helper/secretbinding_test.go+1 0 modified
    @@ -45,6 +45,7 @@ var _ = Describe("Helper", func() {
     			Expect(actual).To(Equal(expected))
     		},
     
    +		Entry("with nil provider type", &gardencorev1beta1.SecretBinding{Provider: nil}, []string{}),
     		Entry("with single-value provider type", &gardencorev1beta1.SecretBinding{Provider: &gardencorev1beta1.SecretBindingProvider{Type: "foo"}}, []string{"foo"}),
     		Entry("with multi-value provider type", &gardencorev1beta1.SecretBinding{Provider: &gardencorev1beta1.SecretBindingProvider{Type: "foo,bar,baz"}}, []string{"foo", "bar", "baz"}),
     	)
    
  • pkg/apiserver/plugins.go+2 0 modified
    @@ -14,6 +14,7 @@ import (
     	"github.com/gardener/gardener/plugin/pkg/global/deletionconfirmation"
     	"github.com/gardener/gardener/plugin/pkg/global/extensionlabels"
     	"github.com/gardener/gardener/plugin/pkg/global/extensionvalidation"
    +	"github.com/gardener/gardener/plugin/pkg/global/finalizerremoval"
     	"github.com/gardener/gardener/plugin/pkg/global/resourcereferencemanager"
     	managedseedshoot "github.com/gardener/gardener/plugin/pkg/managedseed/shoot"
     	managedseedvalidator "github.com/gardener/gardener/plugin/pkg/managedseed/validator"
    @@ -39,6 +40,7 @@ import (
     func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
     	resourcereferencemanager.Register(plugins)
     	deletionconfirmation.Register(plugins)
    +	finalizerremoval.Register(plugins)
     	extensionvalidation.Register(plugins)
     	extensionlabels.Register(plugins)
     	shoottolerationrestriction.Register(plugins)
    
  • pkg/apiserver/registry/core/backupbucket/backupbucket_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBackupBucket(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core BackupBucket Suite")
    +	RunSpecs(t, "APIServer Registry Core BackupBucket Suite")
     }
    
  • pkg/apiserver/registry/core/backupentry/backupentry_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBackupEntry(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core BackupEntry Suite")
    +	RunSpecs(t, "APIServer Registry Core BackupEntry Suite")
     }
    
  • pkg/apiserver/registry/core/cloudprofile/cloudprofile_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestCloudProfile(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core CloudProfile Suite")
    +	RunSpecs(t, "APIServer Registry Core CloudProfile Suite")
     }
    
  • pkg/apiserver/registry/core/controllerinstallation/strategy_test.go+1 1 modified
    @@ -20,7 +20,7 @@ import (
     
     func TestControllerInstallation(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry ControllerInstallation Suite")
    +	RunSpecs(t, "APIServer Registry ControllerInstallation Suite")
     }
     
     var _ = Describe("ToSelectableFields", func() {
    
  • pkg/apiserver/registry/core/namespacedcloudprofile/namespacedcloudprofile_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestNamespacedCloudProfile(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core NamespacedCloudProfile Suite")
    +	RunSpecs(t, "APIServer Registry Core NamespacedCloudProfile Suite")
     }
    
  • pkg/apiserver/registry/core/project/project_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestProject(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Project Suite")
    +	RunSpecs(t, "APIServer Registry Core Project Suite")
     }
    
  • pkg/apiserver/registry/core/secretbinding/secretbinding_suite_test.go+1 1 modified
    @@ -16,5 +16,5 @@ import (
     func TestSecretBinding(t *testing.T) {
     	features.RegisterFeatureGates()
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core SecretBinding Suite")
    +	RunSpecs(t, "APIServer Registry Core SecretBinding Suite")
     }
    
  • pkg/apiserver/registry/core/secretbinding/strategy.go+6 1 modified
    @@ -28,7 +28,12 @@ func (secretBindingStrategy) NamespaceScoped() bool {
     	return true
     }
     
    -func (secretBindingStrategy) PrepareForCreate(_ context.Context, _ runtime.Object) {
    +func (s secretBindingStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) {
    +	binding := obj.(*core.SecretBinding)
    +
    +	if binding.GetName() == "" {
    +		binding.SetName(s.GenerateName(binding.GetGenerateName()))
    +	}
     }
     
     func (secretBindingStrategy) Validate(_ context.Context, obj runtime.Object) field.ErrorList {
    
  • pkg/apiserver/registry/core/secretbinding/strategy_test.go+29 0 modified
    @@ -34,6 +34,35 @@ var _ = Describe("Strategy", func() {
     		}
     	})
     
    +	Describe("#PrepareForCreate", func() {
    +		It("should set the name if not set", func() {
    +			secretBinding.SetName("")
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetName()).NotTo(BeEmpty())
    +		})
    +
    +		It("should set name with generateName as prefix", func() {
    +			genName := "prefix-"
    +			secretBinding.GenerateName = genName
    +			secretBinding.Name = ""
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetGenerateName()).To(Equal(genName))
    +			Expect(secretBinding.GetName()).To(HavePrefix(genName))
    +		})
    +
    +		It("should not overwrite already set name", func() {
    +			secretBinding.SetName("bar")
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetName()).To(Equal("bar"))
    +		})
    +	})
    +
     	Describe("#Validate", func() {
     		It("should forbid creating SecretBinding when provider is nil or empty", func() {
     			secretBinding.Provider = nil
    
  • pkg/apiserver/registry/core/seed/seed_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestSeed(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Seed Suite")
    +	RunSpecs(t, "APIServer Registry Core Seed Suite")
     }
    
  • pkg/apiserver/registry/core/shoot/shoot_suite_test.go+1 1 modified
    @@ -16,5 +16,5 @@ import (
     func TestShoot(t *testing.T) {
     	features.RegisterFeatureGates()
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Shoot Suite")
    +	RunSpecs(t, "APIServer Registry Core Shoot Suite")
     }
    
  • pkg/apiserver/registry/core/shoot/storage/storage_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestStorage(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Shoot Storage Suite")
    +	RunSpecs(t, "APIServer Registry Core Shoot Storage Suite")
     }
    
  • pkg/apiserver/registry/operations/bastion/bastion_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBastion(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Operations Bastion Suite")
    +	RunSpecs(t, "APIServer Registry Operations Bastion Suite")
     }
    
  • pkg/apiserver/registry/security/credentialsbinding/credentialsbinding_suite_test.go+20 0 added
    @@ -0,0 +1,20 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package credentialsbinding_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +
    +	"github.com/gardener/gardener/pkg/apiserver/features"
    +)
    +
    +func TestCredentialsBinding(t *testing.T) {
    +	features.RegisterFeatureGates()
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "APIServer Registry Security CredentialsBinding Suite")
    +}
    
  • pkg/apiserver/registry/security/credentialsbinding/strategy.go+5 1 modified
    @@ -28,8 +28,12 @@ func (credentialsBindingStrategy) NamespaceScoped() bool {
     	return true
     }
     
    -func (credentialsBindingStrategy) PrepareForCreate(_ context.Context, _ runtime.Object) {
    +func (c credentialsBindingStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) {
    +	credentialsbinding := obj.(*security.CredentialsBinding)
     
    +	if credentialsbinding.GetName() == "" {
    +		credentialsbinding.SetName(c.GenerateName(credentialsbinding.GetGenerateName()))
    +	}
     }
     
     func (credentialsBindingStrategy) PrepareForUpdate(_ context.Context, _, _ runtime.Object) {
    
  • pkg/apiserver/registry/security/credentialsbinding/strategy_test.go+58 0 added
    @@ -0,0 +1,58 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package credentialsbinding_test
    +
    +import (
    +	"context"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	credentialsbindingregistry "github.com/gardener/gardener/pkg/apiserver/registry/security/credentialsbinding"
    +)
    +
    +var _ = Describe("Strategy", func() {
    +	var credentialsBinding *security.CredentialsBinding
    +
    +	BeforeEach(func() {
    +		credentialsBinding = &security.CredentialsBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "profile",
    +				Namespace: "garden",
    +			},
    +		}
    +	})
    +
    +	Describe("#PrepareForCreate", func() {
    +		It("should set the name if not set", func() {
    +			credentialsBinding.SetName("")
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetName()).NotTo(BeEmpty())
    +		})
    +
    +		It("should set name with generateName as prefix", func() {
    +			genName := "prefix-"
    +			credentialsBinding.GenerateName = genName
    +			credentialsBinding.Name = ""
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetGenerateName()).To(Equal(genName))
    +			Expect(credentialsBinding.GetName()).To(HavePrefix(genName))
    +		})
    +
    +		It("should not overwrite already set name", func() {
    +			credentialsBinding.SetName("bar")
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetName()).To(Equal("bar"))
    +		})
    +	})
    +})
    
  • pkg/apiserver/registry/security/workloadidentity/storage/storage_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestStorage(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Security WorkloadIdentity Storage Suite")
    +	RunSpecs(t, "APIServer Registry Security WorkloadIdentity Storage Suite")
     }
    
  • pkg/apiserver/registry/security/workloadidentity/workloadidentity_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestWorkloadIdentity(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Security WorkloadIdentity Suite")
    +	RunSpecs(t, "APIServer Registry Security WorkloadIdentity Suite")
     }
    
  • pkg/apiserver/registry/seedmanagement/managedseed/managedseed_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestManagedSeed(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry SeedManagement ManagedSeed Suite")
    +	RunSpecs(t, "APIServer Registry SeedManagement ManagedSeed Suite")
     }
    
  • pkg/apiserver/registry/seedmanagement/managedseedset/managedseedset_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestManagedSeedSet(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry SeedManagement ManagedSeedSet Suite")
    +	RunSpecs(t, "APIServer Registry SeedManagement ManagedSeedSet Suite")
     }
    
  • pkg/component/gardener/admissioncontroller/admission_controller.go+1 0 modified
    @@ -131,6 +131,7 @@ func (a *gardenerAdmissionController) Deploy(ctx context.Context) error {
     		a.clusterRole(),
     		a.clusterRoleBinding(virtualGardenAccessSecret.ServiceAccountName),
     		a.validatingWebhookConfiguration(caSecret),
    +		a.mutatingWebhookConfiguration(caSecret),
     	)
     	if err != nil {
     		return err
    
  • pkg/component/gardener/admissioncontroller/admission_controller_test.go+40 0 modified
    @@ -502,6 +502,7 @@ func verifyExpectations(ctx context.Context, fakeClient client.Client, consistOf
     		clusterRole(),
     		clusterRoleBinding(),
     		validatingWebhookConfiguration(namespace, caGardener.Data["bundle.crt"], testValues),
    +		mutatingWebhookConfiguration(namespace, caGardener.Data["bundle.crt"]),
     	))
     
     	virtualManagedResourceSecret := &corev1.Secret{
    @@ -1312,3 +1313,42 @@ func validatingWebhookConfiguration(namespace string, caBundle []byte, testValue
     
     	return webhookConfig
     }
    +
    +func mutatingWebhookConfiguration(namespace string, caBundle []byte) *admissionregistrationv1.MutatingWebhookConfiguration {
    +	var (
    +		failurePolicyFail = admissionregistrationv1.Fail
    +		sideEffectsNone   = admissionregistrationv1.SideEffectClassNone
    +	)
    +
    +	return &admissionregistrationv1.MutatingWebhookConfiguration{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name: "gardener-admission-controller",
    +		},
    +		Webhooks: []admissionregistrationv1.MutatingWebhook{
    +			{
    +				Name:                    "sync-provider-secret-labels.gardener.cloud",
    +				AdmissionReviewVersions: []string{"v1", "v1beta1"},
    +				TimeoutSeconds:          ptr.To[int32](10),
    +				Rules: []admissionregistrationv1.RuleWithOperations{{
    +					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
    +					Rule: admissionregistrationv1.Rule{
    +						APIGroups:   []string{""},
    +						APIVersions: []string{"v1"},
    +						Resources:   []string{"secrets"},
    +					},
    +				}},
    +				FailurePolicy: &failurePolicyFail,
    +				NamespaceSelector: &metav1.LabelSelector{
    +					MatchLabels: map[string]string{
    +						"gardener.cloud/role": "project",
    +					},
    +				},
    +				ClientConfig: admissionregistrationv1.WebhookClientConfig{
    +					URL:      ptr.To("https://gardener-admission-controller." + namespace + "/webhooks/sync-provider-secret-labels"),
    +					CABundle: caBundle,
    +				},
    +				SideEffects: &sideEffectsNone,
    +			},
    +		},
    +	}
    +}
    
  • pkg/component/gardener/admissioncontroller/webhooks.go+41 0 modified
    @@ -375,6 +375,47 @@ func (a *gardenerAdmissionController) validatingWebhookConfiguration(caSecret *c
     	return validatingWebhook
     }
     
    +func (a *gardenerAdmissionController) mutatingWebhookConfiguration(caSecret *corev1.Secret) *admissionregistrationv1.MutatingWebhookConfiguration {
    +	var (
    +		failurePolicyFail = admissionregistrationv1.Fail
    +		sideEffectsNone   = admissionregistrationv1.SideEffectClassNone
    +
    +		caBundle = caSecret.Data[secrets.DataKeyCertificateBundle]
    +	)
    +
    +	return &admissionregistrationv1.MutatingWebhookConfiguration{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name: DeploymentName,
    +		},
    +		Webhooks: []admissionregistrationv1.MutatingWebhook{
    +			{
    +				Name:                    "sync-provider-secret-labels.gardener.cloud",
    +				AdmissionReviewVersions: []string{"v1", "v1beta1"},
    +				TimeoutSeconds:          ptr.To[int32](10),
    +				Rules: []admissionregistrationv1.RuleWithOperations{{
    +					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
    +					Rule: admissionregistrationv1.Rule{
    +						APIGroups:   []string{corev1.GroupName},
    +						APIVersions: []string{"v1"},
    +						Resources:   []string{"secrets"},
    +					},
    +				}},
    +				FailurePolicy: &failurePolicyFail,
    +				NamespaceSelector: &metav1.LabelSelector{
    +					MatchLabels: map[string]string{
    +						v1beta1constants.GardenRole: v1beta1constants.GardenRoleProject,
    +					},
    +				},
    +				ClientConfig: admissionregistrationv1.WebhookClientConfig{
    +					URL:      buildClientConfigURL("/webhooks/sync-provider-secret-labels", a.namespace),
    +					CABundle: caBundle,
    +				},
    +				SideEffects: &sideEffectsNone,
    +			},
    +		},
    +	}
    +}
    +
     func buildWebhookConfigRulesForResourceSize(config *admissioncontrollerconfigv1alpha1.ResourceAdmissionConfiguration) []admissionregistrationv1.RuleWithOperations {
     	if config == nil || len(config.Limits) == 0 {
     		return nil
    
  • pkg/controllermanager/controller/secretbinding/reconciler.go+9 11 modified
    @@ -143,17 +143,15 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
     		}
     	}
     
    -	if secretBinding.Provider != nil {
    -		types := v1beta1helper.GetSecretBindingTypes(secretBinding)
    -		for _, t := range types {
    -			labelKey := v1beta1constants.LabelShootProviderPrefix + t
    -
    -			if !metav1.HasLabel(secret.ObjectMeta, labelKey) {
    -				patch := client.MergeFrom(secret.DeepCopy())
    -				metav1.SetMetaDataLabel(&secret.ObjectMeta, labelKey, "true")
    -				if err := r.Client.Patch(ctx, secret, patch); err != nil {
    -					return reconcile.Result{}, fmt.Errorf("failed to add provider type label to Secret referenced in SecretBinding: %w", err)
    -				}
    +	types := v1beta1helper.GetSecretBindingTypes(secretBinding)
    +	for _, t := range types {
    +		labelKey := v1beta1constants.LabelShootProviderPrefix + t
    +
    +		if !metav1.HasLabel(secret.ObjectMeta, labelKey) {
    +			patch := client.MergeFrom(secret.DeepCopy())
    +			metav1.SetMetaDataLabel(&secret.ObjectMeta, labelKey, "true")
    +			if err := r.Client.Patch(ctx, secret, patch); err != nil {
    +				return reconcile.Result{}, fmt.Errorf("failed to add provider type label to Secret referenced in SecretBinding: %w", err)
     			}
     		}
     	}
    
  • pkg/provider-local/admission/cmd/options.go+1 0 modified
    @@ -14,6 +14,7 @@ import (
     func GardenWebhookSwitchOptions() *extensionscmdwebhook.SwitchOptions {
     	return extensionscmdwebhook.NewSwitchOptions(
     		extensionscmdwebhook.Switch(validator.Name, validator.New),
    +		extensionscmdwebhook.Switch(validator.SecretsValidatorName, validator.NewSecretsWebhook),
     		extensionscmdwebhook.Switch(mutator.Name, mutator.New),
     	)
     }
    
  • pkg/provider-local/admission/validator/secret.go+48 0 added
    @@ -0,0 +1,48 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package validator
    +
    +import (
    +	"context"
    +	"fmt"
    +
    +	corev1 "k8s.io/api/core/v1"
    +	apiequality "k8s.io/apimachinery/pkg/api/equality"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
    +)
    +
    +type secretValidator struct{}
    +
    +// NewSecretValidator returns a new instance of a secret validator.
    +func NewSecretValidator() extensionswebhook.Validator {
    +	return &secretValidator{}
    +}
    +
    +// Validate checks whether the data is empty.
    +func (s *secretValidator) Validate(_ context.Context, newObj, oldObj client.Object) error {
    +	secret, ok := newObj.(*corev1.Secret)
    +	if !ok {
    +		return fmt.Errorf("wrong object type %T", newObj)
    +	}
    +
    +	if oldObj != nil {
    +		oldSecret, ok := oldObj.(*corev1.Secret)
    +		if !ok {
    +			return fmt.Errorf("wrong object type %T for old object", oldObj)
    +		}
    +
    +		if apiequality.Semantic.DeepEqual(secret.Data, oldSecret.Data) {
    +			return nil
    +		}
    +	}
    +
    +	if len(secret.Data) != 0 {
    +		return fmt.Errorf("secret data should be empty")
    +	}
    +
    +	return nil
    +}
    
  • pkg/provider-local/admission/validator/webhook.go+24 1 modified
    @@ -5,18 +5,22 @@
     package validator
     
     import (
    +	corev1 "k8s.io/api/core/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"sigs.k8s.io/controller-runtime/pkg/log"
     	"sigs.k8s.io/controller-runtime/pkg/manager"
     
     	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
     	"github.com/gardener/gardener/pkg/apis/core"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
     	"github.com/gardener/gardener/pkg/provider-local/local"
     )
     
     const (
     	// Name is a name for a validation webhook.
     	Name = "validator"
    +	// SecretsValidatorName is the name of the secrets validator.
    +	SecretsValidatorName = "secrets." + Name
     )
     
     var logger = log.Log.WithName("local-validator-webhook")
    @@ -31,10 +35,29 @@ func New(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
     		Path:     "/webhooks/validate",
     		Validators: map[extensionswebhook.Validator][]extensionswebhook.Type{
     			NewNamespacedCloudProfileValidator(mgr): {{Obj: &core.NamespacedCloudProfile{}}},
    +			NewWorkloadIdentityValidator():          {{Obj: &securityv1alpha1.WorkloadIdentity{}}},
     		},
     		Target: extensionswebhook.TargetSeed,
     		ObjectSelector: &metav1.LabelSelector{
    -			MatchLabels: map[string]string{"provider.extensions.gardener.cloud/local": "true"},
    +			MatchLabels: map[string]string{"provider.extensions.gardener.cloud/" + local.Type: "true"},
    +		},
    +	})
    +}
    +
    +// NewSecretsWebhook creates a new validation webhook for Secrets.
    +func NewSecretsWebhook(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
    +	logger.Info("Setting up webhook", "name", SecretsValidatorName)
    +
    +	return extensionswebhook.New(mgr, extensionswebhook.Args{
    +		Provider: local.Type,
    +		Name:     SecretsValidatorName,
    +		Path:     "/webhooks/validate/secrets",
    +		Validators: map[extensionswebhook.Validator][]extensionswebhook.Type{
    +			NewSecretValidator(): {{Obj: &corev1.Secret{}}},
    +		},
    +		Target: extensionswebhook.TargetSeed,
    +		ObjectSelector: &metav1.LabelSelector{
    +			MatchLabels: map[string]string{"provider.shoot.gardener.cloud/" + local.Type: "true"},
     		},
     	})
     }
    
  • pkg/provider-local/admission/validator/workloadidentity.go+38 0 added
    @@ -0,0 +1,38 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package validator
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +)
    +
    +type workloadIdentityValidator struct {
    +}
    +
    +// NewWorkloadIdentityValidator returns a new instance of a WorkloadIdentity validator.
    +func NewWorkloadIdentityValidator() extensionswebhook.Validator {
    +	return &workloadIdentityValidator{}
    +}
    +
    +// Validate checks whether the provider config is empty.
    +func (wi *workloadIdentityValidator) Validate(_ context.Context, newObj, _ client.Object) error {
    +	workloadIdentity, ok := newObj.(*securityv1alpha1.WorkloadIdentity)
    +	if !ok {
    +		return fmt.Errorf("wrong object type %T", newObj)
    +	}
    +
    +	if workloadIdentity.Spec.TargetSystem.ProviderConfig != nil {
    +		return errors.New("target system provider config must be empty")
    +	}
    +
    +	return nil
    +}
    
  • plugin/pkg/global/extensionlabels/admission.go+3 5 modified
    @@ -242,11 +242,9 @@ func addMetaDataLabelsSeed(seed *core.Seed) {
     }
     
     func addMetaDataLabelsSecretBinding(secretBinding *core.SecretBinding) {
    -	if secretBinding.Provider != nil {
    -		types := gardencorehelper.GetSecretBindingTypes(secretBinding)
    -		for _, t := range types {
    -			metav1.SetMetaDataLabel(&secretBinding.ObjectMeta, v1beta1constants.LabelExtensionProviderTypePrefix+t, "true")
    -		}
    +	types := gardencorehelper.GetSecretBindingTypes(secretBinding)
    +	for _, t := range types {
    +		metav1.SetMetaDataLabel(&secretBinding.ObjectMeta, v1beta1constants.LabelExtensionProviderTypePrefix+t, "true")
     	}
     }
     
    
  • plugin/pkg/global/finalizerremoval/admission.go+196 0 added
    @@ -0,0 +1,196 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +	"io"
    +	"slices"
    +
    +	apierrors "k8s.io/apimachinery/pkg/api/errors"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/labels"
    +	"k8s.io/apimachinery/pkg/util/sets"
    +	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/utils/ptr"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	"github.com/gardener/gardener/pkg/apis/core"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	admissioninitializer "github.com/gardener/gardener/pkg/apiserver/admission/initializer"
    +	gardencoreinformers "github.com/gardener/gardener/pkg/client/core/informers/externalversions"
    +	gardencorev1beta1listers "github.com/gardener/gardener/pkg/client/core/listers/core/v1beta1"
    +	plugin "github.com/gardener/gardener/plugin/pkg"
    +)
    +
    +// Register registers a plugin.
    +func Register(plugins *admission.Plugins) {
    +	plugins.Register(plugin.PluginNameFinalizerRemoval, func(_ io.Reader) (admission.Interface, error) {
    +		return New()
    +	})
    +}
    +
    +// FinalizerRemoval contains listers and admission handler.
    +type FinalizerRemoval struct {
    +	*admission.Handler
    +	shootLister gardencorev1beta1listers.ShootLister
    +	readyFunc   admission.ReadyFunc
    +}
    +
    +var (
    +	_ = admissioninitializer.WantsCoreInformerFactory(&FinalizerRemoval{})
    +
    +	readyFuncs []admission.ReadyFunc
    +)
    +
    +// New creates a new FinalizerRemoval admission plugin.
    +func New() (*FinalizerRemoval, error) {
    +	return &FinalizerRemoval{
    +		Handler: admission.NewHandler(admission.Update),
    +	}, nil
    +}
    +
    +// AssignReadyFunc assigns the ready function to the admission handler.
    +func (f *FinalizerRemoval) AssignReadyFunc(fn admission.ReadyFunc) {
    +	f.readyFunc = fn
    +	f.SetReadyFunc(fn)
    +}
    +
    +// SetCoreInformerFactory gets Lister from SharedInformerFactory.
    +func (f *FinalizerRemoval) SetCoreInformerFactory(g gardencoreinformers.SharedInformerFactory) {
    +	shootInformer := g.Core().V1beta1().Shoots()
    +	f.shootLister = shootInformer.Lister()
    +
    +	readyFuncs = append(readyFuncs,
    +		shootInformer.Informer().HasSynced,
    +	)
    +}
    +
    +// ValidateInitialization checks whether the plugin was correctly initialized.
    +func (f *FinalizerRemoval) ValidateInitialization() error {
    +	if f.shootLister == nil {
    +		return errors.New("missing shoot lister")
    +	}
    +	return nil
    +}
    +
    +// Admit ensures that finalizers from objects can only be removed if they are not needed anymore.
    +func (f *FinalizerRemoval) Admit(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +	// Wait until the caches have been synced
    +	if f.readyFunc == nil {
    +		f.AssignReadyFunc(func() bool {
    +			for _, readyFunc := range readyFuncs {
    +				if !readyFunc() {
    +					return false
    +				}
    +			}
    +			return true
    +		})
    +	}
    +	if !f.WaitForReady() {
    +		return admission.NewForbidden(a, errors.New("not yet ready to handle request"))
    +	}
    +
    +	var (
    +		err            error
    +		newObj, oldObj client.Object
    +	)
    +
    +	oldObj, ok := a.GetOldObject().(client.Object)
    +	if !ok {
    +		return nil
    +	}
    +
    +	newObj, ok = a.GetObject().(client.Object)
    +	if !ok {
    +		return nil
    +	}
    +
    +	switch a.GetKind().GroupKind() {
    +	case core.Kind("SecretBinding"):
    +		binding, ok := a.GetObject().(*core.SecretBinding)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into SecretBinding object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the SecretBinding is not used by any shoot.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) {
    +			inUse, err := f.isUsedByShoot(binding.Namespace, func(shoot *gardencorev1beta1.Shoot) bool {
    +				return ptr.Deref(shoot.Spec.SecretBindingName, "") == binding.Name
    +			})
    +			if err != nil {
    +				return apierrors.NewInternalError(fmt.Errorf("error checking if secret binding is in use: %w", err))
    +			}
    +			if inUse {
    +				return admission.NewForbidden(a, fmt.Errorf("finalizer must not be removed - secret binding %s/%s is still in use by at least one shoot", binding.Namespace, binding.Name))
    +			}
    +		}
    +	case security.Kind("CredentialsBinding"):
    +		binding, ok := a.GetObject().(*security.CredentialsBinding)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into CredentialsBinding object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the CredentialsBinding is not used by any shoot.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) {
    +			inUse, err := f.isUsedByShoot(binding.Namespace, func(shoot *gardencorev1beta1.Shoot) bool {
    +				return ptr.Deref(shoot.Spec.CredentialsBindingName, "") == binding.Name
    +			})
    +			if err != nil {
    +				return apierrors.NewInternalError(fmt.Errorf("error checking if credentials binding is in use: %w", err))
    +			}
    +			if inUse {
    +				return admission.NewForbidden(a, fmt.Errorf("finalizer must not be removed - credentials binding %s/%s is still in use by at least one shoot", binding.Namespace, binding.Name))
    +			}
    +		}
    +	case core.Kind("Shoot"):
    +		shoot, ok := a.GetObject().(*core.Shoot)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into Shoot object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the Shoot deletion has completed successfully.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) && !shootDeletionSucceeded(shoot) {
    +			return admission.NewForbidden(a, fmt.Errorf("finalizer %q cannot be removed because shoot deletion has not completed successfully yet", core.GardenerName))
    +		}
    +	}
    +
    +	if err != nil {
    +		return admission.NewForbidden(a, err)
    +	}
    +	return nil
    +}
    +
    +func (f *FinalizerRemoval) isUsedByShoot(namespace string, inUse func(*gardencorev1beta1.Shoot) bool) (bool, error) {
    +	shoots, err := f.shootLister.Shoots(namespace).List(labels.Everything())
    +	if err != nil {
    +		return false, fmt.Errorf("error retrieving shoots: %w", err)
    +	}
    +
    +	return slices.ContainsFunc(shoots, inUse), nil
    +}
    +
    +func shootDeletionSucceeded(shoot *core.Shoot) bool {
    +	if len(shoot.Status.TechnicalID) == 0 || shoot.Status.LastOperation == nil {
    +		return true
    +	}
    +
    +	lastOperation := shoot.Status.LastOperation
    +	return lastOperation.Type == core.LastOperationTypeDelete &&
    +		lastOperation.State == core.LastOperationStateSucceeded &&
    +		lastOperation.Progress == 100
    +}
    +
    +func isFinalizerRemoved(old, new metav1.Object, finalizerName string) bool {
    +	var (
    +		oldFinalizers = sets.New(old.GetFinalizers()...)
    +		newFinalizer  = sets.New(new.GetFinalizers()...)
    +	)
    +
    +	return oldFinalizers.Has(finalizerName) && !newFinalizer.Has(finalizerName)
    +}
    
  • plugin/pkg/global/finalizerremoval/admission_test.go+216 0 added
    @@ -0,0 +1,216 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval_test
    +
    +import (
    +	"context"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/utils/ptr"
    +
    +	"github.com/gardener/gardener/pkg/apis/core"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	gardencoreinformers "github.com/gardener/gardener/pkg/client/core/informers/externalversions"
    +	. "github.com/gardener/gardener/plugin/pkg/global/finalizerremoval"
    +)
    +
    +var _ = Describe("finalizerremoval", func() {
    +	Describe("#Admit", func() {
    +		var (
    +			ctx                       context.Context
    +			admissionHandler          *FinalizerRemoval
    +			gardenCoreInformerFactory gardencoreinformers.SharedInformerFactory
    +
    +			finalizers []string
    +
    +			namespace              = "default"
    +			secretBindingName      = "binding-1"
    +			credentialsBindingName = "credentials-binding-1"
    +			shootName              = "shoot-1"
    +
    +			shoot *gardencorev1beta1.Shoot
    +		)
    +
    +		BeforeEach(func() {
    +			ctx = context.Background()
    +			admissionHandler, _ = New()
    +			admissionHandler.AssignReadyFunc(func() bool { return true })
    +
    +			finalizers = []string{core.GardenerName}
    +
    +			shoot = &gardencorev1beta1.Shoot{
    +				ObjectMeta: metav1.ObjectMeta{
    +					Name:      shootName,
    +					Namespace: namespace,
    +				},
    +				Spec: gardencorev1beta1.ShootSpec{
    +					CredentialsBindingName: ptr.To(credentialsBindingName),
    +					SecretBindingName:      ptr.To(secretBindingName),
    +				},
    +			}
    +
    +			gardenCoreInformerFactory = gardencoreinformers.NewSharedInformerFactory(nil, 0)
    +			admissionHandler.SetCoreInformerFactory(gardenCoreInformerFactory)
    +		})
    +
    +		Context("SecretBinding", func() {
    +			var coreSecretBinding *core.SecretBinding
    +
    +			BeforeEach(func() {
    +				coreSecretBinding = &core.SecretBinding{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Name:       secretBindingName,
    +						Namespace:  namespace,
    +						Finalizers: finalizers,
    +					},
    +				}
    +			})
    +
    +			It("should admit the removal because object is not used by any shoot", func() {
    +				attrs := admission.NewAttributesRecord(&core.SecretBinding{}, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should admit the removal because finalizer is irrelevant", func() {
    +				newSecretBinding := coreSecretBinding.DeepCopy()
    +				coreSecretBinding.Finalizers = append(coreSecretBinding.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newSecretBinding, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should reject the removal because object is not used by any shoot", func() {
    +				newSecretBinding := coreSecretBinding.DeepCopy()
    +				newSecretBinding.Finalizers = nil
    +
    +				secondShoot := shoot.DeepCopy()
    +				secondShoot.Name = shootName + "-2"
    +				secondShoot.Spec.SecretBindingName = ptr.To(secretBindingName + "-2")
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shoot)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(secondShoot)).To(Succeed())
    +
    +				attrs := admission.NewAttributesRecord(newSecretBinding, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("finalizer must not be removed")))
    +			})
    +		})
    +
    +		Context("CredentialsBinding", func() {
    +			var coreCredentialsBinding *security.CredentialsBinding
    +
    +			BeforeEach(func() {
    +				coreCredentialsBinding = &security.CredentialsBinding{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Name:       credentialsBindingName,
    +						Namespace:  namespace,
    +						Finalizers: finalizers,
    +					},
    +				}
    +			})
    +
    +			It("should admit the removal because object is not used by any shoot", func() {
    +				attrs := admission.NewAttributesRecord(&security.CredentialsBinding{}, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should admit the removal because finalizer is irrelevant", func() {
    +				newCredentialsBinding := coreCredentialsBinding.DeepCopy()
    +				coreCredentialsBinding.Finalizers = append(coreCredentialsBinding.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newCredentialsBinding, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should reject the removal because object is not used by any shoot", func() {
    +				newCredentialsBinding := coreCredentialsBinding.DeepCopy()
    +				newCredentialsBinding.Finalizers = nil
    +
    +				secondShoot := shoot.DeepCopy()
    +				secondShoot.Name = shootName + "-2"
    +				secondShoot.Spec.CredentialsBindingName = ptr.To(secretBindingName + "-2")
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shoot)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(secondShoot)).To(Succeed())
    +
    +				attrs := admission.NewAttributesRecord(newCredentialsBinding, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("finalizer must not be removed")))
    +			})
    +		})
    +
    +		Context("shoot", func() {
    +			var coreShoot *core.Shoot
    +
    +			BeforeEach(func() {
    +				coreShoot = &core.Shoot{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Finalizers: finalizers,
    +					},
    +					Status: core.ShootStatus{
    +						TechnicalID: "some-id",
    +						LastOperation: &core.LastOperation{
    +							Type:     core.LastOperationTypeReconcile,
    +							State:    core.LastOperationStateSucceeded,
    +							Progress: 100,
    +						},
    +					},
    +				}
    +			})
    +
    +			It("should allow the removal because finalizer is irrelevant", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				coreShoot.Finalizers = append(coreShoot.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, security.Kind("Shoot").WithVersion("version"), "", coreShoot.Name, security.Resource("Shoot").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should admit the removal if the shoot deletion succeeded ", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.LastOperation.Type = core.LastOperationTypeDelete
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should reject the removal if the shoot has not yet been deleted successfully", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("shoot deletion has not completed successfully yet")))
    +			})
    +
    +			It("should admit the removal if the shoot has not yet a last operation", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.LastOperation = nil
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should admit the removal if the shoot has not yet a technical id", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.TechnicalID = ""
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +		})
    +	})
    +})
    
  • plugin/pkg/global/finalizerremoval/finalizerremoval_suite_test.go+17 0 added
    @@ -0,0 +1,17 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +)
    +
    +func TestFinalizerRemoval(t *testing.T) {
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "AdmissionPlugin Global FinalizerRemoval Suite")
    +}
    
  • plugin/pkg/global/resourcereferencemanager/admission.go+91 72 modified
    @@ -10,13 +10,13 @@ import (
     	"fmt"
     	"io"
     	"reflect"
    +	"slices"
     	"strings"
     	"sync"
     	"time"
     
     	"github.com/hashicorp/go-multierror"
     	corev1 "k8s.io/api/core/v1"
    -	rbacv1 "k8s.io/api/rbac/v1"
     	apiequality "k8s.io/apimachinery/pkg/api/equality"
     	apierrors "k8s.io/apimachinery/pkg/api/errors"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    @@ -32,6 +32,7 @@ import (
     	kubeinformers "k8s.io/client-go/informers"
     	"k8s.io/client-go/kubernetes"
     	kubecorev1listers "k8s.io/client-go/listers/core/v1"
    +	"k8s.io/utils/ptr"
     	"sigs.k8s.io/controller-runtime/pkg/client"
     
     	"github.com/gardener/gardener/pkg/apis/core"
    @@ -287,8 +288,8 @@ func (r *ReferenceManager) ValidateInitialization() error {
     	return nil
     }
     
    -// Admit ensures that referenced resources do actually exist.
    -func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +// Validate ensures that referenced resources do actually exist.
    +func (r *ReferenceManager) Validate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
     	// Wait until the caches have been synced
     	if r.readyFunc == nil {
     		r.AssignReadyFunc(func() bool {
    @@ -350,14 +351,6 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     
     		switch a.GetOperation() {
     		case admission.Create:
    -			// Add createdBy annotation to Shoot
    -			annotations := shoot.Annotations
    -			if annotations == nil {
    -				annotations = map[string]string{}
    -			}
    -			annotations[v1beta1constants.GardenCreatedBy] = a.GetUserInfo().GetName()
    -			shoot.Annotations = annotations
    -
     			oldShoot = &core.Shoot{}
     		case admission.Update:
     			// skip verification if spec wasn't changed
    @@ -408,31 +401,9 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     		if utils.SkipVerification(operation, project.ObjectMeta) {
     			return nil
     		}
    -		// Set createdBy field in Project
    +
     		switch a.GetOperation() {
     		case admission.Create:
    -			project.Spec.CreatedBy = &rbacv1.Subject{
    -				APIGroup: "rbac.authorization.k8s.io",
    -				Kind:     rbacv1.UserKind,
    -				Name:     a.GetUserInfo().GetName(),
    -			}
    -
    -			if project.Spec.Owner == nil {
    -				owner := project.Spec.CreatedBy
    -
    -			outer:
    -				for _, member := range project.Spec.Members {
    -					for _, role := range member.Roles {
    -						if role == core.ProjectMemberOwner {
    -							owner = member.Subject.DeepCopy()
    -							break outer
    -						}
    -					}
    -				}
    -
    -				project.Spec.Owner = owner
    -			}
    -
     			err = r.ensureProjectNamespace(project)
     		case admission.Update:
     			oldProject, ok := a.GetOldObject().(*core.Project)
    @@ -444,24 +415,6 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     			}
     		}
     
    -		if project.Spec.Owner != nil {
    -			ownerIsMember := false
    -			for _, member := range project.Spec.Members {
    -				if member.Subject == *project.Spec.Owner {
    -					ownerIsMember = true
    -				}
    -			}
    -			if !ownerIsMember {
    -				project.Spec.Members = append(project.Spec.Members, core.ProjectMember{
    -					Subject: *project.Spec.Owner,
    -					Roles: []string{
    -						core.ProjectMemberAdmin,
    -						core.ProjectMemberOwner,
    -					},
    -				})
    -			}
    -		}
    -
     	case core.Kind("BackupBucket"):
     		if operation == admission.Delete {
     			// The "delete endpoint" handler of the k8s.io/apiserver library calls the admission controllers
    @@ -788,7 +741,10 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		credentialsNamespace  string
     		credentialsName       string
     		credentialsKind       string
    +		providerTypes         []string
    +		credentialsReferenced func(shoot *gardencorev1beta1.Shoot) bool
     	)
    +
     	switch attributes.GetKind().GroupKind() {
     	case core.Kind("SecretBinding"):
     		b, ok := binding.(*core.SecretBinding)
    @@ -802,6 +758,11 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		credentialsNamespace = b.SecretRef.Namespace
     		credentialsName = b.SecretRef.Name
     		credentialsKind = "Secret"
    +		providerTypes = helper.GetSecretBindingTypes(b)
    +		credentialsReferenced = func(shoot *gardencorev1beta1.Shoot) bool {
    +			return ptr.Deref(shoot.Spec.SecretBindingName, "") == b.Name
    +		}
    +
     	case security.Kind("CredentialsBinding"):
     		b, ok := binding.(*security.CredentialsBinding)
     		if !ok {
    @@ -823,9 +784,30 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		}
     		credentialsNamespace = b.CredentialsRef.Namespace
     		credentialsName = b.CredentialsRef.Name
    +		providerTypes = []string{b.Provider.Type}
    +		credentialsReferenced = func(shoot *gardencorev1beta1.Shoot) bool {
    +			return ptr.Deref(shoot.Spec.CredentialsBindingName, "") == b.Name
    +		}
    +
     	default:
     		return fmt.Errorf("%s is neither of kind SecretBinding nor CredentialsBinding", attributes.GetKind().GroupKind())
     	}
    +
    +	shoots, err := r.shootLister.Shoots(attributes.GetNamespace()).List(labels.Everything())
    +	if err != nil {
    +		return fmt.Errorf("failed listing shoots: %w", err)
    +	}
    +
    +	for _, shoot := range shoots {
    +		if !credentialsReferenced(shoot) {
    +			continue
    +		}
    +
    +		if !slices.Contains(providerTypes, shoot.Spec.Provider.Type) {
    +			return fmt.Errorf("%s is referenced by shoot %q, but provider types (%+v) do not match with the shoot provider type %q", attributes.GetKind().Kind, shoot.Name, providerTypes, shoot.Spec.Provider.Type)
    +		}
    +	}
    +
     	readAttributes := authorizer.AttributesRecord{
     		User:            attributes.GetUserInfo(),
     		Verb:            "get",
    @@ -847,10 +829,20 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		if err := r.lookupSecret(ctx, credentialsNamespace, credentialsName); err != nil {
     			return err
     		}
    +		if err := r.sanityCheckProviderSecret(ctx, credentialsNamespace, credentialsName, providerTypes); err != nil {
    +			return err
    +		}
    +
     	case "WorkloadIdentity":
    -		if err := r.lookupWorkloadIdentity(ctx, credentialsNamespace, credentialsName); err != nil {
    +		workloadIdentity, err := r.lookupWorkloadIdentity(ctx, credentialsNamespace, credentialsName)
    +		if err != nil {
     			return err
     		}
    +
    +		if !slices.Contains(providerTypes, workloadIdentity.Spec.TargetSystem.Type) {
    +			return fmt.Errorf("CredentialsBinding provider type (%+v) does not match with WorkloadIdentity provider type %s", providerTypes, workloadIdentity.Spec.TargetSystem.Type)
    +		}
    +
     	default:
     		return fmt.Errorf("unknown credentials kind: %s", credentialsKind)
     	}
    @@ -1175,42 +1167,36 @@ func validateShootWorkersForRemovedMachineImageVersions(channel chan error, shoo
     
     type getFn func(context.Context, string, string) (runtime.Object, error)
     
    -func lookupResource(ctx context.Context, namespace, name string, get getFn, fallbackGet getFn) error {
    +func lookupResource(ctx context.Context, namespace, name string, get getFn, fallbackGet getFn) (runtime.Object, error) {
     	// First try to detect the resource in the cache.
    -	var err error
    -
    -	_, err = get(ctx, namespace, name)
    +	obj, err := get(ctx, namespace, name)
     	if err == nil {
    -		return nil
    +		return obj, nil
     	}
     	if !apierrors.IsNotFound(err) {
    -		return err
    +		return nil, err
     	}
     
     	// Second try to detect the resource in the cache after the first try failed.
     	// Give the cache time to observe the resource before rejecting a create.
     	// This helps when creating a resource and immediately creating a binding referencing it.
     	time.Sleep(MissingResourceWait)
    -	_, err = get(ctx, namespace, name)
    +	obj, err = get(ctx, namespace, name)
     
     	switch {
     	case apierrors.IsNotFound(err):
     		// no-op
     	case err != nil:
    -		return err
    +		return nil, err
     	default:
    -		return nil
    +		return obj, nil
     	}
     
     	// Third try to detect the secret, now by doing a live lookup instead of relying on the cache.
    -	if _, err := fallbackGet(ctx, namespace, name); err != nil {
    -		return err
    -	}
    -
    -	return nil
    +	return fallbackGet(ctx, namespace, name)
     }
     
    -func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace, name string) error {
    +func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace, name string) (*securityv1alpha1.WorkloadIdentity, error) {
     	workloadIdentityFromLister := func(_ context.Context, namespace, name string) (runtime.Object, error) {
     		return r.workloadIdentityLister.WorkloadIdentities(namespace).Get(name)
     	}
    @@ -1219,7 +1205,11 @@ func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace
     		return r.gardenSecurityClient.SecurityV1alpha1().WorkloadIdentities(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, workloadIdentityFromLister, workloadIdentityFromClient)
    +	obj, err := lookupResource(ctx, namespace, name, workloadIdentityFromLister, workloadIdentityFromClient)
    +	if err != nil {
    +		return nil, err
    +	}
    +	return obj.(*securityv1alpha1.WorkloadIdentity), nil
     }
     
     func (r *ReferenceManager) lookupSecret(ctx context.Context, namespace, name string) error {
    @@ -1231,7 +1221,8 @@ func (r *ReferenceManager) lookupSecret(ctx context.Context, namespace, name str
     		return r.kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, secretFromLister, secretFromClient)
    +	_, err := lookupResource(ctx, namespace, name, secretFromLister, secretFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) lookupConfigMap(ctx context.Context, namespace, name string) error {
    @@ -1243,7 +1234,8 @@ func (r *ReferenceManager) lookupConfigMap(ctx context.Context, namespace, name
     		return r.kubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, configMapFromLister, configMapFromClient)
    +	_, err := lookupResource(ctx, namespace, name, configMapFromLister, configMapFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) lookupControllerDeployment(ctx context.Context, name string) error {
    @@ -1255,7 +1247,8 @@ func (r *ReferenceManager) lookupControllerDeployment(ctx context.Context, name
     		return r.gardenCoreClient.CoreV1beta1().ControllerDeployments().Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, "", name, deploymentFromLister, deploymentFromClient)
    +	_, err := lookupResource(ctx, "", name, deploymentFromLister, deploymentFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) getAPIResource(groupVersion, kind string) (*metav1.APIResource, error) {
    @@ -1350,3 +1343,29 @@ func (r *ReferenceManager) ensureResourceReferences(ctx context.Context, attribu
     	}
     	return nil
     }
    +
    +func (r *ReferenceManager) sanityCheckProviderSecret(ctx context.Context, namespace, name string, providerTypes []string) error {
    +	secret, err := r.kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
    +	if err != nil {
    +		return err
    +	}
    +
    +	for _, providerType := range providerTypes {
    +		dummySecret := &corev1.Secret{
    +			ObjectMeta: metav1.ObjectMeta{
    +				GenerateName: name,
    +				Namespace:    namespace,
    +				Annotations:  secret.Annotations,
    +				Labels:       gardenerutils.MergeStringMaps(secret.Labels, map[string]string{v1beta1constants.LabelShootProviderPrefix + providerType: "true"}),
    +			},
    +			Type: secret.Type,
    +			Data: secret.Data,
    +		}
    +
    +		if _, err := r.kubeClient.CoreV1().Secrets(dummySecret.Namespace).Create(ctx, dummySecret, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}); err != nil {
    +			return fmt.Errorf("%s provider secret sanity check failed: %w", providerType, err)
    +		}
    +	}
    +
    +	return nil
    +}
    
  • plugin/pkg/global/resourcereferencemanager/admission_test.go+221 230 modified
    @@ -16,7 +16,6 @@ import (
     	. "github.com/onsi/gomega/gstruct"
     	autoscalingv1 "k8s.io/api/autoscaling/v1"
     	corev1 "k8s.io/api/core/v1"
    -	rbacv1 "k8s.io/api/rbac/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/runtime"
     	"k8s.io/apiserver/pkg/admission"
    @@ -30,7 +29,6 @@ import (
     
     	"github.com/gardener/gardener/pkg/apis/core"
     	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    -	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
     	"github.com/gardener/gardener/pkg/apis/security"
     	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
     	"github.com/gardener/gardener/pkg/apis/seedmanagement"
    @@ -178,6 +176,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: &core.SecretBindingProvider{
    +					Type: "test",
    +				},
     			}
     			secretBinding = gardencorev1beta1.SecretBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -195,6 +196,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: &gardencorev1beta1.SecretBindingProvider{
    +					Type: "test",
    +				},
     			}
     			securityCredentialsBindingRefSecret = security.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -213,6 +217,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: security.CredentialsBindingProvider{
    +					Type: "test",
    +				},
     			}
     			credentialsBindingRefSecret = securityv1alpha1.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -231,6 +238,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: securityv1alpha1.CredentialsBindingProvider{
    +					Type: "test",
    +				},
     			}
     			securityCredentialsBindingRefWorkloadIdentity = security.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -244,6 +254,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					Name:       workloadIdentityName,
     					Namespace:  namespace,
     				},
    +				Provider: security.CredentialsBindingProvider{Type: "wiprovider"},
     				Quotas: []corev1.ObjectReference{
     					{
     						Name:      quotaName,
    @@ -451,18 +462,20 @@ var _ = Describe("resourcereferencemanager", func() {
     
     			err = gardencorev1beta1.Convert_core_Project_To_v1beta1_Project(&coreProject, &project, nil)
     			Expect(err).To(Succeed())
    +
    +			workloadIdentity.Spec.TargetSystem = securityv1alpha1.TargetSystem{Type: "wiprovider"}
     		})
     
     		It("should return nil because the resource is not BackupBucket and operation is delete", func() {
     			attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
     
    -			err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +			err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     			Expect(err).NotTo(HaveOccurred())
     
     			attrs = admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), "", controllerRegistration.Name, core.Resource("shoots").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
     
    -			err = admissionHandler.Admit(context.TODO(), attrs, nil)
    +			err = admissionHandler.Validate(context.TODO(), attrs, nil)
     
     			Expect(err).NotTo(HaveOccurred())
     		})
    @@ -474,7 +487,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -487,7 +500,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -499,7 +512,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					return true, nil, errors.New("nope, out of luck")
     				})
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     				Expect(err.Error()).To(ContainSubstring("nope, out of luck"))
    @@ -514,7 +527,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -533,11 +546,36 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, nil
    +				})
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the sanity check fails", func() {
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, &corev1.Secret{
    +						ObjectMeta: metav1.ObjectMeta{
    +							Namespace: secret.Namespace,
    +							Name:      secret.Name,
    +						},
    +					}, nil
    +				})
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, fmt.Errorf("sanity check failed")
    +				})
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("test provider secret sanity check failed: sanity check failed")))
    +			})
    +
     			It("should reject because the referenced secret does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -547,7 +585,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -559,7 +597,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -570,7 +608,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -582,7 +620,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -617,7 +655,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -652,10 +690,23 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    +
    +			It("should reject because provider types do not match with shoot type", func() {
    +				coreSecretBinding.Provider.Type = "another-provider"
    +				coreSecretBinding.Quotas = nil
    +				shoot.Spec.Provider.Type = "local"
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(&shoot)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(`SecretBinding is referenced by shoot "shoot-1", but provider types ([another-provider]) do not match with the shoot provider type "local"`)))
    +			})
     		})
     
     		Context("tests for CredentialsBinding objects referencing Secret", func() {
    @@ -666,7 +717,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -685,11 +736,36 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, nil
    +				})
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the sanity check fails", func() {
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, &corev1.Secret{
    +						ObjectMeta: metav1.ObjectMeta{
    +							Namespace: secret.Namespace,
    +							Name:      secret.Name,
    +						},
    +					}, nil
    +				})
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, fmt.Errorf("sanity check failed")
    +				})
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("test provider secret sanity check failed: sanity check failed")))
    +			})
    +
     			It("should reject because the referenced secret does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -699,7 +775,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -711,7 +787,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -722,7 +798,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -734,7 +810,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -769,7 +845,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -804,10 +880,23 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    +
    +			It("should reject because provider types do not match with shoot type", func() {
    +				securityCredentialsBindingRefSecret.Provider.Type = "another-provider"
    +				securityCredentialsBindingRefSecret.Quotas = nil
    +				shoot.Spec.Provider.Type = "local"
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(&shoot)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(`CredentialsBinding is referenced by shoot "shoot-1", but provider types ([another-provider]) do not match with the shoot provider type "local"`)))
    +			})
     		})
     
     		Context("tests for CredentialsBinding objects referencing WorkloadIdentity", func() {
    @@ -818,30 +907,38 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
     			It("should accept because all referenced objects have been found (workloadidentity looked up live)", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				gardenSecurityClient.AddReactor("get", "workloadidentities", func(_ testing.Action) (bool, runtime.Object, error) {
    -					return true, &securityv1alpha1.WorkloadIdentity{
    -						ObjectMeta: metav1.ObjectMeta{
    -							Namespace: workloadIdentity.Namespace,
    -							Name:      workloadIdentity.Name,
    -						},
    -					}, nil
    +					return true, &workloadIdentity, nil
     				})
     
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the provider type does not match in WorkloadIdentity and CredentialsBinding", func() {
    +				workloadIdentity.Spec.TargetSystem.Type = "foo"
    +				Expect(gardenSecurityInformerFactory.Security().V1alpha1().WorkloadIdentities().Informer().GetStore().Add(&workloadIdentity)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
    +
    +				Expect(err).To(MatchError(ContainSubstring("does not match with WorkloadIdentity provider type")))
    +			})
    +
     			It("should reject because the referenced workload identity does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				gardenSecurityClient.AddReactor("get", "workloadidentities", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -851,7 +948,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -863,7 +960,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -874,7 +971,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -886,7 +983,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -914,14 +1011,15 @@ var _ = Describe("resourcereferencemanager", func() {
     				quotaRefList = append(quotaRefList, quota2Ref)
     				securityCredentialsBindingRefWorkloadIdentity.Quotas = quotaRefList
     
    +				Expect(gardenSecurityInformerFactory.Security().V1alpha1().WorkloadIdentities().Informer().GetStore().Add(&workloadIdentity)).To(Succeed())
     				Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota2)).To(Succeed())
     
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -975,31 +1073,13 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
     		})
     
     		Context("tests for Shoot objects", func() {
    -			It("should add the created-by annotation", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(gardenSecurityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBindingRefSecret)).To(Succeed())
    -				Expect(kubeInformerFactory.Core().V1().ConfigMaps().Informer().GetStore().Add(&configMap)).To(Succeed())
    -
    -				user := &user.DefaultInfo{Name: allowedUser}
    -				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    -
    -				Expect(coreShoot.Annotations).NotTo(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, user.Name))
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreShoot.Annotations).To(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, user.Name))
    -			})
    -
     			It("should accept because all referenced objects have been found", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    @@ -1010,7 +1090,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1026,7 +1106,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreShoot.Status.TechnicalID = "should-never-change"
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1035,7 +1115,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				It("should reject because the referenced cloud profile does not exist (create)", func() {
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1046,7 +1126,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1061,7 +1141,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1078,7 +1158,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1091,7 +1171,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1103,7 +1183,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1115,7 +1195,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1130,7 +1210,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1150,7 +1230,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				It("should reject because the referenced exposure class does not exists", func() {
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     					Expect(err).To(HaveOccurred())
     				})
     
    @@ -1164,7 +1244,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					Expect(gardenCoreInformerFactory.Core().V1beta1().ExposureClasses().Informer().GetStore().Add(&exposureClass)).To(Succeed())
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     					Expect(err).To(HaveOccurred())
     				})
     			})
    @@ -1176,7 +1256,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1191,7 +1271,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("shoots.core.gardener.cloud \"shoot-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1218,7 +1298,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("shoots.core.gardener.cloud \"shoot-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1234,7 +1314,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1251,7 +1331,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError(ContainSubstring("failed to resolve resource reference")))
     			})
    @@ -1273,7 +1353,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
     				})
     
     				It("should reject because the referenced "+description+" does not exist (update)", func() {
    @@ -1293,7 +1373,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
     				})
     
     				It("should pass because the referenced "+description+" does not exist but shoot has deletion timestamp", func() {
    @@ -1316,7 +1396,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     				})
     
     				It("should pass because the referenced "+description+" exists", func() {
    @@ -1335,7 +1415,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     				})
     			}
     
    @@ -1409,7 +1489,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSeed, oldSeed, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("seeds.core.gardener.cloud \"seed-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1420,7 +1500,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSeed, oldSeed, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1429,7 +1509,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSeed, nil, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError(ContainSubstring("failed to resolve resource reference")))
     			})
    @@ -1439,7 +1519,7 @@ var _ = Describe("resourcereferencemanager", func() {
     			It("should reject if the referred Seed is not found", func() {
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: seed.core.gardener.cloud %q not found", coreBackupBucket.Name, seed.Name)))
    @@ -1453,7 +1533,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("secret not found")))
    @@ -1472,7 +1552,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1483,7 +1563,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1498,7 +1578,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1517,7 +1597,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", backupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: cannot delete BackupBucket because BackupEntries are still referencing it, backupEntryNames: %s/%s,%s/%s", backupBucket.Name, backupEntry.Namespace, backupEntry.Name, backupEntry2.Namespace, backupEntry2.Name)))
    @@ -1533,7 +1613,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1561,7 +1641,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", "", core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: cannot delete BackupBucket because BackupEntries are still referencing it, backupEntryNames: %s/%s,%s/%s", backupBucket2.Name, backupEntry.Namespace, backupEntry.Name, backupEntry2.Namespace, backupEntry2.Name)))
    @@ -1590,7 +1670,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", "", core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1600,7 +1680,7 @@ var _ = Describe("resourcereferencemanager", func() {
     			It("should reject if the referred Seed is not found", func() {
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupEntries.core.gardener.cloud %q is forbidden: seed.core.gardener.cloud %q not found", coreBackupEntry.Name, seed.Name)))
    @@ -1610,7 +1690,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupEntries.core.gardener.cloud %q is forbidden: backupbucket.core.gardener.cloud %q not found", coreBackupEntry.Name, coreBackupBucket.Name)))
    @@ -1621,102 +1701,13 @@ var _ = Describe("resourcereferencemanager", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().BackupBuckets().Informer().GetStore().Add(&backupBucket)).To(Succeed())
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     		})
     
     		Context("tests for Project objects", func() {
    -			It("should set the created-by field", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should set the owner field (member with owner role found)", func() {
    -				projectCopy := project.DeepCopy()
    -				coreProjectCopy := coreProject.DeepCopy()
    -				ownerMember := &rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     "owner",
    -				}
    -				projectCopy.Name = "foo"
    -				projectCopy.Spec.Members = []gardencorev1beta1.ProjectMember{
    -					{
    -						Subject: *ownerMember,
    -						Roles:   []string{core.ProjectMemberOwner},
    -					},
    -				}
    -				coreProjectCopy.Name = "foo"
    -				coreProjectCopy.Spec.Members = []core.ProjectMember{
    -					{
    -						Subject: *ownerMember,
    -						Roles:   []string{core.ProjectMemberOwner},
    -					},
    -				}
    -
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(projectCopy)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(coreProjectCopy, nil, core.Kind("Project").WithVersion("version"), coreProjectCopy.Namespace, coreProjectCopy.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProjectCopy.Spec.Owner).To(Equal(ownerMember))
    -				Expect(coreProjectCopy.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should set the owner field", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.Owner).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should add the owner to members", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.Members).To(ContainElement(Equal(core.ProjectMember{
    -					Subject: rbacv1.Subject{
    -						APIGroup: "rbac.authorization.k8s.io",
    -						Kind:     rbacv1.UserKind,
    -						Name:     defaultUserName,
    -					},
    -					Roles: []string{
    -						core.ProjectMemberAdmin,
    -						core.ProjectMemberOwner,
    -					},
    -				})))
    -			})
    -
     			It("should allow specifying a namespace which is not in use (create)", func() {
     				project.Spec.Namespace = ptr.To("garden-foo")
     				projectCopy := project.DeepCopy()
    @@ -1727,7 +1718,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1745,7 +1736,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, coreProjectOld, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1757,7 +1748,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1771,7 +1762,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{
     					"ErrStatus": MatchFields(IgnoreExtras, Fields{
    @@ -1793,7 +1784,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, &coreProjectOld, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{
     					"ErrStatus": MatchFields(IgnoreExtras, Fields{
    @@ -1835,7 +1826,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfile, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1856,7 +1847,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1876,7 +1867,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     				Expect(err.Error()).To(ContainSubstring("1.24.1"))
    @@ -1904,7 +1895,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(And(
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(And(
     					ContainSubstring("unable to delete Kubernetes version"),
     					ContainSubstring("1.24.1"),
     					ContainSubstring("still in use by NamespacedCloudProfile"),
    @@ -1935,7 +1926,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(And(
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(And(
     					ContainSubstring("unable to delete Kubernetes version"),
     					ContainSubstring("1.24.1"),
     					ContainSubstring("still in use by shoot '/shoot-Two'"),
    @@ -1964,7 +1955,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     			})
     
     			It("should accept removal of kubernetes versions that are used by shoots using another unrelated NamespacedCloudProfile of same name", func() {
    @@ -1987,7 +1978,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     			})
     
     			It("should accept removal of kubernetes version that is still in use by a shoot that is being deleted", func() {
    @@ -2009,7 +2000,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2105,7 +2096,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfile, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2148,7 +2139,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2192,7 +2183,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.Upd
    ... [truncated]
    
  • plugin/pkg/plugins.go+4 0 modified
    @@ -27,6 +27,8 @@ const (
     	PluginNameExtensionLabels = "ExtensionLabels"
     	// PluginNameExtensionValidator is the name of the ExtensionValidator admission plugin.
     	PluginNameExtensionValidator = "ExtensionValidator"
    +	// PluginNameFinalizerRemoval is the name of the FinalizerRemoval admission plugin.
    +	PluginNameFinalizerRemoval = "FinalizerRemoval"
     	// PluginNameResourceReferenceManager is the name of the ResourceReferenceManager admission plugin.
     	PluginNameResourceReferenceManager = "ResourceReferenceManager"
     	// PluginNameManagedSeedShoot is the name of the ManagedSeedShoot admission plugin.
    @@ -88,6 +90,7 @@ func AllPluginNames() []string {
     		PluginNameNamespacedCloudProfileValidator,   // NamespacedCloudProfileValidator
     		PluginNameProjectValidator,                  // ProjectValidator
     		PluginNameDeletionConfirmation,              // DeletionConfirmation
    +		PluginNameFinalizerRemoval,                  // FinalizerRemoval
     		PluginNameOpenIDConnectPreset,               // OpenIDConnectPreset
     		PluginNameClusterOpenIDConnectPreset,        // ClusterOpenIDConnectPreset
     		PluginNameCustomVerbAuthorizer,              // CustomVerbAuthorizer
    @@ -131,6 +134,7 @@ func DefaultOnPlugins() sets.Set[string] {
     		PluginNameNamespacedCloudProfileValidator, // NamespacedCloudProfileValidator
     		PluginNameProjectValidator,                // ProjectValidator
     		PluginNameDeletionConfirmation,            // DeletionConfirmation
    +		PluginNameFinalizerRemoval,                // FinalizerRemoval
     		PluginNameOpenIDConnectPreset,             // OpenIDConnectPreset
     		PluginNameClusterOpenIDConnectPreset,      // ClusterOpenIDConnectPreset
     		PluginNameCustomVerbAuthorizer,            // CustomVerbAuthorizer
    
  • plugin/pkg/project/validator/admission.go+59 4 modified
    @@ -8,15 +8,18 @@ import (
     	"context"
     	"fmt"
     	"io"
    +	"slices"
     	"strings"
     
    +	rbacv1 "k8s.io/api/rbac/v1"
     	apierrors "k8s.io/apimachinery/pkg/api/errors"
     	"k8s.io/apiserver/pkg/admission"
     
     	gardencore "github.com/gardener/gardener/pkg/apis/core"
     	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
     	gardenerutils "github.com/gardener/gardener/pkg/utils/gardener"
     	plugin "github.com/gardener/gardener/plugin/pkg"
    +	"github.com/gardener/gardener/plugin/pkg/utils"
     )
     
     // Register registers a plugin.
    @@ -33,13 +36,13 @@ type handler struct {
     // New creates a new handler admission plugin.
     func New() (*handler, error) {
     	return &handler{
    -		Handler: admission.NewHandler(admission.Create),
    +		Handler: admission.NewHandler(admission.Create, admission.Update),
     	}, nil
     }
     
    -var _ admission.ValidationInterface = &handler{}
    +var _ admission.MutationInterface = &handler{}
     
    -func (v *handler) Validate(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +func (v *handler) Admit(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
     	// Ignore all kinds other than Project
     	if a.GetKind().GroupKind() != gardencore.Kind("Project") {
     		return nil
    @@ -56,10 +59,62 @@ func (v *handler) Validate(_ context.Context, a admission.Attributes, _ admissio
     		return apierrors.NewBadRequest("could not convert object to Project")
     	}
     
    -	// TODO: Remove this admission plugin in favor of static validation in a future release, see https://github.com/gardener/gardener/pull/4228.
    +	// TODO: Remove this check in favor of static validation in a future release, see https://github.com/gardener/gardener/pull/4228.
     	if project.Spec.Namespace != nil && *project.Spec.Namespace != v1beta1constants.GardenNamespace && !strings.HasPrefix(*project.Spec.Namespace, gardenerutils.ProjectNamespacePrefix) {
     		return admission.NewForbidden(a, fmt.Errorf(".spec.namespace must start with %s", gardenerutils.ProjectNamespacePrefix))
     	}
     
    +	if utils.SkipVerification(a.GetOperation(), project.ObjectMeta) {
    +		return nil
    +	}
    +
    +	if a.GetOperation() == admission.Create {
    +		ensureProjectOwner(project, a.GetUserInfo().GetName())
    +	}
    +
    +	ensureOwnerIsMember(project)
    +
     	return nil
     }
    +
    +func ensureProjectOwner(project *gardencore.Project, userName string) {
    +	// Set createdBy field in Project
    +	project.Spec.CreatedBy = &rbacv1.Subject{
    +		APIGroup: "rbac.authorization.k8s.io",
    +		Kind:     rbacv1.UserKind,
    +		Name:     userName,
    +	}
    +
    +	if project.Spec.Owner == nil {
    +		project.Spec.Owner = func() *rbacv1.Subject {
    +			for _, member := range project.Spec.Members {
    +				for _, role := range member.Roles {
    +					if role == gardencore.ProjectMemberOwner {
    +						return member.Subject.DeepCopy()
    +					}
    +				}
    +			}
    +			return project.Spec.CreatedBy
    +		}()
    +	}
    +}
    +
    +func ensureOwnerIsMember(project *gardencore.Project) {
    +	if project.Spec.Owner == nil {
    +		return
    +	}
    +
    +	ownerIsMember := slices.ContainsFunc(project.Spec.Members, func(member gardencore.ProjectMember) bool {
    +		return member.Subject == *project.Spec.Owner
    +	})
    +
    +	if !ownerIsMember {
    +		project.Spec.Members = append(project.Spec.Members, gardencore.ProjectMember{
    +			Subject: *project.Spec.Owner,
    +			Roles: []string{
    +				gardencore.ProjectMemberAdmin,
    +				gardencore.ProjectMemberOwner,
    +			},
    +		})
    +	}
    +}
    
  • plugin/pkg/project/validator/admission_test.go+136 28 modified
    @@ -9,8 +9,10 @@ import (
     
     	. "github.com/onsi/ginkgo/v2"
     	. "github.com/onsi/gomega"
    +	rbacv1 "k8s.io/api/rbac/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/apiserver/pkg/authentication/user"
     	"k8s.io/utils/ptr"
     
     	"github.com/gardener/gardener/pkg/apis/core"
    @@ -23,7 +25,8 @@ var _ = Describe("Admission", func() {
     		var (
     			err              error
     			project          core.Project
    -			admissionHandler admission.ValidationInterface
    +			admissionHandler admission.MutationInterface
    +			attrs            admission.Attributes
     
     			namespaceName = "garden-my-project"
     			projectName   = "my-project"
    @@ -33,43 +36,148 @@ var _ = Describe("Admission", func() {
     					Namespace: namespaceName,
     				},
     			}
    +
    +			userInfo user.Info
     		)
     
     		BeforeEach(func() {
     			admissionHandler, err = New()
     			Expect(err).NotTo(HaveOccurred())
     
     			project = projectBase
    -		})
    -
    -		It("should allow creating the project (namespace nil)", func() {
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
     
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    +			userInfo = &user.DefaultInfo{Name: "foo"}
     		})
     
    -		It("should allow creating the project(namespace non-nil)", func() {
    -			project.Spec.Namespace = &namespaceName
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    +		When("project is created", func() {
    +			BeforeEach(func() {
    +				attrs = admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +			})
    +
    +			It("should allow creating the project (namespace nil)", func() {
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should allow creating the project(namespace non-nil)", func() {
    +				project.Spec.Namespace = &namespaceName
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should allow creating the project (namespace is 'garden')", func() {
    +				project.Spec.Namespace = ptr.To(v1beta1constants.GardenNamespace)
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should prevent creating the project because namespace prefix is missing", func() {
    +				project.Spec.Namespace = ptr.To("foo")
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(".spec.namespace must start with garden-")))
    +			})
    +
    +			It("should maintain createdBy and project owner", func() {
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     userInfo.GetName(),
    +				}))
    +
    +				Expect(project.Spec.Owner).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     userInfo.GetName(),
    +				}))
    +
    +				Expect(project.Spec.Members).To(ConsistOf(core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     userInfo.GetName(),
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}))
    +			})
    +
    +			It("should not overwrite project owner", func() {
    +				project.Spec.Owner = &rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     "bar",
    +				}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Owner).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     "bar",
    +				}))
    +			})
     		})
     
    -		It("should allow creating the project (namespace is 'garden')", func() {
    -			project.Spec.Namespace = ptr.To(v1beta1constants.GardenNamespace)
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    -		})
    -
    -		It("should prevent creating the project because namespace prefix is missing", func() {
    -			project.Spec.Namespace = ptr.To("foo")
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(".spec.namespace must start with garden-")))
    +		When("project is updated", func() {
    +			BeforeEach(func() {
    +				attrs = admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, userInfo)
    +			})
    +
    +			It("should add project owner to members", func() {
    +				projectOwner := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "foo",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}
    +
    +				projectMemberBar := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "bar",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberViewer,
    +					},
    +				}
    +
    +				project.Spec.Owner = &projectOwner.Subject
    +				project.Spec.Members = []core.ProjectMember{projectMemberBar}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Members).To(ConsistOf(projectMemberBar, projectOwner))
    +			})
    +
    +			It("should not re-add owner as member", func() {
    +				projectOwner := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "foo",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}
    +
    +				project.Spec.Owner = &projectOwner.Subject
    +				project.Spec.Members = []core.ProjectMember{projectOwner}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Members).To(ConsistOf(projectOwner))
    +			})
     		})
     	})
     
    @@ -85,11 +193,11 @@ var _ = Describe("Admission", func() {
     	})
     
     	Describe("#New", func() {
    -		It("should only handle CREATE operations", func() {
    +		It("should handle CREATE and UPDATE operations", func() {
     			dr, err := New()
     			Expect(err).ToNot(HaveOccurred())
     			Expect(dr.Handles(admission.Create)).To(BeTrue())
    -			Expect(dr.Handles(admission.Update)).To(BeFalse())
    +			Expect(dr.Handles(admission.Update)).To(BeTrue())
     			Expect(dr.Handles(admission.Connect)).To(BeFalse())
     			Expect(dr.Handles(admission.Delete)).To(BeFalse())
     		})
    
  • plugin/pkg/shoot/validator/admission.go+17 19 modified
    @@ -252,15 +252,19 @@ func (v *ValidateShoot) Admit(ctx context.Context, a admission.Attributes, _ adm
     		}
     	}
     
    +	if a.GetOperation() == admission.Create {
    +		addCreatedByAnnotation(shoot, a.GetUserInfo().GetName())
    +
    +		if len(ptr.Deref(shoot.Spec.CloudProfileName, "")) > 0 && shoot.Spec.CloudProfile != nil {
    +			return fmt.Errorf("new shoot can only specify either cloudProfileName or cloudProfile reference")
    +		}
    +	}
    +
     	cloudProfileSpec, err := admissionutils.GetCloudProfileSpec(v.cloudProfileLister, v.namespacedCloudProfileLister, shoot)
     	if err != nil {
     		return apierrors.NewInternalError(fmt.Errorf("could not find referenced cloud profile: %+v", err.Error()))
     	}
     
    -	if a.GetOperation() == admission.Create && len(ptr.Deref(shoot.Spec.CloudProfileName, "")) > 0 && shoot.Spec.CloudProfile != nil {
    -		return fmt.Errorf("new shoot can only specify either cloudProfileName or cloudProfile reference")
    -	}
    -
     	if err := admissionutils.ValidateCloudProfileChanges(v.cloudProfileLister, v.namespacedCloudProfileLister, shoot, oldShoot); err != nil {
     		return err
     	}
    @@ -609,21 +613,6 @@ func (c *validationContext) validateDeletion(a admission.Attributes) error {
     		}
     	}
     
    -	// Allow removal of `gardener` finalizer only if the Shoot deletion has completed successfully
    -	if len(c.shoot.Status.TechnicalID) > 0 && c.shoot.Status.LastOperation != nil {
    -		oldFinalizers := sets.New(c.oldShoot.Finalizers...)
    -		newFinalizers := sets.New(c.shoot.Finalizers...)
    -
    -		if oldFinalizers.Has(core.GardenerName) && !newFinalizers.Has(core.GardenerName) {
    -			lastOperation := c.shoot.Status.LastOperation
    -			deletionSucceeded := lastOperation.Type == core.LastOperationTypeDelete && lastOperation.State == core.LastOperationStateSucceeded && lastOperation.Progress == 100
    -
    -			if !deletionSucceeded {
    -				return admission.NewForbidden(a, fmt.Errorf("finalizer %q cannot be removed because shoot deletion has not completed successfully yet", core.GardenerName))
    -			}
    -		}
    -	}
    -
     	return nil
     }
     
    @@ -2153,3 +2142,12 @@ func validateMaxNodesTotal(workers []core.Worker, maxNodesTotal int32) field.Err
     
     	return allErrs
     }
    +
    +func addCreatedByAnnotation(shoot *core.Shoot, userName string) {
    +	annotations := shoot.Annotations
    +	if annotations == nil {
    +		annotations = map[string]string{}
    +	}
    +	annotations[v1beta1constants.GardenCreatedBy] = userName
    +	shoot.Annotations = annotations
    +}
    
  • plugin/pkg/shoot/validator/admission_test.go+50 96 modified
    @@ -18,7 +18,6 @@ import (
     	"k8s.io/apimachinery/pkg/api/resource"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/runtime"
    -	"k8s.io/apimachinery/pkg/util/sets"
     	"k8s.io/apiserver/pkg/admission"
     	"k8s.io/apiserver/pkg/authentication/user"
     	"k8s.io/apiserver/pkg/authorization/authorizer"
    @@ -423,106 +422,61 @@ var _ = Describe("validator", func() {
     			})
     		})
     
    -		Context("shoot with generate name", func() {
    +		Context("shoot creation", func() {
     			BeforeEach(func() {
    -				shoot.ObjectMeta = metav1.ObjectMeta{
    -					GenerateName: "demo-",
    -					Namespace:    namespaceName,
    -				}
    -			})
    -
    -			It("should admit Shoot resources", func() {
     				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    -
    -				authorizeAttributes.Name = shoot.Name
    -
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
     			})
     
    -			It("should reject Shoot resources with not fulfilling the length constraints", func() {
    -				tooLongName := "too-long-namespace"
    -				project.ObjectMeta = metav1.ObjectMeta{
    -					Name: tooLongName,
    -				}
    -				shoot.ObjectMeta = metav1.ObjectMeta{
    -					GenerateName: "too-long-name",
    -					Namespace:    namespaceName,
    -				}
    -
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    -
    -				authorizeAttributes.Name = shoot.Name
    +			Context("with generate name", func() {
    +				BeforeEach(func() {
    +					shoot.ObjectMeta = metav1.ObjectMeta{
    +						GenerateName: "demo-",
    +						Namespace:    namespaceName,
    +					}
    +				})
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    +				It("should admit Shoot resources", func() {
    +					authorizeAttributes.Name = shoot.Name
     
    -				Expect(err).To(BeInvalidError())
    -				Expect(err.Error()).To(ContainSubstring("name must not exceed"))
    -			})
    -		})
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +					err := admissionHandler.Admit(ctx, attrs, nil)
     
    -		Context("finalizer removal checks", func() {
    -			var (
    -				oldShoot *core.Shoot
    -			)
    +					Expect(err).NotTo(HaveOccurred())
    +				})
     
    -			BeforeEach(func() {
    -				shoot = *shootBase.DeepCopy()
    +				It("should reject Shoot resources with not fulfilling the length constraints", func() {
    +					tooLongName := "too-long-namespace"
    +					project.ObjectMeta = metav1.ObjectMeta{
    +						Name: tooLongName,
    +					}
    +					shoot.ObjectMeta = metav1.ObjectMeta{
    +						GenerateName: "too-long-name",
    +						Namespace:    namespaceName,
    +					}
     
    -				shoot.Status.TechnicalID = "some-id"
    -				shoot.Status.LastOperation = &core.LastOperation{
    -					Type:     core.LastOperationTypeReconcile,
    -					State:    core.LastOperationStateSucceeded,
    -					Progress: 100,
    -				}
    +					Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
     
    -				// set old shoot for update and add gardener finalizer to it
    -				oldShoot = shoot.DeepCopy()
    -				finalizers := sets.New(oldShoot.GetFinalizers()...)
    -				finalizers.Insert(core.GardenerName)
    -				oldShoot.SetFinalizers(finalizers.UnsortedList())
    -			})
    +					authorizeAttributes.Name = shoot.Name
     
    -			It("should reject removing the gardener finalizer if the shoot has not yet been deleted successfully", func() {
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +					err := admissionHandler.Admit(ctx, attrs, nil)
     
    -				attrs := admission.NewAttributesRecord(&shoot, oldShoot, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -				Expect(err).To(HaveOccurred())
    -				Expect(err.Error()).To(ContainSubstring("shoot deletion has not completed successfully yet"))
    +					Expect(err).To(BeInvalidError())
    +					Expect(err.Error()).To(ContainSubstring("name must not exceed"))
    +				})
     			})
     
    -			It("should admit removing the gardener finalizer if the shoot deletion succeeded ", func() {
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    +			It("should add the created-by annotation", func() {
    +				Expect(shoot.Annotations).NotTo(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, userInfo.Name))
     
    -				shoot.Status.LastOperation = &core.LastOperation{
    -					Type:     core.LastOperationTypeDelete,
    -					State:    core.LastOperationStateSucceeded,
    -					Progress: 100,
    -				}
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
     
    -				attrs := admission.NewAttributesRecord(&shoot, oldShoot, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -				Expect(err).ToNot(HaveOccurred())
    +				Expect(shoot.Annotations).To(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, userInfo.Name))
     			})
     		})
     
    @@ -1692,7 +1646,7 @@ var _ = Describe("validator", func() {
     				shoot.Spec.SeedName = nil
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
    @@ -1705,7 +1659,7 @@ var _ = Describe("validator", func() {
     
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
    @@ -1721,7 +1675,7 @@ var _ = Describe("validator", func() {
     
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
    @@ -1999,23 +1953,23 @@ var _ = Describe("validator", func() {
     						})
     
     						It("should allow scheduling non-HA shoot", func() {
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'node'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeNode}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should reject scheduling HA shoot with failure tolerance type 'zone'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeZone}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(BeForbiddenError())
     						})
     					})
    @@ -2026,23 +1980,23 @@ var _ = Describe("validator", func() {
     						})
     
     						It("should allow scheduling non-HA shoot", func() {
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'node'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeNode}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'zone'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeZone}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     					})
    @@ -2256,7 +2210,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).NotTo(HaveOccurred())
    @@ -2276,7 +2230,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2306,7 +2260,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2337,7 +2291,7 @@ var _ = Describe("validator", func() {
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     						Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2369,7 +2323,7 @@ var _ = Describe("validator", func() {
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     						Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).NotTo(HaveOccurred())
    @@ -2394,7 +2348,7 @@ var _ = Describe("validator", func() {
     					Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     					Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     					err := admissionHandler.Admit(ctx, attrs, nil)
     
     					Expect(err).To(errorMatcher)
    
  • skaffold-operator.yaml+2 0 modified
    @@ -604,6 +604,7 @@ build:
                 - plugin/pkg/global/deletionconfirmation
                 - plugin/pkg/global/extensionlabels
                 - plugin/pkg/global/extensionvalidation
    +            - plugin/pkg/global/finalizerremoval
                 - plugin/pkg/global/resourcereferencemanager
                 - plugin/pkg/managedseed/shoot
                 - plugin/pkg/managedseed/validator
    @@ -855,6 +856,7 @@ build:
                 - pkg/admissioncontroller/webhook/admission/internaldomainsecret
                 - pkg/admissioncontroller/webhook/admission/kubeconfigsecret
                 - pkg/admissioncontroller/webhook/admission/namespacedeletion
    +            - pkg/admissioncontroller/webhook/admission/providersecretlabels
                 - pkg/admissioncontroller/webhook/admission/resourcesize
                 - pkg/admissioncontroller/webhook/admission/seedrestriction
                 - pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref
    
  • skaffold.yaml+2 0 modified
    @@ -203,6 +203,7 @@ build:
                 - plugin/pkg/global/deletionconfirmation
                 - plugin/pkg/global/extensionlabels
                 - plugin/pkg/global/extensionvalidation
    +            - plugin/pkg/global/finalizerremoval
                 - plugin/pkg/global/resourcereferencemanager
                 - plugin/pkg/managedseed/shoot
                 - plugin/pkg/managedseed/validator
    @@ -454,6 +455,7 @@ build:
                 - pkg/admissioncontroller/webhook/admission/internaldomainsecret
                 - pkg/admissioncontroller/webhook/admission/kubeconfigsecret
                 - pkg/admissioncontroller/webhook/admission/namespacedeletion
    +            - pkg/admissioncontroller/webhook/admission/providersecretlabels
                 - pkg/admissioncontroller/webhook/admission/resourcesize
                 - pkg/admissioncontroller/webhook/admission/seedrestriction
                 - pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref
    
  • test/integration/envtest/environment_test.go+1 1 modified
    @@ -44,6 +44,6 @@ var _ = Describe("GardenerTestEnvironment", func() {
     
     	It("should be able to manipulate resource from security.gardener.cloud/v1alpha1", func() {
     		credentialsBinding := &securityv1alpha1.CredentialsBinding{ObjectMeta: metav1.ObjectMeta{GenerateName: "test-", Namespace: testNamespace.Name}}
    -		Expect(testClient.Create(ctx, credentialsBinding)).To(MatchError(ContainSubstring("credentialsbindings.security.gardener.cloud \"test-\" is forbidden")))
    +		Expect(testClient.Create(ctx, credentialsBinding)).To(MatchError(MatchRegexp("CredentialsBinding.security.gardener.cloud \"test-.+\" is invalid")))
     	})
     })
    
bbd19b1dd3a3

Ensure extension admission webhooks validated `WorkloadIdentity`s and `Secret`s when used (#12061)

https://github.com/gardener/gardenerRafael FranzkeMay 13, 2025via ghsa
55 files changed · +1700 493
  • charts/gardener/controlplane/charts/application/templates/mutatingwebhook-admission-controller.yaml+29 1 modified
    @@ -3,5 +3,33 @@ apiVersion: admissionregistration.k8s.io/v1
     kind: MutatingWebhookConfiguration
     metadata:
       name: gardener-admission-controller
    -webhooks: []
    +webhooks:
    +- name: sync-provider-secret-labels.gardener.cloud
    +  admissionReviewVersions: ["v1", "v1beta1"]
    +  timeoutSeconds: 10
    +  rules:
    +  - apiGroups:
    +    - ""
    +    apiVersions:
    +    - v1
    +    operations:
    +    - CREATE
    +    - UPDATE
    +    resources:
    +    - secrets
    +  failurePolicy: Fail
    +  namespaceSelector:
    +    matchExpressions:
    +    - {key: gardener.cloud/role, operator: In, values: [project]}
    +  clientConfig:
    +    {{- if .Values.global.deployment.virtualGarden.enabled }}
    +    url: https://gardener-admission-controller.garden/webhooks/sync-provider-secret-labels
    +    {{- else }}
    +    service:
    +      namespace: garden
    +      name: gardener-admission-controller
    +      path: /webhooks/sync-provider-secret-labels
    +    {{- end }}
    +    caBundle: {{ required ".Values.global.admission.config.server.webhooks.tls.caBundle is required" (b64enc .Values.global.admission.config.server.webhooks.tls.caBundle) }}
    +  sideEffects: None
     {{- end }}
    
  • cmd/gardener-extension-admission-local/app/app.go+4 2 modified
    @@ -26,8 +26,9 @@ import (
     	extensionscmdcontroller "github.com/gardener/gardener/extensions/pkg/controller/cmd"
     	"github.com/gardener/gardener/extensions/pkg/util"
     	extensionscmdwebhook "github.com/gardener/gardener/extensions/pkg/webhook/cmd"
    -	"github.com/gardener/gardener/pkg/apis/core/install"
    +	gardencoreinstall "github.com/gardener/gardener/pkg/apis/core/install"
     	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
    +	securityinstall "github.com/gardener/gardener/pkg/apis/security/install"
     	gardenerhealthz "github.com/gardener/gardener/pkg/healthz"
     	admissioncmd "github.com/gardener/gardener/pkg/provider-local/admission/cmd"
     	localinstall "github.com/gardener/gardener/pkg/provider-local/apis/local/install"
    @@ -123,7 +124,8 @@ func NewAdmissionCommand(ctx context.Context) *cobra.Command {
     				return fmt.Errorf("could not instantiate manager: %w", err)
     			}
     
    -			install.Install(mgr.GetScheme())
    +			gardencoreinstall.Install(mgr.GetScheme())
    +			securityinstall.Install(mgr.GetScheme())
     
     			if err := localinstall.AddToScheme(mgr.GetScheme()); err != nil {
     				return fmt.Errorf("could not update manager scheme: %w", err)
    
  • docs/concepts/apiserver-admission-plugins.md+17 6 modified
    @@ -74,14 +74,27 @@ _(enabled by default)_
     
     This admission controller reacts on `CREATE` and `UPDATE` operations for `BackupBucket`s, `BackupEntry`s, `CloudProfile`s, `NamespacedCloudProfile`s, `Seed`s, `SecretBinding`s, `CredentialsBinding`s, `WorkloadIdentity`s and `Shoot`s. For all the various extension types in the specifications of these objects, it adds a corresponding label in the resource. This would allow extension admission webhooks to filter out the resources they are responsible for and ignore all others. This label is of the form `<extension-type>.extensions.gardener.cloud/<extension-name> : "true"`. For example, an extension label for provider extension type `aws`, looks like `provider.extensions.gardener.cloud/aws : "true"`.
     
    +## `FinalizerRemoval`
    +
    +_(enabled by default)_
    +
    +This admission controller reacts on `UPDATE` operations for `CredentialsBinding`s, `SecretBinding`s, `Shoot`s. 
    +It ensures that the finalizers of these resources are not removed by users, as long as the affected resource is still in use.
    +For `CredentialsBinding`s and `SecretBinding`s this means, that the `gardener` finalizer can only be removed if the binding is not referenced by any `Shoot`.
    +In case of `Shoot`s, the `gardener` finalizer can only be removed if the last operation of the `Shoot` indicates a successful deletion. 
    +
     ## `ProjectValidator`
     
     _(enabled by default)_
     
    -This admission controller reacts on `CREATE` operations for `Project`s.
    +This admission controller reacts on `CREATE` and `UPDATE` operations for `Project`s.
     It prevents creating `Project`s with a non-empty `.spec.namespace` if the value in `.spec.namespace` does not start with `garden-`.
     
    -⚠️ This admission plugin will be removed in a future release and its business logic will be incorporated into the static validation of the `gardener-apiserver`.
    +In addition, the project specification is initialized during creation:
    +- `.spec.createdBy` is set to the user creating the project.
    +- `.spec.owner` defaults to the value of `.spec.createdBy` if it is not specified.
    +
    +During subsequent updates, it ensures that the project owner is included in the `.spec.members` list.
     
     ## `ResourceQuota`
     
    @@ -96,11 +109,9 @@ _(enabled by default)_
     
     This admission controller reacts on `CREATE` and `UPDATE` operations for `CloudProfile`s, `Project`s, `SecretBinding`s, `Seed`s, and `Shoot`s.
     Generally, it checks whether referred resources stated in the specifications of these objects exist in the system (e.g., if a referenced `Secret` exists).
    -However, it also has some special behaviours for certain resources:
     
    +However, it also has some special behaviours for certain resources:
     * `CloudProfile`s: It rejects removing Kubernetes or machine image versions if there is at least one `Shoot` that refers to them.
    -* `Project`s: It sets the `.spec.createdBy` field for newly created `Project` resources, and defaults the `.spec.owner` field in case it is empty (to the same value of `.spec.createdBy`).
    -* `Shoot`s: It sets the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.
     
     ## `SeedValidator`
     
    @@ -187,7 +198,7 @@ _(enabled by default)_
     This admission controller reacts on `CREATE`, `UPDATE` and `DELETE` operations for `Shoot`s.
     It validates certain configurations in the specification against the referred `CloudProfile` (e.g., machine images, machine types, used Kubernetes version, ...).
     Generally, it performs validations that cannot be handled by the static API validation due to their dynamic nature (e.g., when something needs to be checked against referred resources).
    -Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version).
    +Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version) and setting the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.
     
     ## `ShootManagedSeed`
     
    
  • pkg/admissioncontroller/webhook/add.go+8 0 modified
    @@ -18,6 +18,7 @@ import (
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/internaldomainsecret"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/kubeconfigsecret"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/namespacedeletion"
    +	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/providersecretlabels"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/resourcesize"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/seedrestriction"
     	"github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref"
    @@ -65,6 +66,13 @@ func AddToManager(
     		return fmt.Errorf("failed adding %s webhook handler: %w", namespacedeletion.HandlerName, err)
     	}
     
    +	if err := (&providersecretlabels.Handler{
    +		Logger: mgr.GetLogger().WithName("webhook").WithName(providersecretlabels.HandlerName),
    +		Client: mgr.GetClient(),
    +	}).AddToManager(mgr); err != nil {
    +		return fmt.Errorf("failed adding %s webhook handler: %w", providersecretlabels.HandlerName, err)
    +	}
    +
     	if err := (&resourcesize.Handler{
     		Logger: mgr.GetLogger().WithName("webhook").WithName(resourcesize.HandlerName),
     		Config: cfg.Server.ResourceAdmissionConfiguration,
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/add.go+28 0 added
    @@ -0,0 +1,28 @@
    +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels
    +
    +import (
    +	corev1 "k8s.io/api/core/v1"
    +	"sigs.k8s.io/controller-runtime/pkg/manager"
    +	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    +)
    +
    +const (
    +	// HandlerName is the name of this admission webhook handler.
    +	HandlerName = "sync-provider-secret-labels"
    +	// WebhookPath is the HTTP handler path for this admission webhook handler.
    +	WebhookPath = "/webhooks/sync-provider-secret-labels"
    +)
    +
    +// AddToManager adds Handler to the given manager.
    +func (h *Handler) AddToManager(mgr manager.Manager) error {
    +	webhook := admission.
    +		WithCustomDefaulter(mgr.GetScheme(), &corev1.Secret{}, h).
    +		WithRecoverPanic(true)
    +
    +	mgr.GetWebhookServer().Register(WebhookPath, webhook)
    +	return nil
    +}
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/handler.go+99 0 added
    @@ -0,0 +1,99 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels
    +
    +import (
    +	"context"
    +	"fmt"
    +	"strings"
    +
    +	"github.com/go-logr/logr"
    +	corev1 "k8s.io/api/core/v1"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/runtime"
    +	"k8s.io/apimachinery/pkg/util/sets"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
    +	v1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +)
    +
    +// Handler syncs the provider labels on Secrets referenced in SecretBindings or CredentialsBindings.
    +type Handler struct {
    +	Logger logr.Logger
    +	Client client.Client
    +}
    +
    +// Default syncs the provider labels.
    +func (h *Handler) Default(ctx context.Context, obj runtime.Object) error {
    +	secret, ok := obj.(*corev1.Secret)
    +	if !ok {
    +		return fmt.Errorf("expected secret but got %T", obj)
    +	}
    +
    +	typesFromSecretBindings, err := h.fetchProviderTypesFromSecretBindings(ctx, secret)
    +	if err != nil {
    +		return fmt.Errorf("failed fetching provider types from SecretBindings: %w", err)
    +	}
    +
    +	typesFromCredentialsBindings, err := h.fetchProviderTypesFromCredentialsBindings(ctx, secret)
    +	if err != nil {
    +		return fmt.Errorf("failed fetching provider types from CredentialsBindings: %w", err)
    +	}
    +
    +	if typesFromSecretBindings.Len()+typesFromCredentialsBindings.Len() > 0 {
    +		maintainLabels(secret, typesFromSecretBindings.Union(typesFromCredentialsBindings).UnsortedList()...)
    +	}
    +
    +	return nil
    +}
    +
    +func (h *Handler) fetchProviderTypesFromSecretBindings(ctx context.Context, secret *corev1.Secret) (sets.Set[string], error) {
    +	secretBindingList := &gardencorev1beta1.SecretBindingList{}
    +	if err := h.Client.List(ctx, secretBindingList); err != nil {
    +		return nil, fmt.Errorf("failed to list SecretBindings: %w", err)
    +	}
    +
    +	providerTypes := sets.New[string]()
    +	for _, secretBinding := range secretBindingList.Items {
    +		if secretBinding.SecretRef.Name == secret.Name &&
    +			secretBinding.SecretRef.Namespace == secret.Namespace {
    +			providerTypes.Insert(v1beta1helper.GetSecretBindingTypes(&secretBinding)...)
    +		}
    +	}
    +	return providerTypes, nil
    +}
    +
    +func (h *Handler) fetchProviderTypesFromCredentialsBindings(ctx context.Context, secret *corev1.Secret) (sets.Set[string], error) {
    +	credentialsBindingList := &securityv1alpha1.CredentialsBindingList{}
    +	if err := h.Client.List(ctx, credentialsBindingList); err != nil {
    +		return nil, fmt.Errorf("failed to list CredentialsBindings: %w", err)
    +	}
    +
    +	providerTypes := sets.New[string]()
    +	for _, credentialsBinding := range credentialsBindingList.Items {
    +		if credentialsBinding.CredentialsRef.APIVersion == corev1.SchemeGroupVersion.String() &&
    +			credentialsBinding.CredentialsRef.Kind == "Secret" &&
    +			credentialsBinding.CredentialsRef.Name == secret.Name &&
    +			credentialsBinding.CredentialsRef.Namespace == secret.Namespace {
    +			providerTypes.Insert(credentialsBinding.Provider.Type)
    +		}
    +	}
    +	return providerTypes, nil
    +}
    +
    +func maintainLabels(secret *corev1.Secret, providerTypes ...string) {
    +	for k := range secret.Labels {
    +		if strings.HasPrefix(k, v1beta1constants.LabelShootProviderPrefix) {
    +			delete(secret.Labels, k)
    +		}
    +	}
    +
    +	for _, providerType := range providerTypes {
    +		metav1.SetMetaDataLabel(&secret.ObjectMeta, v1beta1constants.LabelShootProviderPrefix+providerType, "true")
    +	}
    +}
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/handler_test.go+138 0 added
    @@ -0,0 +1,138 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels_test
    +
    +import (
    +	"context"
    +
    +	"github.com/go-logr/logr"
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	corev1 "k8s.io/api/core/v1"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    +	logzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
    +
    +	. "github.com/gardener/gardener/pkg/admissioncontroller/webhook/admission/providersecretlabels"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +	"github.com/gardener/gardener/pkg/client/kubernetes"
    +	"github.com/gardener/gardener/pkg/logger"
    +)
    +
    +var _ = Describe("handler", func() {
    +	var (
    +		ctx context.Context
    +
    +		log        logr.Logger
    +		fakeClient client.Client
    +		handler    *Handler
    +
    +		namespace            string
    +		provider1, provider2 string
    +		secret               *corev1.Secret
    +		secretBinding        *gardencorev1beta1.SecretBinding
    +		credentialsBinding   *securityv1alpha1.CredentialsBinding
    +	)
    +
    +	BeforeEach(func() {
    +		ctx = context.Background()
    +		log = logger.MustNewZapLogger(logger.DebugLevel, logger.FormatJSON, logzap.WriteTo(GinkgoWriter))
    +
    +		fakeClient = fakeclient.NewClientBuilder().WithScheme(kubernetes.GardenScheme).Build()
    +		handler = &Handler{
    +			Logger: log,
    +			Client: fakeClient,
    +		}
    +
    +		namespace = "test"
    +		secret = &corev1.Secret{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-secret",
    +				Namespace: namespace,
    +			},
    +		}
    +
    +		provider1 = "provider1"
    +		provider2 = "provider2"
    +
    +		secretBinding = &gardencorev1beta1.SecretBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-secret-binding",
    +				Namespace: namespace,
    +			},
    +			SecretRef: corev1.SecretReference{
    +				Name:      "test-secret",
    +				Namespace: namespace,
    +			},
    +			Provider: &gardencorev1beta1.SecretBindingProvider{
    +				Type: provider1,
    +			},
    +		}
    +
    +		credentialsBinding = &securityv1alpha1.CredentialsBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "test-credentials-binding",
    +				Namespace: "another-namespace",
    +			},
    +			CredentialsRef: corev1.ObjectReference{
    +				APIVersion: corev1.SchemeGroupVersion.String(),
    +				Kind:       "Secret",
    +				Name:       "test-secret",
    +				Namespace:  namespace,
    +			},
    +			Provider: securityv1alpha1.CredentialsBindingProvider{
    +				Type: provider2,
    +			},
    +		}
    +	})
    +
    +	It("should set the provider label based on the available credential and secret bindings", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +	})
    +
    +	It("should remove undesired provider type", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +		secret.Labels = map[string]string{
    +			"provider.shoot.gardener.cloud/provider1": "true",
    +			"provider.shoot.gardener.cloud/provider2": "true",
    +			"provider.shoot.gardener.cloud/provider3": "true",
    +		}
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +		Expect(secret.Labels).NotTo(HaveKey("provider.shoot.gardener.cloud/provider3"))
    +	})
    +
    +	It("should add the missing provider and delete the wrong one", func() {
    +		Expect(fakeClient.Create(ctx, secretBinding)).To(Succeed())
    +		Expect(fakeClient.Create(ctx, credentialsBinding)).To(Succeed())
    +		secret.Labels = map[string]string{
    +			"provider.shoot.gardener.cloud/provider1": "true",
    +			"provider.shoot.gardener.cloud/provider3": "true",
    +		}
    +
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider1", "true"))
    +		Expect(secret.Labels).To(HaveKeyWithValue("provider.shoot.gardener.cloud/provider2", "true"))
    +		Expect(secret.Labels).NotTo(HaveKey("provider.shoot.gardener.cloud/provider3"))
    +	})
    +
    +	It("should not add provider labels when secret is unreferenced", func() {
    +		Expect(handler.Default(ctx, secret)).To(Succeed())
    +		Expect(secret.Labels).To(BeEmpty())
    +	})
    +})
    
  • pkg/admissioncontroller/webhook/admission/providersecretlabels/providersecretlabels_suite_test.go+17 0 added
    @@ -0,0 +1,17 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package providersecretlabels_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +)
    +
    +func TestProviderSecretLabels(t *testing.T) {
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "AdmissionController Webhook Admission ProviderSecretLabels Suite")
    +}
    
  • pkg/apis/core/helper/secretbinding.go+3 0 modified
    @@ -12,5 +12,8 @@ import (
     
     // GetSecretBindingTypes returns the SecretBinding provider types.
     func GetSecretBindingTypes(secretBinding *core.SecretBinding) []string {
    +	if secretBinding.Provider == nil {
    +		return []string{}
    +	}
     	return strings.Split(secretBinding.Provider.Type, ",")
     }
    
  • pkg/apis/core/helper/secretbinding_test.go+1 0 modified
    @@ -19,6 +19,7 @@ var _ = Describe("Helper", func() {
     			Expect(actual).To(Equal(expected))
     		},
     
    +		Entry("with nil provider type", &core.SecretBinding{Provider: nil}, []string{}),
     		Entry("with single-value provider type", &core.SecretBinding{Provider: &core.SecretBindingProvider{Type: "foo"}}, []string{"foo"}),
     		Entry("with multi-value provider type", &core.SecretBinding{Provider: &core.SecretBindingProvider{Type: "foo,bar,baz"}}, []string{"foo", "bar", "baz"}),
     	)
    
  • pkg/apis/core/v1beta1/helper/secretbinding.go+3 0 modified
    @@ -44,5 +44,8 @@ func AddTypeToSecretBinding(secretBinding *gardencorev1beta1.SecretBinding, prov
     
     // GetSecretBindingTypes returns the SecretBinding provider types.
     func GetSecretBindingTypes(secretBinding *gardencorev1beta1.SecretBinding) []string {
    +	if secretBinding.Provider == nil {
    +		return []string{}
    +	}
     	return strings.Split(secretBinding.Provider.Type, ",")
     }
    
  • pkg/apis/core/v1beta1/helper/secretbinding_test.go+1 0 modified
    @@ -45,6 +45,7 @@ var _ = Describe("Helper", func() {
     			Expect(actual).To(Equal(expected))
     		},
     
    +		Entry("with nil provider type", &gardencorev1beta1.SecretBinding{Provider: nil}, []string{}),
     		Entry("with single-value provider type", &gardencorev1beta1.SecretBinding{Provider: &gardencorev1beta1.SecretBindingProvider{Type: "foo"}}, []string{"foo"}),
     		Entry("with multi-value provider type", &gardencorev1beta1.SecretBinding{Provider: &gardencorev1beta1.SecretBindingProvider{Type: "foo,bar,baz"}}, []string{"foo", "bar", "baz"}),
     	)
    
  • pkg/apiserver/plugins.go+2 0 modified
    @@ -14,6 +14,7 @@ import (
     	"github.com/gardener/gardener/plugin/pkg/global/deletionconfirmation"
     	"github.com/gardener/gardener/plugin/pkg/global/extensionlabels"
     	"github.com/gardener/gardener/plugin/pkg/global/extensionvalidation"
    +	"github.com/gardener/gardener/plugin/pkg/global/finalizerremoval"
     	"github.com/gardener/gardener/plugin/pkg/global/resourcereferencemanager"
     	managedseedshoot "github.com/gardener/gardener/plugin/pkg/managedseed/shoot"
     	managedseedvalidator "github.com/gardener/gardener/plugin/pkg/managedseed/validator"
    @@ -39,6 +40,7 @@ import (
     func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
     	resourcereferencemanager.Register(plugins)
     	deletionconfirmation.Register(plugins)
    +	finalizerremoval.Register(plugins)
     	extensionvalidation.Register(plugins)
     	extensionlabels.Register(plugins)
     	shoottolerationrestriction.Register(plugins)
    
  • pkg/apiserver/registry/core/backupbucket/backupbucket_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBackupBucket(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core BackupBucket Suite")
    +	RunSpecs(t, "APIServer Registry Core BackupBucket Suite")
     }
    
  • pkg/apiserver/registry/core/backupentry/backupentry_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBackupEntry(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core BackupEntry Suite")
    +	RunSpecs(t, "APIServer Registry Core BackupEntry Suite")
     }
    
  • pkg/apiserver/registry/core/cloudprofile/cloudprofile_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestCloudProfile(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core CloudProfile Suite")
    +	RunSpecs(t, "APIServer Registry Core CloudProfile Suite")
     }
    
  • pkg/apiserver/registry/core/controllerinstallation/strategy_test.go+1 1 modified
    @@ -20,7 +20,7 @@ import (
     
     func TestControllerInstallation(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry ControllerInstallation Suite")
    +	RunSpecs(t, "APIServer Registry ControllerInstallation Suite")
     }
     
     var _ = Describe("ToSelectableFields", func() {
    
  • pkg/apiserver/registry/core/namespacedcloudprofile/namespacedcloudprofile_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestNamespacedCloudProfile(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core NamespacedCloudProfile Suite")
    +	RunSpecs(t, "APIServer Registry Core NamespacedCloudProfile Suite")
     }
    
  • pkg/apiserver/registry/core/project/project_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestProject(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Project Suite")
    +	RunSpecs(t, "APIServer Registry Core Project Suite")
     }
    
  • pkg/apiserver/registry/core/secretbinding/secretbinding_suite_test.go+1 1 modified
    @@ -16,5 +16,5 @@ import (
     func TestSecretBinding(t *testing.T) {
     	features.RegisterFeatureGates()
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core SecretBinding Suite")
    +	RunSpecs(t, "APIServer Registry Core SecretBinding Suite")
     }
    
  • pkg/apiserver/registry/core/secretbinding/strategy.go+6 1 modified
    @@ -28,7 +28,12 @@ func (secretBindingStrategy) NamespaceScoped() bool {
     	return true
     }
     
    -func (secretBindingStrategy) PrepareForCreate(_ context.Context, _ runtime.Object) {
    +func (s secretBindingStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) {
    +	binding := obj.(*core.SecretBinding)
    +
    +	if binding.GetName() == "" {
    +		binding.SetName(s.GenerateName(binding.GetGenerateName()))
    +	}
     }
     
     func (secretBindingStrategy) Validate(_ context.Context, obj runtime.Object) field.ErrorList {
    
  • pkg/apiserver/registry/core/secretbinding/strategy_test.go+29 0 modified
    @@ -34,6 +34,35 @@ var _ = Describe("Strategy", func() {
     		}
     	})
     
    +	Describe("#PrepareForCreate", func() {
    +		It("should set the name if not set", func() {
    +			secretBinding.SetName("")
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetName()).NotTo(BeEmpty())
    +		})
    +
    +		It("should set name with generateName as prefix", func() {
    +			genName := "prefix-"
    +			secretBinding.GenerateName = genName
    +			secretBinding.Name = ""
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetGenerateName()).To(Equal(genName))
    +			Expect(secretBinding.GetName()).To(HavePrefix(genName))
    +		})
    +
    +		It("should not overwrite already set name", func() {
    +			secretBinding.SetName("bar")
    +
    +			secretbindingregistry.Strategy.PrepareForCreate(context.TODO(), secretBinding)
    +
    +			Expect(secretBinding.GetName()).To(Equal("bar"))
    +		})
    +	})
    +
     	Describe("#Validate", func() {
     		It("should forbid creating SecretBinding when provider is nil or empty", func() {
     			secretBinding.Provider = nil
    
  • pkg/apiserver/registry/core/seed/seed_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestSeed(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Seed Suite")
    +	RunSpecs(t, "APIServer Registry Core Seed Suite")
     }
    
  • pkg/apiserver/registry/core/shoot/shoot_suite_test.go+1 1 modified
    @@ -16,5 +16,5 @@ import (
     func TestShoot(t *testing.T) {
     	features.RegisterFeatureGates()
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Shoot Suite")
    +	RunSpecs(t, "APIServer Registry Core Shoot Suite")
     }
    
  • pkg/apiserver/registry/core/shoot/storage/storage_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestStorage(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Core Shoot Storage Suite")
    +	RunSpecs(t, "APIServer Registry Core Shoot Storage Suite")
     }
    
  • pkg/apiserver/registry/operations/bastion/bastion_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestBastion(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Operations Bastion Suite")
    +	RunSpecs(t, "APIServer Registry Operations Bastion Suite")
     }
    
  • pkg/apiserver/registry/security/credentialsbinding/credentialsbinding_suite_test.go+20 0 added
    @@ -0,0 +1,20 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package credentialsbinding_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +
    +	"github.com/gardener/gardener/pkg/apiserver/features"
    +)
    +
    +func TestCredentialsBinding(t *testing.T) {
    +	features.RegisterFeatureGates()
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "APIServer Registry Security CredentialsBinding Suite")
    +}
    
  • pkg/apiserver/registry/security/credentialsbinding/strategy.go+5 1 modified
    @@ -28,8 +28,12 @@ func (credentialsBindingStrategy) NamespaceScoped() bool {
     	return true
     }
     
    -func (credentialsBindingStrategy) PrepareForCreate(_ context.Context, _ runtime.Object) {
    +func (c credentialsBindingStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) {
    +	credentialsbinding := obj.(*security.CredentialsBinding)
     
    +	if credentialsbinding.GetName() == "" {
    +		credentialsbinding.SetName(c.GenerateName(credentialsbinding.GetGenerateName()))
    +	}
     }
     
     func (credentialsBindingStrategy) PrepareForUpdate(_ context.Context, _, _ runtime.Object) {
    
  • pkg/apiserver/registry/security/credentialsbinding/strategy_test.go+58 0 added
    @@ -0,0 +1,58 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package credentialsbinding_test
    +
    +import (
    +	"context"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	credentialsbindingregistry "github.com/gardener/gardener/pkg/apiserver/registry/security/credentialsbinding"
    +)
    +
    +var _ = Describe("Strategy", func() {
    +	var credentialsBinding *security.CredentialsBinding
    +
    +	BeforeEach(func() {
    +		credentialsBinding = &security.CredentialsBinding{
    +			ObjectMeta: metav1.ObjectMeta{
    +				Name:      "profile",
    +				Namespace: "garden",
    +			},
    +		}
    +	})
    +
    +	Describe("#PrepareForCreate", func() {
    +		It("should set the name if not set", func() {
    +			credentialsBinding.SetName("")
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetName()).NotTo(BeEmpty())
    +		})
    +
    +		It("should set name with generateName as prefix", func() {
    +			genName := "prefix-"
    +			credentialsBinding.GenerateName = genName
    +			credentialsBinding.Name = ""
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetGenerateName()).To(Equal(genName))
    +			Expect(credentialsBinding.GetName()).To(HavePrefix(genName))
    +		})
    +
    +		It("should not overwrite already set name", func() {
    +			credentialsBinding.SetName("bar")
    +
    +			credentialsbindingregistry.Strategy.PrepareForCreate(context.TODO(), credentialsBinding)
    +
    +			Expect(credentialsBinding.GetName()).To(Equal("bar"))
    +		})
    +	})
    +})
    
  • pkg/apiserver/registry/security/workloadidentity/storage/storage_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestStorage(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Security WorkloadIdentity Storage Suite")
    +	RunSpecs(t, "APIServer Registry Security WorkloadIdentity Storage Suite")
     }
    
  • pkg/apiserver/registry/security/workloadidentity/workloadidentity_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestWorkloadIdentity(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry Security WorkloadIdentity Suite")
    +	RunSpecs(t, "APIServer Registry Security WorkloadIdentity Suite")
     }
    
  • pkg/apiserver/registry/seedmanagement/managedseed/managedseed_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestManagedSeed(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry SeedManagement ManagedSeed Suite")
    +	RunSpecs(t, "APIServer Registry SeedManagement ManagedSeed Suite")
     }
    
  • pkg/apiserver/registry/seedmanagement/managedseedset/managedseedset_suite_test.go+1 1 modified
    @@ -13,5 +13,5 @@ import (
     
     func TestManagedSeedSet(t *testing.T) {
     	RegisterFailHandler(Fail)
    -	RunSpecs(t, "Registry SeedManagement ManagedSeedSet Suite")
    +	RunSpecs(t, "APIServer Registry SeedManagement ManagedSeedSet Suite")
     }
    
  • pkg/component/gardener/admissioncontroller/admission_controller.go+1 0 modified
    @@ -131,6 +131,7 @@ func (a *gardenerAdmissionController) Deploy(ctx context.Context) error {
     		a.clusterRole(),
     		a.clusterRoleBinding(virtualGardenAccessSecret.ServiceAccountName),
     		a.validatingWebhookConfiguration(caSecret),
    +		a.mutatingWebhookConfiguration(caSecret),
     	)
     	if err != nil {
     		return err
    
  • pkg/component/gardener/admissioncontroller/admission_controller_test.go+40 0 modified
    @@ -502,6 +502,7 @@ func verifyExpectations(ctx context.Context, fakeClient client.Client, consistOf
     		clusterRole(),
     		clusterRoleBinding(),
     		validatingWebhookConfiguration(namespace, caGardener.Data["bundle.crt"], testValues),
    +		mutatingWebhookConfiguration(namespace, caGardener.Data["bundle.crt"]),
     	))
     
     	virtualManagedResourceSecret := &corev1.Secret{
    @@ -1312,3 +1313,42 @@ func validatingWebhookConfiguration(namespace string, caBundle []byte, testValue
     
     	return webhookConfig
     }
    +
    +func mutatingWebhookConfiguration(namespace string, caBundle []byte) *admissionregistrationv1.MutatingWebhookConfiguration {
    +	var (
    +		failurePolicyFail = admissionregistrationv1.Fail
    +		sideEffectsNone   = admissionregistrationv1.SideEffectClassNone
    +	)
    +
    +	return &admissionregistrationv1.MutatingWebhookConfiguration{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name: "gardener-admission-controller",
    +		},
    +		Webhooks: []admissionregistrationv1.MutatingWebhook{
    +			{
    +				Name:                    "sync-provider-secret-labels.gardener.cloud",
    +				AdmissionReviewVersions: []string{"v1", "v1beta1"},
    +				TimeoutSeconds:          ptr.To[int32](10),
    +				Rules: []admissionregistrationv1.RuleWithOperations{{
    +					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
    +					Rule: admissionregistrationv1.Rule{
    +						APIGroups:   []string{""},
    +						APIVersions: []string{"v1"},
    +						Resources:   []string{"secrets"},
    +					},
    +				}},
    +				FailurePolicy: &failurePolicyFail,
    +				NamespaceSelector: &metav1.LabelSelector{
    +					MatchLabels: map[string]string{
    +						"gardener.cloud/role": "project",
    +					},
    +				},
    +				ClientConfig: admissionregistrationv1.WebhookClientConfig{
    +					URL:      ptr.To("https://gardener-admission-controller." + namespace + "/webhooks/sync-provider-secret-labels"),
    +					CABundle: caBundle,
    +				},
    +				SideEffects: &sideEffectsNone,
    +			},
    +		},
    +	}
    +}
    
  • pkg/component/gardener/admissioncontroller/webhooks.go+41 0 modified
    @@ -375,6 +375,47 @@ func (a *gardenerAdmissionController) validatingWebhookConfiguration(caSecret *c
     	return validatingWebhook
     }
     
    +func (a *gardenerAdmissionController) mutatingWebhookConfiguration(caSecret *corev1.Secret) *admissionregistrationv1.MutatingWebhookConfiguration {
    +	var (
    +		failurePolicyFail = admissionregistrationv1.Fail
    +		sideEffectsNone   = admissionregistrationv1.SideEffectClassNone
    +
    +		caBundle = caSecret.Data[secrets.DataKeyCertificateBundle]
    +	)
    +
    +	return &admissionregistrationv1.MutatingWebhookConfiguration{
    +		ObjectMeta: metav1.ObjectMeta{
    +			Name: DeploymentName,
    +		},
    +		Webhooks: []admissionregistrationv1.MutatingWebhook{
    +			{
    +				Name:                    "sync-provider-secret-labels.gardener.cloud",
    +				AdmissionReviewVersions: []string{"v1", "v1beta1"},
    +				TimeoutSeconds:          ptr.To[int32](10),
    +				Rules: []admissionregistrationv1.RuleWithOperations{{
    +					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
    +					Rule: admissionregistrationv1.Rule{
    +						APIGroups:   []string{corev1.GroupName},
    +						APIVersions: []string{"v1"},
    +						Resources:   []string{"secrets"},
    +					},
    +				}},
    +				FailurePolicy: &failurePolicyFail,
    +				NamespaceSelector: &metav1.LabelSelector{
    +					MatchLabels: map[string]string{
    +						v1beta1constants.GardenRole: v1beta1constants.GardenRoleProject,
    +					},
    +				},
    +				ClientConfig: admissionregistrationv1.WebhookClientConfig{
    +					URL:      buildClientConfigURL("/webhooks/sync-provider-secret-labels", a.namespace),
    +					CABundle: caBundle,
    +				},
    +				SideEffects: &sideEffectsNone,
    +			},
    +		},
    +	}
    +}
    +
     func buildWebhookConfigRulesForResourceSize(config *admissioncontrollerconfigv1alpha1.ResourceAdmissionConfiguration) []admissionregistrationv1.RuleWithOperations {
     	if config == nil || len(config.Limits) == 0 {
     		return nil
    
  • pkg/controllermanager/controller/secretbinding/reconciler.go+9 11 modified
    @@ -143,17 +143,15 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
     		}
     	}
     
    -	if secretBinding.Provider != nil {
    -		types := v1beta1helper.GetSecretBindingTypes(secretBinding)
    -		for _, t := range types {
    -			labelKey := v1beta1constants.LabelShootProviderPrefix + t
    -
    -			if !metav1.HasLabel(secret.ObjectMeta, labelKey) {
    -				patch := client.MergeFrom(secret.DeepCopy())
    -				metav1.SetMetaDataLabel(&secret.ObjectMeta, labelKey, "true")
    -				if err := r.Client.Patch(ctx, secret, patch); err != nil {
    -					return reconcile.Result{}, fmt.Errorf("failed to add provider type label to Secret referenced in SecretBinding: %w", err)
    -				}
    +	types := v1beta1helper.GetSecretBindingTypes(secretBinding)
    +	for _, t := range types {
    +		labelKey := v1beta1constants.LabelShootProviderPrefix + t
    +
    +		if !metav1.HasLabel(secret.ObjectMeta, labelKey) {
    +			patch := client.MergeFrom(secret.DeepCopy())
    +			metav1.SetMetaDataLabel(&secret.ObjectMeta, labelKey, "true")
    +			if err := r.Client.Patch(ctx, secret, patch); err != nil {
    +				return reconcile.Result{}, fmt.Errorf("failed to add provider type label to Secret referenced in SecretBinding: %w", err)
     			}
     		}
     	}
    
  • pkg/provider-local/admission/cmd/options.go+1 0 modified
    @@ -14,6 +14,7 @@ import (
     func GardenWebhookSwitchOptions() *extensionscmdwebhook.SwitchOptions {
     	return extensionscmdwebhook.NewSwitchOptions(
     		extensionscmdwebhook.Switch(validator.Name, validator.New),
    +		extensionscmdwebhook.Switch(validator.SecretsValidatorName, validator.NewSecretsWebhook),
     		extensionscmdwebhook.Switch(mutator.Name, mutator.New),
     	)
     }
    
  • pkg/provider-local/admission/validator/secret.go+48 0 added
    @@ -0,0 +1,48 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package validator
    +
    +import (
    +	"context"
    +	"fmt"
    +
    +	corev1 "k8s.io/api/core/v1"
    +	apiequality "k8s.io/apimachinery/pkg/api/equality"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
    +)
    +
    +type secretValidator struct{}
    +
    +// NewSecretValidator returns a new instance of a secret validator.
    +func NewSecretValidator() extensionswebhook.Validator {
    +	return &secretValidator{}
    +}
    +
    +// Validate checks whether the data is empty.
    +func (s *secretValidator) Validate(_ context.Context, newObj, oldObj client.Object) error {
    +	secret, ok := newObj.(*corev1.Secret)
    +	if !ok {
    +		return fmt.Errorf("wrong object type %T", newObj)
    +	}
    +
    +	if oldObj != nil {
    +		oldSecret, ok := oldObj.(*corev1.Secret)
    +		if !ok {
    +			return fmt.Errorf("wrong object type %T for old object", oldObj)
    +		}
    +
    +		if apiequality.Semantic.DeepEqual(secret.Data, oldSecret.Data) {
    +			return nil
    +		}
    +	}
    +
    +	if len(secret.Data) != 0 {
    +		return fmt.Errorf("secret data should be empty")
    +	}
    +
    +	return nil
    +}
    
  • pkg/provider-local/admission/validator/webhook.go+24 1 modified
    @@ -5,18 +5,22 @@
     package validator
     
     import (
    +	corev1 "k8s.io/api/core/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"sigs.k8s.io/controller-runtime/pkg/log"
     	"sigs.k8s.io/controller-runtime/pkg/manager"
     
     	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
     	"github.com/gardener/gardener/pkg/apis/core"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
     	"github.com/gardener/gardener/pkg/provider-local/local"
     )
     
     const (
     	// Name is a name for a validation webhook.
     	Name = "validator"
    +	// SecretsValidatorName is the name of the secrets validator.
    +	SecretsValidatorName = "secrets." + Name
     )
     
     var logger = log.Log.WithName("local-validator-webhook")
    @@ -31,10 +35,29 @@ func New(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
     		Path:     "/webhooks/validate",
     		Validators: map[extensionswebhook.Validator][]extensionswebhook.Type{
     			NewNamespacedCloudProfileValidator(mgr): {{Obj: &core.NamespacedCloudProfile{}}},
    +			NewWorkloadIdentityValidator():          {{Obj: &securityv1alpha1.WorkloadIdentity{}}},
     		},
     		Target: extensionswebhook.TargetSeed,
     		ObjectSelector: &metav1.LabelSelector{
    -			MatchLabels: map[string]string{"provider.extensions.gardener.cloud/local": "true"},
    +			MatchLabels: map[string]string{"provider.extensions.gardener.cloud/" + local.Type: "true"},
    +		},
    +	})
    +}
    +
    +// NewSecretsWebhook creates a new validation webhook for Secrets.
    +func NewSecretsWebhook(mgr manager.Manager) (*extensionswebhook.Webhook, error) {
    +	logger.Info("Setting up webhook", "name", SecretsValidatorName)
    +
    +	return extensionswebhook.New(mgr, extensionswebhook.Args{
    +		Provider: local.Type,
    +		Name:     SecretsValidatorName,
    +		Path:     "/webhooks/validate/secrets",
    +		Validators: map[extensionswebhook.Validator][]extensionswebhook.Type{
    +			NewSecretValidator(): {{Obj: &corev1.Secret{}}},
    +		},
    +		Target: extensionswebhook.TargetSeed,
    +		ObjectSelector: &metav1.LabelSelector{
    +			MatchLabels: map[string]string{"provider.shoot.gardener.cloud/" + local.Type: "true"},
     		},
     	})
     }
    
  • pkg/provider-local/admission/validator/workloadidentity.go+38 0 added
    @@ -0,0 +1,38 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package validator
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook"
    +	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
    +)
    +
    +type workloadIdentityValidator struct {
    +}
    +
    +// NewWorkloadIdentityValidator returns a new instance of a WorkloadIdentity validator.
    +func NewWorkloadIdentityValidator() extensionswebhook.Validator {
    +	return &workloadIdentityValidator{}
    +}
    +
    +// Validate checks whether the provider config is empty.
    +func (wi *workloadIdentityValidator) Validate(_ context.Context, newObj, _ client.Object) error {
    +	workloadIdentity, ok := newObj.(*securityv1alpha1.WorkloadIdentity)
    +	if !ok {
    +		return fmt.Errorf("wrong object type %T", newObj)
    +	}
    +
    +	if workloadIdentity.Spec.TargetSystem.ProviderConfig != nil {
    +		return errors.New("target system provider config must be empty")
    +	}
    +
    +	return nil
    +}
    
  • plugin/pkg/global/extensionlabels/admission.go+3 5 modified
    @@ -242,11 +242,9 @@ func addMetaDataLabelsSeed(seed *core.Seed) {
     }
     
     func addMetaDataLabelsSecretBinding(secretBinding *core.SecretBinding) {
    -	if secretBinding.Provider != nil {
    -		types := gardencorehelper.GetSecretBindingTypes(secretBinding)
    -		for _, t := range types {
    -			metav1.SetMetaDataLabel(&secretBinding.ObjectMeta, v1beta1constants.LabelExtensionProviderTypePrefix+t, "true")
    -		}
    +	types := gardencorehelper.GetSecretBindingTypes(secretBinding)
    +	for _, t := range types {
    +		metav1.SetMetaDataLabel(&secretBinding.ObjectMeta, v1beta1constants.LabelExtensionProviderTypePrefix+t, "true")
     	}
     }
     
    
  • plugin/pkg/global/finalizerremoval/admission.go+196 0 added
    @@ -0,0 +1,196 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +	"io"
    +	"slices"
    +
    +	apierrors "k8s.io/apimachinery/pkg/api/errors"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apimachinery/pkg/labels"
    +	"k8s.io/apimachinery/pkg/util/sets"
    +	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/utils/ptr"
    +	"sigs.k8s.io/controller-runtime/pkg/client"
    +
    +	"github.com/gardener/gardener/pkg/apis/core"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	admissioninitializer "github.com/gardener/gardener/pkg/apiserver/admission/initializer"
    +	gardencoreinformers "github.com/gardener/gardener/pkg/client/core/informers/externalversions"
    +	gardencorev1beta1listers "github.com/gardener/gardener/pkg/client/core/listers/core/v1beta1"
    +	plugin "github.com/gardener/gardener/plugin/pkg"
    +)
    +
    +// Register registers a plugin.
    +func Register(plugins *admission.Plugins) {
    +	plugins.Register(plugin.PluginNameFinalizerRemoval, func(_ io.Reader) (admission.Interface, error) {
    +		return New()
    +	})
    +}
    +
    +// FinalizerRemoval contains listers and admission handler.
    +type FinalizerRemoval struct {
    +	*admission.Handler
    +	shootLister gardencorev1beta1listers.ShootLister
    +	readyFunc   admission.ReadyFunc
    +}
    +
    +var (
    +	_ = admissioninitializer.WantsCoreInformerFactory(&FinalizerRemoval{})
    +
    +	readyFuncs []admission.ReadyFunc
    +)
    +
    +// New creates a new FinalizerRemoval admission plugin.
    +func New() (*FinalizerRemoval, error) {
    +	return &FinalizerRemoval{
    +		Handler: admission.NewHandler(admission.Update),
    +	}, nil
    +}
    +
    +// AssignReadyFunc assigns the ready function to the admission handler.
    +func (f *FinalizerRemoval) AssignReadyFunc(fn admission.ReadyFunc) {
    +	f.readyFunc = fn
    +	f.SetReadyFunc(fn)
    +}
    +
    +// SetCoreInformerFactory gets Lister from SharedInformerFactory.
    +func (f *FinalizerRemoval) SetCoreInformerFactory(g gardencoreinformers.SharedInformerFactory) {
    +	shootInformer := g.Core().V1beta1().Shoots()
    +	f.shootLister = shootInformer.Lister()
    +
    +	readyFuncs = append(readyFuncs,
    +		shootInformer.Informer().HasSynced,
    +	)
    +}
    +
    +// ValidateInitialization checks whether the plugin was correctly initialized.
    +func (f *FinalizerRemoval) ValidateInitialization() error {
    +	if f.shootLister == nil {
    +		return errors.New("missing shoot lister")
    +	}
    +	return nil
    +}
    +
    +// Admit ensures that finalizers from objects can only be removed if they are not needed anymore.
    +func (f *FinalizerRemoval) Admit(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +	// Wait until the caches have been synced
    +	if f.readyFunc == nil {
    +		f.AssignReadyFunc(func() bool {
    +			for _, readyFunc := range readyFuncs {
    +				if !readyFunc() {
    +					return false
    +				}
    +			}
    +			return true
    +		})
    +	}
    +	if !f.WaitForReady() {
    +		return admission.NewForbidden(a, errors.New("not yet ready to handle request"))
    +	}
    +
    +	var (
    +		err            error
    +		newObj, oldObj client.Object
    +	)
    +
    +	oldObj, ok := a.GetOldObject().(client.Object)
    +	if !ok {
    +		return nil
    +	}
    +
    +	newObj, ok = a.GetObject().(client.Object)
    +	if !ok {
    +		return nil
    +	}
    +
    +	switch a.GetKind().GroupKind() {
    +	case core.Kind("SecretBinding"):
    +		binding, ok := a.GetObject().(*core.SecretBinding)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into SecretBinding object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the SecretBinding is not used by any shoot.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) {
    +			inUse, err := f.isUsedByShoot(binding.Namespace, func(shoot *gardencorev1beta1.Shoot) bool {
    +				return ptr.Deref(shoot.Spec.SecretBindingName, "") == binding.Name
    +			})
    +			if err != nil {
    +				return apierrors.NewInternalError(fmt.Errorf("error checking if secret binding is in use: %w", err))
    +			}
    +			if inUse {
    +				return admission.NewForbidden(a, fmt.Errorf("finalizer must not be removed - secret binding %s/%s is still in use by at least one shoot", binding.Namespace, binding.Name))
    +			}
    +		}
    +	case security.Kind("CredentialsBinding"):
    +		binding, ok := a.GetObject().(*security.CredentialsBinding)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into CredentialsBinding object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the CredentialsBinding is not used by any shoot.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) {
    +			inUse, err := f.isUsedByShoot(binding.Namespace, func(shoot *gardencorev1beta1.Shoot) bool {
    +				return ptr.Deref(shoot.Spec.CredentialsBindingName, "") == binding.Name
    +			})
    +			if err != nil {
    +				return apierrors.NewInternalError(fmt.Errorf("error checking if credentials binding is in use: %w", err))
    +			}
    +			if inUse {
    +				return admission.NewForbidden(a, fmt.Errorf("finalizer must not be removed - credentials binding %s/%s is still in use by at least one shoot", binding.Namespace, binding.Name))
    +			}
    +		}
    +	case core.Kind("Shoot"):
    +		shoot, ok := a.GetObject().(*core.Shoot)
    +		if !ok {
    +			return apierrors.NewBadRequest("could not convert resource into Shoot object")
    +		}
    +
    +		// Allow removal of `gardener` finalizer only if the Shoot deletion has completed successfully.
    +		if isFinalizerRemoved(oldObj, newObj, gardencorev1beta1.GardenerName) && !shootDeletionSucceeded(shoot) {
    +			return admission.NewForbidden(a, fmt.Errorf("finalizer %q cannot be removed because shoot deletion has not completed successfully yet", core.GardenerName))
    +		}
    +	}
    +
    +	if err != nil {
    +		return admission.NewForbidden(a, err)
    +	}
    +	return nil
    +}
    +
    +func (f *FinalizerRemoval) isUsedByShoot(namespace string, inUse func(*gardencorev1beta1.Shoot) bool) (bool, error) {
    +	shoots, err := f.shootLister.Shoots(namespace).List(labels.Everything())
    +	if err != nil {
    +		return false, fmt.Errorf("error retrieving shoots: %w", err)
    +	}
    +
    +	return slices.ContainsFunc(shoots, inUse), nil
    +}
    +
    +func shootDeletionSucceeded(shoot *core.Shoot) bool {
    +	if len(shoot.Status.TechnicalID) == 0 || shoot.Status.LastOperation == nil {
    +		return true
    +	}
    +
    +	lastOperation := shoot.Status.LastOperation
    +	return lastOperation.Type == core.LastOperationTypeDelete &&
    +		lastOperation.State == core.LastOperationStateSucceeded &&
    +		lastOperation.Progress == 100
    +}
    +
    +func isFinalizerRemoved(old, new metav1.Object, finalizerName string) bool {
    +	var (
    +		oldFinalizers = sets.New(old.GetFinalizers()...)
    +		newFinalizer  = sets.New(new.GetFinalizers()...)
    +	)
    +
    +	return oldFinalizers.Has(finalizerName) && !newFinalizer.Has(finalizerName)
    +}
    
  • plugin/pkg/global/finalizerremoval/admission_test.go+216 0 added
    @@ -0,0 +1,216 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval_test
    +
    +import (
    +	"context"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    +	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/utils/ptr"
    +
    +	"github.com/gardener/gardener/pkg/apis/core"
    +	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    +	"github.com/gardener/gardener/pkg/apis/security"
    +	gardencoreinformers "github.com/gardener/gardener/pkg/client/core/informers/externalversions"
    +	. "github.com/gardener/gardener/plugin/pkg/global/finalizerremoval"
    +)
    +
    +var _ = Describe("finalizerremoval", func() {
    +	Describe("#Admit", func() {
    +		var (
    +			ctx                       context.Context
    +			admissionHandler          *FinalizerRemoval
    +			gardenCoreInformerFactory gardencoreinformers.SharedInformerFactory
    +
    +			finalizers []string
    +
    +			namespace              = "default"
    +			secretBindingName      = "binding-1"
    +			credentialsBindingName = "credentials-binding-1"
    +			shootName              = "shoot-1"
    +
    +			shoot *gardencorev1beta1.Shoot
    +		)
    +
    +		BeforeEach(func() {
    +			ctx = context.Background()
    +			admissionHandler, _ = New()
    +			admissionHandler.AssignReadyFunc(func() bool { return true })
    +
    +			finalizers = []string{core.GardenerName}
    +
    +			shoot = &gardencorev1beta1.Shoot{
    +				ObjectMeta: metav1.ObjectMeta{
    +					Name:      shootName,
    +					Namespace: namespace,
    +				},
    +				Spec: gardencorev1beta1.ShootSpec{
    +					CredentialsBindingName: ptr.To(credentialsBindingName),
    +					SecretBindingName:      ptr.To(secretBindingName),
    +				},
    +			}
    +
    +			gardenCoreInformerFactory = gardencoreinformers.NewSharedInformerFactory(nil, 0)
    +			admissionHandler.SetCoreInformerFactory(gardenCoreInformerFactory)
    +		})
    +
    +		Context("SecretBinding", func() {
    +			var coreSecretBinding *core.SecretBinding
    +
    +			BeforeEach(func() {
    +				coreSecretBinding = &core.SecretBinding{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Name:       secretBindingName,
    +						Namespace:  namespace,
    +						Finalizers: finalizers,
    +					},
    +				}
    +			})
    +
    +			It("should admit the removal because object is not used by any shoot", func() {
    +				attrs := admission.NewAttributesRecord(&core.SecretBinding{}, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should admit the removal because finalizer is irrelevant", func() {
    +				newSecretBinding := coreSecretBinding.DeepCopy()
    +				coreSecretBinding.Finalizers = append(coreSecretBinding.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newSecretBinding, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should reject the removal because object is not used by any shoot", func() {
    +				newSecretBinding := coreSecretBinding.DeepCopy()
    +				newSecretBinding.Finalizers = nil
    +
    +				secondShoot := shoot.DeepCopy()
    +				secondShoot.Name = shootName + "-2"
    +				secondShoot.Spec.SecretBindingName = ptr.To(secretBindingName + "-2")
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shoot)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(secondShoot)).To(Succeed())
    +
    +				attrs := admission.NewAttributesRecord(newSecretBinding, coreSecretBinding, core.Kind("SecretBinding").WithVersion("version"), "", coreSecretBinding.Name, core.Resource("SecretBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("finalizer must not be removed")))
    +			})
    +		})
    +
    +		Context("CredentialsBinding", func() {
    +			var coreCredentialsBinding *security.CredentialsBinding
    +
    +			BeforeEach(func() {
    +				coreCredentialsBinding = &security.CredentialsBinding{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Name:       credentialsBindingName,
    +						Namespace:  namespace,
    +						Finalizers: finalizers,
    +					},
    +				}
    +			})
    +
    +			It("should admit the removal because object is not used by any shoot", func() {
    +				attrs := admission.NewAttributesRecord(&security.CredentialsBinding{}, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should admit the removal because finalizer is irrelevant", func() {
    +				newCredentialsBinding := coreCredentialsBinding.DeepCopy()
    +				coreCredentialsBinding.Finalizers = append(coreCredentialsBinding.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newCredentialsBinding, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
    +			})
    +
    +			It("should reject the removal because object is not used by any shoot", func() {
    +				newCredentialsBinding := coreCredentialsBinding.DeepCopy()
    +				newCredentialsBinding.Finalizers = nil
    +
    +				secondShoot := shoot.DeepCopy()
    +				secondShoot.Name = shootName + "-2"
    +				secondShoot.Spec.CredentialsBindingName = ptr.To(secretBindingName + "-2")
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shoot)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(secondShoot)).To(Succeed())
    +
    +				attrs := admission.NewAttributesRecord(newCredentialsBinding, coreCredentialsBinding, security.Kind("CredentialsBinding").WithVersion("version"), "", coreCredentialsBinding.Name, security.Resource("CredentialsBinding").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("finalizer must not be removed")))
    +			})
    +		})
    +
    +		Context("shoot", func() {
    +			var coreShoot *core.Shoot
    +
    +			BeforeEach(func() {
    +				coreShoot = &core.Shoot{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Finalizers: finalizers,
    +					},
    +					Status: core.ShootStatus{
    +						TechnicalID: "some-id",
    +						LastOperation: &core.LastOperation{
    +							Type:     core.LastOperationTypeReconcile,
    +							State:    core.LastOperationStateSucceeded,
    +							Progress: 100,
    +						},
    +					},
    +				}
    +			})
    +
    +			It("should allow the removal because finalizer is irrelevant", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				coreShoot.Finalizers = append(coreShoot.Finalizers, "irrelevant-finalizer")
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, security.Kind("Shoot").WithVersion("version"), "", coreShoot.Name, security.Resource("Shoot").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
    +
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should admit the removal if the shoot deletion succeeded ", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.LastOperation.Type = core.LastOperationTypeDelete
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should reject the removal if the shoot has not yet been deleted successfully", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(MatchError(ContainSubstring("shoot deletion has not completed successfully yet")))
    +			})
    +
    +			It("should admit the removal if the shoot has not yet a last operation", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.LastOperation = nil
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should admit the removal if the shoot has not yet a technical id", func() {
    +				newShoot := coreShoot.DeepCopy()
    +				newShoot.Finalizers = nil
    +				newShoot.Status.TechnicalID = ""
    +
    +				attrs := admission.NewAttributesRecord(newShoot, coreShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
    +			})
    +		})
    +	})
    +})
    
  • plugin/pkg/global/finalizerremoval/finalizerremoval_suite_test.go+17 0 added
    @@ -0,0 +1,17 @@
    +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package finalizerremoval_test
    +
    +import (
    +	"testing"
    +
    +	. "github.com/onsi/ginkgo/v2"
    +	. "github.com/onsi/gomega"
    +)
    +
    +func TestFinalizerRemoval(t *testing.T) {
    +	RegisterFailHandler(Fail)
    +	RunSpecs(t, "AdmissionPlugin Global FinalizerRemoval Suite")
    +}
    
  • plugin/pkg/global/resourcereferencemanager/admission.go+91 72 modified
    @@ -10,13 +10,13 @@ import (
     	"fmt"
     	"io"
     	"reflect"
    +	"slices"
     	"strings"
     	"sync"
     	"time"
     
     	"github.com/hashicorp/go-multierror"
     	corev1 "k8s.io/api/core/v1"
    -	rbacv1 "k8s.io/api/rbac/v1"
     	apiequality "k8s.io/apimachinery/pkg/api/equality"
     	apierrors "k8s.io/apimachinery/pkg/api/errors"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    @@ -32,6 +32,7 @@ import (
     	kubeinformers "k8s.io/client-go/informers"
     	"k8s.io/client-go/kubernetes"
     	kubecorev1listers "k8s.io/client-go/listers/core/v1"
    +	"k8s.io/utils/ptr"
     	"sigs.k8s.io/controller-runtime/pkg/client"
     
     	"github.com/gardener/gardener/pkg/apis/core"
    @@ -287,8 +288,8 @@ func (r *ReferenceManager) ValidateInitialization() error {
     	return nil
     }
     
    -// Admit ensures that referenced resources do actually exist.
    -func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +// Validate ensures that referenced resources do actually exist.
    +func (r *ReferenceManager) Validate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
     	// Wait until the caches have been synced
     	if r.readyFunc == nil {
     		r.AssignReadyFunc(func() bool {
    @@ -350,14 +351,6 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     
     		switch a.GetOperation() {
     		case admission.Create:
    -			// Add createdBy annotation to Shoot
    -			annotations := shoot.Annotations
    -			if annotations == nil {
    -				annotations = map[string]string{}
    -			}
    -			annotations[v1beta1constants.GardenCreatedBy] = a.GetUserInfo().GetName()
    -			shoot.Annotations = annotations
    -
     			oldShoot = &core.Shoot{}
     		case admission.Update:
     			// skip verification if spec wasn't changed
    @@ -408,31 +401,9 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     		if utils.SkipVerification(operation, project.ObjectMeta) {
     			return nil
     		}
    -		// Set createdBy field in Project
    +
     		switch a.GetOperation() {
     		case admission.Create:
    -			project.Spec.CreatedBy = &rbacv1.Subject{
    -				APIGroup: "rbac.authorization.k8s.io",
    -				Kind:     rbacv1.UserKind,
    -				Name:     a.GetUserInfo().GetName(),
    -			}
    -
    -			if project.Spec.Owner == nil {
    -				owner := project.Spec.CreatedBy
    -
    -			outer:
    -				for _, member := range project.Spec.Members {
    -					for _, role := range member.Roles {
    -						if role == core.ProjectMemberOwner {
    -							owner = member.Subject.DeepCopy()
    -							break outer
    -						}
    -					}
    -				}
    -
    -				project.Spec.Owner = owner
    -			}
    -
     			err = r.ensureProjectNamespace(project)
     		case admission.Update:
     			oldProject, ok := a.GetOldObject().(*core.Project)
    @@ -444,24 +415,6 @@ func (r *ReferenceManager) Admit(ctx context.Context, a admission.Attributes, _
     			}
     		}
     
    -		if project.Spec.Owner != nil {
    -			ownerIsMember := false
    -			for _, member := range project.Spec.Members {
    -				if member.Subject == *project.Spec.Owner {
    -					ownerIsMember = true
    -				}
    -			}
    -			if !ownerIsMember {
    -				project.Spec.Members = append(project.Spec.Members, core.ProjectMember{
    -					Subject: *project.Spec.Owner,
    -					Roles: []string{
    -						core.ProjectMemberAdmin,
    -						core.ProjectMemberOwner,
    -					},
    -				})
    -			}
    -		}
    -
     	case core.Kind("BackupBucket"):
     		if operation == admission.Delete {
     			// The "delete endpoint" handler of the k8s.io/apiserver library calls the admission controllers
    @@ -788,7 +741,10 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		credentialsNamespace  string
     		credentialsName       string
     		credentialsKind       string
    +		providerTypes         []string
    +		credentialsReferenced func(shoot *gardencorev1beta1.Shoot) bool
     	)
    +
     	switch attributes.GetKind().GroupKind() {
     	case core.Kind("SecretBinding"):
     		b, ok := binding.(*core.SecretBinding)
    @@ -802,6 +758,11 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		credentialsNamespace = b.SecretRef.Namespace
     		credentialsName = b.SecretRef.Name
     		credentialsKind = "Secret"
    +		providerTypes = helper.GetSecretBindingTypes(b)
    +		credentialsReferenced = func(shoot *gardencorev1beta1.Shoot) bool {
    +			return ptr.Deref(shoot.Spec.SecretBindingName, "") == b.Name
    +		}
    +
     	case security.Kind("CredentialsBinding"):
     		b, ok := binding.(*security.CredentialsBinding)
     		if !ok {
    @@ -823,9 +784,30 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		}
     		credentialsNamespace = b.CredentialsRef.Namespace
     		credentialsName = b.CredentialsRef.Name
    +		providerTypes = []string{b.Provider.Type}
    +		credentialsReferenced = func(shoot *gardencorev1beta1.Shoot) bool {
    +			return ptr.Deref(shoot.Spec.CredentialsBindingName, "") == b.Name
    +		}
    +
     	default:
     		return fmt.Errorf("%s is neither of kind SecretBinding nor CredentialsBinding", attributes.GetKind().GroupKind())
     	}
    +
    +	shoots, err := r.shootLister.Shoots(attributes.GetNamespace()).List(labels.Everything())
    +	if err != nil {
    +		return fmt.Errorf("failed listing shoots: %w", err)
    +	}
    +
    +	for _, shoot := range shoots {
    +		if !credentialsReferenced(shoot) {
    +			continue
    +		}
    +
    +		if !slices.Contains(providerTypes, shoot.Spec.Provider.Type) {
    +			return fmt.Errorf("%s is referenced by shoot %q, but provider types (%+v) do not match with the shoot provider type %q", attributes.GetKind().Kind, shoot.Name, providerTypes, shoot.Spec.Provider.Type)
    +		}
    +	}
    +
     	readAttributes := authorizer.AttributesRecord{
     		User:            attributes.GetUserInfo(),
     		Verb:            "get",
    @@ -847,10 +829,20 @@ func (r *ReferenceManager) ensureBindingReferences(ctx context.Context, attribut
     		if err := r.lookupSecret(ctx, credentialsNamespace, credentialsName); err != nil {
     			return err
     		}
    +		if err := r.sanityCheckProviderSecret(ctx, credentialsNamespace, credentialsName, providerTypes); err != nil {
    +			return err
    +		}
    +
     	case "WorkloadIdentity":
    -		if err := r.lookupWorkloadIdentity(ctx, credentialsNamespace, credentialsName); err != nil {
    +		workloadIdentity, err := r.lookupWorkloadIdentity(ctx, credentialsNamespace, credentialsName)
    +		if err != nil {
     			return err
     		}
    +
    +		if !slices.Contains(providerTypes, workloadIdentity.Spec.TargetSystem.Type) {
    +			return fmt.Errorf("CredentialsBinding provider type (%+v) does not match with WorkloadIdentity provider type %s", providerTypes, workloadIdentity.Spec.TargetSystem.Type)
    +		}
    +
     	default:
     		return fmt.Errorf("unknown credentials kind: %s", credentialsKind)
     	}
    @@ -1175,42 +1167,36 @@ func validateShootWorkersForRemovedMachineImageVersions(channel chan error, shoo
     
     type getFn func(context.Context, string, string) (runtime.Object, error)
     
    -func lookupResource(ctx context.Context, namespace, name string, get getFn, fallbackGet getFn) error {
    +func lookupResource(ctx context.Context, namespace, name string, get getFn, fallbackGet getFn) (runtime.Object, error) {
     	// First try to detect the resource in the cache.
    -	var err error
    -
    -	_, err = get(ctx, namespace, name)
    +	obj, err := get(ctx, namespace, name)
     	if err == nil {
    -		return nil
    +		return obj, nil
     	}
     	if !apierrors.IsNotFound(err) {
    -		return err
    +		return nil, err
     	}
     
     	// Second try to detect the resource in the cache after the first try failed.
     	// Give the cache time to observe the resource before rejecting a create.
     	// This helps when creating a resource and immediately creating a binding referencing it.
     	time.Sleep(MissingResourceWait)
    -	_, err = get(ctx, namespace, name)
    +	obj, err = get(ctx, namespace, name)
     
     	switch {
     	case apierrors.IsNotFound(err):
     		// no-op
     	case err != nil:
    -		return err
    +		return nil, err
     	default:
    -		return nil
    +		return obj, nil
     	}
     
     	// Third try to detect the secret, now by doing a live lookup instead of relying on the cache.
    -	if _, err := fallbackGet(ctx, namespace, name); err != nil {
    -		return err
    -	}
    -
    -	return nil
    +	return fallbackGet(ctx, namespace, name)
     }
     
    -func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace, name string) error {
    +func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace, name string) (*securityv1alpha1.WorkloadIdentity, error) {
     	workloadIdentityFromLister := func(_ context.Context, namespace, name string) (runtime.Object, error) {
     		return r.workloadIdentityLister.WorkloadIdentities(namespace).Get(name)
     	}
    @@ -1219,7 +1205,11 @@ func (r *ReferenceManager) lookupWorkloadIdentity(ctx context.Context, namespace
     		return r.gardenSecurityClient.SecurityV1alpha1().WorkloadIdentities(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, workloadIdentityFromLister, workloadIdentityFromClient)
    +	obj, err := lookupResource(ctx, namespace, name, workloadIdentityFromLister, workloadIdentityFromClient)
    +	if err != nil {
    +		return nil, err
    +	}
    +	return obj.(*securityv1alpha1.WorkloadIdentity), nil
     }
     
     func (r *ReferenceManager) lookupSecret(ctx context.Context, namespace, name string) error {
    @@ -1231,7 +1221,8 @@ func (r *ReferenceManager) lookupSecret(ctx context.Context, namespace, name str
     		return r.kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, secretFromLister, secretFromClient)
    +	_, err := lookupResource(ctx, namespace, name, secretFromLister, secretFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) lookupConfigMap(ctx context.Context, namespace, name string) error {
    @@ -1243,7 +1234,8 @@ func (r *ReferenceManager) lookupConfigMap(ctx context.Context, namespace, name
     		return r.kubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, namespace, name, configMapFromLister, configMapFromClient)
    +	_, err := lookupResource(ctx, namespace, name, configMapFromLister, configMapFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) lookupControllerDeployment(ctx context.Context, name string) error {
    @@ -1255,7 +1247,8 @@ func (r *ReferenceManager) lookupControllerDeployment(ctx context.Context, name
     		return r.gardenCoreClient.CoreV1beta1().ControllerDeployments().Get(ctx, name, kubernetesclient.DefaultGetOptions())
     	}
     
    -	return lookupResource(ctx, "", name, deploymentFromLister, deploymentFromClient)
    +	_, err := lookupResource(ctx, "", name, deploymentFromLister, deploymentFromClient)
    +	return err
     }
     
     func (r *ReferenceManager) getAPIResource(groupVersion, kind string) (*metav1.APIResource, error) {
    @@ -1350,3 +1343,29 @@ func (r *ReferenceManager) ensureResourceReferences(ctx context.Context, attribu
     	}
     	return nil
     }
    +
    +func (r *ReferenceManager) sanityCheckProviderSecret(ctx context.Context, namespace, name string, providerTypes []string) error {
    +	secret, err := r.kubeClient.CoreV1().Secrets(namespace).Get(ctx, name, kubernetesclient.DefaultGetOptions())
    +	if err != nil {
    +		return err
    +	}
    +
    +	for _, providerType := range providerTypes {
    +		dummySecret := &corev1.Secret{
    +			ObjectMeta: metav1.ObjectMeta{
    +				GenerateName: name,
    +				Namespace:    namespace,
    +				Annotations:  secret.Annotations,
    +				Labels:       gardenerutils.MergeStringMaps(secret.Labels, map[string]string{v1beta1constants.LabelShootProviderPrefix + providerType: "true"}),
    +			},
    +			Type: secret.Type,
    +			Data: secret.Data,
    +		}
    +
    +		if _, err := r.kubeClient.CoreV1().Secrets(dummySecret.Namespace).Create(ctx, dummySecret, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}); err != nil {
    +			return fmt.Errorf("%s provider secret sanity check failed: %w", providerType, err)
    +		}
    +	}
    +
    +	return nil
    +}
    
  • plugin/pkg/global/resourcereferencemanager/admission_test.go+221 230 modified
    @@ -16,7 +16,6 @@ import (
     	. "github.com/onsi/gomega/gstruct"
     	autoscalingv1 "k8s.io/api/autoscaling/v1"
     	corev1 "k8s.io/api/core/v1"
    -	rbacv1 "k8s.io/api/rbac/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/runtime"
     	"k8s.io/apiserver/pkg/admission"
    @@ -30,7 +29,6 @@ import (
     
     	"github.com/gardener/gardener/pkg/apis/core"
     	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    -	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
     	"github.com/gardener/gardener/pkg/apis/security"
     	securityv1alpha1 "github.com/gardener/gardener/pkg/apis/security/v1alpha1"
     	"github.com/gardener/gardener/pkg/apis/seedmanagement"
    @@ -178,6 +176,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: &core.SecretBindingProvider{
    +					Type: "test",
    +				},
     			}
     			secretBinding = gardencorev1beta1.SecretBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -195,6 +196,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: &gardencorev1beta1.SecretBindingProvider{
    +					Type: "test",
    +				},
     			}
     			securityCredentialsBindingRefSecret = security.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -213,6 +217,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: security.CredentialsBindingProvider{
    +					Type: "test",
    +				},
     			}
     			credentialsBindingRefSecret = securityv1alpha1.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -231,6 +238,9 @@ var _ = Describe("resourcereferencemanager", func() {
     						Namespace: namespace,
     					},
     				},
    +				Provider: securityv1alpha1.CredentialsBindingProvider{
    +					Type: "test",
    +				},
     			}
     			securityCredentialsBindingRefWorkloadIdentity = security.CredentialsBinding{
     				ObjectMeta: metav1.ObjectMeta{
    @@ -244,6 +254,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					Name:       workloadIdentityName,
     					Namespace:  namespace,
     				},
    +				Provider: security.CredentialsBindingProvider{Type: "wiprovider"},
     				Quotas: []corev1.ObjectReference{
     					{
     						Name:      quotaName,
    @@ -451,18 +462,20 @@ var _ = Describe("resourcereferencemanager", func() {
     
     			err = gardencorev1beta1.Convert_core_Project_To_v1beta1_Project(&coreProject, &project, nil)
     			Expect(err).To(Succeed())
    +
    +			workloadIdentity.Spec.TargetSystem = securityv1alpha1.TargetSystem{Type: "wiprovider"}
     		})
     
     		It("should return nil because the resource is not BackupBucket and operation is delete", func() {
     			attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
     
    -			err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +			err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     			Expect(err).NotTo(HaveOccurred())
     
     			attrs = admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), "", controllerRegistration.Name, core.Resource("shoots").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil)
     
    -			err = admissionHandler.Admit(context.TODO(), attrs, nil)
    +			err = admissionHandler.Validate(context.TODO(), attrs, nil)
     
     			Expect(err).NotTo(HaveOccurred())
     		})
    @@ -474,7 +487,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -487,7 +500,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&controllerRegistration, nil, core.Kind("ControllerRegistration").WithVersion("version"), "", controllerRegistration.Name, core.Resource("controllerregistrations").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -499,7 +512,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					return true, nil, errors.New("nope, out of luck")
     				})
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     				Expect(err.Error()).To(ContainSubstring("nope, out of luck"))
    @@ -514,7 +527,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -533,11 +546,36 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, nil
    +				})
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the sanity check fails", func() {
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, &corev1.Secret{
    +						ObjectMeta: metav1.ObjectMeta{
    +							Namespace: secret.Namespace,
    +							Name:      secret.Name,
    +						},
    +					}, nil
    +				})
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, fmt.Errorf("sanity check failed")
    +				})
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("test provider secret sanity check failed: sanity check failed")))
    +			})
    +
     			It("should reject because the referenced secret does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -547,7 +585,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -559,7 +597,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -570,7 +608,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -582,7 +620,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -617,7 +655,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -652,10 +690,23 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    +
    +			It("should reject because provider types do not match with shoot type", func() {
    +				coreSecretBinding.Provider.Type = "another-provider"
    +				coreSecretBinding.Quotas = nil
    +				shoot.Spec.Provider.Type = "local"
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(&shoot)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&coreSecretBinding, nil, core.Kind("SecretBinding").WithVersion("version"), coreSecretBinding.Namespace, coreSecretBinding.Name, core.Resource("secretbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(`SecretBinding is referenced by shoot "shoot-1", but provider types ([another-provider]) do not match with the shoot provider type "local"`)))
    +			})
     		})
     
     		Context("tests for CredentialsBinding objects referencing Secret", func() {
    @@ -666,7 +717,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -685,11 +736,36 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, nil
    +				})
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the sanity check fails", func() {
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, &corev1.Secret{
    +						ObjectMeta: metav1.ObjectMeta{
    +							Namespace: secret.Namespace,
    +							Name:      secret.Name,
    +						},
    +					}, nil
    +				})
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				kubeClient.AddReactor("create", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    +					return true, nil, fmt.Errorf("sanity check failed")
    +				})
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("test provider secret sanity check failed: sanity check failed")))
    +			})
    +
     			It("should reject because the referenced secret does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				kubeClient.AddReactor("get", "secrets", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -699,7 +775,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -711,7 +787,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -722,7 +798,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -734,7 +810,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -769,7 +845,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -804,10 +880,23 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    +
    +			It("should reject because provider types do not match with shoot type", func() {
    +				securityCredentialsBindingRefSecret.Provider.Type = "another-provider"
    +				securityCredentialsBindingRefSecret.Quotas = nil
    +				shoot.Spec.Provider.Type = "local"
    +
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(&shoot)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefSecret, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefSecret.Namespace, securityCredentialsBindingRefSecret.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(`CredentialsBinding is referenced by shoot "shoot-1", but provider types ([another-provider]) do not match with the shoot provider type "local"`)))
    +			})
     		})
     
     		Context("tests for CredentialsBinding objects referencing WorkloadIdentity", func() {
    @@ -818,30 +907,38 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
     			It("should accept because all referenced objects have been found (workloadidentity looked up live)", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				gardenSecurityClient.AddReactor("get", "workloadidentities", func(_ testing.Action) (bool, runtime.Object, error) {
    -					return true, &securityv1alpha1.WorkloadIdentity{
    -						ObjectMeta: metav1.ObjectMeta{
    -							Namespace: workloadIdentity.Namespace,
    -							Name:      workloadIdentity.Name,
    -						},
    -					}, nil
    +					return true, &workloadIdentity, nil
     				})
     
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     
    +			It("should reject because the provider type does not match in WorkloadIdentity and CredentialsBinding", func() {
    +				workloadIdentity.Spec.TargetSystem.Type = "foo"
    +				Expect(gardenSecurityInformerFactory.Security().V1alpha1().WorkloadIdentities().Informer().GetStore().Add(&workloadIdentity)).To(Succeed())
    +				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
    +
    +				user := &user.DefaultInfo{Name: allowedUser}
    +				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    +
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
    +
    +				Expect(err).To(MatchError(ContainSubstring("does not match with WorkloadIdentity provider type")))
    +			})
    +
     			It("should reject because the referenced workload identity does not exist", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				gardenSecurityClient.AddReactor("get", "workloadidentities", func(_ testing.Action) (bool, runtime.Object, error) {
    @@ -851,7 +948,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -863,7 +960,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -874,7 +971,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -886,7 +983,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -914,14 +1011,15 @@ var _ = Describe("resourcereferencemanager", func() {
     				quotaRefList = append(quotaRefList, quota2Ref)
     				securityCredentialsBindingRefWorkloadIdentity.Quotas = quotaRefList
     
    +				Expect(gardenSecurityInformerFactory.Security().V1alpha1().WorkloadIdentities().Informer().GetStore().Add(&workloadIdentity)).To(Succeed())
     				Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add(&quota2)).To(Succeed())
     
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -975,31 +1073,13 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&securityCredentialsBindingRefWorkloadIdentity, nil, security.Kind("CredentialsBinding").WithVersion("version"), securityCredentialsBindingRefWorkloadIdentity.Namespace, securityCredentialsBindingRefWorkloadIdentity.Name, security.Resource("credentialsbindings").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
     		})
     
     		Context("tests for Shoot objects", func() {
    -			It("should add the created-by annotation", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(gardenSecurityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBindingRefSecret)).To(Succeed())
    -				Expect(kubeInformerFactory.Core().V1().ConfigMaps().Informer().GetStore().Add(&configMap)).To(Succeed())
    -
    -				user := &user.DefaultInfo{Name: allowedUser}
    -				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
    -
    -				Expect(coreShoot.Annotations).NotTo(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, user.Name))
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreShoot.Annotations).To(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, user.Name))
    -			})
    -
     			It("should accept because all referenced objects have been found", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    @@ -1010,7 +1090,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1026,7 +1106,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreShoot.Status.TechnicalID = "should-never-change"
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1035,7 +1115,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				It("should reject because the referenced cloud profile does not exist (create)", func() {
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1046,7 +1126,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1061,7 +1141,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1078,7 +1158,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     					Expect(err).To(HaveOccurred())
     				})
    @@ -1091,7 +1171,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1103,7 +1183,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1115,7 +1195,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1130,7 +1210,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1150,7 +1230,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				It("should reject because the referenced exposure class does not exists", func() {
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     					Expect(err).To(HaveOccurred())
     				})
     
    @@ -1164,7 +1244,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					Expect(gardenCoreInformerFactory.Core().V1beta1().ExposureClasses().Informer().GetStore().Add(&exposureClass)).To(Succeed())
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -					err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +					err := admissionHandler.Validate(context.TODO(), attrs, nil)
     					Expect(err).To(HaveOccurred())
     				})
     			})
    @@ -1176,7 +1256,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1191,7 +1271,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("shoots.core.gardener.cloud \"shoot-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1218,7 +1298,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("shoots.core.gardener.cloud \"shoot-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1234,7 +1314,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1251,7 +1331,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError(ContainSubstring("failed to resolve resource reference")))
     			})
    @@ -1273,7 +1353,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
     				})
     
     				It("should reject because the referenced "+description+" does not exist (update)", func() {
    @@ -1293,7 +1373,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(expectedErrorMessage)))
     				})
     
     				It("should pass because the referenced "+description+" does not exist but shoot has deletion timestamp", func() {
    @@ -1316,7 +1396,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, oldShoot, core.Kind("Shoot").WithVersion("version"), coreShoot.Namespace, coreShoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     				})
     
     				It("should pass because the referenced "+description+" exists", func() {
    @@ -1335,7 +1415,7 @@ var _ = Describe("resourcereferencemanager", func() {
     					user := &user.DefaultInfo{Name: allowedUser}
     					attrs := admission.NewAttributesRecord(&coreShoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -					Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +					Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     				})
     			}
     
    @@ -1409,7 +1489,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSeed, oldSeed, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError("seeds.core.gardener.cloud \"seed-1\" is forbidden: cannot reference a resource you are not allowed to read"))
     			})
    @@ -1420,7 +1500,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: "disallowed-user"}
     				attrs := admission.NewAttributesRecord(&coreSeed, oldSeed, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1429,7 +1509,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				user := &user.DefaultInfo{Name: allowedUser}
     				attrs := admission.NewAttributesRecord(&coreSeed, nil, core.Kind("Seed").WithVersion("version"), "", coreSeed.Name, core.Resource("seeds").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, user)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(MatchError(ContainSubstring("failed to resolve resource reference")))
     			})
    @@ -1439,7 +1519,7 @@ var _ = Describe("resourcereferencemanager", func() {
     			It("should reject if the referred Seed is not found", func() {
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: seed.core.gardener.cloud %q not found", coreBackupBucket.Name, seed.Name)))
    @@ -1453,7 +1533,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("secret not found")))
    @@ -1472,7 +1552,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1483,7 +1563,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreBackupBucket, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1498,7 +1578,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1517,7 +1597,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", backupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: cannot delete BackupBucket because BackupEntries are still referencing it, backupEntryNames: %s/%s,%s/%s", backupBucket.Name, backupEntry.Namespace, backupEntry.Name, backupEntry2.Namespace, backupEntry2.Name)))
    @@ -1533,7 +1613,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", coreBackupBucket.Name, core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     			})
    @@ -1561,7 +1641,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", "", core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupBuckets.core.gardener.cloud %q is forbidden: cannot delete BackupBucket because BackupEntries are still referencing it, backupEntryNames: %s/%s,%s/%s", backupBucket2.Name, backupEntry.Namespace, backupEntry.Name, backupEntry2.Namespace, backupEntry2.Name)))
    @@ -1590,7 +1670,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(nil, nil, core.Kind("BackupBucket").WithVersion("version"), "", "", core.Resource("backupBuckets").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1600,7 +1680,7 @@ var _ = Describe("resourcereferencemanager", func() {
     			It("should reject if the referred Seed is not found", func() {
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupEntries.core.gardener.cloud %q is forbidden: seed.core.gardener.cloud %q not found", coreBackupEntry.Name, seed.Name)))
    @@ -1610,7 +1690,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
     				Expect(err).To(MatchError(ContainSubstring("backupEntries.core.gardener.cloud %q is forbidden: backupbucket.core.gardener.cloud %q not found", coreBackupEntry.Name, coreBackupBucket.Name)))
    @@ -1621,102 +1701,13 @@ var _ = Describe("resourcereferencemanager", func() {
     				Expect(gardenCoreInformerFactory.Core().V1beta1().BackupBuckets().Informer().GetStore().Add(&backupBucket)).To(Succeed())
     				attrs := admission.NewAttributesRecord(&coreBackupEntry, nil, core.Kind("BackupEntry").WithVersion("version"), coreBackupEntry.Namespace, coreBackupEntry.Name, core.Resource("backupEntries").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
     		})
     
     		Context("tests for Project objects", func() {
    -			It("should set the created-by field", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should set the owner field (member with owner role found)", func() {
    -				projectCopy := project.DeepCopy()
    -				coreProjectCopy := coreProject.DeepCopy()
    -				ownerMember := &rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     "owner",
    -				}
    -				projectCopy.Name = "foo"
    -				projectCopy.Spec.Members = []gardencorev1beta1.ProjectMember{
    -					{
    -						Subject: *ownerMember,
    -						Roles:   []string{core.ProjectMemberOwner},
    -					},
    -				}
    -				coreProjectCopy.Name = "foo"
    -				coreProjectCopy.Spec.Members = []core.ProjectMember{
    -					{
    -						Subject: *ownerMember,
    -						Roles:   []string{core.ProjectMemberOwner},
    -					},
    -				}
    -
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(projectCopy)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(coreProjectCopy, nil, core.Kind("Project").WithVersion("version"), coreProjectCopy.Namespace, coreProjectCopy.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProjectCopy.Spec.Owner).To(Equal(ownerMember))
    -				Expect(coreProjectCopy.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should set the owner field", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.Owner).To(Equal(&rbacv1.Subject{
    -					APIGroup: "rbac.authorization.k8s.io",
    -					Kind:     rbacv1.UserKind,
    -					Name:     defaultUserName,
    -				}))
    -			})
    -
    -			It("should add the owner to members", func() {
    -				Expect(gardenCoreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -
    -				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
    -
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
    -				Expect(coreProject.Spec.Members).To(ContainElement(Equal(core.ProjectMember{
    -					Subject: rbacv1.Subject{
    -						APIGroup: "rbac.authorization.k8s.io",
    -						Kind:     rbacv1.UserKind,
    -						Name:     defaultUserName,
    -					},
    -					Roles: []string{
    -						core.ProjectMemberAdmin,
    -						core.ProjectMemberOwner,
    -					},
    -				})))
    -			})
    -
     			It("should allow specifying a namespace which is not in use (create)", func() {
     				project.Spec.Namespace = ptr.To("garden-foo")
     				projectCopy := project.DeepCopy()
    @@ -1727,7 +1718,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1745,7 +1736,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, coreProjectOld, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1757,7 +1748,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(Not(HaveOccurred()))
     			})
    @@ -1771,7 +1762,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, nil, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{
     					"ErrStatus": MatchFields(IgnoreExtras, Fields{
    @@ -1793,7 +1784,7 @@ var _ = Describe("resourcereferencemanager", func() {
     				coreProject.Spec.Namespace = ptr.To("garden-foo")
     				attrs := admission.NewAttributesRecord(&coreProject, &coreProjectOld, core.Kind("Project").WithVersion("version"), coreProject.Namespace, coreProject.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{
     					"ErrStatus": MatchFields(IgnoreExtras, Fields{
    @@ -1835,7 +1826,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfile, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1856,7 +1847,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -1876,7 +1867,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).To(HaveOccurred())
     				Expect(err.Error()).To(ContainSubstring("1.24.1"))
    @@ -1904,7 +1895,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(And(
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(And(
     					ContainSubstring("unable to delete Kubernetes version"),
     					ContainSubstring("1.24.1"),
     					ContainSubstring("still in use by NamespacedCloudProfile"),
    @@ -1935,7 +1926,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(And(
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(And(
     					ContainSubstring("unable to delete Kubernetes version"),
     					ContainSubstring("1.24.1"),
     					ContainSubstring("still in use by shoot '/shoot-Two'"),
    @@ -1964,7 +1955,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     			})
     
     			It("should accept removal of kubernetes versions that are used by shoots using another unrelated NamespacedCloudProfile of same name", func() {
    @@ -1987,7 +1978,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +				Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
     			})
     
     			It("should accept removal of kubernetes version that is still in use by a shoot that is being deleted", func() {
    @@ -2009,7 +2000,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2105,7 +2096,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfile, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2148,7 +2139,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo)
     
    -				err := admissionHandler.Admit(context.TODO(), attrs, nil)
    +				err := admissionHandler.Validate(context.TODO(), attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
     			})
    @@ -2192,7 +2183,7 @@ var _ = Describe("resourcereferencemanager", func() {
     
     				attrs := admission.NewAttributesRecord(&cloudProfileNew, &cloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.Upd
    ... [truncated]
    
  • plugin/pkg/plugins.go+4 0 modified
    @@ -27,6 +27,8 @@ const (
     	PluginNameExtensionLabels = "ExtensionLabels"
     	// PluginNameExtensionValidator is the name of the ExtensionValidator admission plugin.
     	PluginNameExtensionValidator = "ExtensionValidator"
    +	// PluginNameFinalizerRemoval is the name of the FinalizerRemoval admission plugin.
    +	PluginNameFinalizerRemoval = "FinalizerRemoval"
     	// PluginNameResourceReferenceManager is the name of the ResourceReferenceManager admission plugin.
     	PluginNameResourceReferenceManager = "ResourceReferenceManager"
     	// PluginNameManagedSeedShoot is the name of the ManagedSeedShoot admission plugin.
    @@ -88,6 +90,7 @@ func AllPluginNames() []string {
     		PluginNameNamespacedCloudProfileValidator,   // NamespacedCloudProfileValidator
     		PluginNameProjectValidator,                  // ProjectValidator
     		PluginNameDeletionConfirmation,              // DeletionConfirmation
    +		PluginNameFinalizerRemoval,                  // FinalizerRemoval
     		PluginNameOpenIDConnectPreset,               // OpenIDConnectPreset
     		PluginNameClusterOpenIDConnectPreset,        // ClusterOpenIDConnectPreset
     		PluginNameCustomVerbAuthorizer,              // CustomVerbAuthorizer
    @@ -131,6 +134,7 @@ func DefaultOnPlugins() sets.Set[string] {
     		PluginNameNamespacedCloudProfileValidator, // NamespacedCloudProfileValidator
     		PluginNameProjectValidator,                // ProjectValidator
     		PluginNameDeletionConfirmation,            // DeletionConfirmation
    +		PluginNameFinalizerRemoval,                // FinalizerRemoval
     		PluginNameOpenIDConnectPreset,             // OpenIDConnectPreset
     		PluginNameClusterOpenIDConnectPreset,      // ClusterOpenIDConnectPreset
     		PluginNameCustomVerbAuthorizer,            // CustomVerbAuthorizer
    
  • plugin/pkg/project/validator/admission.go+59 4 modified
    @@ -8,15 +8,18 @@ import (
     	"context"
     	"fmt"
     	"io"
    +	"slices"
     	"strings"
     
    +	rbacv1 "k8s.io/api/rbac/v1"
     	apierrors "k8s.io/apimachinery/pkg/api/errors"
     	"k8s.io/apiserver/pkg/admission"
     
     	gardencore "github.com/gardener/gardener/pkg/apis/core"
     	v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
     	gardenerutils "github.com/gardener/gardener/pkg/utils/gardener"
     	plugin "github.com/gardener/gardener/plugin/pkg"
    +	"github.com/gardener/gardener/plugin/pkg/utils"
     )
     
     // Register registers a plugin.
    @@ -33,13 +36,13 @@ type handler struct {
     // New creates a new handler admission plugin.
     func New() (*handler, error) {
     	return &handler{
    -		Handler: admission.NewHandler(admission.Create),
    +		Handler: admission.NewHandler(admission.Create, admission.Update),
     	}, nil
     }
     
    -var _ admission.ValidationInterface = &handler{}
    +var _ admission.MutationInterface = &handler{}
     
    -func (v *handler) Validate(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    +func (v *handler) Admit(_ context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
     	// Ignore all kinds other than Project
     	if a.GetKind().GroupKind() != gardencore.Kind("Project") {
     		return nil
    @@ -56,10 +59,62 @@ func (v *handler) Validate(_ context.Context, a admission.Attributes, _ admissio
     		return apierrors.NewBadRequest("could not convert object to Project")
     	}
     
    -	// TODO: Remove this admission plugin in favor of static validation in a future release, see https://github.com/gardener/gardener/pull/4228.
    +	// TODO: Remove this check in favor of static validation in a future release, see https://github.com/gardener/gardener/pull/4228.
     	if project.Spec.Namespace != nil && *project.Spec.Namespace != v1beta1constants.GardenNamespace && !strings.HasPrefix(*project.Spec.Namespace, gardenerutils.ProjectNamespacePrefix) {
     		return admission.NewForbidden(a, fmt.Errorf(".spec.namespace must start with %s", gardenerutils.ProjectNamespacePrefix))
     	}
     
    +	if utils.SkipVerification(a.GetOperation(), project.ObjectMeta) {
    +		return nil
    +	}
    +
    +	if a.GetOperation() == admission.Create {
    +		ensureProjectOwner(project, a.GetUserInfo().GetName())
    +	}
    +
    +	ensureOwnerIsMember(project)
    +
     	return nil
     }
    +
    +func ensureProjectOwner(project *gardencore.Project, userName string) {
    +	// Set createdBy field in Project
    +	project.Spec.CreatedBy = &rbacv1.Subject{
    +		APIGroup: "rbac.authorization.k8s.io",
    +		Kind:     rbacv1.UserKind,
    +		Name:     userName,
    +	}
    +
    +	if project.Spec.Owner == nil {
    +		project.Spec.Owner = func() *rbacv1.Subject {
    +			for _, member := range project.Spec.Members {
    +				for _, role := range member.Roles {
    +					if role == gardencore.ProjectMemberOwner {
    +						return member.Subject.DeepCopy()
    +					}
    +				}
    +			}
    +			return project.Spec.CreatedBy
    +		}()
    +	}
    +}
    +
    +func ensureOwnerIsMember(project *gardencore.Project) {
    +	if project.Spec.Owner == nil {
    +		return
    +	}
    +
    +	ownerIsMember := slices.ContainsFunc(project.Spec.Members, func(member gardencore.ProjectMember) bool {
    +		return member.Subject == *project.Spec.Owner
    +	})
    +
    +	if !ownerIsMember {
    +		project.Spec.Members = append(project.Spec.Members, gardencore.ProjectMember{
    +			Subject: *project.Spec.Owner,
    +			Roles: []string{
    +				gardencore.ProjectMemberAdmin,
    +				gardencore.ProjectMemberOwner,
    +			},
    +		})
    +	}
    +}
    
  • plugin/pkg/project/validator/admission_test.go+136 28 modified
    @@ -9,8 +9,10 @@ import (
     
     	. "github.com/onsi/ginkgo/v2"
     	. "github.com/onsi/gomega"
    +	rbacv1 "k8s.io/api/rbac/v1"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apiserver/pkg/admission"
    +	"k8s.io/apiserver/pkg/authentication/user"
     	"k8s.io/utils/ptr"
     
     	"github.com/gardener/gardener/pkg/apis/core"
    @@ -23,7 +25,8 @@ var _ = Describe("Admission", func() {
     		var (
     			err              error
     			project          core.Project
    -			admissionHandler admission.ValidationInterface
    +			admissionHandler admission.MutationInterface
    +			attrs            admission.Attributes
     
     			namespaceName = "garden-my-project"
     			projectName   = "my-project"
    @@ -33,43 +36,148 @@ var _ = Describe("Admission", func() {
     					Namespace: namespaceName,
     				},
     			}
    +
    +			userInfo user.Info
     		)
     
     		BeforeEach(func() {
     			admissionHandler, err = New()
     			Expect(err).NotTo(HaveOccurred())
     
     			project = projectBase
    -		})
    -
    -		It("should allow creating the project (namespace nil)", func() {
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
     
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    +			userInfo = &user.DefaultInfo{Name: "foo"}
     		})
     
    -		It("should allow creating the project(namespace non-nil)", func() {
    -			project.Spec.Namespace = &namespaceName
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    +		When("project is created", func() {
    +			BeforeEach(func() {
    +				attrs = admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +			})
    +
    +			It("should allow creating the project (namespace nil)", func() {
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should allow creating the project(namespace non-nil)", func() {
    +				project.Spec.Namespace = &namespaceName
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should allow creating the project (namespace is 'garden')", func() {
    +				project.Spec.Namespace = ptr.To(v1beta1constants.GardenNamespace)
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +			})
    +
    +			It("should prevent creating the project because namespace prefix is missing", func() {
    +				project.Spec.Namespace = ptr.To("foo")
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(".spec.namespace must start with garden-")))
    +			})
    +
    +			It("should maintain createdBy and project owner", func() {
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.CreatedBy).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     userInfo.GetName(),
    +				}))
    +
    +				Expect(project.Spec.Owner).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     userInfo.GetName(),
    +				}))
    +
    +				Expect(project.Spec.Members).To(ConsistOf(core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     userInfo.GetName(),
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}))
    +			})
    +
    +			It("should not overwrite project owner", func() {
    +				project.Spec.Owner = &rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     "bar",
    +				}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Owner).To(Equal(&rbacv1.Subject{
    +					APIGroup: "rbac.authorization.k8s.io",
    +					Kind:     "User",
    +					Name:     "bar",
    +				}))
    +			})
     		})
     
    -		It("should allow creating the project (namespace is 'garden')", func() {
    -			project.Spec.Namespace = ptr.To(v1beta1constants.GardenNamespace)
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed())
    -		})
    -
    -		It("should prevent creating the project because namespace prefix is missing", func() {
    -			project.Spec.Namespace = ptr.To("foo")
    -
    -			attrs := admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    -
    -			Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring(".spec.namespace must start with garden-")))
    +		When("project is updated", func() {
    +			BeforeEach(func() {
    +				attrs = admission.NewAttributesRecord(&project, nil, core.Kind("Project").WithVersion("version"), "", project.Name, core.Resource("projects").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, userInfo)
    +			})
    +
    +			It("should add project owner to members", func() {
    +				projectOwner := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "foo",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}
    +
    +				projectMemberBar := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "bar",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberViewer,
    +					},
    +				}
    +
    +				project.Spec.Owner = &projectOwner.Subject
    +				project.Spec.Members = []core.ProjectMember{projectMemberBar}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Members).To(ConsistOf(projectMemberBar, projectOwner))
    +			})
    +
    +			It("should not re-add owner as member", func() {
    +				projectOwner := core.ProjectMember{
    +					Subject: rbacv1.Subject{
    +						APIGroup: "rbac.authorization.k8s.io",
    +						Kind:     "User",
    +						Name:     "foo",
    +					},
    +					Roles: []string{
    +						core.ProjectMemberAdmin,
    +						core.ProjectMemberOwner,
    +					},
    +				}
    +
    +				project.Spec.Owner = &projectOwner.Subject
    +				project.Spec.Members = []core.ProjectMember{projectOwner}
    +
    +				Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed())
    +
    +				Expect(project.Spec.Members).To(ConsistOf(projectOwner))
    +			})
     		})
     	})
     
    @@ -85,11 +193,11 @@ var _ = Describe("Admission", func() {
     	})
     
     	Describe("#New", func() {
    -		It("should only handle CREATE operations", func() {
    +		It("should handle CREATE and UPDATE operations", func() {
     			dr, err := New()
     			Expect(err).ToNot(HaveOccurred())
     			Expect(dr.Handles(admission.Create)).To(BeTrue())
    -			Expect(dr.Handles(admission.Update)).To(BeFalse())
    +			Expect(dr.Handles(admission.Update)).To(BeTrue())
     			Expect(dr.Handles(admission.Connect)).To(BeFalse())
     			Expect(dr.Handles(admission.Delete)).To(BeFalse())
     		})
    
  • plugin/pkg/shoot/validator/admission.go+17 19 modified
    @@ -253,15 +253,19 @@ func (v *ValidateShoot) Admit(ctx context.Context, a admission.Attributes, _ adm
     		}
     	}
     
    +	if a.GetOperation() == admission.Create {
    +		addCreatedByAnnotation(shoot, a.GetUserInfo().GetName())
    +
    +		if len(ptr.Deref(shoot.Spec.CloudProfileName, "")) > 0 && shoot.Spec.CloudProfile != nil {
    +			return fmt.Errorf("new shoot can only specify either cloudProfileName or cloudProfile reference")
    +		}
    +	}
    +
     	cloudProfileSpec, err := admissionutils.GetCloudProfileSpec(v.cloudProfileLister, v.namespacedCloudProfileLister, shoot)
     	if err != nil {
     		return apierrors.NewInternalError(fmt.Errorf("could not find referenced cloud profile: %+v", err.Error()))
     	}
     
    -	if a.GetOperation() == admission.Create && len(ptr.Deref(shoot.Spec.CloudProfileName, "")) > 0 && shoot.Spec.CloudProfile != nil {
    -		return fmt.Errorf("new shoot can only specify either cloudProfileName or cloudProfile reference")
    -	}
    -
     	if err := admissionutils.ValidateCloudProfileChanges(v.cloudProfileLister, v.namespacedCloudProfileLister, shoot, oldShoot); err != nil {
     		return err
     	}
    @@ -608,21 +612,6 @@ func (c *validationContext) validateDeletion(a admission.Attributes) error {
     		}
     	}
     
    -	// Allow removal of `gardener` finalizer only if the Shoot deletion has completed successfully
    -	if len(c.shoot.Status.TechnicalID) > 0 && c.shoot.Status.LastOperation != nil {
    -		oldFinalizers := sets.New(c.oldShoot.Finalizers...)
    -		newFinalizers := sets.New(c.shoot.Finalizers...)
    -
    -		if oldFinalizers.Has(core.GardenerName) && !newFinalizers.Has(core.GardenerName) {
    -			lastOperation := c.shoot.Status.LastOperation
    -			deletionSucceeded := lastOperation.Type == core.LastOperationTypeDelete && lastOperation.State == core.LastOperationStateSucceeded && lastOperation.Progress == 100
    -
    -			if !deletionSucceeded {
    -				return admission.NewForbidden(a, fmt.Errorf("finalizer %q cannot be removed because shoot deletion has not completed successfully yet", core.GardenerName))
    -			}
    -		}
    -	}
    -
     	return nil
     }
     
    @@ -2162,3 +2151,12 @@ func validateMaxNodesTotal(workers []core.Worker, maxNodesTotal int32) field.Err
     
     	return allErrs
     }
    +
    +func addCreatedByAnnotation(shoot *core.Shoot, userName string) {
    +	annotations := shoot.Annotations
    +	if annotations == nil {
    +		annotations = map[string]string{}
    +	}
    +	annotations[v1beta1constants.GardenCreatedBy] = userName
    +	shoot.Annotations = annotations
    +}
    
  • plugin/pkg/shoot/validator/admission_test.go+50 96 modified
    @@ -18,7 +18,6 @@ import (
     	"k8s.io/apimachinery/pkg/api/resource"
     	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     	"k8s.io/apimachinery/pkg/runtime"
    -	"k8s.io/apimachinery/pkg/util/sets"
     	"k8s.io/apiserver/pkg/admission"
     	"k8s.io/apiserver/pkg/authentication/user"
     	"k8s.io/apiserver/pkg/authorization/authorizer"
    @@ -423,106 +422,61 @@ var _ = Describe("validator", func() {
     			})
     		})
     
    -		Context("shoot with generate name", func() {
    +		Context("shoot creation", func() {
     			BeforeEach(func() {
    -				shoot.ObjectMeta = metav1.ObjectMeta{
    -					GenerateName: "demo-",
    -					Namespace:    namespaceName,
    -				}
    -			})
    -
    -			It("should admit Shoot resources", func() {
     				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
     				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    -
    -				authorizeAttributes.Name = shoot.Name
    -
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -
    -				Expect(err).NotTo(HaveOccurred())
     			})
     
    -			It("should reject Shoot resources with not fulfilling the length constraints", func() {
    -				tooLongName := "too-long-namespace"
    -				project.ObjectMeta = metav1.ObjectMeta{
    -					Name: tooLongName,
    -				}
    -				shoot.ObjectMeta = metav1.ObjectMeta{
    -					GenerateName: "too-long-name",
    -					Namespace:    namespaceName,
    -				}
    -
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    -
    -				authorizeAttributes.Name = shoot.Name
    +			Context("with generate name", func() {
    +				BeforeEach(func() {
    +					shoot.ObjectMeta = metav1.ObjectMeta{
    +						GenerateName: "demo-",
    +						Namespace:    namespaceName,
    +					}
    +				})
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    +				It("should admit Shoot resources", func() {
    +					authorizeAttributes.Name = shoot.Name
     
    -				Expect(err).To(BeInvalidError())
    -				Expect(err.Error()).To(ContainSubstring("name must not exceed"))
    -			})
    -		})
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +					err := admissionHandler.Admit(ctx, attrs, nil)
     
    -		Context("finalizer removal checks", func() {
    -			var (
    -				oldShoot *core.Shoot
    -			)
    +					Expect(err).NotTo(HaveOccurred())
    +				})
     
    -			BeforeEach(func() {
    -				shoot = *shootBase.DeepCopy()
    +				It("should reject Shoot resources with not fulfilling the length constraints", func() {
    +					tooLongName := "too-long-namespace"
    +					project.ObjectMeta = metav1.ObjectMeta{
    +						Name: tooLongName,
    +					}
    +					shoot.ObjectMeta = metav1.ObjectMeta{
    +						GenerateName: "too-long-name",
    +						Namespace:    namespaceName,
    +					}
     
    -				shoot.Status.TechnicalID = "some-id"
    -				shoot.Status.LastOperation = &core.LastOperation{
    -					Type:     core.LastOperationTypeReconcile,
    -					State:    core.LastOperationStateSucceeded,
    -					Progress: 100,
    -				}
    +					Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
     
    -				// set old shoot for update and add gardener finalizer to it
    -				oldShoot = shoot.DeepCopy()
    -				finalizers := sets.New(oldShoot.GetFinalizers()...)
    -				finalizers.Insert(core.GardenerName)
    -				oldShoot.SetFinalizers(finalizers.UnsortedList())
    -			})
    +					authorizeAttributes.Name = shoot.Name
     
    -			It("should reject removing the gardener finalizer if the shoot has not yet been deleted successfully", func() {
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +					err := admissionHandler.Admit(ctx, attrs, nil)
     
    -				attrs := admission.NewAttributesRecord(&shoot, oldShoot, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -				Expect(err).To(HaveOccurred())
    -				Expect(err.Error()).To(ContainSubstring("shoot deletion has not completed successfully yet"))
    +					Expect(err).To(BeInvalidError())
    +					Expect(err.Error()).To(ContainSubstring("name must not exceed"))
    +				})
     			})
     
    -			It("should admit removing the gardener finalizer if the shoot deletion succeeded ", func() {
    -				Expect(coreInformerFactory.Core().V1beta1().Projects().Informer().GetStore().Add(&project)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().CloudProfiles().Informer().GetStore().Add(&cloudProfile)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().Seeds().Informer().GetStore().Add(&seed)).To(Succeed())
    -				Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
    -				Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
    +			It("should add the created-by annotation", func() {
    +				Expect(shoot.Annotations).NotTo(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, userInfo.Name))
     
    -				shoot.Status.LastOperation = &core.LastOperation{
    -					Type:     core.LastOperationTypeDelete,
    -					State:    core.LastOperationStateSucceeded,
    -					Progress: 100,
    -				}
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
    +				Expect(admissionHandler.Admit(ctx, attrs, nil)).NotTo(HaveOccurred())
     
    -				attrs := admission.NewAttributesRecord(&shoot, oldShoot, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
    -				err := admissionHandler.Admit(ctx, attrs, nil)
    -				Expect(err).ToNot(HaveOccurred())
    +				Expect(shoot.Annotations).To(HaveKeyWithValue(v1beta1constants.GardenCreatedBy, userInfo.Name))
     			})
     		})
     
    @@ -1706,7 +1660,7 @@ var _ = Describe("validator", func() {
     				shoot.Spec.SeedName = nil
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
    @@ -1719,7 +1673,7 @@ var _ = Describe("validator", func() {
     
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).To(BeForbiddenError())
    @@ -1735,7 +1689,7 @@ var _ = Describe("validator", func() {
     
     				shoot.Spec.AccessRestrictions = []core.AccessRestrictionWithOptions{{AccessRestriction: core.AccessRestriction{Name: "foo"}}}
     
    -				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, nil)
    +				attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.UpdateOptions{}, false, userInfo)
     				err := admissionHandler.Admit(ctx, attrs, nil)
     
     				Expect(err).NotTo(HaveOccurred())
    @@ -2013,23 +1967,23 @@ var _ = Describe("validator", func() {
     						})
     
     						It("should allow scheduling non-HA shoot", func() {
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'node'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeNode}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should reject scheduling HA shoot with failure tolerance type 'zone'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeZone}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(BeForbiddenError())
     						})
     					})
    @@ -2040,23 +1994,23 @@ var _ = Describe("validator", func() {
     						})
     
     						It("should allow scheduling non-HA shoot", func() {
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'node'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeNode}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     
     						It("should allow scheduling HA shoot with failure tolerance type 'zone'", func() {
     							shoot.Annotations = make(map[string]string)
     							shoot.Spec.ControlPlane = &core.ControlPlane{HighAvailability: &core.HighAvailability{FailureTolerance: core.FailureTolerance{Type: core.FailureToleranceTypeZone}}}
     
    -							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +							attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     							Expect(admissionHandler.Admit(ctx, attrs, nil)).To(Succeed())
     						})
     					})
    @@ -2270,7 +2224,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).NotTo(HaveOccurred())
    @@ -2290,7 +2244,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2320,7 +2274,7 @@ var _ = Describe("validator", func() {
     						Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2351,7 +2305,7 @@ var _ = Describe("validator", func() {
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     						Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).To(BeForbiddenError())
    @@ -2383,7 +2337,7 @@ var _ = Describe("validator", func() {
     						Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     						Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(&secret)).To(Succeed())
     
    -						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +						attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     						err := admissionHandler.Admit(ctx, attrs, nil)
     
     						Expect(err).NotTo(HaveOccurred())
    @@ -2408,7 +2362,7 @@ var _ = Describe("validator", func() {
     					Expect(coreInformerFactory.Core().V1beta1().SecretBindings().Informer().GetStore().Add(&secretBinding)).To(Succeed())
     					Expect(securityInformerFactory.Security().V1alpha1().CredentialsBindings().Informer().GetStore().Add(&credentialsBinding)).To(Succeed())
     
    -					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
    +					attrs := admission.NewAttributesRecord(&shoot, nil, core.Kind("Shoot").WithVersion("version"), shoot.Namespace, shoot.Name, core.Resource("shoots").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, userInfo)
     					err := admissionHandler.Admit(ctx, attrs, nil)
     
     					Expect(err).To(errorMatcher)
    
  • skaffold-operator.yaml+2 0 modified
    @@ -606,6 +606,7 @@ build:
                 - plugin/pkg/global/deletionconfirmation
                 - plugin/pkg/global/extensionlabels
                 - plugin/pkg/global/extensionvalidation
    +            - plugin/pkg/global/finalizerremoval
                 - plugin/pkg/global/resourcereferencemanager
                 - plugin/pkg/managedseed/shoot
                 - plugin/pkg/managedseed/validator
    @@ -865,6 +866,7 @@ build:
                 - pkg/admissioncontroller/webhook/admission/internaldomainsecret
                 - pkg/admissioncontroller/webhook/admission/kubeconfigsecret
                 - pkg/admissioncontroller/webhook/admission/namespacedeletion
    +            - pkg/admissioncontroller/webhook/admission/providersecretlabels
                 - pkg/admissioncontroller/webhook/admission/resourcesize
                 - pkg/admissioncontroller/webhook/admission/seedrestriction
                 - pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref
    
  • skaffold.yaml+2 0 modified
    @@ -203,6 +203,7 @@ build:
                 - plugin/pkg/global/deletionconfirmation
                 - plugin/pkg/global/extensionlabels
                 - plugin/pkg/global/extensionvalidation
    +            - plugin/pkg/global/finalizerremoval
                 - plugin/pkg/global/resourcereferencemanager
                 - plugin/pkg/managedseed/shoot
                 - plugin/pkg/managedseed/validator
    @@ -462,6 +463,7 @@ build:
                 - pkg/admissioncontroller/webhook/admission/internaldomainsecret
                 - pkg/admissioncontroller/webhook/admission/kubeconfigsecret
                 - pkg/admissioncontroller/webhook/admission/namespacedeletion
    +            - pkg/admissioncontroller/webhook/admission/providersecretlabels
                 - pkg/admissioncontroller/webhook/admission/resourcesize
                 - pkg/admissioncontroller/webhook/admission/seedrestriction
                 - pkg/admissioncontroller/webhook/admission/shootkubeconfigsecretref
    
  • test/integration/envtest/environment_test.go+1 1 modified
    @@ -44,6 +44,6 @@ var _ = Describe("GardenerTestEnvironment", func() {
     
     	It("should be able to manipulate resource from security.gardener.cloud/v1alpha1", func() {
     		credentialsBinding := &securityv1alpha1.CredentialsBinding{ObjectMeta: metav1.ObjectMeta{GenerateName: "test-", Namespace: testNamespace.Name}}
    -		Expect(testClient.Create(ctx, credentialsBinding)).To(MatchError(ContainSubstring("credentialsbindings.security.gardener.cloud \"test-\" is forbidden")))
    +		Expect(testClient.Create(ctx, credentialsBinding)).To(MatchError(MatchRegexp("CredentialsBinding.security.gardener.cloud \"test-.+\" is invalid")))
     	})
     })
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.