feat: add scheduler enforcement rule (#1971)

* 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>
This commit is contained in:
Oliver Bähler
2026-06-19 14:34:16 +02:00
committed by GitHub
parent 8a7f8a9388
commit 0b11582e4a
120 changed files with 6616 additions and 1528 deletions
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package api
import (
"fmt"
"regexp"
"slices"
)
// At least one of Exact or Exp must be set.
// Both may be set together.
// +kubebuilder:object:generate=true
// +kubebuilder:validation:XValidation:rule="has(self.exact) || has(self.exp)",message="at least one of exact or exp must be set"
type ExpressionMatch struct {
ExpressionRegex `json:",inline"`
// Exact matches one of the provided values exactly.
//
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:Items:MinLength=1
// +optional
Exact []string `json:"exact,omitempty"`
}
type ExpressionRegex struct {
// Exp matches regular expression.
//
// +kubebuilder:validation:MinLength=1
// +optional
Expression string `json:"exp,omitempty"`
// Negate regular Expression
//+kubebuilder:default:=false
Negate bool `json:"negate,omitempty"`
}
type ExpressionRegexMatcher interface {
MatchRegex(expression ExpressionRegex, value string) (bool, error)
}
func (m ExpressionMatch) Matches(value string) (bool, error) {
matched, err := m.matches(value)
if err != nil {
return false, err
}
return m.applyNegate(matched), nil
}
func (m ExpressionMatch) MatchesWithExpressionMatcher(
matcher ExpressionRegexMatcher,
value string,
) (bool, error) {
if len(m.Exact) == 0 && m.Expression == "" {
return false, fmt.Errorf("expression match must define at least one of exact or exp")
}
matched := containsExact(m.Exact, value)
if matched {
return m.applyNegate(true), nil
}
if m.Expression == "" {
return m.applyNegate(false), nil
}
if matcher == nil {
return m.Matches(value)
}
matched, err := matcher.MatchRegex(m.ExpressionRegex, value)
if err != nil {
return false, err
}
// Important: assume MatchRegex already applies ExpressionRegex.Negate.
// If your RegexCache.MatchRegex already handles Negate, return directly.
return matched, nil
}
func (m ExpressionMatch) matches(value string) (bool, error) {
if len(m.Exact) == 0 && m.Expression == "" {
return false, fmt.Errorf("expression match must define at least one of exact or exp")
}
if containsExact(m.Exact, value) {
return true, nil
}
if m.Expression == "" {
return false, nil
}
re, err := regexp.Compile(m.Expression)
if err != nil {
return false, fmt.Errorf("compile regexp %q: %w", m.Expression, err)
}
return re.MatchString(value), nil
}
func containsExact(values []string, value string) bool {
return slices.Contains(values, value)
}
func (m ExpressionMatch) applyNegate(matched bool) bool {
if m.Negate {
return !matched
}
return matched
}
+917
View File
@@ -0,0 +1,917 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package api
import (
"errors"
"fmt"
"regexp"
"testing"
)
type fakeExpressionRegexMatcher struct {
t *testing.T
calls int
err error
matches map[string]bool
seen []ExpressionRegex
}
func (m *fakeExpressionRegexMatcher) MatchRegex(expr ExpressionRegex, value string) (bool, error) {
m.t.Helper()
m.calls++
m.seen = append(m.seen, expr)
if m.err != nil {
return false, m.err
}
key := fmt.Sprintf("%s|%t|%s", expr.Expression, expr.Negate, value)
if matched, ok := m.matches[key]; ok {
return matched, nil
}
re, err := regexp.Compile(expr.Expression)
if err != nil {
return false, err
}
matched := re.MatchString(value)
if expr.Negate {
return !matched, nil
}
return matched, nil
}
func TestExpressionMatch_Matches(t *testing.T) {
t.Parallel()
tests := []struct {
name string
match ExpressionMatch
value string
wantMatch bool
wantErr bool
}{
{
name: "exact matches single value",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
},
value: "default-scheduler",
wantMatch: true,
},
{
name: "exact does not match different value",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
},
value: "custom-scheduler",
wantMatch: false,
},
{
name: "exact matches one of multiple values",
match: ExpressionMatch{
Exact: []string{"default-scheduler", "custom-scheduler", "team-scheduler"},
},
value: "custom-scheduler",
wantMatch: true,
},
{
name: "exact does not match any of multiple values",
match: ExpressionMatch{
Exact: []string{"default-scheduler", "custom-scheduler"},
},
value: "other-scheduler",
wantMatch: false,
},
{
name: "exact is case sensitive",
match: ExpressionMatch{
Exact: []string{"Default-Scheduler"},
},
value: "default-scheduler",
wantMatch: false,
},
{
name: "exact uses literal string not pattern",
match: ExpressionMatch{
Exact: []string{"team-.*"},
},
value: "team-a",
wantMatch: false,
},
{
name: "exact matches literal pattern string",
match: ExpressionMatch{
Exact: []string{"team-.*"},
},
value: "team-.*",
wantMatch: true,
},
{
name: "exact with empty value can match empty string when present",
match: ExpressionMatch{
Exact: []string{""},
},
value: "",
wantMatch: true,
},
{
name: "regex matches value",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^team-[a-z0-9-]+$",
},
},
value: "team-alpha-1",
wantMatch: true,
},
{
name: "regex does not match value",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^team-[a-z0-9-]+$",
},
},
value: "kube-scheduler",
wantMatch: false,
},
{
name: "regex is not implicitly anchored",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "team",
},
},
value: "my-team-scheduler",
wantMatch: true,
},
{
name: "invalid regex returns error",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "[",
},
},
value: "team-alpha",
wantErr: true,
},
{
name: "combined exact and regex matches by exact",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
ExpressionRegex: ExpressionRegex{
Expression: "^team-[a-z0-9-]+$",
},
},
value: "default-scheduler",
wantMatch: true,
},
{
name: "combined exact and regex matches by regex",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
ExpressionRegex: ExpressionRegex{
Expression: "^team-[a-z0-9-]+$",
},
},
value: "team-alpha",
wantMatch: true,
},
{
name: "combined exact and regex does not match either",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
ExpressionRegex: ExpressionRegex{
Expression: "^team-[a-z0-9-]+$",
},
},
value: "other-scheduler",
wantMatch: false,
},
{
name: "combined exact match skips invalid regex",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
ExpressionRegex: ExpressionRegex{
Expression: "[",
},
},
value: "default-scheduler",
wantMatch: true,
},
{
name: "combined exact miss evaluates invalid regex and returns error",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
ExpressionRegex: ExpressionRegex{
Expression: "[",
},
},
value: "team-alpha",
wantErr: true,
},
{
name: "negated exact matching value returns false",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
ExpressionRegex: ExpressionRegex{
Negate: true,
},
},
value: "default-scheduler",
wantMatch: false,
},
{
name: "negated exact non matching value returns true",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
ExpressionRegex: ExpressionRegex{
Negate: true,
},
},
value: "custom-scheduler",
wantMatch: true,
},
{
name: "negated exact with multiple values matching one returns false",
match: ExpressionMatch{
Exact: []string{"default-scheduler", "custom-scheduler"},
ExpressionRegex: ExpressionRegex{
Negate: true,
},
},
value: "custom-scheduler",
wantMatch: false,
},
{
name: "negated regex matching value returns false",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^trusted/.*",
Negate: true,
},
},
value: "trusted/platform/app:1",
wantMatch: false,
},
{
name: "negated regex non matching value returns true",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^trusted/.*",
Negate: true,
},
},
value: "docker.io/library/nginx:latest",
wantMatch: true,
},
{
name: "negated combined exact match returns false",
match: ExpressionMatch{
Exact: []string{"trusted/platform/app:1"},
ExpressionRegex: ExpressionRegex{
Expression: "^trusted/.+",
Negate: true,
},
},
value: "trusted/platform/app:1",
wantMatch: false,
},
{
name: "negated combined regex match returns false",
match: ExpressionMatch{
Exact: []string{"trusted/platform/app:1"},
ExpressionRegex: ExpressionRegex{
Expression: "^trusted/.+",
Negate: true,
},
},
value: "trusted/other/app:1",
wantMatch: false,
},
{
name: "negated combined no match returns true",
match: ExpressionMatch{
Exact: []string{"trusted/platform/app:1"},
ExpressionRegex: ExpressionRegex{
Expression: "^trusted/.+",
Negate: true,
},
},
value: "harbor/platform/app:1",
wantMatch: true,
},
{
name: "empty matcher returns error",
match: ExpressionMatch{},
value: "anything",
wantErr: true,
wantMatch: false,
},
{
name: "empty exact slice with empty regex returns error",
match: ExpressionMatch{
Exact: []string{},
},
value: "anything",
wantErr: true,
},
{
name: "nil exact with whitespace regex is treated as regex and does not match",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: " ",
},
},
value: "anything",
wantMatch: false,
},
{
name: "duplicate exact values still match",
match: ExpressionMatch{
Exact: []string{"a", "a", "b"},
},
value: "a",
wantMatch: true,
},
{
name: "exact values are not trimmed",
match: ExpressionMatch{
Exact: []string{" value "},
},
value: "value",
wantMatch: false,
},
{
name: "exact values match with spaces when value has spaces",
match: ExpressionMatch{
Exact: []string{" value "},
},
value: " value ",
wantMatch: true,
},
{
name: "regex can match empty value",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^$",
},
},
value: "",
wantMatch: true,
},
{
name: "negated regex can reject empty value",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^$",
Negate: true,
},
},
value: "",
wantMatch: false,
},
{
name: "negated regex can match non empty value against empty regex",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^$",
Negate: true,
},
},
value: "non-empty",
wantMatch: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := tt.match.Matches(tt.value)
if tt.wantErr {
if err == nil {
t.Fatalf("Matches() expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("Matches() unexpected error: %v", err)
}
if got != tt.wantMatch {
t.Fatalf("Matches() = %t, want %t", got, tt.wantMatch)
}
})
}
}
func TestExpressionMatch_MatchesWithExpressionMatcher_NilMatcherFallback(t *testing.T) {
t.Parallel()
tests := []struct {
name string
match ExpressionMatch
value string
wantMatch bool
wantErr bool
}{
{
name: "nil matcher exact match",
match: ExpressionMatch{
Exact: []string{"default-scheduler"},
},
value: "default-scheduler",
wantMatch: true,
},
{
name: "nil matcher regex match",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^team-.*",
},
},
value: "team-a",
wantMatch: true,
},
{
name: "nil matcher negated regex non match returns true",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^trusted/.*",
Negate: true,
},
},
value: "docker.io/library/nginx:latest",
wantMatch: true,
},
{
name: "nil matcher invalid regex returns error",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "[",
},
},
value: "team-a",
wantErr: true,
},
{
name: "nil matcher empty expression match returns error",
match: ExpressionMatch{},
value: "team-a",
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := tt.match.MatchesWithExpressionMatcher(nil, tt.value)
if tt.wantErr {
if err == nil {
t.Fatalf("MatchesWithExpressionMatcher(nil) expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("MatchesWithExpressionMatcher(nil) unexpected error: %v", err)
}
if got != tt.wantMatch {
t.Fatalf("MatchesWithExpressionMatcher(nil) = %t, want %t", got, tt.wantMatch)
}
})
}
}
func TestExpressionMatch_MatchesWithExpressionMatcher_UsesMatcherForRegex(t *testing.T) {
t.Parallel()
matcher := &fakeExpressionRegexMatcher{
t: t,
matches: map[string]bool{
"^team-.*|false|team-a": true,
},
}
match := ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^team-.*",
},
}
got, err := match.MatchesWithExpressionMatcher(matcher, "team-a")
if err != nil {
t.Fatalf("MatchesWithExpressionMatcher() unexpected error: %v", err)
}
if !got {
t.Fatalf("MatchesWithExpressionMatcher() = false, want true")
}
if matcher.calls != 1 {
t.Fatalf("MatchRegex() calls = %d, want 1", matcher.calls)
}
if len(matcher.seen) != 1 {
t.Fatalf("seen expressions = %d, want 1", len(matcher.seen))
}
if matcher.seen[0].Expression != "^team-.*" {
t.Fatalf("seen expression = %q, want %q", matcher.seen[0].Expression, "^team-.*")
}
if matcher.seen[0].Negate {
t.Fatalf("seen negate = true, want false")
}
}
func TestExpressionMatch_MatchesWithExpressionMatcher_PassesNegateToMatcher(t *testing.T) {
t.Parallel()
matcher := &fakeExpressionRegexMatcher{
t: t,
matches: map[string]bool{
"^trusted/.*|true|docker.io/library/nginx:latest": true,
},
}
match := ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^trusted/.*",
Negate: true,
},
}
got, err := match.MatchesWithExpressionMatcher(matcher, "docker.io/library/nginx:latest")
if err != nil {
t.Fatalf("MatchesWithExpressionMatcher() unexpected error: %v", err)
}
if !got {
t.Fatalf("MatchesWithExpressionMatcher() = false, want true")
}
if matcher.calls != 1 {
t.Fatalf("MatchRegex() calls = %d, want 1", matcher.calls)
}
if len(matcher.seen) != 1 {
t.Fatalf("seen expressions = %d, want 1", len(matcher.seen))
}
if !matcher.seen[0].Negate {
t.Fatalf("seen negate = false, want true")
}
}
func TestExpressionMatch_MatchesWithExpressionMatcher_DoesNotUseMatcherWhenExactMatches(t *testing.T) {
t.Parallel()
matcher := &fakeExpressionRegexMatcher{
t: t,
err: errors.New("matcher should not be called"),
}
match := ExpressionMatch{
Exact: []string{"default-scheduler"},
ExpressionRegex: ExpressionRegex{
Expression: "[",
},
}
got, err := match.MatchesWithExpressionMatcher(matcher, "default-scheduler")
if err != nil {
t.Fatalf("MatchesWithExpressionMatcher() unexpected error: %v", err)
}
if !got {
t.Fatalf("MatchesWithExpressionMatcher() = false, want true")
}
if matcher.calls != 0 {
t.Fatalf("MatchRegex() calls = %d, want 0", matcher.calls)
}
}
func TestExpressionMatch_MatchesWithExpressionMatcher_UsesMatcherWhenExactDoesNotMatch(t *testing.T) {
t.Parallel()
matcher := &fakeExpressionRegexMatcher{
t: t,
matches: map[string]bool{
"^team-.*|false|team-a": true,
},
}
match := ExpressionMatch{
Exact: []string{"default-scheduler"},
ExpressionRegex: ExpressionRegex{
Expression: "^team-.*",
},
}
got, err := match.MatchesWithExpressionMatcher(matcher, "team-a")
if err != nil {
t.Fatalf("MatchesWithExpressionMatcher() unexpected error: %v", err)
}
if !got {
t.Fatalf("MatchesWithExpressionMatcher() = false, want true")
}
if matcher.calls != 1 {
t.Fatalf("MatchRegex() calls = %d, want 1", matcher.calls)
}
}
func TestExpressionMatch_MatchesWithExpressionMatcher_ReturnsMatcherError(t *testing.T) {
t.Parallel()
wantErr := errors.New("compile failed")
matcher := &fakeExpressionRegexMatcher{
t: t,
err: wantErr,
}
match := ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^team-.*",
},
}
got, err := match.MatchesWithExpressionMatcher(matcher, "team-a")
if err == nil {
t.Fatalf("MatchesWithExpressionMatcher() expected error, got nil")
}
if !errors.Is(err, wantErr) {
t.Fatalf("MatchesWithExpressionMatcher() error = %v, want %v", err, wantErr)
}
if got {
t.Fatalf("MatchesWithExpressionMatcher() = true, want false on error")
}
if matcher.calls != 1 {
t.Fatalf("MatchRegex() calls = %d, want 1", matcher.calls)
}
}
func TestExpressionMatch_MatchesAndMatchesWithExpressionMatcher_AgreeForNilMatcher(t *testing.T) {
t.Parallel()
tests := []struct {
name string
match ExpressionMatch
value string
}{
{
name: "exact only",
match: ExpressionMatch{
Exact: []string{"a", "b"},
},
value: "a",
},
{
name: "regex only",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^a+$",
},
},
value: "aaa",
},
{
name: "combined exact and regex exact wins",
match: ExpressionMatch{
Exact: []string{"a"},
ExpressionRegex: ExpressionRegex{
Expression: "^b+$",
},
},
value: "a",
},
{
name: "combined exact and regex regex wins",
match: ExpressionMatch{
Exact: []string{"a"},
ExpressionRegex: ExpressionRegex{
Expression: "^b+$",
},
},
value: "bbb",
},
{
name: "negated exact",
match: ExpressionMatch{
Exact: []string{"a"},
ExpressionRegex: ExpressionRegex{
Negate: true,
},
},
value: "b",
},
{
name: "negated regex",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "^a+$",
Negate: true,
},
},
value: "bbb",
},
{
name: "invalid regex",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Expression: "[",
},
},
value: "a",
},
{
name: "empty matcher",
match: ExpressionMatch{},
value: "a",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotMatches, errMatches := tt.match.Matches(tt.value)
gotWithMatcher, errWithMatcher := tt.match.MatchesWithExpressionMatcher(nil, tt.value)
if (errMatches != nil) != (errWithMatcher != nil) {
t.Fatalf(
"error mismatch: Matches() err=%v, MatchesWithExpressionMatcher(nil) err=%v",
errMatches,
errWithMatcher,
)
}
if gotMatches != gotWithMatcher {
t.Fatalf(
"result mismatch: Matches()=%t, MatchesWithExpressionMatcher(nil)=%t",
gotMatches,
gotWithMatcher,
)
}
})
}
}
func TestContainsExact(t *testing.T) {
t.Parallel()
tests := []struct {
name string
values []string
value string
want bool
}{
{
name: "nil values",
values: nil,
value: "a",
want: false,
},
{
name: "empty values",
values: []string{},
value: "a",
want: false,
},
{
name: "contains value",
values: []string{"a", "b", "c"},
value: "b",
want: true,
},
{
name: "does not contain value",
values: []string{"a", "b", "c"},
value: "d",
want: false,
},
{
name: "case sensitive",
values: []string{"A"},
value: "a",
want: false,
},
{
name: "empty string",
values: []string{""},
value: "",
want: true,
},
{
name: "whitespace is significant",
values: []string{" a "},
value: "a",
want: false,
},
{
name: "whitespace matches exactly",
values: []string{" a "},
value: " a ",
want: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := containsExact(tt.values, tt.value)
if got != tt.want {
t.Fatalf("containsExact(%v, %q) = %t, want %t", tt.values, tt.value, got, tt.want)
}
})
}
}
func TestExpressionMatch_applyNegate(t *testing.T) {
t.Parallel()
tests := []struct {
name string
match ExpressionMatch
matched bool
want bool
}{
{
name: "non negated true",
match: ExpressionMatch{},
matched: true,
want: true,
},
{
name: "non negated false",
match: ExpressionMatch{},
matched: false,
want: false,
},
{
name: "negated true",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Negate: true,
},
},
matched: true,
want: false,
},
{
name: "negated false",
match: ExpressionMatch{
ExpressionRegex: ExpressionRegex{
Negate: true,
},
},
matched: false,
want: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := tt.match.applyNegate(tt.matched)
if got != tt.want {
t.Fatalf("applyNegate(%t) = %t, want %t", tt.matched, got, tt.want)
}
})
}
}
+6
View File
@@ -37,6 +37,12 @@ const (
ResourceQuotaAnnotationPrefix = "quota.resources.capsule.clastix.io"
ResourceUsedAnnotationPrefix = "used.resources.capsule.clastix.io"
// Audit Annotations.
AuditRequestUID = "audit.projectcapsule.dev/request-uid"
AuditUsername = "audit.projectcapsule.dev/username"
AuditRuleSetName = "audit.projectcapsule.dev/rule-set"
AuditRuleSetAction = "audit.projectcapsule.dev/rule-action"
)
func ReleaseAnnotationTriggers(obj client.Object) bool {
-13
View File
@@ -1,13 +0,0 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package api
// +kubebuilder:object:generate=true
type RegExpression struct {
// Expression used to evaluate regex
Expression string `json:"exp,omitempty"`
// Negate regular Expression
//+kubebuilder:default:=false
Negate bool `json:"negate,omitempty"`
}
@@ -18,14 +18,10 @@ func (i ImagePullPolicySpec) String() string {
// +kubebuilder:object:generate=true
type OCIRegistry struct {
api.RegExpression `json:",inline"`
api.ExpressionMatch `json:",inline"`
// Allowed PullPolicy for the given registry. Supplying no value allows all policies.
// +optional
// +kubebuilder:validation:Items:Enum=Always;Never;IfNotPresent
Policy []corev1.PullPolicy `json:"policy,omitempty"`
}
func (r OCIRegistry) Expression() api.RegExpression {
return r.RegExpression
}
+14 -1
View File
@@ -3,7 +3,11 @@
package rules
import corev1 "k8s.io/api/core/v1"
import (
corev1 "k8s.io/api/core/v1"
"github.com/projectcapsule/capsule/pkg/api"
)
// +kubebuilder:validation:Enum=pod/initcontainers;pod/ephemeralcontainers;pod/containers;pod/volumes
type WorkloadValidationTarget string
@@ -31,5 +35,14 @@ type NamespaceRuleEnforceWorkloadsBody struct {
// Define registries which are allowed to be used within this tenant
// The rules are aggregated, since you can use Regular Expressions the match registry endpoints
// +optional
Registries []OCIRegistry `json:"registries,omitempty"`
// Schedulers defines schedulerName matchers for Pod admission.
//
// The rule is evaluated against pod.spec.schedulerName.
// Empty schedulerName is ignored and is not normalized to default-scheduler.
//
// +optional
Schedulers []api.ExpressionMatch `json:"schedulers,omitempty"`
}
+9 -1
View File
@@ -8,6 +8,7 @@
package rules
import (
"github.com/projectcapsule/capsule/pkg/api"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -94,6 +95,13 @@ func (in *NamespaceRuleEnforceWorkloadsBody) DeepCopyInto(out *NamespaceRuleEnfo
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Schedulers != nil {
in, out := &in.Schedulers, &out.Schedulers
*out = make([]api.ExpressionMatch, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceRuleEnforceWorkloadsBody.
@@ -160,7 +168,7 @@ func (in *NamespaceRulePromotionRule) DeepCopy() *NamespaceRulePromotionRule {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OCIRegistry) DeepCopyInto(out *OCIRegistry) {
*out = *in
out.RegExpression = in.RegExpression
in.ExpressionMatch.DeepCopyInto(&out.ExpressionMatch)
if in.Policy != nil {
in, out := &in.Policy, &out.Policy
*out = make([]v1.PullPolicy, len(*in))
+21 -15
View File
@@ -142,6 +142,27 @@ func (in *DefaultAllowedListSpec) DeepCopy() *DefaultAllowedListSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExpressionMatch) DeepCopyInto(out *ExpressionMatch) {
*out = *in
out.ExpressionRegex = in.ExpressionRegex
if in.Exact != nil {
in, out := &in.Exact, &out.Exact
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpressionMatch.
func (in *ExpressionMatch) DeepCopy() *ExpressionMatch {
if in == nil {
return nil
}
out := new(ExpressionMatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) {
*out = *in
@@ -288,21 +309,6 @@ func (in *PoolExhaustionResource) DeepCopy() *PoolExhaustionResource {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RegExpression) DeepCopyInto(out *RegExpression) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegExpression.
func (in *RegExpression) DeepCopy() *RegExpression {
if in == nil {
return nil
}
out := new(RegExpression)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) {
*out = *in