Files
glpi-neural-brain/internal/sourceagent/controller_test.go
jbergner 440423c5b6
All checks were successful
release-tag / release-image (push) Successful in 2m43s
RC-3
2026-08-09 11:29:13 +02:00

104 lines
3.9 KiB
Go

package sourceagent
import (
"context"
"net/url"
"path/filepath"
"testing"
)
func TestNormalizeControllerPolicySecureDefaultsAndRoots(t *testing.T) {
p, err := normalizeControllerPolicy(defaultControllerPolicy())
if err != nil {
t.Fatal(err)
}
if p.Enabled || p.AutonomousEnabled || !p.DryRun || p.AllowDestructive {
t.Fatalf("unsafe default controller policy: %+v", p)
}
if p.MaxConcurrentJobs != 1 || p.MaxJobDuration <= 0 {
t.Fatalf("unexpected limits: %+v", p)
}
root := t.TempDir()
p.AllowedComposeRoots = []string{root, root}
p, err = normalizeControllerPolicy(p)
if err != nil {
t.Fatal(err)
}
if len(p.AllowedComposeRoots) != 1 || !filepath.IsAbs(p.AllowedComposeRoots[0]) {
t.Fatalf("compose roots were not normalized: %#v", p.AllowedComposeRoots)
}
}
func TestValidateControllerJobAutonomyAndDestructivePolicy(t *testing.T) {
base := ControllerPolicy{Enabled: true, AutonomousEnabled: true, AllowDestructive: false}
for _, kind := range []string{"evidence_http_probe", "health_recovery", "compute_capacity_compose", "compose_smoke_test"} {
if err := validateControllerJob(ControllerJob{Kind: kind, Autonomous: true}, base); err != nil {
t.Fatalf("%s: %v", kind, err)
}
}
if err := validateControllerJob(ControllerJob{Kind: "container_create", Autonomous: true}, base); err == nil {
t.Fatal("autonomous arbitrary container creation must be denied")
}
if err := validateControllerJob(ControllerJob{Kind: "container_remove"}, base); err == nil {
t.Fatal("destructive removal must require policy")
}
base.AllowDestructive = true
if err := validateControllerJob(ControllerJob{Kind: "container_remove"}, base); err != nil {
t.Fatal(err)
}
base.AutonomousEnabled = false
if err := validateControllerJob(ControllerJob{Kind: "evidence_http_probe", Autonomous: true}, base); err == nil {
t.Fatal("autonomous off must deny autonomous jobs")
}
}
func TestControllerImageAllowlistDoesNotUseAmbiguousPrefix(t *testing.T) {
allowed := []string{"curlimages/curl:", "registry.example/team/", "exact/tool:1"}
good := []string{"curlimages/curl:8.11.1", "registry.example/team/worker:2", "exact/tool:1"}
bad := []string{"curlimages/curl-malicious:8", "registry.example/team-malicious/worker:2", "exact/tool:10"}
for _, image := range good {
if !imageAllowed(image, allowed) {
t.Fatalf("expected allowed: %s", image)
}
}
for _, image := range bad {
if imageAllowed(image, allowed) {
t.Fatalf("unexpected allow: %s", image)
}
}
}
func TestSplitDockerImageReferenceHandlesRegistryPortsAndDigests(t *testing.T) {
cases := []struct{ in, repo, tag string }{
{"curlimages/curl:8.11.1", "curlimages/curl", "8.11.1"},
{"registry.example:5000/team/tool:2", "registry.example:5000/team/tool", "2"},
{"registry.example:5000/team/tool", "registry.example:5000/team/tool", ""},
{"tool@sha256:012345", "tool@sha256:012345", ""},
}
for _, tc := range cases {
repo, tag := splitDockerImageReference(tc.in)
if repo != tc.repo || tag != tc.tag {
t.Fatalf("%s => %s/%s, want %s/%s", tc.in, repo, tag, tc.repo, tc.tag)
}
}
}
func TestControllerJobHistoryRejectsInlineSecrets(t *testing.T) {
if err := validateControllerParametersForStorage(ControllerJob{Kind: "container_create", Parameters: map[string]any{"env": []string{"MODE=test", "API_TOKEN=secret"}}}); err == nil {
t.Fatal("expected inline secret to be rejected")
}
if err := validateControllerParametersForStorage(ControllerJob{Kind: "container_create", Parameters: map[string]any{"env": []string{"MODE=test", "LOG_LEVEL=debug"}}}); err != nil {
t.Fatal(err)
}
}
func TestEvidenceProbeBlocksLocalTargets(t *testing.T) {
for _, raw := range []string{"http://127.0.0.1/x", "http://10.1.2.3/x", "http://[::1]/x", "http://localhost/x"} {
u, _ := url.Parse(raw)
if err := validateEvidenceProbeTarget(context.Background(), u); err == nil {
t.Fatalf("expected local target rejection: %s", raw)
}
}
}