// Package security provides the impure loader for security policy YAML files. // It reads security/ directory files and returns a pure security.SecurityPolicy. package security import ( "errors" "fmt" "os" "path/filepath" "gopkg.in/yaml.v3" "github.com/enmanuel/agents/pkg/security" ) // --- YAML intermediate types (private, only for parsing) --- type yamlUserGroups struct { Groups map[string]struct { Members []string `yaml:"members"` } `yaml:"groups"` } type yamlAgentGroups struct { Groups map[string]struct { Agents []string `yaml:"agents"` } `yaml:"groups"` } type yamlPermissions struct { Policies []struct { AgentGroup string `yaml:"agent_group"` Permissions []struct { UserGroup string `yaml:"user_group"` Actions []string `yaml:"actions"` } `yaml:"permissions"` } `yaml:"policies"` } // Load reads the security YAML files from dir and returns a SecurityPolicy. // If dir does not exist or is empty, returns an empty policy without error. // If an individual file is missing, that section is left empty. // If a YAML file is malformed, returns an error naming the file. func Load(dir string) (security.SecurityPolicy, error) { if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { return security.SecurityPolicy{}, nil } userGroups, err := loadUserGroups(filepath.Join(dir, "user-groups.yaml")) if err != nil { return security.SecurityPolicy{}, err } agentGroups, err := loadAgentGroups(filepath.Join(dir, "agent-groups.yaml")) if err != nil { return security.SecurityPolicy{}, err } policies, err := loadPermissions(filepath.Join(dir, "permissions.yaml")) if err != nil { return security.SecurityPolicy{}, err } return security.SecurityPolicy{ UserGroups: userGroups, AgentGroups: agentGroups, Policies: policies, }, nil } func loadUserGroups(path string) ([]security.UserGroup, error) { data, err := os.ReadFile(path) if errors.Is(err, os.ErrNotExist) { return nil, nil } if err != nil { return nil, fmt.Errorf("security: reading %s: %w", path, err) } var raw yamlUserGroups if err := yaml.Unmarshal(data, &raw); err != nil { return nil, fmt.Errorf("security: parsing %s: %w", path, err) } groups := make([]security.UserGroup, 0, len(raw.Groups)) for name, g := range raw.Groups { groups = append(groups, security.UserGroup{ Name: name, Members: g.Members, }) } return groups, nil } func loadAgentGroups(path string) ([]security.AgentGroup, error) { data, err := os.ReadFile(path) if errors.Is(err, os.ErrNotExist) { return nil, nil } if err != nil { return nil, fmt.Errorf("security: reading %s: %w", path, err) } var raw yamlAgentGroups if err := yaml.Unmarshal(data, &raw); err != nil { return nil, fmt.Errorf("security: parsing %s: %w", path, err) } groups := make([]security.AgentGroup, 0, len(raw.Groups)) for name, g := range raw.Groups { groups = append(groups, security.AgentGroup{ Name: name, Agents: g.Agents, }) } return groups, nil } func loadPermissions(path string) ([]security.AgentPolicy, error) { data, err := os.ReadFile(path) if errors.Is(err, os.ErrNotExist) { return nil, nil } if err != nil { return nil, fmt.Errorf("security: reading %s: %w", path, err) } var raw yamlPermissions if err := yaml.Unmarshal(data, &raw); err != nil { return nil, fmt.Errorf("security: parsing %s: %w", path, err) } policies := make([]security.AgentPolicy, 0, len(raw.Policies)) for _, p := range raw.Policies { perms := make([]security.Permission, 0, len(p.Permissions)) for _, perm := range p.Permissions { perms = append(perms, security.Permission{ UserGroup: perm.UserGroup, Actions: perm.Actions, }) } policies = append(policies, security.AgentPolicy{ AgentGroup: p.AgentGroup, Permissions: perms, }) } return policies, nil }