Files
capsule/internal/webhook/rules/generic/validation/metadata_test.go
T
Oliver BählerandGitHub f1b760a87a feat(rules): improve metadata enforcement and add ingress rules (#2050)
* feat: implement namespace metadata enforcement

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add ingress enforcment

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
2026-07-22 22:24:54 +02:00

1435 lines
34 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package validation
import (
"reflect"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/projectcapsule/capsule/internal/cache"
"github.com/projectcapsule/capsule/pkg/api/meta"
apirules "github.com/projectcapsule/capsule/pkg/api/rules"
"github.com/projectcapsule/capsule/pkg/api/runtime"
"github.com/projectcapsule/capsule/pkg/ruleengine"
"github.com/projectcapsule/capsule/pkg/runtime/events"
)
func TestValidateMetadata(t *testing.T) {
t.Parallel()
tests := []struct {
name string
obj genericObject
gvk schema.GroupVersionKind
enforceBodies []*apirules.NamespaceRuleEnforceBody
wantNil bool
wantBlocking bool
wantAudits int
wantMessage string
wantPath string
}{
{
name: "nil object returns nil",
obj: nil,
gvk: coreGVK("ConfigMap"),
wantNil: true,
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
},
{
name: "empty enforce bodies returns nil",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
wantNil: true,
},
{
name: "required missing label denies",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
wantBlocking: true,
wantMessage: `metadata label "env" is required`,
wantPath: `metadata.labels["env"]`,
},
{
name: "required present matching label allows",
obj: metadataObject(
map[string]string{"env": "prod"},
nil,
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
},
{
name: "required present non matching label denies",
obj: metadataObject(
map[string]string{"env": "stage"},
nil,
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
wantBlocking: true,
wantMessage: "Allowed metadata values",
wantPath: `metadata.labels["env"]`,
},
{
name: "required without values enforces presence only",
obj: metadataObject(
map[string]string{"env": "anything"},
nil,
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true),
},
nil,
),
},
},
{
name: "required without values denies when missing",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true),
},
nil,
),
},
wantBlocking: true,
wantMessage: `metadata label "env" is required`,
wantPath: `metadata.labels["env"]`,
},
{
name: "optional missing label is ignored",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(false, exact("prod")),
},
nil,
),
},
wantNil: true,
},
{
name: "optional present invalid label denies",
obj: metadataObject(
map[string]string{"env": "stage"},
nil,
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(false, exact("prod")),
},
nil,
),
},
wantBlocking: true,
wantMessage: "Allowed metadata values",
wantPath: `metadata.labels["env"]`,
},
{
name: "annotation regex match allows",
obj: metadataObject(
nil,
map[string]string{"example.corp/cost-center": "INV-1234"},
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
nil,
map[string]apirules.MetadataValueRule{
"example.corp/cost-center": metadataPolicy(false, expression("^INV-[0-9]{4}$")),
},
),
},
},
{
name: "deny matching label blocks",
obj: metadataObject(
map[string]string{"env": "blocked"},
nil,
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeDeny,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(false, exact("blocked")),
},
nil,
),
},
wantBlocking: true,
wantMessage: "denied",
wantPath: `metadata.labels["env"]`,
},
{
name: "deny required missing label is ignored",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeDeny,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("blocked")),
},
nil,
),
},
wantNil: true,
},
{
name: "audit matching label emits audit and does not block",
obj: metadataObject(
map[string]string{"audit": "audit-this"},
nil,
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAudit,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"audit": metadataPolicy(false, expression("^audit-.*")),
},
nil,
),
},
wantAudits: 1,
},
{
name: "audit required missing label is ignored",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAudit,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"audit": metadataPolicy(true, exact("true")),
},
nil,
),
},
wantNil: true,
},
{
name: "non matching gvk returns nil",
obj: metadataObject(
map[string]string{"env": "stage"},
nil,
),
gvk: coreGVK("Secret"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
wantNil: true,
},
{
name: "empty metadata value is evaluated",
obj: metadataObject(
map[string]string{"env": ""},
nil,
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("")),
},
nil,
),
},
},
{
name: "invalid regex returns error",
obj: metadataObject(
map[string]string{"env": "prod"},
nil,
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, expression("[")),
},
nil,
),
},
wantBlocking: false,
wantMessage: "error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
got, err := h.validateMetadata(tt.obj, tt.gvk, tt.enforceBodies)
if tt.wantMessage == "error" {
if err == nil {
t.Fatalf("expected error")
}
return
}
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if tt.wantNil {
if got != nil {
t.Fatalf("expected nil evaluation, got %#v", got)
}
return
}
if got == nil {
t.Fatalf("expected evaluation")
}
if len(got.Audits) != tt.wantAudits {
t.Fatalf("expected %d audit decisions, got %d", tt.wantAudits, len(got.Audits))
}
blockingErr := got.BlockingError()
if tt.wantBlocking && blockingErr == nil {
t.Fatalf("expected blocking error")
}
if !tt.wantBlocking && blockingErr != nil {
t.Fatalf("expected no blocking error, got %v", blockingErr)
}
if tt.wantMessage != "" {
var message string
if got.Blocking != nil {
message = got.Blocking.Message
} else if len(got.Audits) > 0 {
message = got.Audits[0].Message
}
if !strings.Contains(message, tt.wantMessage) {
t.Fatalf("expected message to contain %q, got %q", tt.wantMessage, message)
}
}
if tt.wantPath != "" {
if got.Blocking == nil {
t.Fatalf("expected blocking decision with path %q", tt.wantPath)
}
if got.Blocking.Value.Path != tt.wantPath {
t.Fatalf("expected path %q, got %q", tt.wantPath, got.Blocking.Value.Path)
}
}
})
}
}
func TestControlledMetadataEntriesMatchesKeyPatternsWithRegexCache(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
obj := &metav1.PartialObjectMetadata{ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"example.corp/cost-center": "INV-1234",
"unrelated": "ignored",
},
}}
enforce := []*apirules.NamespaceRuleEnforceBody{{
Metadata: []apirules.MetadataRule{{
VersionKinds: runtime.VersionKinds{APIGroups: []string{"v1"}, Kinds: []string{"Namespace"}},
Annotations: map[string]apirules.MetadataValueRule{
"example.corp/*": {},
},
}},
}}
entries, err := h.controlledMetadataEntries(
obj,
schema.GroupVersionKind{Version: "v1", Kind: "Namespace"},
enforce,
)
if err != nil {
t.Fatalf("controlledMetadataEntries() error = %v", err)
}
if len(entries) != 1 || entries[0].Key != "example.corp/cost-center" {
t.Fatalf("unexpected pattern-matched entries: %#v", entries)
}
if h.regexCache.Stats() != 1 {
t.Fatalf("expected key expression to use regex cache, got %d entries", h.regexCache.Stats())
}
}
func TestControlledMetadataEntries(t *testing.T) {
t.Parallel()
tests := []struct {
name string
managedLabels []string
managedAnnotations []string
obj genericObject
gvk schema.GroupVersionKind
enforceBodies []*apirules.NamespaceRuleEnforceBody
want []metadataEntry
}{
{
name: "no matching metadata returns nil",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
enforceBodies: nil,
want: nil,
},
{
name: "collects present label and annotation entries",
obj: metadataObject(
map[string]string{"env": "prod"},
map[string]string{"example.corp/cost-center": "INV-1234"},
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(false, exact("prod")),
},
map[string]apirules.MetadataValueRule{
"example.corp/cost-center": metadataPolicy(false, expression("^INV-")),
},
),
},
want: []metadataEntry{
{
Field: metadataFieldAnnotation,
Key: "example.corp/cost-center",
Value: "INV-1234",
Path: `metadata.annotations["example.corp/cost-center"]`,
Present: true,
},
{
Field: metadataFieldLabel,
Key: "env",
Value: "prod",
Path: `metadata.labels["env"]`,
Present: true,
},
},
},
{
name: "includes missing required allow label",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
want: []metadataEntry{
{
Field: metadataFieldLabel,
Key: "env",
Path: `metadata.labels["env"]`,
Required: true,
},
},
},
{
name: "skips missing optional allow label",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(false, exact("prod")),
},
nil,
),
},
want: nil,
},
{
name: "skips missing required deny and audit labels",
obj: metadataObject(nil, nil),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeDeny,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"deny-required": metadataPolicy(true, exact("true")),
},
nil,
),
enforceMetadata(
apirules.ActionTypeAudit,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"audit-required": metadataPolicy(true, exact("true")),
},
nil,
),
},
want: nil,
},
{
name: "required flag is merged across matching rules",
obj: metadataObject(
map[string]string{"env": "prod"},
nil,
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(false, exact("prod")),
},
nil,
),
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
want: []metadataEntry{
{
Field: metadataFieldLabel,
Key: "env",
Value: "prod",
Path: `metadata.labels["env"]`,
Present: true,
Required: true,
},
},
},
{
name: "does not collect non matching gvk rule",
obj: metadataObject(
map[string]string{"env": "prod"},
nil,
),
gvk: coreGVK("Secret"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
want: nil,
},
{
name: "apiVersion empty matches only core v1",
obj: metadataObject(
map[string]string{"env": "prod"},
nil,
),
gvk: schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{""},
[]string{"Deployment"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
want: nil,
},
{
name: "apiVersion wildcard matches grouped resources",
obj: metadataObject(
map[string]string{"env": "prod"},
nil,
),
gvk: schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"Deployment"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
want: []metadataEntry{
{
Field: metadataFieldLabel,
Key: "env",
Value: "prod",
Path: `metadata.labels["env"]`,
Present: true,
Required: true,
},
},
},
{
name: "multiple kinds match selected kind",
obj: metadataObject(
map[string]string{"env": "prod"},
nil,
),
gvk: coreGVK("Service"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap", "Service"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
want: []metadataEntry{
{
Field: metadataFieldLabel,
Key: "env",
Value: "prod",
Path: `metadata.labels["env"]`,
Present: true,
Required: true,
},
},
},
{
name: "kind wildcard matches any kind",
obj: metadataObject(
map[string]string{"env": "prod"},
nil,
),
gvk: coreGVK("Service"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"*"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
),
},
want: []metadataEntry{
{
Field: metadataFieldLabel,
Key: "env",
Value: "prod",
Path: `metadata.labels["env"]`,
Present: true,
Required: true,
},
},
},
{
name: "managed label and annotation are skipped",
obj: metadataObject(
map[string]string{
meta.TenantLabel: "tenant-a",
},
map[string]string{
meta.ReconcileAnnotation: "true",
},
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
meta.TenantLabel: metadataPolicy(true, exact("tenant-b")),
},
map[string]apirules.MetadataValueRule{
meta.ReconcileAnnotation: metadataPolicy(true, exact("false")),
},
),
},
want: nil,
},
{
name: "managed metadata does not satisfy required selectors",
obj: metadataObject(
map[string]string{
meta.TenantLabel: "tenant-a",
},
map[string]string{
meta.ReconcileAnnotation: "true",
},
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"capsule.clastix.io/*": metadataPolicy(true, exact("tenant-a")),
},
map[string]apirules.MetadataValueRule{
"reconcile.projectcapsule.dev/*": metadataPolicy(true, exact("true")),
},
),
},
want: []metadataEntry{
{
Field: metadataFieldAnnotation,
Key: "reconcile.projectcapsule.dev/*",
Path: `metadata.annotations["reconcile.projectcapsule.dev/*"]`,
Required: true,
},
{
Field: metadataFieldLabel,
Key: "capsule.clastix.io/*",
Path: `metadata.labels["capsule.clastix.io/*"]`,
Required: true,
},
},
},
{
name: "custom parameters add label and annotation to managed metadata",
managedLabels: []string{
meta.TenantLabel,
},
managedAnnotations: []string{
meta.ReconcileAnnotation,
},
obj: metadataObject(
map[string]string{
meta.TenantLabel: "tenant-a",
},
map[string]string{
meta.ReconcileAnnotation: "true",
},
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
meta.TenantLabel: metadataPolicy(true, exact("tenant-a")),
},
map[string]apirules.MetadataValueRule{
meta.ReconcileAnnotation: metadataPolicy(true, exact("true")),
},
),
},
want: nil,
},
{
name: "managed annotation prefixes are skipped",
obj: metadataObject(
nil,
map[string]string{
meta.ResourceQuotaAnnotationPrefix + "cpu": "1",
},
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
nil,
map[string]apirules.MetadataValueRule{
meta.ResourceQuotaAnnotationPrefix + "cpu": metadataPolicy(true, exact("2")),
},
),
},
want: nil,
},
{
name: "same key in labels and annotations is tracked independently",
obj: metadataObject(
map[string]string{"shared": "label"},
map[string]string{"shared": "annotation"},
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"shared": metadataPolicy(true, exact("label")),
},
map[string]apirules.MetadataValueRule{
"shared": metadataPolicy(true, exact("annotation")),
},
),
},
want: []metadataEntry{
{
Field: metadataFieldAnnotation,
Key: "shared",
Value: "annotation",
Path: `metadata.annotations["shared"]`,
Present: true,
Required: true,
},
{
Field: metadataFieldLabel,
Key: "shared",
Value: "label",
Path: `metadata.labels["shared"]`,
Present: true,
Required: true,
},
},
},
{
name: "output is sorted by path",
obj: metadataObject(
map[string]string{
"z": "1",
"a": "1",
},
map[string]string{
"m": "1",
},
),
gvk: coreGVK("ConfigMap"),
enforceBodies: []*apirules.NamespaceRuleEnforceBody{
enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"z": metadataPolicy(false, exact("1")),
"a": metadataPolicy(false, exact("1")),
},
map[string]apirules.MetadataValueRule{
"m": metadataPolicy(false, exact("1")),
},
),
},
want: []metadataEntry{
{
Field: metadataFieldAnnotation,
Key: "m",
Value: "1",
Path: `metadata.annotations["m"]`,
Present: true,
},
{
Field: metadataFieldLabel,
Key: "a",
Value: "1",
Path: `metadata.labels["a"]`,
Present: true,
},
{
Field: metadataFieldLabel,
Key: "z",
Value: "1",
Path: `metadata.labels["z"]`,
Present: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(tt.managedLabels, tt.managedAnnotations)
got, err := h.controlledMetadataEntries(tt.obj, tt.gvk, tt.enforceBodies)
if err != nil {
t.Fatalf("controlledMetadataEntries() error = %v", err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Fatalf("unexpected entries\nwant: %#v\n got: %#v", tt.want, got)
}
})
}
}
func TestMetadataSet(t *testing.T) {
t.Parallel()
t.Run("values returns the captured metadata entry value", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
entry := metadataEntry{
Field: metadataFieldLabel,
Key: "env",
Value: "prod",
Path: `metadata.labels["env"]`,
}
set := h.metadataSet(coreGVK("ConfigMap"), entry)
got := set.Values(metadataObject(nil, nil))
want := []ruleengine.Value{
{
Value: "prod",
Path: `metadata.labels["env"]`,
},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("expected %#v, got %#v", want, got)
}
})
t.Run("rules returns label values from matching gvk only", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
entry := metadataEntry{
Field: metadataFieldLabel,
Key: "env",
Value: "prod",
Path: `metadata.labels["env"]`,
}
set := h.metadataSet(coreGVK("ConfigMap"), entry)
got, err := set.RulesWithError(enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod"), expression("^p")),
},
nil,
))
if err != nil {
t.Fatalf("RulesWithError() error = %v", err)
}
if len(got) != 2 {
t.Fatalf("expected two matchers, got %d", len(got))
}
if got[0].Exact[0] != "prod" {
t.Fatalf("expected first exact matcher, got %#v", got[0])
}
if got[1].Expression != "^p" {
t.Fatalf("expected second expression matcher, got %#v", got[1])
}
})
t.Run("rules returns annotation values from matching gvk only", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
entry := metadataEntry{
Field: metadataFieldAnnotation,
Key: "cost-center",
Value: "INV-1234",
Path: `metadata.annotations["cost-center"]`,
}
set := h.metadataSet(coreGVK("ConfigMap"), entry)
got, err := set.RulesWithError(enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
nil,
map[string]apirules.MetadataValueRule{
"cost-center": metadataPolicy(true, expression("^INV-[0-9]{4}$")),
},
))
if err != nil {
t.Fatalf("RulesWithError() error = %v", err)
}
if len(got) != 1 {
t.Fatalf("expected one matcher, got %d", len(got))
}
if got[0].Expression != "^INV-[0-9]{4}$" {
t.Fatalf("expected expression matcher, got %#v", got[0])
}
})
t.Run("rules returns nil for nil enforce", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
set := h.metadataSet(coreGVK("ConfigMap"), metadataEntry{
Field: metadataFieldLabel,
Key: "env",
})
got, err := set.RulesWithError(nil)
if err != nil {
t.Fatalf("RulesWithError() error = %v", err)
}
if got != nil {
t.Fatalf("expected nil, got %#v", got)
}
})
t.Run("rules returns nil for non matching gvk", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
set := h.metadataSet(coreGVK("Secret"), metadataEntry{
Field: metadataFieldLabel,
Key: "env",
})
got, err := set.RulesWithError(enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{
"env": metadataPolicy(true, exact("prod")),
},
nil,
))
if err != nil {
t.Fatalf("RulesWithError() error = %v", err)
}
if got != nil {
t.Fatalf("expected nil, got %#v", got)
}
})
t.Run("rules returns invalid key selector errors", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
entry metadataEntry
body *apirules.NamespaceRuleEnforceBody
}{
{
name: "label",
entry: metadataEntry{Field: metadataFieldLabel, Key: "env"},
body: enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
map[string]apirules.MetadataValueRule{"[": metadataPolicy(false, exact("prod"))},
nil,
),
},
{
name: "annotation",
entry: metadataEntry{Field: metadataFieldAnnotation, Key: "env"},
body: enforceMetadata(
apirules.ActionTypeAllow,
[]string{"*"},
[]string{"ConfigMap"},
nil,
map[string]apirules.MetadataValueRule{"[": metadataPolicy(false, exact("prod"))},
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
set := newMetadataTestRules(nil, nil).metadataSet(coreGVK("ConfigMap"), tt.entry)
if _, err := set.RulesWithError(tt.body); err == nil {
t.Fatal("RulesWithError() error = nil, want invalid selector error")
}
})
}
})
t.Run("matches exact value", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
set := h.metadataSet(coreGVK("ConfigMap"), metadataEntry{})
got, err := set.Matches(exact("prod"), ruleengine.Value{Value: "prod"})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if !got.Matched {
t.Fatalf("expected match")
}
})
t.Run("matches regex value", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
set := h.metadataSet(coreGVK("ConfigMap"), metadataEntry{})
got, err := set.Matches(expression("^prod|test$"), ruleengine.Value{Value: "prod"})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if !got.Matched {
t.Fatalf("expected match")
}
})
t.Run("returns regex error", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
set := h.metadataSet(coreGVK("ConfigMap"), metadataEntry{})
if _, err := set.Matches(expression("["), ruleengine.Value{Value: "prod"}); err == nil {
t.Fatalf("expected regex error")
}
})
t.Run("rule description delegates to runtime description", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
set := h.metadataSet(coreGVK("ConfigMap"), metadataEntry{})
got := set.RuleDescription(expression("^prod$"))
if !strings.Contains(got, "^prod$") {
t.Fatalf("expected description to contain expression, got %q", got)
}
})
t.Run("set metadata fields are stable", func(t *testing.T) {
t.Parallel()
h := newMetadataTestRules(nil, nil)
set := h.metadataSet(coreGVK("ConfigMap"), metadataEntry{
Field: metadataFieldAnnotation,
Key: "cost-center",
})
if set.Name != "metadata annotation" {
t.Fatalf("expected metadata annotation set name, got %q", set.Name)
}
if set.EventReason != events.ReasonForbiddenMetadata {
t.Fatalf("expected event reason %q, got %q", events.ReasonForbiddenMetadata, set.EventReason)
}
if set.AllowedDescription != "Allowed metadata values" {
t.Fatalf("expected allowed description, got %q", set.AllowedDescription)
}
})
}
func TestMetadataRequiredDecision(t *testing.T) {
t.Parallel()
tests := []struct {
name string
entry metadataEntry
want *ruleengine.Decision
}{
{
name: "label decision",
entry: metadataEntry{
Field: metadataFieldLabel,
Key: "env",
Path: `metadata.labels["env"]`,
},
want: &ruleengine.Decision{
SetName: "metadata label",
EventReason: events.ReasonForbiddenMetadata,
Action: apirules.ActionTypeDeny,
Value: ruleengine.Value{
Value: "",
Path: `metadata.labels["env"]`,
},
Message: `metadata label "env" is required at metadata.labels["env"]`,
},
},
{
name: "annotation decision",
entry: metadataEntry{
Field: metadataFieldAnnotation,
Key: "cost-center",
Path: `metadata.annotations["cost-center"]`,
},
want: &ruleengine.Decision{
SetName: "metadata annotation",
EventReason: events.ReasonForbiddenMetadata,
Action: apirules.ActionTypeDeny,
Value: ruleengine.Value{
Value: "",
Path: `metadata.annotations["cost-center"]`,
},
Message: `metadata annotation "cost-center" is required at metadata.annotations["cost-center"]`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := metadataRequiredDecision(tt.entry)
if !reflect.DeepEqual(got, tt.want) {
t.Fatalf("unexpected decision\nwant: %#v\n got: %#v", tt.want, got)
}
})
}
}
func TestMetadataHelpers(t *testing.T) {
t.Parallel()
tests := []struct {
name string
got string
want string
}{
{
name: "label set name",
got: metadataSetName(metadataFieldLabel),
want: "metadata label",
},
{
name: "annotation set name",
got: metadataSetName(metadataFieldAnnotation),
want: "metadata annotation",
},
{
name: "unknown set name",
got: metadataSetName(metadataField("unknown")),
want: "metadata",
},
{
name: "label path",
got: metadataLabelPath("example.com/key"),
want: `metadata.labels["example.com/key"]`,
},
{
name: "annotation path",
got: metadataAnnotationPath("example.com/key"),
want: `metadata.annotations["example.com/key"]`,
},
{
name: "label path quotes key",
got: metadataLabelPath(`a"b`),
want: `metadata.labels["a\"b"]`,
},
{
name: "annotation path quotes key",
got: metadataAnnotationPath(`a"b`),
want: `metadata.annotations["a\"b"]`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if tt.got != tt.want {
t.Fatalf("expected %q, got %q", tt.want, tt.got)
}
})
}
}
func newMetadataTestRules(
managedLabels []string,
managedAnnotations []string,
) *genericRules {
return &genericRules{
regexCache: cache.NewRegexCache(),
managedMetadata: meta.NewManagedMetadata(
managedLabels,
managedAnnotations,
),
}
}
func metadataObject(
labels map[string]string,
annotations map[string]string,
) genericObject {
return &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
Annotations: annotations,
},
}
}
func coreGVK(kind string) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: kind,
}
}
func enforceMetadata(
action apirules.ActionType,
apiVersion []string,
kinds []string,
labels map[string]apirules.MetadataValueRule,
annotations map[string]apirules.MetadataValueRule,
) *apirules.NamespaceRuleEnforceBody {
return &apirules.NamespaceRuleEnforceBody{
Action: action,
Metadata: []apirules.MetadataRule{
{
VersionKinds: runtime.VersionKinds{
APIGroups: apiVersion,
Kinds: kinds,
},
Labels: labels,
Annotations: annotations,
},
},
}
}
func metadataPolicy(
required bool,
values ...runtime.ExpressionMatch,
) apirules.MetadataValueRule {
return apirules.MetadataValueRule{
Required: required,
Values: values,
}
}
func exact(values ...string) runtime.ExpressionMatch {
return runtime.ExpressionMatch{
Exact: values,
}
}
func expression(value string) runtime.ExpressionMatch {
return runtime.ExpressionMatch{
ExpressionRegex: runtime.ExpressionRegex{
Expression: value,
},
}
}