mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-23 22:46:59 +00:00
* 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: preserve ca-bundles injected from external providers Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat: abstract ruling Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat: migrate events api * feat: migrate events api * feat: migrate events api * feat: migrate events api * feat: migrate events api * feat: migrate events api * feat: migrate events api * feat: migrate events api * feat: migrate events api * feat: migrate events api --------- Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
166 lines
3.7 KiB
Go
166 lines
3.7 KiB
Go
// Copyright 2020-2026 Project Capsule Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package validation
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
|
|
|
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
|
ad "github.com/projectcapsule/capsule/pkg/runtime/admission"
|
|
"github.com/projectcapsule/capsule/pkg/runtime/events"
|
|
"github.com/projectcapsule/capsule/pkg/runtime/handlers"
|
|
"github.com/projectcapsule/capsule/pkg/users"
|
|
)
|
|
|
|
type requiredMetadataHandler struct{}
|
|
|
|
func RequiredMetadataHandler() handlers.TypedHandlerWithTenantUser[*corev1.Namespace] {
|
|
return &requiredMetadataHandler{}
|
|
}
|
|
|
|
func (h *requiredMetadataHandler) OnCreate(
|
|
_ client.Client,
|
|
_ client.Reader,
|
|
_ users.AdmissionUser,
|
|
ns *corev1.Namespace,
|
|
_ admission.Decoder,
|
|
_ events.EventRecorder,
|
|
tnt *capsulev1beta2.Tenant,
|
|
) handlers.Func {
|
|
return func(_ context.Context, _ admission.Request) *admission.Response {
|
|
no := tnt.Spec.NamespaceOptions
|
|
if no == nil || no.RequiredMetadata == nil {
|
|
return nil
|
|
}
|
|
|
|
rm := no.RequiredMetadata
|
|
|
|
if resp := validateRequiredMapCreate(
|
|
"label",
|
|
rm.Labels,
|
|
ns.GetLabels(),
|
|
); resp != nil {
|
|
return resp
|
|
}
|
|
|
|
if resp := validateRequiredMapCreate(
|
|
"annotation",
|
|
rm.Annotations,
|
|
ns.GetAnnotations(),
|
|
); resp != nil {
|
|
return resp
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (h *requiredMetadataHandler) OnUpdate(
|
|
_ client.Client,
|
|
_ client.Reader,
|
|
_ users.AdmissionUser,
|
|
newNs *corev1.Namespace,
|
|
oldNs *corev1.Namespace,
|
|
_ admission.Decoder,
|
|
_ events.EventRecorder,
|
|
tnt *capsulev1beta2.Tenant,
|
|
) handlers.Func {
|
|
return func(_ context.Context, _ admission.Request) *admission.Response {
|
|
no := tnt.Spec.NamespaceOptions
|
|
if no == nil || no.RequiredMetadata == nil {
|
|
return nil
|
|
}
|
|
|
|
rm := no.RequiredMetadata
|
|
|
|
if resp := validateRequiredMapUpdate(
|
|
"label",
|
|
rm.Labels,
|
|
newNs.GetLabels(),
|
|
oldNs.GetLabels(),
|
|
); resp != nil {
|
|
return resp
|
|
}
|
|
|
|
if resp := validateRequiredMapUpdate(
|
|
"annotation",
|
|
rm.Annotations,
|
|
newNs.GetAnnotations(),
|
|
oldNs.GetAnnotations(),
|
|
); resp != nil {
|
|
return resp
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (h *requiredMetadataHandler) OnDelete(
|
|
client.Client,
|
|
client.Reader,
|
|
users.AdmissionUser,
|
|
*corev1.Namespace,
|
|
admission.Decoder,
|
|
events.EventRecorder,
|
|
*capsulev1beta2.Tenant,
|
|
) handlers.Func {
|
|
return func(context.Context, admission.Request) *admission.Response { return nil }
|
|
}
|
|
|
|
func validateRequiredMapCreate(kind string, required map[string]string, actual map[string]string) *admission.Response {
|
|
for key, exp := range required {
|
|
val, ok := actual[key]
|
|
if !ok {
|
|
return ad.Denyf("required %s %q not present", kind, key)
|
|
}
|
|
|
|
re, reErr := regexp.Compile(exp)
|
|
if reErr != nil {
|
|
return ad.Denyf("invalid required %s regex for %q: %q: %v", kind, key, exp, reErr)
|
|
}
|
|
|
|
if !re.MatchString(val) {
|
|
return ad.Denyf("required %s %q value %q does not match regex %q", kind, key, val, exp)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateRequiredMapUpdate(kind string, required map[string]string, newMap, oldMap map[string]string) *admission.Response {
|
|
mismatchKind := kind
|
|
if kind == "label" {
|
|
mismatchKind = "annotation"
|
|
}
|
|
|
|
for key, exp := range required {
|
|
valNew, newOK := newMap[key]
|
|
valOld, oldOK := oldMap[key]
|
|
|
|
if newOK == oldOK && (!newOK || valNew == valOld) {
|
|
continue
|
|
}
|
|
|
|
if !newOK {
|
|
return ad.Denyf("required %s %q not present", kind, key)
|
|
}
|
|
|
|
re, reErr := regexp.Compile(exp)
|
|
if reErr != nil {
|
|
return ad.Denyf("invalid required %s regex for %q: %q: %v", kind, key, exp, reErr)
|
|
}
|
|
|
|
if !re.MatchString(valNew) {
|
|
return ad.Denyf("required %s %q value %q does not match regex %q", mismatchKind, key, valNew, exp)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|