158 lines
3.2 KiB
Go
158 lines
3.2 KiB
Go
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
|
|
}
|