go-crypt/trust/config.go
Claude 7407b89b8d
refactor(ax): AX RFC-025 compliance sweep pass 1
Remove banned imports (fmt, strings, os, errors, path/filepath) across all
production and test files, replace with core.* primitives, coreio.ReadStream,
and coreerr.E. Upgrade dappco.re/go/core v0.5.0 → v0.7.0 for core.PathBase
and core.Is. Fix isRepoScoped to exclude pr.* capabilities (enforcement is at
the forge layer, not the policy engine). Add Good/Bad/Ugly test coverage to
all packages missing the mandatory three-category naming convention.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-31 08:48:56 +01:00

159 lines
4.4 KiB
Go

package trust
import (
"encoding/json"
goio "io"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
)
// PolicyConfig is the JSON-serialisable representation of a trust policy.
type PolicyConfig struct {
Tier int `json:"tier"`
Allowed []string `json:"allowed"`
RequiresApproval []string `json:"requires_approval,omitempty"`
Denied []string `json:"denied,omitempty"`
}
// PoliciesConfig is the top-level configuration containing all tier policies.
type PoliciesConfig struct {
Policies []PolicyConfig `json:"policies"`
}
// LoadPoliciesFromFile reads a JSON file and returns parsed policies.
//
// policies, err := trust.LoadPoliciesFromFile("/etc/agent/policies.json")
func LoadPoliciesFromFile(path string) ([]Policy, error) {
reader, err := coreio.ReadStream(coreio.Local, path)
if err != nil {
return nil, coreerr.E("trust.LoadPoliciesFromFile", "failed to open file", err)
}
defer func() { _ = reader.Close() }()
return LoadPolicies(reader)
}
// LoadPolicies reads JSON from a reader and returns parsed policies.
//
// policies, err := trust.LoadPolicies(strings.NewReader(jsonInput))
func LoadPolicies(r goio.Reader) ([]Policy, error) {
const op = "trust.LoadPolicies"
var cfg PoliciesConfig
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
if err := dec.Decode(&cfg); err != nil {
return nil, coreerr.E(op, "failed to decode JSON", err)
}
// Reject trailing data after the decoded value
var extra json.RawMessage
if err := dec.Decode(&extra); err != goio.EOF {
return nil, coreerr.E(op, "unexpected trailing data in JSON", nil)
}
return convertPolicies(cfg)
}
// convertPolicies transforms config DTOs into domain Policy structs.
func convertPolicies(cfg PoliciesConfig) ([]Policy, error) {
var policies []Policy
for i, pc := range cfg.Policies {
tier := Tier(pc.Tier)
if !tier.Valid() {
return nil, coreerr.E("trust.LoadPolicies", core.Sprintf("invalid tier %d at index %d", pc.Tier, i), nil)
}
p := Policy{
Tier: tier,
Allowed: toCapabilities(pc.Allowed),
RequiresApproval: toCapabilities(pc.RequiresApproval),
Denied: toCapabilities(pc.Denied),
}
policies = append(policies, p)
}
return policies, nil
}
// ApplyPolicies loads policies from a reader and sets them on the engine,
// replacing any existing policies for the same tiers.
//
// err := engine.ApplyPolicies(strings.NewReader(policyJSON))
func (pe *PolicyEngine) ApplyPolicies(r goio.Reader) error {
policies, err := LoadPolicies(r)
if err != nil {
return err
}
for _, p := range policies {
if err := pe.SetPolicy(p); err != nil {
return coreerr.E("trust.ApplyPolicies", "failed to set policy", err)
}
}
return nil
}
// ApplyPoliciesFromFile loads policies from a JSON file and sets them on the engine.
//
// err := engine.ApplyPoliciesFromFile("/etc/agent/policies.json")
func (pe *PolicyEngine) ApplyPoliciesFromFile(path string) error {
reader, err := coreio.ReadStream(coreio.Local, path)
if err != nil {
return coreerr.E("trust.ApplyPoliciesFromFile", "failed to open file", err)
}
defer func() { _ = reader.Close() }()
return pe.ApplyPolicies(reader)
}
// ExportPolicies serialises the current policies as JSON to the given writer.
//
// var buf bytes.Buffer
// err := engine.ExportPolicies(&buf)
func (pe *PolicyEngine) ExportPolicies(w goio.Writer) error {
var cfg PoliciesConfig
for _, tier := range []Tier{TierUntrusted, TierVerified, TierFull} {
p := pe.GetPolicy(tier)
if p == nil {
continue
}
cfg.Policies = append(cfg.Policies, PolicyConfig{
Tier: int(p.Tier),
Allowed: fromCapabilities(p.Allowed),
RequiresApproval: fromCapabilities(p.RequiresApproval),
Denied: fromCapabilities(p.Denied),
})
}
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(cfg); err != nil {
return coreerr.E("trust.ExportPolicies", "failed to encode JSON", err)
}
return nil
}
// toCapabilities converts string slices to Capability slices.
func toCapabilities(ss []string) []Capability {
if len(ss) == 0 {
return nil
}
caps := make([]Capability, len(ss))
for i, s := range ss {
caps[i] = Capability(s)
}
return caps
}
// fromCapabilities converts Capability slices to string slices.
func fromCapabilities(caps []Capability) []string {
if len(caps) == 0 {
return nil
}
ss := make([]string, len(caps))
for i, c := range caps {
ss[i] = string(c)
}
return ss
}