mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-25 16:07:24 +00:00
fix: allow managed metadata defined per tenant (#1947)
* fix: allow managed metadata defined per tenant Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * fix: allow managed metadata defined per tenant Signed-off-by: Oliver Baehler <oliver@sudo-i.net> --------- Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
This commit is contained in:
@@ -8,12 +8,12 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
"github.com/projectcapsule/capsule/pkg/api/meta"
|
||||
)
|
||||
@@ -23,8 +23,10 @@ import (
|
||||
|
||||
func (r *Manager) syncLimitRanges(ctx context.Context, log logr.Logger, tenant *capsulev1beta2.Tenant) error {
|
||||
// getting requested LimitRange keys
|
||||
//nolint:staticcheck
|
||||
keys := make([]string, 0, len(tenant.Spec.LimitRanges.Items))
|
||||
|
||||
//nolint:staticcheck
|
||||
for i := range tenant.Spec.LimitRanges.Items {
|
||||
keys = append(keys, strconv.Itoa(i))
|
||||
}
|
||||
@@ -45,6 +47,7 @@ func (r *Manager) syncLimitRange(
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
for i, spec := range tenant.Spec.LimitRanges.Items {
|
||||
target := &corev1.LimitRange{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
||||
@@ -342,7 +342,8 @@ func (r *Manager) reconcile(ctx context.Context, log logr.Logger, instance *caps
|
||||
}
|
||||
|
||||
// Ensuring LimitRange resources
|
||||
r.Log.V(4).Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges.Items))
|
||||
//nolint:staticcheck
|
||||
log.V(4).Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges.Items))
|
||||
|
||||
if err = r.syncLimitRanges(ctx, log, instance); err != nil {
|
||||
errs = append(errs, fmt.Errorf("cannot sync limitrange items: %w", err))
|
||||
|
||||
@@ -23,8 +23,10 @@ import (
|
||||
//
|
||||
|
||||
func (r *Manager) syncNetworkPolicies(ctx context.Context, log logr.Logger, tenant *capsulev1beta2.Tenant) error {
|
||||
//nolint:staticcheck
|
||||
keys := make([]string, 0, len(tenant.Spec.NetworkPolicies.Items))
|
||||
|
||||
//nolint:staticcheck
|
||||
for i := range tenant.Spec.NetworkPolicies.Items {
|
||||
keys = append(keys, strconv.Itoa(i))
|
||||
}
|
||||
@@ -39,6 +41,7 @@ func (r *Manager) syncNetworkPolicy(ctx context.Context, log logr.Logger, tenant
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
for i, spec := range tenant.Spec.NetworkPolicies.Items {
|
||||
target := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
||||
@@ -342,6 +342,7 @@ func listObjectNamesBySelector(
|
||||
|
||||
var regex *regexp.Regexp
|
||||
|
||||
//nolint:staticcheck
|
||||
if allowed.Regex != "" {
|
||||
regex, err = regexp.Compile(allowed.Regex)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ package validation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
ad "github.com/projectcapsule/capsule/pkg/runtime/admission"
|
||||
evt "github.com/projectcapsule/capsule/pkg/runtime/events"
|
||||
"github.com/projectcapsule/capsule/pkg/runtime/handlers"
|
||||
"github.com/projectcapsule/capsule/pkg/tenant"
|
||||
"github.com/projectcapsule/capsule/pkg/users"
|
||||
)
|
||||
|
||||
@@ -36,23 +38,14 @@ func (h *userMetadataHandler) OnCreate(
|
||||
tnt *capsulev1beta2.Tenant,
|
||||
) handlers.Func {
|
||||
return func(ctx context.Context, req admission.Request) *admission.Response {
|
||||
ns.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Namespace"))
|
||||
|
||||
if tnt.Spec.NamespaceOptions != nil {
|
||||
err := api.ValidateForbidden(ns.Annotations, tnt.Spec.NamespaceOptions.ForbiddenAnnotations)
|
||||
labels, annotations, err := userMetadataForValidation(ns, nil, tnt)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "namespace annotations validation failed")
|
||||
recorder.Eventf(ns, ns, corev1.EventTypeWarning, evt.ReasonForbiddenAnnotation, evt.ActionValidationDenied, err.Error())
|
||||
|
||||
return ad.Deny(err.Error())
|
||||
return ad.ErroredResponse(err)
|
||||
}
|
||||
|
||||
err = api.ValidateForbidden(ns.Labels, tnt.Spec.NamespaceOptions.ForbiddenLabels)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "namespace labels validation failed")
|
||||
recorder.Eventf(ns, ns, corev1.EventTypeWarning, evt.ReasonForbiddenLabel, evt.ActionValidationDenied, err.Error())
|
||||
|
||||
return ad.Deny(err.Error())
|
||||
if response := validateUserMetadata(ns, labels, annotations, tnt.Spec.NamespaceOptions, recorder); response != nil {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,15 +83,8 @@ func (h *userMetadataHandler) OnUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
labels, annotations := oldNs.GetLabels(), oldNs.GetAnnotations()
|
||||
|
||||
if labels == nil {
|
||||
labels = make(map[string]string)
|
||||
}
|
||||
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
labels := maps.Clone(oldNs.GetLabels())
|
||||
annotations := maps.Clone(oldNs.GetAnnotations())
|
||||
|
||||
for key, value := range newNs.GetLabels() {
|
||||
v, ok := labels[key]
|
||||
@@ -131,20 +117,13 @@ func (h *userMetadataHandler) OnUpdate(
|
||||
}
|
||||
|
||||
if tnt.Spec.NamespaceOptions != nil {
|
||||
err := api.ValidateForbidden(annotations, tnt.Spec.NamespaceOptions.ForbiddenAnnotations)
|
||||
labels, annotations, err := userMetadataForValidation(newNs, oldNs, tnt)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "namespace annotations validation failed")
|
||||
recorder.Eventf(oldNs, oldNs, corev1.EventTypeWarning, evt.ReasonForbiddenAnnotation, evt.ActionValidationDenied, err.Error())
|
||||
|
||||
return ad.Deny(err.Error())
|
||||
return ad.ErroredResponse(err)
|
||||
}
|
||||
|
||||
err = api.ValidateForbidden(labels, tnt.Spec.NamespaceOptions.ForbiddenLabels)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "namespace labels validation failed")
|
||||
recorder.Eventf(oldNs, oldNs, corev1.EventTypeWarning, evt.ReasonForbiddenLabel, evt.ActionValidationDenied, err.Error())
|
||||
|
||||
return ad.Deny(err.Error())
|
||||
if response := validateUserMetadata(oldNs, labels, annotations, tnt.Spec.NamespaceOptions, recorder); response != nil {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,3 +144,89 @@ func (h *userMetadataHandler) OnDelete(
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func validateUserMetadata(
|
||||
ns *corev1.Namespace,
|
||||
labels map[string]string,
|
||||
annotations map[string]string,
|
||||
options *capsulev1beta2.NamespaceOptions,
|
||||
recorder events.EventRecorder,
|
||||
) *admission.Response {
|
||||
err := api.ValidateForbidden(annotations, options.ForbiddenAnnotations)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "namespace annotations validation failed")
|
||||
recorder.Eventf(ns, ns, corev1.EventTypeWarning, evt.ReasonForbiddenAnnotation, evt.ActionValidationDenied, err.Error())
|
||||
|
||||
return ad.Deny(err.Error())
|
||||
}
|
||||
|
||||
err = api.ValidateForbidden(labels, options.ForbiddenLabels)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "namespace labels validation failed")
|
||||
recorder.Eventf(ns, ns, corev1.EventTypeWarning, evt.ReasonForbiddenLabel, evt.ActionValidationDenied, err.Error())
|
||||
|
||||
return ad.Deny(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func userMetadataForValidation(
|
||||
newNs *corev1.Namespace,
|
||||
oldNs *corev1.Namespace,
|
||||
tnt *capsulev1beta2.Tenant,
|
||||
) (map[string]string, map[string]string, error) {
|
||||
labels := metadataForValidation(newNs.GetLabels(), nil)
|
||||
annotations := metadataForValidation(newNs.GetAnnotations(), nil)
|
||||
|
||||
// On update, validate only metadata that was added or changed by the request.
|
||||
if oldNs != nil {
|
||||
labels = metadataForValidation(newNs.GetLabels(), oldNs.GetLabels())
|
||||
annotations = metadataForValidation(newNs.GetAnnotations(), oldNs.GetAnnotations())
|
||||
}
|
||||
|
||||
managedLabels, managedAnnotations, err := tenant.BuildNamespaceMetadataForTenant(newNs, tnt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tenant.AddNamespaceNameLabels(managedLabels, newNs)
|
||||
tenant.AddTenantNameLabel(managedLabels, tnt)
|
||||
|
||||
removeManagedMetadata(labels, managedLabels)
|
||||
removeManagedMetadata(annotations, managedAnnotations)
|
||||
|
||||
return labels, annotations, nil
|
||||
}
|
||||
|
||||
func metadataForValidation(newMetadata, oldMetadata map[string]string) map[string]string {
|
||||
if oldMetadata == nil {
|
||||
return maps.Clone(newMetadata)
|
||||
}
|
||||
|
||||
metadata := make(map[string]string)
|
||||
|
||||
for key, newValue := range newMetadata {
|
||||
oldValue, ok := oldMetadata[key]
|
||||
if !ok || oldValue != newValue {
|
||||
metadata[key] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
func removeManagedMetadata(metadata map[string]string, managed map[string]string) {
|
||||
for key, managedValue := range managed {
|
||||
value, ok := metadata[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only ignore metadata Capsule itself would manage.
|
||||
// Same key with a different value is still user-controlled and must be validated.
|
||||
if value == managedValue {
|
||||
delete(metadata, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user