package policy import ( "bufio" "bytes" "context" "encoding/json" "errors" "os" "path/filepath" "strings" "github.com/go-viper/mapstructure/v2" "github.com/open-policy-agent/opa/v1/rego" "git.maze.io/maze/conduit/logger" ) type Policy interface { // Package name. Package() string // Query is the policy query. Query(query string, input, result any) error // Verify the policy definition. Verify() error } type Loader interface { LoadPolicy(name string) (Policy, error) } type FileSystemLoader struct { Root string } func (l FileSystemLoader) LoadPolicy(name string) (Policy, error) { path := name if !strings.ContainsRune(filepath.Base(name), '.') || filepath.Ext(path) == "" { path += ".rego" } log := logger.StandardLog.Value("policy", path) if !filepath.IsAbs(path) { var err error if path, err = filepath.Abs(filepath.Join(l.Root, path)); err != nil { log.Err(err).Warn("Error resolving absolute policy path") return nil, err } } b, err := os.ReadFile(path) if err != nil { log.Err(err).Value("path", path).Warn("Error reading policy file") return nil, err } pkg, err := decodePackage(b) if err != nil { log.Err(err).Warn("Error decoding policy package") return nil, err } return &policyFile{ path: []string{path}, pkg: pkg, }, nil } func decodePackage(b []byte) (name string, err error) { s := bufio.NewScanner(bytes.NewReader(b)) for s.Scan() { line := s.Text() if strings.HasPrefix(line, "package ") { return strings.TrimSpace(line[len("package "):]), nil } } if err = s.Err(); err != nil { return } return "", errors.New("policy: no package name found") } type policyFile struct { path []string pkg string } func (p policyFile) Package() string { return p.pkg } func (p policyFile) Query(query string, input, value any) error { log := logger.StandardLog.Values(logger.Values{ "policy": p.path[0], "package": p.pkg, "query": query, }) log.Trace("Policy query evaluating") options := []func(*rego.Rego){ rego.Dump(os.Stderr), rego.Query(query), rego.Load(p.path, nil), rego.Strict(true), } if input != nil { debug := json.NewEncoder(os.Stdout) debug.SetIndent("", " ") debug.Encode(input) options = append(options, rego.Input(input)) } ctx := context.TODO() q, err := rego.New(options...).PrepareForEval(ctx) if err != nil { log.Err(err).Warn("Policy query prepare failed") return err } results, err := q.Eval(ctx) if err != nil { log.Err(err).Warn("Policy evaluation failed") return err } log = log.Value("results", len(results)) if value == nil { log.Debug("Policy query results processing ended, nil return value") return nil } log.Debug("Policy query results processing") mapped := make(map[string]any) for _, result := range results { for _, expr := range result.Expressions { if value, ok := expr.Value.(map[string]any); ok { for k, v := range value { mapped[k] = v } } else { log.Debugf("Unhandled expression value %T", expr) } } } log.Value("mapped", mapped).Debug("Policy query results done") return mapstructure.Decode(mapped, value) } func (p policyFile) Verify() error { _, err := rego.New( rego.Strict(true), rego.Load(p.path, nil), ).PrepareForEval(context.TODO()) return err }