diff --git a/charts/gardener/controlplane/charts/application/templates/mutatingwebhook-admission-controller.yaml b/charts/gardener/controlplane/charts/application/templates/mutatingwebhook-admission-controller.yaml index f95c74334f3..e190dd4e6e7 100644 --- a/charts/gardener/controlplane/charts/application/templates/mutatingwebhook-admission-controller.yaml +++ b/charts/gardener/controlplane/charts/application/templates/mutatingwebhook-admission-controller.yaml @@ -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 }} diff --git a/cmd/gardener-controller-manager/app/app.go b/cmd/gardener-controller-manager/app/app.go index 2ca3571bb0e..09531a8e5dd 100644 --- a/cmd/gardener-controller-manager/app/app.go +++ b/cmd/gardener-controller-manager/app/app.go @@ -34,6 +34,7 @@ import ( "github.com/gardener/gardener/cmd/utils/initrun" "github.com/gardener/gardener/pkg/api/indexer" gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" + v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" seedmanagementv1alpha1 "github.com/gardener/gardener/pkg/apis/seedmanagement/v1alpha1" "github.com/gardener/gardener/pkg/client/kubernetes" controllermanagerconfigv1alpha1 "github.com/gardener/gardener/pkg/controllermanager/apis/config/v1alpha1" @@ -216,6 +217,26 @@ func run(ctx context.Context, log logr.Logger, cfg *controllermanagerconfigv1alp } } + managedSeedList := &seedmanagementv1alpha1.ManagedSeedList{} + if err := mgr.GetClient().List(ctx, managedSeedList); err != nil { + return fmt.Errorf("failed listing managed seeds: %w", err) + } + for _, managedSeed := range managedSeedList.Items { + fns = append(fns, func(ctx context.Context) error { + obj := &managedSeed + label := v1beta1constants.LabelPrefixSeedName + obj.GetName() + if _, ok := obj.GetLabels()[label]; ok { + emptyPatch := client.MergeFrom(obj) + if err := mgr.GetClient().Patch(ctx, obj, emptyPatch); err != nil { + return fmt.Errorf("failed to patch managed seed %s: %w", client.ObjectKeyFromObject(obj), err) + } + if _, ok := obj.GetLabels()[label]; ok { + return fmt.Errorf("the label %s on the managed seed %s is still present, the mutating webhook is running in an older version", label, client.ObjectKeyFromObject(obj)) + } + } + return nil + }) + } return flow.Parallel(fns...)(ctx) })); err != nil { return fmt.Errorf("failed adding seed name label removal runnable to manager: %w", err) diff --git a/cmd/gardener-extension-admission-local/app/app.go b/cmd/gardener-extension-admission-local/app/app.go index 00a7a0937cd..3f8f9a8584d 100644 --- a/cmd/gardener-extension-admission-local/app/app.go +++ b/cmd/gardener-extension-admission-local/app/app.go @@ -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) diff --git a/docs/concepts/admission-controller.md b/docs/concepts/admission-controller.md index 1fff35ed2c9..07a79df6dbd 100644 --- a/docs/concepts/admission-controller.md +++ b/docs/concepts/admission-controller.md @@ -100,6 +100,10 @@ This information can be used by third parties so that they establish trust to sp This handler protects `secrets` and `configmaps` against tampering. It denies `CREATE`, `UPDATE` and `DELETE` requests if the resource is labeled with `gardener.cloud/update-restriction=true` and the request is not made by a `gardenlet`. +In addition, the following service accounts are allowed to perform certain operations: +- `system:serviceaccount:kube-system:generic-garbage-collector` is allowed to `DELETE` restricted resources. +- `system:serviceaccount:kube-system:gardener-internal` is allowed to `UPDATE` restricted resources. + ## Authorization Webhook Handlers This section describes the authorization webhook handlers that are currently served. diff --git a/docs/concepts/apiserver-admission-plugins.md b/docs/concepts/apiserver-admission-plugins.md index be256436e44..523dcbb480d 100644 --- a/docs/concepts/apiserver-admission-plugins.md +++ b/docs/concepts/apiserver-admission-plugins.md @@ -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 `.extensions.gardener.cloud/ : "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=` 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=` annotation for newly created `Shoot` resources. ## `ShootManagedSeed` diff --git a/pkg/admissioncontroller/webhook/add.go b/pkg/admissioncontroller/webhook/add.go index 6fb52383895..3052e17657d 100644 --- a/pkg/admissioncontroller/webhook/add.go +++ b/pkg/admissioncontroller/webhook/add.go @@ -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, diff --git a/pkg/admissioncontroller/webhook/admission/providersecretlabels/add.go b/pkg/admissioncontroller/webhook/admission/providersecretlabels/add.go new file mode 100644 index 00000000000..ad81969d2cf --- /dev/null +++ b/pkg/admissioncontroller/webhook/admission/providersecretlabels/add.go @@ -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 +} diff --git a/pkg/admissioncontroller/webhook/admission/providersecretlabels/handler.go b/pkg/admissioncontroller/webhook/admission/providersecretlabels/handler.go new file mode 100644 index 00000000000..008a1a90032 --- /dev/null +++ b/pkg/admissioncontroller/webhook/admission/providersecretlabels/handler.go @@ -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") + } +} diff --git a/pkg/admissioncontroller/webhook/admission/providersecretlabels/handler_test.go b/pkg/admissioncontroller/webhook/admission/providersecretlabels/handler_test.go new file mode 100644 index 00000000000..a2b72ff8acf --- /dev/null +++ b/pkg/admissioncontroller/webhook/admission/providersecretlabels/handler_test.go @@ -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()) + }) +}) diff --git a/pkg/admissioncontroller/webhook/admission/providersecretlabels/providersecretlabels_suite_test.go b/pkg/admissioncontroller/webhook/admission/providersecretlabels/providersecretlabels_suite_test.go new file mode 100644 index 00000000000..2bd3fe8fd65 --- /dev/null +++ b/pkg/admissioncontroller/webhook/admission/providersecretlabels/providersecretlabels_suite_test.go @@ -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") +} diff --git a/pkg/admissioncontroller/webhook/admission/updaterestriction/handler.go b/pkg/admissioncontroller/webhook/admission/updaterestriction/handler.go index 98a7475a73b..cc1a278d40d 100644 --- a/pkg/admissioncontroller/webhook/admission/updaterestriction/handler.go +++ b/pkg/admissioncontroller/webhook/admission/updaterestriction/handler.go @@ -29,6 +29,12 @@ func (h *Handler) Handle(_ context.Context, req admission.Request) admission.Res return admission.Allowed("generic-garbage-collector is allowed to delete system resources") } + // Allow the gardener-internal service account to update resources. + // This service account is used by the gardener-operator to label all encrypted resources with the name of the current ETCD encryption key secret. + if req.UserInfo.Username == "system:serviceaccount:kube-system:gardener-internal" && req.Operation == admissionv1.Update { + return admission.Allowed("system:serviceaccount:kube-system:gardener-internal is allowed to update system resources") + } + if slices.Contains(req.UserInfo.Groups, v1beta1constants.SeedsGroup) { return admission.Allowed("gardenlet is allowed to modify system resources") } diff --git a/pkg/admissioncontroller/webhook/admission/updaterestriction/handler_test.go b/pkg/admissioncontroller/webhook/admission/updaterestriction/handler_test.go index 895fb46ce76..159839aaab0 100644 --- a/pkg/admissioncontroller/webhook/admission/updaterestriction/handler_test.go +++ b/pkg/admissioncontroller/webhook/admission/updaterestriction/handler_test.go @@ -100,5 +100,39 @@ var _ = Describe("handler", func() { }, })) }) + + It("should allow the update request as it is made by the gardener-internal serviceaccount", func() { + req.UserInfo = authenticationv1.UserInfo{ + Username: "system:serviceaccount:kube-system:gardener-internal", + } + req.Operation = admissionv1.Update + resp := handler.Handle(ctx, req) + Expect(resp.Allowed).To(BeTrue()) + Expect(resp.AdmissionResponse).To(Equal(admissionv1.AdmissionResponse{ + Allowed: true, + Result: &metav1.Status{ + Code: int32(200), + Reason: "", + Message: "system:serviceaccount:kube-system:gardener-internal is allowed to update system resources", + }, + })) + }) + + It("should not allow the update request even if it is made by the gardener-internal serviceaccount", func() { + req.UserInfo = authenticationv1.UserInfo{ + Username: "system:serviceaccount:kube-system:gardener-internal", + } + req.Operation = admissionv1.Delete + resp := handler.Handle(ctx, req) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.AdmissionResponse).To(Equal(admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Code: int32(403), + Reason: "Forbidden", + Message: "user \"system:serviceaccount:kube-system:gardener-internal\" is not allowed to DELETE system configmaps", + }, + })) + }) }) }) diff --git a/pkg/apis/core/helper/secretbinding.go b/pkg/apis/core/helper/secretbinding.go index 0025f2a4533..d101fe26b4f 100644 --- a/pkg/apis/core/helper/secretbinding.go +++ b/pkg/apis/core/helper/secretbinding.go @@ -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, ",") } diff --git a/pkg/apis/core/helper/secretbinding_test.go b/pkg/apis/core/helper/secretbinding_test.go index 0ccbd92b205..2f7b2533c59 100644 --- a/pkg/apis/core/helper/secretbinding_test.go +++ b/pkg/apis/core/helper/secretbinding_test.go @@ -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"}), ) diff --git a/pkg/apis/core/v1beta1/helper/secretbinding.go b/pkg/apis/core/v1beta1/helper/secretbinding.go index fd1acda57b3..5d937b8763c 100644 --- a/pkg/apis/core/v1beta1/helper/secretbinding.go +++ b/pkg/apis/core/v1beta1/helper/secretbinding.go @@ -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, ",") } diff --git a/pkg/apis/core/v1beta1/helper/secretbinding_test.go b/pkg/apis/core/v1beta1/helper/secretbinding_test.go index 2080cdf3f66..7d0aec50389 100644 --- a/pkg/apis/core/v1beta1/helper/secretbinding_test.go +++ b/pkg/apis/core/v1beta1/helper/secretbinding_test.go @@ -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"}), ) diff --git a/pkg/apiserver/plugins.go b/pkg/apiserver/plugins.go index 4671f7e7875..7fab940785f 100644 --- a/pkg/apiserver/plugins.go +++ b/pkg/apiserver/plugins.go @@ -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) diff --git a/pkg/apiserver/registry/core/backupbucket/backupbucket_suite_test.go b/pkg/apiserver/registry/core/backupbucket/backupbucket_suite_test.go index 42fb542e1c3..e254d5a35de 100644 --- a/pkg/apiserver/registry/core/backupbucket/backupbucket_suite_test.go +++ b/pkg/apiserver/registry/core/backupbucket/backupbucket_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/core/backupentry/backupentry_suite_test.go b/pkg/apiserver/registry/core/backupentry/backupentry_suite_test.go index defa1be0169..06df8fadc75 100644 --- a/pkg/apiserver/registry/core/backupentry/backupentry_suite_test.go +++ b/pkg/apiserver/registry/core/backupentry/backupentry_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/core/cloudprofile/cloudprofile_suite_test.go b/pkg/apiserver/registry/core/cloudprofile/cloudprofile_suite_test.go index e7e74ea3f2a..f1a35f4fe87 100644 --- a/pkg/apiserver/registry/core/cloudprofile/cloudprofile_suite_test.go +++ b/pkg/apiserver/registry/core/cloudprofile/cloudprofile_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/core/controllerinstallation/strategy_test.go b/pkg/apiserver/registry/core/controllerinstallation/strategy_test.go index 3ec0f773e54..4cd8d465932 100644 --- a/pkg/apiserver/registry/core/controllerinstallation/strategy_test.go +++ b/pkg/apiserver/registry/core/controllerinstallation/strategy_test.go @@ -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() { diff --git a/pkg/apiserver/registry/core/namespacedcloudprofile/namespacedcloudprofile_suite_test.go b/pkg/apiserver/registry/core/namespacedcloudprofile/namespacedcloudprofile_suite_test.go index e63dcb3601b..8e6f416e70f 100644 --- a/pkg/apiserver/registry/core/namespacedcloudprofile/namespacedcloudprofile_suite_test.go +++ b/pkg/apiserver/registry/core/namespacedcloudprofile/namespacedcloudprofile_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/core/project/project_suite_test.go b/pkg/apiserver/registry/core/project/project_suite_test.go index a49eee2f498..2cc5e1dad7e 100644 --- a/pkg/apiserver/registry/core/project/project_suite_test.go +++ b/pkg/apiserver/registry/core/project/project_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/core/secretbinding/secretbinding_suite_test.go b/pkg/apiserver/registry/core/secretbinding/secretbinding_suite_test.go index 4dff9680985..e3882b12ff3 100644 --- a/pkg/apiserver/registry/core/secretbinding/secretbinding_suite_test.go +++ b/pkg/apiserver/registry/core/secretbinding/secretbinding_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/core/secretbinding/strategy.go b/pkg/apiserver/registry/core/secretbinding/strategy.go index d47994b53b0..9403a9b0a5e 100644 --- a/pkg/apiserver/registry/core/secretbinding/strategy.go +++ b/pkg/apiserver/registry/core/secretbinding/strategy.go @@ -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 { diff --git a/pkg/apiserver/registry/core/secretbinding/strategy_test.go b/pkg/apiserver/registry/core/secretbinding/strategy_test.go index 69c14cd03e5..eb58b9e50bc 100644 --- a/pkg/apiserver/registry/core/secretbinding/strategy_test.go +++ b/pkg/apiserver/registry/core/secretbinding/strategy_test.go @@ -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 diff --git a/pkg/apiserver/registry/core/seed/seed_suite_test.go b/pkg/apiserver/registry/core/seed/seed_suite_test.go index 6df305ae924..a769852c1bd 100644 --- a/pkg/apiserver/registry/core/seed/seed_suite_test.go +++ b/pkg/apiserver/registry/core/seed/seed_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/core/shoot/shoot_suite_test.go b/pkg/apiserver/registry/core/shoot/shoot_suite_test.go index c296c8b226c..6e7160bb8b2 100644 --- a/pkg/apiserver/registry/core/shoot/shoot_suite_test.go +++ b/pkg/apiserver/registry/core/shoot/shoot_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/core/shoot/storage/storage_suite_test.go b/pkg/apiserver/registry/core/shoot/storage/storage_suite_test.go index c4e718c240b..267b1ab41e1 100644 --- a/pkg/apiserver/registry/core/shoot/storage/storage_suite_test.go +++ b/pkg/apiserver/registry/core/shoot/storage/storage_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/operations/bastion/bastion_suite_test.go b/pkg/apiserver/registry/operations/bastion/bastion_suite_test.go index 555d3f18fb2..279ce10b4a2 100644 --- a/pkg/apiserver/registry/operations/bastion/bastion_suite_test.go +++ b/pkg/apiserver/registry/operations/bastion/bastion_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/security/credentialsbinding/credentialsbinding_suite_test.go b/pkg/apiserver/registry/security/credentialsbinding/credentialsbinding_suite_test.go new file mode 100644 index 00000000000..a1bb46ef8ae --- /dev/null +++ b/pkg/apiserver/registry/security/credentialsbinding/credentialsbinding_suite_test.go @@ -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") +} diff --git a/pkg/apiserver/registry/security/credentialsbinding/strategy.go b/pkg/apiserver/registry/security/credentialsbinding/strategy.go index 889c6ab5553..adc4db53a46 100644 --- a/pkg/apiserver/registry/security/credentialsbinding/strategy.go +++ b/pkg/apiserver/registry/security/credentialsbinding/strategy.go @@ -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) { diff --git a/pkg/apiserver/registry/security/credentialsbinding/strategy_test.go b/pkg/apiserver/registry/security/credentialsbinding/strategy_test.go new file mode 100644 index 00000000000..d33850a9bb3 --- /dev/null +++ b/pkg/apiserver/registry/security/credentialsbinding/strategy_test.go @@ -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")) + }) + }) +}) diff --git a/pkg/apiserver/registry/security/workloadidentity/storage/storage_suite_test.go b/pkg/apiserver/registry/security/workloadidentity/storage/storage_suite_test.go index 975eaa4be97..dcd0431aea4 100644 --- a/pkg/apiserver/registry/security/workloadidentity/storage/storage_suite_test.go +++ b/pkg/apiserver/registry/security/workloadidentity/storage/storage_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/security/workloadidentity/workloadidentity_suite_test.go b/pkg/apiserver/registry/security/workloadidentity/workloadidentity_suite_test.go index f5ddb6eaaee..d861f445192 100644 --- a/pkg/apiserver/registry/security/workloadidentity/workloadidentity_suite_test.go +++ b/pkg/apiserver/registry/security/workloadidentity/workloadidentity_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/seedmanagement/managedseed/managedseed_suite_test.go b/pkg/apiserver/registry/seedmanagement/managedseed/managedseed_suite_test.go index 4c897ea4e07..76a3ed97746 100644 --- a/pkg/apiserver/registry/seedmanagement/managedseed/managedseed_suite_test.go +++ b/pkg/apiserver/registry/seedmanagement/managedseed/managedseed_suite_test.go @@ -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") } diff --git a/pkg/apiserver/registry/seedmanagement/managedseedset/managedseedset_suite_test.go b/pkg/apiserver/registry/seedmanagement/managedseedset/managedseedset_suite_test.go index 3270ebf6c22..ac57d4fda5b 100644 --- a/pkg/apiserver/registry/seedmanagement/managedseedset/managedseedset_suite_test.go +++ b/pkg/apiserver/registry/seedmanagement/managedseedset/managedseedset_suite_test.go @@ -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") } diff --git a/pkg/component/autoscaling/vpa/general.go b/pkg/component/autoscaling/vpa/general.go index 05ccd2db390..3694827a36e 100644 --- a/pkg/component/autoscaling/vpa/general.go +++ b/pkg/component/autoscaling/vpa/general.go @@ -75,9 +75,9 @@ func (v *vpa) reconcileGeneralClusterRoleActor(clusterRole *rbacv1.ClusterRole) Verbs: []string{"get", "list", "watch"}, }, { - APIGroups: []string{""}, + APIGroups: []string{"", "events.k8s.io"}, Resources: []string{"events"}, - Verbs: []string{"get", "list", "watch", "create"}, + Verbs: []string{"create", "get", "list", "watch", "patch", "update"}, }, { APIGroups: []string{"autoscaling.k8s.io"}, diff --git a/pkg/component/autoscaling/vpa/vpa_test.go b/pkg/component/autoscaling/vpa/vpa_test.go index 538eb514cd5..af9a83951f4 100644 --- a/pkg/component/autoscaling/vpa/vpa_test.go +++ b/pkg/component/autoscaling/vpa/vpa_test.go @@ -1260,9 +1260,9 @@ var _ = Describe("VPA", func() { Verbs: []string{"get", "list", "watch"}, }, { - APIGroups: []string{""}, + APIGroups: []string{"", "events.k8s.io"}, Resources: []string{"events"}, - Verbs: []string{"get", "list", "watch", "create"}, + Verbs: []string{"create", "get", "list", "watch", "patch", "update"}, }, { APIGroups: []string{"autoscaling.k8s.io"}, diff --git a/pkg/component/gardener/admissioncontroller/admission_controller.go b/pkg/component/gardener/admissioncontroller/admission_controller.go index e7e78d4e1db..97bf366b7ac 100644 --- a/pkg/component/gardener/admissioncontroller/admission_controller.go +++ b/pkg/component/gardener/admissioncontroller/admission_controller.go @@ -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 diff --git a/pkg/component/gardener/admissioncontroller/admission_controller_test.go b/pkg/component/gardener/admissioncontroller/admission_controller_test.go index 2fbd379548c..fa505c30c04 100644 --- a/pkg/component/gardener/admissioncontroller/admission_controller_test.go +++ b/pkg/component/gardener/admissioncontroller/admission_controller_test.go @@ -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, + }, + }, + } +} diff --git a/pkg/component/gardener/admissioncontroller/webhooks.go b/pkg/component/gardener/admissioncontroller/webhooks.go index a5d94292c44..d682b455b69 100644 --- a/pkg/component/gardener/admissioncontroller/webhooks.go +++ b/pkg/component/gardener/admissioncontroller/webhooks.go @@ -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 diff --git a/pkg/component/observability/monitoring/prometheus/cache/assets/scrapeconfigs/cadvisor.yaml b/pkg/component/observability/monitoring/prometheus/cache/assets/scrapeconfigs/cadvisor.yaml index 72b51e431d8..b8d4ff31aae 100644 --- a/pkg/component/observability/monitoring/prometheus/cache/assets/scrapeconfigs/cadvisor.yaml +++ b/pkg/component/observability/monitoring/prometheus/cache/assets/scrapeconfigs/cadvisor.yaml @@ -6,7 +6,7 @@ metrics_path: /metrics/cadvisor tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt - insecure_skip_verify: true + insecure_skip_verify: {{.SeedIsShoot}} bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token kubernetes_sd_configs: diff --git a/pkg/component/observability/monitoring/prometheus/cache/assets/scrapeconfigs/kubelet.yaml b/pkg/component/observability/monitoring/prometheus/cache/assets/scrapeconfigs/kubelet.yaml index b984c74240c..4552baffd21 100644 --- a/pkg/component/observability/monitoring/prometheus/cache/assets/scrapeconfigs/kubelet.yaml +++ b/pkg/component/observability/monitoring/prometheus/cache/assets/scrapeconfigs/kubelet.yaml @@ -4,7 +4,7 @@ scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt - insecure_skip_verify: true + insecure_skip_verify: {{.SeedIsShoot}} bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token kubernetes_sd_configs: diff --git a/pkg/component/observability/monitoring/prometheus/cache/scrapeconfigs.go b/pkg/component/observability/monitoring/prometheus/cache/scrapeconfigs.go index 319d946c7a8..14dfd58d8a8 100644 --- a/pkg/component/observability/monitoring/prometheus/cache/scrapeconfigs.go +++ b/pkg/component/observability/monitoring/prometheus/cache/scrapeconfigs.go @@ -20,20 +20,20 @@ var ( // Data represents the data for the template. type Data struct { - IsManagedSeed bool + SeedIsShoot bool } // AdditionalScrapeConfigs returns the additional scrape configs for the cache prometheus. -func AdditionalScrapeConfigs(isManagedSeed bool) ([]string, error) { +func AdditionalScrapeConfigs(seedIsShoot bool) ([]string, error) { var out []string - if result, err := process(cAdvisor, isManagedSeed); err != nil { + if result, err := process(cAdvisor, seedIsShoot); err != nil { return nil, fmt.Errorf("failed processing cadvisor scrape config template: %w", err) } else { out = append(out, result) } - if result, err := process(kubelet, isManagedSeed); err != nil { + if result, err := process(kubelet, seedIsShoot); err != nil { return nil, fmt.Errorf("failed processing kubelet scrape config template: %w", err) } else { out = append(out, result) @@ -42,9 +42,9 @@ func AdditionalScrapeConfigs(isManagedSeed bool) ([]string, error) { return out, nil } -func process(text string, isManagedSeed bool) (string, error) { +func process(text string, seedIsShoot bool) (string, error) { data := Data{ - IsManagedSeed: isManagedSeed, + SeedIsShoot: seedIsShoot, } tmpl, err := template.New("Template").Parse(text) diff --git a/pkg/component/observability/monitoring/prometheus/cache/scrapeconfigs_test.go b/pkg/component/observability/monitoring/prometheus/cache/scrapeconfigs_test.go index 64e469a4881..829d5c8a515 100644 --- a/pkg/component/observability/monitoring/prometheus/cache/scrapeconfigs_test.go +++ b/pkg/component/observability/monitoring/prometheus/cache/scrapeconfigs_test.go @@ -13,7 +13,7 @@ import ( var _ = Describe("PrometheusRules", func() { Describe("#AdditionalScrapeConfigs", func() { - When("isManagedSeed", func() { + When("seedIsShoot", func() { It("should return the expected objects (with TLS verification skipped)", func() { result, err := cache.AdditionalScrapeConfigs(true) Expect(err).NotTo(HaveOccurred()) @@ -122,7 +122,7 @@ metrics_path: /metrics/cadvisor tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt - insecure_skip_verify: true + insecure_skip_verify: false bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token kubernetes_sd_configs: @@ -183,7 +183,7 @@ scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt - insecure_skip_verify: true + insecure_skip_verify: false bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token kubernetes_sd_configs: diff --git a/pkg/controllermanager/controller/secretbinding/reconciler.go b/pkg/controllermanager/controller/secretbinding/reconciler.go index 81810fc40cc..756f015c4cf 100644 --- a/pkg/controllermanager/controller/secretbinding/reconciler.go +++ b/pkg/controllermanager/controller/secretbinding/reconciler.go @@ -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) } } } diff --git a/pkg/gardenlet/controller/seed/seed/components.go b/pkg/gardenlet/controller/seed/seed/components.go index ec3b024f7a1..268d2640735 100644 --- a/pkg/gardenlet/controller/seed/seed/components.go +++ b/pkg/gardenlet/controller/seed/seed/components.go @@ -124,7 +124,7 @@ func (r *Reconciler) instantiateComponents( globalMonitoringSecretSeed *corev1.Secret, alertingSMTPSecret *corev1.Secret, wildCardCertSecret *corev1.Secret, - isManagedSeed bool, + seedIsShoot bool, ) ( c components, err error, @@ -232,7 +232,7 @@ func (r *Reconciler) instantiateComponents( if err != nil { return } - c.cachePrometheus, err = r.newCachePrometheus(log, seed, isManagedSeed) + c.cachePrometheus, err = r.newCachePrometheus(log, seed, seedIsShoot) if err != nil { return } @@ -536,8 +536,8 @@ func (r *Reconciler) newPlutono(seed *seedpkg.Seed, secretsManager secretsmanage ) } -func (r *Reconciler) newCachePrometheus(log logr.Logger, seed *seedpkg.Seed, isManagedSeed bool) (component.DeployWaiter, error) { - additionalScrapeConfigs, err := cacheprometheus.AdditionalScrapeConfigs(isManagedSeed) +func (r *Reconciler) newCachePrometheus(log logr.Logger, seed *seedpkg.Seed, seedIsShoot bool) (component.DeployWaiter, error) { + additionalScrapeConfigs, err := cacheprometheus.AdditionalScrapeConfigs(seedIsShoot) if err != nil { return nil, fmt.Errorf("failed getting additional scrape configs: %w", err) } diff --git a/pkg/gardenlet/controller/seed/seed/reconciler.go b/pkg/gardenlet/controller/seed/seed/reconciler.go index f3275aa533f..ce5d333d328 100644 --- a/pkg/gardenlet/controller/seed/seed/reconciler.go +++ b/pkg/gardenlet/controller/seed/seed/reconciler.go @@ -30,7 +30,6 @@ import ( gardenerutils "github.com/gardener/gardener/pkg/utils/gardener" gardenletutils "github.com/gardener/gardener/pkg/utils/gardener/gardenlet" "github.com/gardener/gardener/pkg/utils/imagevector" - kubernetesutils "github.com/gardener/gardener/pkg/utils/kubernetes" ) // Reconciler reconciles Seed resources and provisions or de-provisions the seed system components. @@ -60,13 +59,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) ( return reconcile.Result{}, fmt.Errorf("error retrieving object from store: %w", err) } - managedSeed, err := kubernetesutils.GetManagedSeedByName(ctx, r.GardenClient, seed.Name) - if err != nil { + var seedIsShoot bool + if err := r.SeedClientSet.Client().Get(ctx, client.ObjectKey{ + Namespace: metav1.NamespaceSystem, + Name: v1beta1constants.ConfigMapNameShootInfo, + }, &corev1.ConfigMap{}); err != nil { if !apierrors.IsNotFound(err) { - return reconcile.Result{}, fmt.Errorf("failed to get managed seed: %w", err) + return reconcile.Result{}, fmt.Errorf("failed to check if this seed is a shoot: %w", err) } + seedIsShoot = false + } else { + seedIsShoot = true } - isManagedSeed := managedSeed != nil operationType := gardencorev1beta1.LastOperationTypeReconcile if seed.DeletionTimestamp != nil { @@ -106,14 +110,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) ( } if seed.DeletionTimestamp != nil { - result, err := r.delete(ctx, log, seedObj, seedIsGarden, isManagedSeed) + result, err := r.delete(ctx, log, seedObj, seedIsGarden, seedIsShoot) if err != nil { return result, r.updateStatusOperationError(ctx, seed, err, operationType) } return result, nil } - if err := r.reconcile(ctx, log, seedObj, seedIsGarden, isManagedSeed); err != nil { + if err := r.reconcile(ctx, log, seedObj, seedIsGarden, seedIsShoot); err != nil { return reconcile.Result{}, r.updateStatusOperationError(ctx, seed, err, operationType) } diff --git a/pkg/gardenlet/controller/seed/seed/reconciler_delete.go b/pkg/gardenlet/controller/seed/seed/reconciler_delete.go index 76ae344f073..d276d2642ff 100644 --- a/pkg/gardenlet/controller/seed/seed/reconciler_delete.go +++ b/pkg/gardenlet/controller/seed/seed/reconciler_delete.go @@ -34,7 +34,7 @@ func (r *Reconciler) delete( log logr.Logger, seedObj *seedpkg.Seed, seedIsGarden bool, - isManagedSeed bool, + seedIsShoot bool, ) ( reconcile.Result, error, @@ -80,7 +80,7 @@ func (r *Reconciler) delete( } log.Info("No Shoots or BackupBuckets are referencing the Seed, deletion accepted") - if err := r.runDeleteSeedFlow(ctx, log, seedObj, seedIsGarden, isManagedSeed); err != nil { + if err := r.runDeleteSeedFlow(ctx, log, seedObj, seedIsGarden, seedIsShoot); err != nil { return reconcile.Result{}, err } @@ -100,10 +100,10 @@ func (r *Reconciler) runDeleteSeedFlow( log logr.Logger, seed *seedpkg.Seed, seedIsGarden bool, - isManagedSeed bool, + seedIsShoot bool, ) error { log.Info("Instantiating component deployers") - c, err := r.instantiateComponents(ctx, log, seed, nil, seedIsGarden, nil, nil, nil, isManagedSeed) + c, err := r.instantiateComponents(ctx, log, seed, nil, seedIsGarden, nil, nil, nil, seedIsShoot) if err != nil { return err } diff --git a/pkg/gardenlet/controller/seed/seed/reconciler_reconcile.go b/pkg/gardenlet/controller/seed/seed/reconciler_reconcile.go index a1795bc8302..73cb62b383f 100644 --- a/pkg/gardenlet/controller/seed/seed/reconciler_reconcile.go +++ b/pkg/gardenlet/controller/seed/seed/reconciler_reconcile.go @@ -52,7 +52,7 @@ func (r *Reconciler) reconcile( log logr.Logger, seedObj *seedpkg.Seed, seedIsGarden bool, - isManagedSeed bool, + seedIsShoot bool, ) error { seed := seedObj.GetInfo() @@ -68,7 +68,7 @@ func (r *Reconciler) reconcile( return err } - if err := r.runReconcileSeedFlow(ctx, log, seedObj, seedIsGarden, isManagedSeed); err != nil { + if err := r.runReconcileSeedFlow(ctx, log, seedObj, seedIsGarden, seedIsShoot); err != nil { return err } @@ -102,7 +102,7 @@ func (r *Reconciler) runReconcileSeedFlow( log logr.Logger, seed *seedpkg.Seed, seedIsGarden bool, - isManagedSeed bool, + seedIsShoot bool, ) error { // VPA is a prerequisite. If it's enabled then we deploy the CRD (and later also the related components) as part of // the flow. However, when it's disabled then we check whether it is indeed available (and fail, otherwise). @@ -191,7 +191,7 @@ func (r *Reconciler) runReconcileSeedFlow( } log.Info("Instantiating component deployers") - c, err := r.instantiateComponents(ctx, log, seed, secretsManager, seedIsGarden, globalMonitoringSecretSeed, alertingSMTPSecret, wildcardCertSecret, isManagedSeed) + c, err := r.instantiateComponents(ctx, log, seed, secretsManager, seedIsGarden, globalMonitoringSecretSeed, alertingSMTPSecret, wildcardCertSecret, seedIsShoot) if err != nil { return err } diff --git a/pkg/gardenlet/operation/botanist/resources_test.go b/pkg/gardenlet/operation/botanist/resources_test.go index f42c3eeaacb..10ed796521e 100644 --- a/pkg/gardenlet/operation/botanist/resources_test.go +++ b/pkg/gardenlet/operation/botanist/resources_test.go @@ -142,10 +142,8 @@ var _ = Describe("Resources", func() { expectReferencedResourcesInSeed( &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "ref-" + resource.Name, - Namespace: controlPlaneNamespace, - Labels: resource.Labels, - Annotations: resource.Annotations, + Name: "ref-" + resource.Name, + Namespace: controlPlaneNamespace, }, Type: resource.Type, Data: resource.Data, diff --git a/pkg/provider-local/admission/cmd/options.go b/pkg/provider-local/admission/cmd/options.go index cfa541d469a..6f6dc0545f6 100644 --- a/pkg/provider-local/admission/cmd/options.go +++ b/pkg/provider-local/admission/cmd/options.go @@ -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), ) } diff --git a/pkg/provider-local/admission/validator/secret.go b/pkg/provider-local/admission/validator/secret.go new file mode 100644 index 00000000000..8f05499ca4d --- /dev/null +++ b/pkg/provider-local/admission/validator/secret.go @@ -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 +} diff --git a/pkg/provider-local/admission/validator/webhook.go b/pkg/provider-local/admission/validator/webhook.go index 8a52d47ad95..9b2c5a2e8da 100644 --- a/pkg/provider-local/admission/validator/webhook.go +++ b/pkg/provider-local/admission/validator/webhook.go @@ -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"}, }, }) } diff --git a/pkg/provider-local/admission/validator/workloadidentity.go b/pkg/provider-local/admission/validator/workloadidentity.go new file mode 100644 index 00000000000..c1c727eeefc --- /dev/null +++ b/pkg/provider-local/admission/validator/workloadidentity.go @@ -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 +} diff --git a/pkg/utils/gardener/resources.go b/pkg/utils/gardener/resources.go index e26f01af653..e61b65e85ca 100644 --- a/pkg/utils/gardener/resources.go +++ b/pkg/utils/gardener/resources.go @@ -37,13 +37,9 @@ func PrepareReferencedResourcesForSeedCopy(ctx context.Context, cl client.Client unstructuredObj.SetNamespace(targetNamespace) unstructuredObj.SetName(v1beta1constants.ReferencedResourcesPrefix + unstructuredObj.GetName()) - // Drop unwanted annotations before copying the resource to the seed. - // All annotations contained in the ManagedResource secret will end up in `ManagedResource.status.resources[].annotations`. - // We don't want this to happen for the last applied annotation of secrets, which includes the secret data in plain - // text. This would put sensitive secret data into the ManagedResource object which is probably unencrypted in etcd. - annotations := unstructuredObj.GetAnnotations() - delete(annotations, "kubectl.kubernetes.io/last-applied-configuration") - unstructuredObj.SetAnnotations(annotations) + // We don't want to keep user-defined annotations or labels when copying the resource to the seed. + unstructuredObj.SetAnnotations(nil) + unstructuredObj.SetLabels(nil) unstructuredObjs = append(unstructuredObjs, unstructuredObj) } diff --git a/pkg/utils/gardener/resources_test.go b/pkg/utils/gardener/resources_test.go index 1e8a2f4a342..ae8b869fbdf 100644 --- a/pkg/utils/gardener/resources_test.go +++ b/pkg/utils/gardener/resources_test.go @@ -7,7 +7,6 @@ package gardener_test import ( "context" "encoding/base64" - "maps" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -107,14 +106,11 @@ var _ = Describe("Resources", func() { for _, unstructuredObj := range unstructuredObjs { Expect(unstructuredObj.GetNamespace()).To(Equal(targetNamespace), unstructuredObj.GetName()+" should have target namespace "+targetNamespace) - Expect(unstructuredObj.GetLabels()).To(Equal(secret.Labels), unstructuredObj.GetName()+" should have no finalizers") + Expect(unstructuredObj.GetAnnotations()).To(BeEmpty(), unstructuredObj.GetName()+" should have no annotations") + Expect(unstructuredObj.GetLabels()).To(BeEmpty(), unstructuredObj.GetName()+" should have no labels") Expect(unstructuredObj.GetFinalizers()).To(BeEmpty(), unstructuredObj.GetName()+" should have no finalizers") Expect(unstructuredObj.Object).To(HaveKey("data"), unstructuredObj.GetName()+" should have data field") - expectedAnnotations := maps.Clone(annotations) - delete(expectedAnnotations, "kubectl.kubernetes.io/last-applied-configuration") - Expect(unstructuredObj.GetAnnotations()).To(Equal(expectedAnnotations), unstructuredObj.GetName()+" should have no annotations") - switch unstructuredObj.GetKind() { case "Secret": Expect(unstructuredObj.GetName()).To(Equal("ref-" + secret.Name)) diff --git a/plugin/pkg/global/extensionlabels/admission.go b/plugin/pkg/global/extensionlabels/admission.go index d1ce027bd76..d5db81603af 100644 --- a/plugin/pkg/global/extensionlabels/admission.go +++ b/plugin/pkg/global/extensionlabels/admission.go @@ -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") } } diff --git a/plugin/pkg/global/finalizerremoval/admission.go b/plugin/pkg/global/finalizerremoval/admission.go new file mode 100644 index 00000000000..26e872031dc --- /dev/null +++ b/plugin/pkg/global/finalizerremoval/admission.go @@ -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) +} diff --git a/plugin/pkg/global/finalizerremoval/admission_test.go b/plugin/pkg/global/finalizerremoval/admission_test.go new file mode 100644 index 00000000000..df43397c370 --- /dev/null +++ b/plugin/pkg/global/finalizerremoval/admission_test.go @@ -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()) + }) + }) + }) +}) diff --git a/plugin/pkg/global/finalizerremoval/finalizerremoval_suite_test.go b/plugin/pkg/global/finalizerremoval/finalizerremoval_suite_test.go new file mode 100644 index 00000000000..8ba131b5d4d --- /dev/null +++ b/plugin/pkg/global/finalizerremoval/finalizerremoval_suite_test.go @@ -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") +} diff --git a/plugin/pkg/global/resourcereferencemanager/admission.go b/plugin/pkg/global/resourcereferencemanager/admission.go index f422b069b90..cfaa41fa2a8 100644 --- a/plugin/pkg/global/resourcereferencemanager/admission.go +++ b/plugin/pkg/global/resourcereferencemanager/admission.go @@ -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 +} diff --git a/plugin/pkg/global/resourcereferencemanager/admission_test.go b/plugin/pkg/global/resourcereferencemanager/admission_test.go index 7d81bf0933b..c77196cebf9 100644 --- a/plugin/pkg/global/resourcereferencemanager/admission_test.go +++ b/plugin/pkg/global/resourcereferencemanager/admission_test.go @@ -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("a)).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("a)).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("a)).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("a)).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,7 +907,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).NotTo(HaveOccurred()) }) @@ -826,22 +915,30 @@ var _ = Describe("resourcereferencemanager", func() { It("should accept because all referenced objects have been found (workloadidentity looked up live)", func() { Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add("a)).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("a)).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("a)).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,6 +1011,7 @@ 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("a)).To(Succeed()) Expect(gardenCoreInformerFactory.Core().V1beta1().Quotas().Informer().GetStore().Add("a2)).To(Succeed()) @@ -921,7 +1019,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).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.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.17.2")) @@ -2244,7 +2235,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()) }) @@ -2312,7 +2303,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.17.2")) Expect(err.Error()).To(ContainSubstring(s.Spec.Provider.Workers[1].Name)) @@ -2363,7 +2354,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.17.2")) @@ -2428,7 +2419,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 fail for removal of a machine version that is used by a NamespacedCloudProfile", func() { @@ -2487,7 +2478,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 MachineImage version"), ContainSubstring("1.16.0"), ContainSubstring("still in use by NamespacedCloudProfile"), @@ -2534,7 +2525,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 MachineImage version"), ContainSubstring("1.16.0"), ContainSubstring("still in use by NamespacedCloudProfile"), @@ -2581,7 +2572,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 MachineImage \"coreos\""), ContainSubstring("still in use by NamespacedCloudProfile"), ))) @@ -2634,7 +2625,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 add MachineImage \"gardenlinux\""), ContainSubstring("already defined by NamespacedCloudProfile \"project-123/profile-42\""), ))) @@ -2716,7 +2707,7 @@ var _ = Describe("resourcereferencemanager", func() { Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shootTwo)).To(Succeed()) attrs := admission.NewAttributesRecord(cloudProfile, oldCloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo) - err := admissionHandler.Admit(ctx, attrs, nil) + err := admissionHandler.Validate(ctx, attrs, nil) Expect(err).NotTo(HaveOccurred()) }) @@ -2734,7 +2725,7 @@ var _ = Describe("resourcereferencemanager", func() { Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shootTwo)).To(Succeed()) attrs := admission.NewAttributesRecord(cloudProfile, oldCloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo) - err := admissionHandler.Admit(ctx, attrs, nil) + err := admissionHandler.Validate(ctx, attrs, nil) Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{ "ErrStatus": MatchFields(IgnoreExtras, Fields{ @@ -2772,7 +2763,7 @@ var _ = Describe("resourcereferencemanager", func() { Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shootOne)).To(Succeed()) attrs := admission.NewAttributesRecord(cloudProfile, oldCloudProfile, core.Kind("CloudProfile").WithVersion("version"), "", cloudProfile.Name, core.Resource("CloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo) - err := admissionHandler.Admit(ctx, attrs, nil) + err := admissionHandler.Validate(ctx, attrs, nil) Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{ "ErrStatus": MatchFields(IgnoreExtras, Fields{ @@ -2817,7 +2808,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 succeed for the complete Kubernetes section being removed without usages", func() { @@ -2839,7 +2830,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 succeed if a used and already extended kubernetes version expiration is changed to another value still in the future", func() { @@ -2867,7 +2858,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 succeed if a used and extended kubernetes version already expired is not modified", func() { @@ -2903,7 +2894,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 succeed if an extended and used Kubernetes version is removed with the base version still being valid", func() { @@ -2937,7 +2928,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 fail if an extended and used Kubernetes version is being removed with the base version being already expired", func() { @@ -2972,7 +2963,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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(MatchError(And( ContainSubstring("unable to delete Kubernetes version"), ContainSubstring("1.29.0"), @@ -3005,7 +2996,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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.29.0"), ContainSubstring("still in use by shoot"), @@ -3044,7 +3035,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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()) }) }) @@ -3110,7 +3101,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 succeed if a used and extended MachineImage version already expired is not modified", func() { @@ -3137,7 +3128,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 succeed if an extended and used MachineImage version is removed with the base version still being valid", func() { @@ -3164,7 +3155,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 fail if an extended and used MachineImage version is being removed with the base version being already expired", func() { @@ -3192,7 +3183,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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(MatchError(And( ContainSubstring("unable to delete Machine image version"), ContainSubstring("1.17.3"), @@ -3236,7 +3227,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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(MatchError(And( ContainSubstring("unable to delete Machine image version"), ContainSubstring("'coreos/1.1.2'"), @@ -3280,7 +3271,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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(MatchError(And( ContainSubstring("unable to delete Machine image version"), ContainSubstring("'custom-namespaced-image/1.1.2'"), @@ -3313,7 +3304,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 Machine image version"), ContainSubstring("1.17.3"), ContainSubstring("still in use by shoot"), @@ -3344,7 +3335,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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 succeed if a new but unused MachineImage version is removed", func() { @@ -3371,7 +3362,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(updatedNamespacedCloudProfile, namespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").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()) }) }) @@ -3464,7 +3455,7 @@ var _ = Describe("resourcereferencemanager", func() { Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shootTwo)).To(Succeed()) attrs := admission.NewAttributesRecord(namespacedCloudProfile, oldNamespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespacedCloudProfile.Namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo) - err := admissionHandler.Admit(ctx, attrs, nil) + err := admissionHandler.Validate(ctx, attrs, nil) Expect(err).NotTo(HaveOccurred()) }) @@ -3482,7 +3473,7 @@ var _ = Describe("resourcereferencemanager", func() { Expect(gardenCoreInformerFactory.Core().V1beta1().Shoots().Informer().GetStore().Add(shootTwo)).To(Succeed()) attrs := admission.NewAttributesRecord(namespacedCloudProfile, oldNamespacedCloudProfile, core.Kind("NamespacedCloudProfile").WithVersion("version"), namespacedCloudProfile.Namespace, namespacedCloudProfile.Name, core.Resource("NamespacedCloudProfile").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, defaultUserInfo) - err := admissionHandler.Admit(ctx, attrs, nil) + err := admissionHandler.Validate(ctx, attrs, nil) Expect(err).To(PointTo(MatchFields(IgnoreExtras, Fields{ "ErrStatus": MatchFields(IgnoreExtras, Fields{ @@ -3511,7 +3502,7 @@ var _ = Describe("resourcereferencemanager", func() { It("should accept because there is no managed seed with the same name", func() { attrs := admission.NewAttributesRecord(gardenlet, nil, seedmanagement.Kind("Gardenlet").WithVersion("version"), gardenlet.Namespace, gardenlet.Name, seedmanagement.Resource("gardenlets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{Name: allowedUser}) - Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed()) + Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed()) }) It("should forbid because there is a managed seed with the same name", func() { @@ -3519,7 +3510,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(gardenlet, nil, seedmanagement.Kind("Gardenlet").WithVersion("version"), gardenlet.Namespace, gardenlet.Name, seedmanagement.Resource("gardenlets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{Name: allowedUser}) - Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("there is already a ManagedSeed object with the same name"))) + Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("there is already a ManagedSeed object with the same name"))) }) }) @@ -3537,7 +3528,7 @@ var _ = Describe("resourcereferencemanager", func() { It("should accept because there is no gardenlet with the same name", func() { attrs := admission.NewAttributesRecord(managedSeed, nil, seedmanagement.Kind("ManagedSeed").WithVersion("version"), gardenlet.Namespace, gardenlet.Name, seedmanagement.Resource("gardenlets").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{Name: allowedUser}) - Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(Succeed()) + Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(Succeed()) }) It("should forbid because there is a gardenlet with the same name", func() { @@ -3545,7 +3536,7 @@ var _ = Describe("resourcereferencemanager", func() { attrs := admission.NewAttributesRecord(managedSeed, nil, seedmanagement.Kind("ManagedSeed").WithVersion("version"), managedSeed.Namespace, managedSeed.Name, seedmanagement.Resource("managedseeds").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{Name: allowedUser}) - Expect(admissionHandler.Admit(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("there is already a Gardenlet object with the same name"))) + Expect(admissionHandler.Validate(context.TODO(), attrs, nil)).To(MatchError(ContainSubstring("there is already a Gardenlet object with the same name"))) }) }) }) diff --git a/plugin/pkg/managedseed/validator/admission.go b/plugin/pkg/managedseed/validator/admission.go index 4fea57adcee..78b21b786b9 100644 --- a/plugin/pkg/managedseed/validator/admission.go +++ b/plugin/pkg/managedseed/validator/admission.go @@ -238,7 +238,7 @@ func (v *ManagedSeed) Admit(ctx context.Context, a admission.Attributes, _ admis } allErrs = append(allErrs, errs...) - gardenerutils.MaintainSeedNameLabels(managedSeed, shoot.Spec.SeedName, &managedSeed.Name) + gardenerutils.MaintainSeedNameLabels(managedSeed, shoot.Spec.SeedName) switch a.GetOperation() { case admission.Create: diff --git a/plugin/pkg/managedseed/validator/admission_test.go b/plugin/pkg/managedseed/validator/admission_test.go index b4b923ed084..2ec82f0d4c9 100644 --- a/plugin/pkg/managedseed/validator/admission_test.go +++ b/plugin/pkg/managedseed/validator/admission_test.go @@ -374,24 +374,22 @@ var _ = Describe("ManagedSeed", func() { Expect(kubeInformerFactory.Core().V1().Secrets().Informer().GetStore().Add(secret)).To(Succeed()) }) - It("should add the label for the parent and the current seed name", func() { + It("should add the label for the parent seed name", func() { Expect(admissionHandler.Admit(context.TODO(), getManagedSeedAttributes(managedSeed), nil)).To(Succeed()) Expect(managedSeed.Labels).To(And( HaveKeyWithValue("name.seed.gardener.cloud/parent-seed", "true"), - HaveKeyWithValue("name.seed.gardener.cloud/foo", "true"), )) }) It("should remove unneeded labels", func() { - metav1.SetMetaDataLabel(&seed.ObjectMeta, "name.seed.gardener.cloud/bar", "true") + metav1.SetMetaDataLabel(&seed.ObjectMeta, "name.seed.gardener.cloud/foo", "true") Expect(admissionHandler.Admit(context.TODO(), getManagedSeedAttributes(managedSeed), nil)).To(Succeed()) Expect(managedSeed.Labels).To(And( HaveKeyWithValue("name.seed.gardener.cloud/parent-seed", "true"), - HaveKeyWithValue("name.seed.gardener.cloud/foo", "true"), - Not(HaveKey("name.seed.gardener.cloud/bar")), + Not(HaveKey("name.seed.gardener.cloud/foo")), )) }) }) diff --git a/plugin/pkg/plugins.go b/plugin/pkg/plugins.go index 2407cdd5d82..2bccd4b8e5f 100644 --- a/plugin/pkg/plugins.go +++ b/plugin/pkg/plugins.go @@ -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 diff --git a/plugin/pkg/project/validator/admission.go b/plugin/pkg/project/validator/admission.go index 4192241ea44..c2e578ee4f5 100644 --- a/plugin/pkg/project/validator/admission.go +++ b/plugin/pkg/project/validator/admission.go @@ -8,8 +8,10 @@ import ( "context" "fmt" "io" + "slices" "strings" + rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apiserver/pkg/admission" @@ -17,6 +19,7 @@ import ( 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, + }, + }) + } +} diff --git a/plugin/pkg/project/validator/admission_test.go b/plugin/pkg/project/validator/admission_test.go index 790ec653670..e921a158ce3 100644 --- a/plugin/pkg/project/validator/admission_test.go +++ b/plugin/pkg/project/validator/admission_test.go @@ -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,6 +36,8 @@ var _ = Describe("Admission", func() { Namespace: namespaceName, }, } + + userInfo user.Info ) BeforeEach(func() { @@ -40,36 +45,139 @@ var _ = Describe("Admission", func() { 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()) }) diff --git a/plugin/pkg/shoot/validator/admission.go b/plugin/pkg/shoot/validator/admission.go index da4136ba762..de5137b45a3 100644 --- a/plugin/pkg/shoot/validator/admission.go +++ b/plugin/pkg/shoot/validator/admission.go @@ -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 +} diff --git a/plugin/pkg/shoot/validator/admission_test.go b/plugin/pkg/shoot/validator/admission_test.go index c362b6a0a90..ed2e148a6c9 100644 --- a/plugin/pkg/shoot/validator/admission_test.go +++ b/plugin/pkg/shoot/validator/admission_test.go @@ -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,7 +1967,7 @@ 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()) }) @@ -2021,7 +1975,7 @@ var _ = Describe("validator", 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()) }) @@ -2029,7 +1983,7 @@ var _ = Describe("validator", 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,7 +1994,7 @@ 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()) }) @@ -2048,7 +2002,7 @@ var _ = Describe("validator", 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()) }) @@ -2056,7 +2010,7 @@ var _ = Describe("validator", 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) diff --git a/skaffold-operator.yaml b/skaffold-operator.yaml index c5dbf73681e..31346a7e406 100644 --- a/skaffold-operator.yaml +++ b/skaffold-operator.yaml @@ -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 diff --git a/skaffold.yaml b/skaffold.yaml index c157127e84b..5fd204456ab 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -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 diff --git a/test/integration/envtest/environment_test.go b/test/integration/envtest/environment_test.go index cbf5529a334..d4a447445f0 100644 --- a/test/integration/envtest/environment_test.go +++ b/test/integration/envtest/environment_test.go @@ -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"))) }) })