Files
agents_and_robots/shell/security/loader_test.go
T
egutierrez 65377afde4 feat: security/ YAML files + shell/security/ loader (issue 0024b)
Crea los tres archivos YAML de configuración de seguridad centralizada en
security/ (user-groups.yaml, agent-groups.yaml, permissions.yaml) y el
loader impuro shell/security/loader.go que los lee y construye un
security.SecurityPolicy puro.

- security/user-groups.yaml: grupos de usuarios (admins, everyone)
- security/agent-groups.yaml: grupos de agentes (assistants, all)
- security/permissions.yaml: políticas de permisos por grupo de agentes
- shell/security/loader.go: Load(dir) → SecurityPolicy; usa structs YAML
  intermedios para mantener pkg/security/ libre de gopkg.in/yaml.v3
- shell/security/loader_test.go: 6 tests cubren los casos del issue
  (dir inexistente, vacío, 3 YAMLs, solo uno, malformado, wildcards)

El código se mergea con feature flag centralized-security-groups = false
(loader creado, todavía no wired al launcher — eso es 0024c).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:32:05 +00:00

190 lines
4.8 KiB
Go

package security_test
import (
"os"
"path/filepath"
"testing"
shellsecurity "github.com/enmanuel/agents/shell/security"
)
// writeFile is a helper that creates a file in dir with the given content.
func writeFile(t *testing.T, dir, name, content string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil {
t.Fatalf("writeFile %s: %v", name, err)
}
}
// --- Test 3.1: directorio inexistente → policy vacía, sin error ---
func TestLoad_NonExistentDir(t *testing.T) {
policy, err := shellsecurity.Load("/tmp/does-not-exist-security-xyz")
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
if len(policy.UserGroups) != 0 || len(policy.AgentGroups) != 0 || len(policy.Policies) != 0 {
t.Errorf("expected empty policy, got: %+v", policy)
}
}
// --- Test 3.2: directorio vacío (sin YAML) → policy vacía, sin error ---
func TestLoad_EmptyDir(t *testing.T) {
dir := t.TempDir()
policy, err := shellsecurity.Load(dir)
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
if len(policy.UserGroups) != 0 || len(policy.AgentGroups) != 0 || len(policy.Policies) != 0 {
t.Errorf("expected empty policy, got: %+v", policy)
}
}
// --- Test 3.3: los 3 YAML válidos → policy con todos los campos ---
func TestLoad_AllFiles(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "user-groups.yaml", `
groups:
admins:
members: ["@admin:example.com"]
everyone:
members: ["*"]
`)
writeFile(t, dir, "agent-groups.yaml", `
groups:
assistants:
agents:
- assistant-bot
all:
agents: ["*"]
`)
writeFile(t, dir, "permissions.yaml", `
policies:
- agent_group: all
permissions:
- user_group: admins
actions: ["*"]
- user_group: everyone
actions: ["ask"]
`)
policy, err := shellsecurity.Load(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(policy.UserGroups) != 2 {
t.Errorf("expected 2 user groups, got %d", len(policy.UserGroups))
}
if len(policy.AgentGroups) != 2 {
t.Errorf("expected 2 agent groups, got %d", len(policy.AgentGroups))
}
if len(policy.Policies) != 1 {
t.Errorf("expected 1 policy, got %d", len(policy.Policies))
}
if len(policy.Policies[0].Permissions) != 2 {
t.Errorf("expected 2 permissions, got %d", len(policy.Policies[0].Permissions))
}
}
// --- Test 3.4: solo user-groups.yaml → user groups poblados, resto vacío ---
func TestLoad_OnlyUserGroups(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "user-groups.yaml", `
groups:
admins:
members: ["@admin:example.com"]
`)
policy, err := shellsecurity.Load(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(policy.UserGroups) != 1 {
t.Errorf("expected 1 user group, got %d", len(policy.UserGroups))
}
if policy.UserGroups[0].Name != "admins" {
t.Errorf("expected group name 'admins', got %q", policy.UserGroups[0].Name)
}
if len(policy.AgentGroups) != 0 {
t.Errorf("expected no agent groups, got %d", len(policy.AgentGroups))
}
if len(policy.Policies) != 0 {
t.Errorf("expected no policies, got %d", len(policy.Policies))
}
}
// --- Test 3.5: YAML malformado → error con nombre de archivo en el mensaje ---
func TestLoad_MalformedYAML(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "user-groups.yaml", `this: is: not: valid: yaml: [`)
_, err := shellsecurity.Load(dir)
if err == nil {
t.Fatal("expected error for malformed YAML, got nil")
}
if got := err.Error(); len(got) == 0 {
t.Fatal("error message is empty")
}
// Must mention the filename
if !containsString(err.Error(), "user-groups.yaml") {
t.Errorf("error message should contain filename, got: %s", err.Error())
}
}
// --- Test 3.6: "*" como string literal en members y agents ---
func TestLoad_WildcardStrings(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "user-groups.yaml", `
groups:
everyone:
members: ["*"]
`)
writeFile(t, dir, "agent-groups.yaml", `
groups:
all:
agents: ["*"]
`)
policy, err := shellsecurity.Load(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(policy.UserGroups) != 1 {
t.Fatalf("expected 1 user group, got %d", len(policy.UserGroups))
}
if len(policy.UserGroups[0].Members) != 1 || policy.UserGroups[0].Members[0] != "*" {
t.Errorf("expected members=[\"*\"], got %v", policy.UserGroups[0].Members)
}
if len(policy.AgentGroups) != 1 {
t.Fatalf("expected 1 agent group, got %d", len(policy.AgentGroups))
}
if len(policy.AgentGroups[0].Agents) != 1 || policy.AgentGroups[0].Agents[0] != "*" {
t.Errorf("expected agents=[\"*\"], got %v", policy.AgentGroups[0].Agents)
}
}
func containsString(s, sub string) bool {
return len(s) >= len(sub) && (s == sub || len(s) > 0 && containsSubstr(s, sub))
}
func containsSubstr(s, sub string) bool {
for i := 0; i <= len(s)-len(sub); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}