Refactor Policy Enforcer to Use Unified PolicyPaths
- Updated the Policy Enforcer to consolidate policy source configuration under a single `policyPaths` key, allowing for auto-detection of URLs, directories, and files. - Removed deprecated keys such as `policyFile` and `policyUrls` from the configuration structure. - Adjusted related code and tests to ensure compatibility with the new configuration format. - Enhanced documentation to clarify the usage of `policyPaths` and provide examples for various configurations.
This commit is contained in:
@@ -51,6 +51,16 @@ modules:
|
|||||||
policyEnforcer:
|
policyEnforcer:
|
||||||
id: policyenforcer
|
id: policyenforcer
|
||||||
config:
|
config:
|
||||||
|
# policyPaths: polymorphic, auto-detects each entry as URL, directory, or file
|
||||||
|
# Examples:
|
||||||
|
# policyPaths: "./policies" # local directory
|
||||||
|
# policyPaths: "https://example.com/compliance.rego" # remote URL
|
||||||
|
# policyPaths: "./policies/compliance.rego" # local file
|
||||||
|
# For multiple sources, use YAML folded scalar (>-):
|
||||||
|
# policyPaths: >-
|
||||||
|
# https://example.com/compliance.rego,
|
||||||
|
# https://example.com/safety.rego,
|
||||||
|
# ./policies
|
||||||
policyPaths: "./policies"
|
policyPaths: "./policies"
|
||||||
signValidator:
|
signValidator:
|
||||||
id: signvalidator
|
id: signvalidator
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ OPA/Rego-based policy enforcement for beckn-onix adapters. Evaluates incoming be
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The `policyenforcer` plugin is a **Step plugin** that:
|
The `policyenforcer` plugin is a **Step plugin** that:
|
||||||
- Loads `.rego` policy files from local directories, files, URLs, or local paths
|
- Loads `.rego` policy files from URLs, local directories, or local files
|
||||||
- Evaluates incoming messages against compiled OPA policies
|
- Evaluates incoming messages against compiled OPA policies
|
||||||
- Returns a `BadReqErr` (NACK) when policy violations are detected
|
- Returns a `BadReqErr` (NACK) when policy violations are detected
|
||||||
- Fails closed on evaluation errors (treats as NACK)
|
- Fails closed on evaluation errors (treats as NACK)
|
||||||
@@ -17,9 +17,7 @@ All config keys are passed via `map[string]string` in the adapter YAML config.
|
|||||||
|
|
||||||
| Key | Required | Default | Description |
|
| Key | Required | Default | Description |
|
||||||
|-----|----------|---------|-------------|
|
|-----|----------|---------|-------------|
|
||||||
| `policyUrls` | At least one of `policyUrls`, `policyDir`, or `policyFile` required | — | Comma-separated list of URLs, local file paths, or directory paths to `.rego` files |
|
| `policyPaths` | Yes (at least one source required) | `./policies` (if dir exists) | Comma-separated list of policy sources — each entry is auto-detected as a **URL**, **directory**, or **file** |
|
||||||
| `policyPaths` | | `./policies` | Local directory or path containing `.rego` files |
|
|
||||||
| `policyFile` | | — | Single local `.rego` file path |
|
|
||||||
| `query` | No | `data.policy.violations` | Rego query returning violation strings |
|
| `query` | No | `data.policy.violations` | Rego query returning violation strings |
|
||||||
| `actions` | No | *(empty — all actions)* | Comma-separated beckn actions to enforce. When omitted, all actions are evaluated and the Rego policy itself decides which to gate. |
|
| `actions` | No | *(empty — all actions)* | Comma-separated beckn actions to enforce. When omitted, all actions are evaluated and the Rego policy itself decides which to gate. |
|
||||||
| `enabled` | No | `true` | Enable/disable the plugin |
|
| `enabled` | No | `true` | Enable/disable the plugin |
|
||||||
@@ -28,26 +26,34 @@ All config keys are passed via `map[string]string` in the adapter YAML config.
|
|||||||
|
|
||||||
### Policy Sources
|
### Policy Sources
|
||||||
|
|
||||||
`policyUrls` is the primary configuration key. It accepts a comma-separated list of:
|
`policyPaths` is the single configuration key for all policy sources. Each comma-separated entry is **auto-detected** as:
|
||||||
- **Remote URLs**: `https://policies.example.com/compliance.rego`
|
- **Remote URL** (`http://` or `https://`): fetched via HTTP at startup
|
||||||
- **Local file paths**: `/etc/policies/local.rego`
|
- **Local directory**: all `.rego` files loaded (`_test.rego` excluded)
|
||||||
- **Directory paths**: `/etc/policies/` (all `.rego` files loaded, `_test.rego` excluded)
|
- **Local file**: loaded directly
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
# Single directory
|
||||||
config:
|
config:
|
||||||
policyUrls: "https://policies.example.com/compliance.rego,/etc/policies/,/local/safety.rego"
|
policyPaths: "./policies"
|
||||||
|
|
||||||
|
# Single remote URL
|
||||||
|
config:
|
||||||
|
policyPaths: "https://policies.example.com/compliance.rego"
|
||||||
|
|
||||||
|
# Mix of URLs, directories, and files
|
||||||
|
config:
|
||||||
|
policyPaths: "https://policies.example.com/compliance.rego,./policies,/local/safety.rego"
|
||||||
```
|
```
|
||||||
|
|
||||||
When specifying many URLs, use the YAML folded scalar (`>-`) to keep the config readable:
|
When specifying many sources, use the YAML folded scalar (`>-`) to keep the config readable:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
config:
|
config:
|
||||||
policyUrls: >-
|
policyPaths: >-
|
||||||
https://policies.example.com/compliance.rego,
|
https://policies.example.com/compliance.rego,
|
||||||
https://policies.example.com/safety.rego,
|
https://policies.example.com/safety.rego,
|
||||||
https://policies.example.com/rate-limit.rego,
|
./policies,
|
||||||
/local/policies/,
|
/local/overrides/rate-limit.rego
|
||||||
https://policies.example.com/auth.rego
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `>-` folds newlines into spaces, so the value is parsed as a single comma-separated string.
|
The `>-` folds newlines into spaces, so the value is parsed as a single comma-separated string.
|
||||||
@@ -68,7 +74,7 @@ Or specify a custom policy location:
|
|||||||
policyEnforcer:
|
policyEnforcer:
|
||||||
id: policyenforcer
|
id: policyenforcer
|
||||||
config:
|
config:
|
||||||
policyUrls: "./policies/compliance.rego"
|
policyPaths: "./policies/compliance.rego"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Air-Gapped Deployments
|
### Air-Gapped Deployments
|
||||||
@@ -77,7 +83,7 @@ For environments without internet access, use local file paths or volume mounts:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
config:
|
config:
|
||||||
policyUrls: "/mounted-policies/compliance.rego,/mounted-policies/safety.rego"
|
policyPaths: "/mounted-policies/compliance.rego,/mounted-policies/safety.rego"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example Config
|
## Example Config
|
||||||
@@ -87,7 +93,9 @@ plugins:
|
|||||||
policyEnforcer:
|
policyEnforcer:
|
||||||
id: policyenforcer
|
id: policyenforcer
|
||||||
config:
|
config:
|
||||||
policyUrls: "https://policies.example.com/compliance.rego,/local/policies/"
|
policyPaths: >-
|
||||||
|
/local/policies/,
|
||||||
|
https://policies.example.com/compliance.rego
|
||||||
minDeliveryLeadHours: "4"
|
minDeliveryLeadHours: "4"
|
||||||
debugLogging: "true"
|
debugLogging: "true"
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -8,17 +8,12 @@ import (
|
|||||||
|
|
||||||
// Config holds the configuration for the Policy Enforcer plugin.
|
// Config holds the configuration for the Policy Enforcer plugin.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// PolicyPaths is a local directory containing .rego policy files (all loaded).
|
// PolicyPaths is a list of policy sources. Each entry is auto-detected as:
|
||||||
// At least one policy source (PolicyPaths, PolicyFile, or PolicyUrls) is required.
|
// - Remote URL (http:// or https://) → fetched via HTTP
|
||||||
PolicyPaths string
|
// - Local directory → all .rego files loaded (excluding _test.rego)
|
||||||
|
// - Local file → loaded directly
|
||||||
// PolicyFile is a single local .rego file path.
|
// Parsed from the comma-separated "policyPaths" config key.
|
||||||
PolicyFile string
|
PolicyPaths []string
|
||||||
|
|
||||||
// PolicyUrls is a list of URLs (or local file paths) pointing to .rego files,
|
|
||||||
// fetched at startup or read from disk.
|
|
||||||
// Parsed from the comma-separated "policyUrls" config key.
|
|
||||||
PolicyUrls []string
|
|
||||||
|
|
||||||
// Query is the Rego query that returns a set of violation strings.
|
// Query is the Rego query that returns a set of violation strings.
|
||||||
// Default: "data.policy.violations".
|
// Default: "data.policy.violations".
|
||||||
@@ -43,8 +38,6 @@ type Config struct {
|
|||||||
// Known config keys that are handled directly (not forwarded to RuntimeConfig).
|
// Known config keys that are handled directly (not forwarded to RuntimeConfig).
|
||||||
var knownKeys = map[string]bool{
|
var knownKeys = map[string]bool{
|
||||||
"policyPaths": true,
|
"policyPaths": true,
|
||||||
"policyFile": true,
|
|
||||||
"policyUrls": true,
|
|
||||||
"query": true,
|
"query": true,
|
||||||
"actions": true,
|
"actions": true,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
@@ -65,29 +58,22 @@ func DefaultConfig() *Config {
|
|||||||
func ParseConfig(cfg map[string]string) (*Config, error) {
|
func ParseConfig(cfg map[string]string) (*Config, error) {
|
||||||
config := DefaultConfig()
|
config := DefaultConfig()
|
||||||
|
|
||||||
if dir, ok := cfg["policyPaths"]; ok && dir != "" {
|
// Comma-separated policyPaths (each entry auto-detected as URL, directory, or file)
|
||||||
config.PolicyPaths = dir
|
if paths, ok := cfg["policyPaths"]; ok && paths != "" {
|
||||||
}
|
for _, p := range strings.Split(paths, ",") {
|
||||||
if file, ok := cfg["policyFile"]; ok && file != "" {
|
p = strings.TrimSpace(p)
|
||||||
config.PolicyFile = file
|
if p != "" {
|
||||||
}
|
config.PolicyPaths = append(config.PolicyPaths, p)
|
||||||
|
|
||||||
// Comma-separated policyUrls (supports URLs, local files, and directory paths)
|
|
||||||
if urls, ok := cfg["policyUrls"]; ok && urls != "" {
|
|
||||||
for _, u := range strings.Split(urls, ",") {
|
|
||||||
u = strings.TrimSpace(u)
|
|
||||||
if u != "" {
|
|
||||||
config.PolicyUrls = append(config.PolicyUrls, u)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.PolicyPaths == "" && config.PolicyFile == "" && len(config.PolicyUrls) == 0 {
|
if len(config.PolicyPaths) == 0 {
|
||||||
// Fall back to the default ./policies directory if it exists on disk.
|
// Fall back to the default ./policies directory if it exists on disk.
|
||||||
if info, err := os.Stat("./policies"); err == nil && info.IsDir() {
|
if info, err := os.Stat("./policies"); err == nil && info.IsDir() {
|
||||||
config.PolicyPaths = "./policies"
|
config.PolicyPaths = append(config.PolicyPaths, "./policies")
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("at least one policy source is required (policyPaths, policyFile, or policyUrls)")
|
return nil, fmt.Errorf("at least one policy source is required (policyPaths)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func New(cfg map[string]string) (*PolicyEnforcer, error) {
|
|||||||
return nil, fmt.Errorf("policyenforcer: config error: %w", err)
|
return nil, fmt.Errorf("policyenforcer: config error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluator, err := NewEvaluator(config.PolicyPaths, config.PolicyFile, config.PolicyUrls, config.Query, config.RuntimeConfig)
|
evaluator, err := NewEvaluator(config.PolicyPaths, config.Query, config.RuntimeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("policyenforcer: failed to initialize OPA evaluator: %w", err)
|
return nil, fmt.Errorf("policyenforcer: failed to initialize OPA evaluator: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func writePolicyDir(t *testing.T, filename, content string) string {
|
|||||||
func TestParseConfig_RequiresPolicySource(t *testing.T) {
|
func TestParseConfig_RequiresPolicySource(t *testing.T) {
|
||||||
_, err := ParseConfig(map[string]string{})
|
_, err := ParseConfig(map[string]string{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error when no policyPaths, policyFile, or policyUrls given")
|
t.Fatal("expected error when no policyPaths given")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,24 +93,21 @@ func TestParseConfig_CustomActions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseConfig_PolicyUrls(t *testing.T) {
|
func TestParseConfig_PolicyPaths(t *testing.T) {
|
||||||
cfg, err := ParseConfig(map[string]string{
|
cfg, err := ParseConfig(map[string]string{
|
||||||
"policyUrls": "https://example.com/a.rego, https://example.com/b.rego",
|
"policyPaths": "https://example.com/a.rego, https://example.com/b.rego",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if len(cfg.PolicyUrls) != 2 {
|
if len(cfg.PolicyPaths) != 2 {
|
||||||
t.Fatalf("expected 2 URLs, got %d: %v", len(cfg.PolicyUrls), cfg.PolicyUrls)
|
t.Fatalf("expected 2 paths, got %d: %v", len(cfg.PolicyPaths), cfg.PolicyPaths)
|
||||||
}
|
}
|
||||||
if cfg.PolicyUrls[0] != "https://example.com/a.rego" {
|
if cfg.PolicyPaths[0] != "https://example.com/a.rego" {
|
||||||
t.Errorf("url[0] = %q", cfg.PolicyUrls[0])
|
t.Errorf("path[0] = %q", cfg.PolicyPaths[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: policySources support was removed; we intentionally only support
|
|
||||||
// comma-separated policyUrls and local paths via policyUrls entries.
|
|
||||||
|
|
||||||
// --- Evaluator Tests (with inline policies) ---
|
// --- Evaluator Tests (with inline policies) ---
|
||||||
|
|
||||||
func TestEvaluator_NoViolations(t *testing.T) {
|
func TestEvaluator_NoViolations(t *testing.T) {
|
||||||
@@ -123,7 +120,7 @@ violations contains msg if {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
dir := writePolicyDir(t, "test.rego", policy)
|
dir := writePolicyDir(t, "test.rego", policy)
|
||||||
eval, err := NewEvaluator(dir, "", nil, "data.policy.violations", nil)
|
eval, err := NewEvaluator([]string{dir}, "data.policy.violations", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEvaluator failed: %v", err)
|
t.Fatalf("NewEvaluator failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -147,7 +144,7 @@ violations contains msg if {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
dir := writePolicyDir(t, "test.rego", policy)
|
dir := writePolicyDir(t, "test.rego", policy)
|
||||||
eval, err := NewEvaluator(dir, "", nil, "data.policy.violations", nil)
|
eval, err := NewEvaluator([]string{dir}, "data.policy.violations", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEvaluator failed: %v", err)
|
t.Fatalf("NewEvaluator failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -174,7 +171,7 @@ violations contains msg if {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
dir := writePolicyDir(t, "test.rego", policy)
|
dir := writePolicyDir(t, "test.rego", policy)
|
||||||
eval, err := NewEvaluator(dir, "", nil, "data.policy.violations", map[string]string{"maxValue": "100"})
|
eval, err := NewEvaluator([]string{dir}, "data.policy.violations", map[string]string{"maxValue": "100"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEvaluator failed: %v", err)
|
t.Fatalf("NewEvaluator failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -217,7 +214,7 @@ test_something if { count(policy.violations) > 0 }
|
|||||||
`
|
`
|
||||||
os.WriteFile(filepath.Join(dir, "policy_test.rego"), []byte(testFile), 0644)
|
os.WriteFile(filepath.Join(dir, "policy_test.rego"), []byte(testFile), 0644)
|
||||||
|
|
||||||
eval, err := NewEvaluator(dir, "", nil, "data.policy.violations", nil)
|
eval, err := NewEvaluator([]string{dir}, "data.policy.violations", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEvaluator should skip _test.rego files, but failed: %v", err)
|
t.Fatalf("NewEvaluator should skip _test.rego files, but failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -238,7 +235,7 @@ import rego.v1
|
|||||||
violations := set()
|
violations := set()
|
||||||
`
|
`
|
||||||
dir := writePolicyDir(t, "test.rego", policy)
|
dir := writePolicyDir(t, "test.rego", policy)
|
||||||
eval, err := NewEvaluator(dir, "", nil, "data.policy.violations", nil)
|
eval, err := NewEvaluator([]string{dir}, "data.policy.violations", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEvaluator failed: %v", err)
|
t.Fatalf("NewEvaluator failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -267,7 +264,7 @@ violations contains msg if {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
eval, err := NewEvaluator("", "", []string{srv.URL + "/test_policy.rego"}, "data.policy.violations", nil)
|
eval, err := NewEvaluator([]string{srv.URL + "/test_policy.rego"}, "data.policy.violations", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEvaluator with URL failed: %v", err)
|
t.Fatalf("NewEvaluator with URL failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -295,14 +292,14 @@ func TestEvaluator_FetchURL_NotFound(t *testing.T) {
|
|||||||
srv := httptest.NewServer(http.NotFoundHandler())
|
srv := httptest.NewServer(http.NotFoundHandler())
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
_, err := NewEvaluator("", "", []string{srv.URL + "/missing.rego"}, "data.policy.violations", nil)
|
_, err := NewEvaluator([]string{srv.URL + "/missing.rego"}, "data.policy.violations", nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for 404 URL")
|
t.Fatal("expected error for 404 URL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEvaluator_FetchURL_InvalidScheme(t *testing.T) {
|
func TestEvaluator_FetchURL_InvalidScheme(t *testing.T) {
|
||||||
_, err := NewEvaluator("", "", []string{"ftp://example.com/policy.rego"}, "data.policy.violations", nil)
|
_, err := NewEvaluator([]string{"ftp://example.com/policy.rego"}, "data.policy.violations", nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for ftp:// scheme")
|
t.Fatal("expected error for ftp:// scheme")
|
||||||
}
|
}
|
||||||
@@ -328,7 +325,7 @@ violations contains "remote_violation" if { input.remote_bad }
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
eval, err := NewEvaluator(dir, "", []string{srv.URL + "/remote.rego"}, "data.policy.violations", nil)
|
eval, err := NewEvaluator([]string{dir, srv.URL + "/remote.rego"}, "data.policy.violations", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEvaluator failed: %v", err)
|
t.Fatalf("NewEvaluator failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -355,7 +352,7 @@ violations contains "from_file" if { input.bad }
|
|||||||
policyPath := filepath.Join(dir, "local_policy.rego")
|
policyPath := filepath.Join(dir, "local_policy.rego")
|
||||||
os.WriteFile(policyPath, []byte(policy), 0644)
|
os.WriteFile(policyPath, []byte(policy), 0644)
|
||||||
|
|
||||||
eval, err := NewEvaluator("", "", []string{policyPath}, "data.policy.violations", nil)
|
eval, err := NewEvaluator([]string{policyPath}, "data.policy.violations", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewEvaluator with local path failed: %v", err)
|
t.Fatalf("NewEvaluator with local path failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -487,9 +484,9 @@ violations contains "blocked" if { input.context.action == "confirm" }
|
|||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
enforcer, err := New(map[string]string{
|
enforcer, err := New(map[string]string{
|
||||||
"policyUrls": srv.URL + "/block_confirm.rego",
|
"policyPaths": srv.URL + "/block_confirm.rego",
|
||||||
"query": "data.policy.violations",
|
"query": "data.policy.violations",
|
||||||
"actions": "confirm",
|
"actions": "confirm",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("New failed: %v", err)
|
t.Fatalf("New failed: %v", err)
|
||||||
|
|||||||
@@ -40,63 +40,28 @@ const maxPolicySize = 1 << 20
|
|||||||
|
|
||||||
// NewEvaluator creates an Evaluator by loading .rego files from local paths
|
// NewEvaluator creates an Evaluator by loading .rego files from local paths
|
||||||
// and/or URLs, then compiling them. runtimeConfig is passed to Rego as data.config.
|
// and/or URLs, then compiling them. runtimeConfig is passed to Rego as data.config.
|
||||||
func NewEvaluator(policyPaths, policyFile string, policyUrls []string, query string, runtimeConfig map[string]string) (*Evaluator, error) {
|
func NewEvaluator(policyPaths []string, query string, runtimeConfig map[string]string) (*Evaluator, error) {
|
||||||
modules := make(map[string]string)
|
modules := make(map[string]string)
|
||||||
|
|
||||||
// Load from local directory
|
// Load from policyPaths (each entry auto-detected as URL, directory, or file)
|
||||||
if policyPaths != "" {
|
for _, source := range policyPaths {
|
||||||
entries, err := os.ReadDir(policyPaths)
|
if isURL(source) {
|
||||||
if err != nil {
|
name, content, err := fetchPolicy(source)
|
||||||
return nil, fmt.Errorf("failed to read policy directory %s: %w", policyPaths, err)
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(entry.Name(), ".rego") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Skip test files — they shouldn't be compiled into the runtime evaluator
|
|
||||||
if strings.HasSuffix(entry.Name(), "_test.rego") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fpath := filepath.Join(policyPaths, entry.Name())
|
|
||||||
data, err := os.ReadFile(fpath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read policy file %s: %w", fpath, err)
|
return nil, fmt.Errorf("failed to fetch policy from %s: %w", source, err)
|
||||||
}
|
|
||||||
modules[entry.Name()] = string(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load single local file
|
|
||||||
if policyFile != "" {
|
|
||||||
data, err := os.ReadFile(policyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read policy file %s: %w", policyFile, err)
|
|
||||||
}
|
|
||||||
modules[filepath.Base(policyFile)] = string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load from URLs, local file paths, and directory paths (policyUrls)
|
|
||||||
for _, rawSource := range policyUrls {
|
|
||||||
if isURL(rawSource) {
|
|
||||||
name, content, err := fetchPolicy(rawSource)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to fetch policy from %s: %w", rawSource, err)
|
|
||||||
}
|
}
|
||||||
modules[name] = content
|
modules[name] = content
|
||||||
} else if info, err := os.Stat(rawSource); err == nil && info.IsDir() {
|
} else if info, err := os.Stat(source); err == nil && info.IsDir() {
|
||||||
// Treat as directory — load all .rego files inside
|
// Directory — load all .rego files inside
|
||||||
entries, err := os.ReadDir(rawSource)
|
entries, err := os.ReadDir(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read policy directory %s: %w", rawSource, err)
|
return nil, fmt.Errorf("failed to read policy directory %s: %w", source, err)
|
||||||
}
|
}
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".rego") || strings.HasSuffix(entry.Name(), "_test.rego") {
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".rego") || strings.HasSuffix(entry.Name(), "_test.rego") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fpath := filepath.Join(rawSource, entry.Name())
|
fpath := filepath.Join(source, entry.Name())
|
||||||
data, err := os.ReadFile(fpath)
|
data, err := os.ReadFile(fpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read policy file %s: %w", fpath, err)
|
return nil, fmt.Errorf("failed to read policy file %s: %w", fpath, err)
|
||||||
@@ -104,12 +69,12 @@ func NewEvaluator(policyPaths, policyFile string, policyUrls []string, query str
|
|||||||
modules[entry.Name()] = string(data)
|
modules[entry.Name()] = string(data)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Treat as local file path
|
// Local file path
|
||||||
data, err := os.ReadFile(rawSource)
|
data, err := os.ReadFile(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read local policy source %s: %w", rawSource, err)
|
return nil, fmt.Errorf("failed to read policy file %s: %w", source, err)
|
||||||
}
|
}
|
||||||
modules[filepath.Base(rawSource)] = string(data)
|
modules[filepath.Base(source)] = string(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user