go-crypt/trust/config.go
2026-02-20 04:02:47 +00:00

137 lines
3.6 KiB
Go

package trust
import (
"encoding/json"
"fmt"
"io"
"os"
)
// 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.
func LoadPoliciesFromFile(path string) ([]Policy, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("trust.LoadPoliciesFromFile: %w", err)
}
defer f.Close()
return LoadPolicies(f)
}
// LoadPolicies reads JSON from a reader and returns parsed policies.
func LoadPolicies(r io.Reader) ([]Policy, error) {
var cfg PoliciesConfig
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
if err := dec.Decode(&cfg); err != nil {
return nil, fmt.Errorf("trust.LoadPolicies: %w", err)
}
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, fmt.Errorf("trust.LoadPolicies: invalid tier %d at index %d", pc.Tier, i)
}
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.
func (pe *PolicyEngine) ApplyPolicies(r io.Reader) error {
policies, err := LoadPolicies(r)
if err != nil {
return err
}
for _, p := range policies {
if err := pe.SetPolicy(p); err != nil {
return fmt.Errorf("trust.ApplyPolicies: %w", err)
}
}
return nil
}
// ApplyPoliciesFromFile loads policies from a JSON file and sets them on the engine.
func (pe *PolicyEngine) ApplyPoliciesFromFile(path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("trust.ApplyPoliciesFromFile: %w", err)
}
defer f.Close()
return pe.ApplyPolicies(f)
}
// ExportPolicies serialises the current policies as JSON to the given writer.
func (pe *PolicyEngine) ExportPolicies(w io.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 fmt.Errorf("trust.ExportPolicies: %w", 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
}