Files
capsule/internal/controllers/admission/mutating.go
Oliver Bähler a6b830b1af feat: add ruleset api(#1844)
* fix(controller): decode old object for delete requests

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* fix(config): remove usergroups default

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* fix(config): remove usergroups default

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* sec(ghsa-2ww6-hf35-mfjm): intercept namespace subresource

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* feat(api): add rulestatus api

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: conflicts

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* feat(api): add rulestatus api

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* feat(api): add rulestatus api

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* feat(api): add rulestatus api

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* feat(api): add rulestatus api

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* feat(api): add rulestatus api

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* feat(api): add rulestatus api

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2026-01-27 14:28:48 +01:00

167 lines
4.1 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
//nolint:dupl
package admission
import (
"context"
"maps"
"sort"
"github.com/go-logr/logr"
admissionv1 "k8s.io/api/admissionregistration/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/internal/controllers/utils"
"github.com/projectcapsule/capsule/pkg/api/meta"
clt "github.com/projectcapsule/capsule/pkg/runtime/client"
"github.com/projectcapsule/capsule/pkg/runtime/configuration"
"github.com/projectcapsule/capsule/pkg/runtime/predicates"
)
type mutatingReconciler struct {
client client.Client
configuration configuration.Configuration
log logr.Logger
}
func (r *mutatingReconciler) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.ControllerOptions) error {
return ctrl.NewControllerManagedBy(mgr).
Named("capsule/admission/mutating").
For(
&capsulev1beta2.CapsuleConfiguration{},
builder.WithPredicates(
predicate.GenerationChangedPredicate{},
predicates.NamesMatchingPredicate{Names: []string{ctrlConfig.ConfigurationName}},
),
).
WithOptions(controller.Options{MaxConcurrentReconciles: ctrlConfig.MaxConcurrentReconciles}).
Complete(r)
}
func (r *mutatingReconciler) Reconcile(ctx context.Context, request reconcile.Request) (res reconcile.Result, err error) {
err = r.reconcileConfiguration(ctx, r.configuration.Admission().Mutating)
return res, err
}
func (r *mutatingReconciler) reconcileConfiguration(
ctx context.Context,
cfg capsulev1beta2.DynamicAdmissionConfig,
) error {
desiredName := string(cfg.Name)
hooks, err := r.webhooks(ctx, cfg)
if err != nil {
return err
}
if len(hooks) == 0 {
managed, err := r.listManagedWebhookConfigs(ctx)
if err != nil {
return err
}
for i := range managed {
if err := r.deleteWebhookConfig(ctx, managed[i].Name); err != nil {
return err
}
}
return nil
}
obj := &admissionv1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: string(cfg.Name)},
}
sort.Slice(hooks, func(i, j int) bool { return hooks[i].Name < hooks[j].Name })
labels := obj.GetLabels()
if labels == nil {
labels = make(map[string]string)
}
maps.Copy(labels, cfg.Labels)
labels[meta.CreatedByCapsuleLabel] = meta.ControllerValue
obj.SetLabels(labels)
annotations := obj.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
maps.Copy(annotations, cfg.Annotations)
obj.SetAnnotations(annotations)
if err := clt.CreateOrPatch(ctx, r.client, obj, meta.FieldManagerCapsuleController, true); err != nil {
return err
}
// Garbage-collect any old managed validating webhook configs with different name
managed, err := r.listManagedWebhookConfigs(ctx)
if err != nil {
return err
}
for i := range managed {
if managed[i].Name == desiredName {
continue
}
if err := r.deleteWebhookConfig(ctx, managed[i].Name); err != nil {
return err
}
}
return nil
}
func (r *mutatingReconciler) listManagedWebhookConfigs(ctx context.Context) ([]admissionv1.MutatingWebhookConfiguration, error) {
list := &admissionv1.MutatingWebhookConfigurationList{}
if err := r.client.List(ctx, list, client.MatchingLabels{
meta.CreatedByCapsuleLabel: meta.ControllerValue,
}); err != nil {
return nil, err
}
return list.Items, nil
}
func (r *mutatingReconciler) deleteWebhookConfig(ctx context.Context, name string) error {
if name == "" {
return nil
}
obj := &admissionv1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: name},
}
err := r.client.Delete(ctx, obj)
if apierrors.IsNotFound(err) {
return nil
}
return err
}
func (r *mutatingReconciler) webhooks(
ctx context.Context,
cfg capsulev1beta2.DynamicAdmissionConfig,
) (hooks []admissionv1.MutatingWebhook, err error) {
return
}