Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
21 changes: 21 additions & 0 deletions cmd/gardener-controller-manager/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions cmd/gardener-extension-admission-local/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions docs/concepts/admission-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 17 additions & 6 deletions docs/concepts/apiserver-admission-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,27 @@ _(enabled by default)_

This admission controller reacts on `CREATE` and `UPDATE` operations for `BackupBucket`s, `BackupEntry`s, `CloudProfile`s, `NamespacedCloudProfile`s, `Seed`s, `SecretBinding`s, `CredentialsBinding`s, `WorkloadIdentity`s and `Shoot`s. For all the various extension types in the specifications of these objects, it adds a corresponding label in the resource. This would allow extension admission webhooks to filter out the resources they are responsible for and ignore all others. This label is of the form `<extension-type>.extensions.gardener.cloud/<extension-name> : "true"`. For example, an extension label for provider extension type `aws`, looks like `provider.extensions.gardener.cloud/aws : "true"`.

## `FinalizerRemoval`

_(enabled by default)_

This admission controller reacts on `UPDATE` operations for `CredentialsBinding`s, `SecretBinding`s, `Shoot`s.
It ensures that the finalizers of these resources are not removed by users, as long as the affected resource is still in use.
For `CredentialsBinding`s and `SecretBinding`s this means, that the `gardener` finalizer can only be removed if the binding is not referenced by any `Shoot`.
In case of `Shoot`s, the `gardener` finalizer can only be removed if the last operation of the `Shoot` indicates a successful deletion.

## `ProjectValidator`

_(enabled by default)_

This admission controller reacts on `CREATE` operations for `Project`s.
This admission controller reacts on `CREATE` and `UPDATE` operations for `Project`s.
It prevents creating `Project`s with a non-empty `.spec.namespace` if the value in `.spec.namespace` does not start with `garden-`.

⚠️ This admission plugin will be removed in a future release and its business logic will be incorporated into the static validation of the `gardener-apiserver`.
In addition, the project specification is initialized during creation:
- `.spec.createdBy` is set to the user creating the project.
- `.spec.owner` defaults to the value of `.spec.createdBy` if it is not specified.

During subsequent updates, it ensures that the project owner is included in the `.spec.members` list.

## `ResourceQuota`

Expand All @@ -96,11 +109,9 @@ _(enabled by default)_

This admission controller reacts on `CREATE` and `UPDATE` operations for `CloudProfile`s, `Project`s, `SecretBinding`s, `Seed`s, and `Shoot`s.
Generally, it checks whether referred resources stated in the specifications of these objects exist in the system (e.g., if a referenced `Secret` exists).
However, it also has some special behaviours for certain resources:

However, it also has some special behaviours for certain resources:
* `CloudProfile`s: It rejects removing Kubernetes or machine image versions if there is at least one `Shoot` that refers to them.
* `Project`s: It sets the `.spec.createdBy` field for newly created `Project` resources, and defaults the `.spec.owner` field in case it is empty (to the same value of `.spec.createdBy`).
* `Shoot`s: It sets the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.

## `SeedValidator`

Expand Down Expand Up @@ -187,7 +198,7 @@ _(enabled by default)_
This admission controller reacts on `CREATE`, `UPDATE` and `DELETE` operations for `Shoot`s.
It validates certain configurations in the specification against the referred `CloudProfile` (e.g., machine images, machine types, used Kubernetes version, ...).
Generally, it performs validations that cannot be handled by the static API validation due to their dynamic nature (e.g., when something needs to be checked against referred resources).
Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version).
Additionally, it takes over certain defaulting tasks (e.g., default machine image for worker pools, default Kubernetes version) and setting the `gardener.cloud/created-by=<username>` annotation for newly created `Shoot` resources.

## `ShootManagedSeed`

Expand Down
8 changes: 8 additions & 0 deletions pkg/admissioncontroller/webhook/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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")
}
}
Loading