Files
capsule/pkg/ruleengine/validate_test.go
T
Oliver BählerandGitHub 755cef54bf feat(rules): add service enforcement rules (#1982)
* 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(rules): add service enforcement rules

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

* feat(rules): add service enforcement rules

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

* feat(rules): add service enforcement rules

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

* feat(rules): add service enforcement rules

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

* feat(rules): add service enforcement rules

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

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
2026-06-24 11:20:23 +02:00

361 lines
8.6 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package ruleengine
import (
"strings"
"testing"
"github.com/projectcapsule/capsule/pkg/api"
"github.com/projectcapsule/capsule/pkg/api/rules"
)
func TestValidateRuleStatusBody(t *testing.T) {
tests := []struct {
name string
bodies []*rules.NamespaceRuleBodyNamespace
wantErr string
}{
{
name: "nil bodies are valid",
bodies: []*rules.NamespaceRuleBodyNamespace{
nil,
{},
{
Enforce: nil,
},
},
},
{
name: "valid workload and service rules",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Action: rules.ActionTypeAllow,
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Registries: []rules.OCIRegistry{
{
ExpressionMatch: api.ExpressionMatch{
ExpressionRegex: api.ExpressionRegex{
Expression: "harbor/.*",
},
},
},
{
ExpressionMatch: api.ExpressionMatch{
Exact: []string{
"harbor/platform/debian:latest",
},
},
},
},
Schedulers: []api.ExpressionMatch{
{
ExpressionRegex: api.ExpressionRegex{
Expression: "tenant-[a-z0-9-]+",
},
},
},
},
Services: rules.NamespaceRuleEnforceServicesBody{
Types: []rules.ServiceType{
rules.ServiceTypeClusterIP,
rules.ServiceTypeNodePort,
rules.ServiceTypeLoadBalancer,
rules.ServiceTypeExternalName,
},
LoadBalancers: &rules.ServiceLoadBalancerRule{
CIDRs: []string{
"10.0.0.2/32",
"10.0.1.0/24",
"2001:db8::/32",
"10.0.0.3",
},
},
ExternalNames: &rules.ServiceExternalNameRule{
Hostnames: []api.ExpressionMatch{
{
Exact: []string{
"internal.git.com",
},
},
{
ExpressionRegex: api.ExpressionRegex{
Expression: ".*\\.example\\.com",
},
},
{
ExpressionRegex: api.ExpressionRegex{
Expression: "trusted\\..*",
Negate: true,
},
},
},
},
NodePorts: &rules.ServiceNodePortRule{
Ports: []rules.ServiceNodePortRange{
{
From: 30000,
To: 32767,
},
{
From: 30500,
To: 30500,
},
},
},
},
},
},
},
},
{
name: "invalid workload registry regex",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Registries: []rules.OCIRegistry{
{
ExpressionMatch: api.ExpressionMatch{
ExpressionRegex: api.ExpressionRegex{
Expression: "[",
},
},
},
},
},
},
},
},
wantErr: `rules[0].enforce.workloads.registries[0].exp "[" is invalid`,
},
{
name: "invalid workload scheduler regex",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
Schedulers: []api.ExpressionMatch{
{
ExpressionRegex: api.ExpressionRegex{
Expression: "[",
},
},
},
},
},
},
},
wantErr: `rules[0].enforce.workloads.schedulers[0].exp "[" is invalid`,
},
{
name: "invalid service type",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
Types: []rules.ServiceType{
rules.ServiceTypeClusterIP,
rules.ServiceType("InvalidType"),
},
},
},
},
},
wantErr: `rules[0].enforce.services.types[1] "InvalidType" is invalid`,
},
{
name: "invalid loadBalancer CIDR",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
LoadBalancers: &rules.ServiceLoadBalancerRule{
CIDRs: []string{
"10.0.0.0/33",
},
},
},
},
},
},
wantErr: `rules[0].enforce.services.loadBalancers.cidrs[0] "10.0.0.0/33" is invalid`,
},
{
name: "empty loadBalancer CIDR",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
LoadBalancers: &rules.ServiceLoadBalancerRule{
CIDRs: []string{
"",
},
},
},
},
},
},
wantErr: `rules[0].enforce.services.loadBalancers.cidrs[0] "" is invalid: CIDR is empty`,
},
{
name: "invalid externalName hostname regex",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
ExternalNames: &rules.ServiceExternalNameRule{
Hostnames: []api.ExpressionMatch{
{
ExpressionRegex: api.ExpressionRegex{
Expression: "[",
},
},
},
},
},
},
},
},
wantErr: `rules[0].enforce.services.externalNames.hostnames[0].exp "[" is invalid`,
},
{
name: "nodePort from greater than to",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
NodePorts: &rules.ServiceNodePortRule{
Ports: []rules.ServiceNodePortRange{
{
From: 32767,
To: 30000,
},
},
},
},
},
},
},
wantErr: `rules[0].enforce.services.nodePorts.ports[0] is invalid: from 32767 must be lower than or equal to 30000`,
},
{
name: "nodePort from below valid port range",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
NodePorts: &rules.ServiceNodePortRule{
Ports: []rules.ServiceNodePortRange{
{
From: 0,
To: 30000,
},
},
},
},
},
},
},
wantErr: `rules[0].enforce.services.nodePorts.ports[0] is invalid: from 0 must be between 1 and 65535`,
},
{
name: "nodePort to above valid port range",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
NodePorts: &rules.ServiceNodePortRule{
Ports: []rules.ServiceNodePortRange{
{
From: 30000,
To: 70000,
},
},
},
},
},
},
},
wantErr: `rules[0].enforce.services.nodePorts.ports[0] is invalid: to 70000 must be between 1 and 65535`,
},
{
name: "single nodePort range is valid",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
NodePorts: &rules.ServiceNodePortRule{
Ports: []rules.ServiceNodePortRange{
{
From: 30500,
To: 30500,
},
},
},
},
},
},
},
},
{
name: "reports correct indexes across multiple rules",
bodies: []*rules.NamespaceRuleBodyNamespace{
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
Types: []rules.ServiceType{
rules.ServiceTypeClusterIP,
},
},
},
},
{
Enforce: &rules.NamespaceRuleEnforceBody{
Services: rules.NamespaceRuleEnforceServicesBody{
ExternalNames: &rules.ServiceExternalNameRule{
Hostnames: []api.ExpressionMatch{
{
ExpressionRegex: api.ExpressionRegex{
Expression: "valid\\..*",
},
},
{
ExpressionRegex: api.ExpressionRegex{
Expression: "[",
},
},
},
},
},
},
},
},
wantErr: `rules[1].enforce.services.externalNames.hostnames[1].exp "[" is invalid`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateRuleStatusBody(tt.bodies)
if tt.wantErr == "" {
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
return
}
if err == nil {
t.Fatalf("expected error containing %q, got nil", tt.wantErr)
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("expected error containing %q, got %q", tt.wantErr, err.Error())
}
})
}
}