Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions cmd/wfctl/type_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,19 +440,7 @@ func KnownModuleTypes() map[string]ModuleTypeInfo {
ConfigKeys: []string{"account", "region", "service_role", "compute_type", "image", "source_type"},
},

// policy plugin
"policy.opa": {
Type: "policy.opa",
Plugin: "policy",
Stateful: false,
ConfigKeys: []string{"endpoint", "policies"},
},
"policy.cedar": {
Type: "policy.cedar",
Plugin: "policy",
Stateful: false,
ConfigKeys: []string{"policies"},
},
// policy plugin (OPA and Cedar are external plugins: workflow-plugin-policy-opa, workflow-plugin-policy-cedar)
"policy.mock": {
Type: "policy.mock",
Plugin: "policy",
Expand Down
126 changes: 3 additions & 123 deletions module/policy_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package module
import (
"context"
"fmt"
"log/slog"
"sync"

"github.com/CrisisTextLine/modular"
Expand Down Expand Up @@ -31,7 +30,7 @@ type PolicyInfo struct {
}

// PolicyEngineModule is a workflow module wrapping a pluggable PolicyEngine backend.
// Supported backends: "mock", "opa", "cedar".
// Supported backends: "mock". For OPA or Cedar, use external plugins.
type PolicyEngineModule struct {
name string
config map[string]any
Expand All @@ -54,20 +53,13 @@ func (m *PolicyEngineModule) Init(app modular.Application) error {
m.backend = "mock"
}

allowStub := isTruthy(m.config["allow_stub_backends"])

switch m.backend {
case "mock":
m.engine = newMockPolicyEngine()
case "opa":
endpoint, _ := m.config["endpoint"].(string)
m.engine = newOPAPolicyEngine(endpoint, allowStub)
slog.Warn("WARNING: using stub policy engine — all requests will be DENIED. Set allow_stub_backends: true in config to use stub backends for testing.",
"module", m.name, "backend", "opa", "allow_stub_backends", allowStub)
return fmt.Errorf("opa backend not built-in; use the workflow-plugin-policy-opa external plugin")
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message could be more actionable by including a brief instruction on how to use the external plugin. Consider: "opa backend not built-in; install and load the workflow-plugin-policy-opa external plugin, then use backend: 'mock' or configure the OPA module type from the plugin"

Copilot uses AI. Check for mistakes.
case "cedar":
m.engine = newCedarPolicyEngine(allowStub)
slog.Warn("WARNING: using stub policy engine — all requests will be DENIED. Set allow_stub_backends: true in config to use stub backends for testing.",
"module", m.name, "backend", "cedar", "allow_stub_backends", allowStub)
return fmt.Errorf("cedar backend not built-in; use the workflow-plugin-policy-cedar external plugin")
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message could be more actionable by including a brief instruction on how to use the external plugin. Consider: "cedar backend not built-in; install and load the workflow-plugin-policy-cedar external plugin, then use backend: 'mock' or configure the Cedar module type from the plugin"

Suggested change
return fmt.Errorf("cedar backend not built-in; use the workflow-plugin-policy-cedar external plugin")
return fmt.Errorf("cedar backend not built-in; install and load the workflow-plugin-policy-cedar external plugin, then use backend: 'mock' or configure the Cedar module type from the plugin")

Copilot uses AI. Check for mistakes.
default:
return fmt.Errorf("policy.engine %q: unsupported backend %q", m.name, m.backend)
}
Expand Down Expand Up @@ -172,17 +164,6 @@ func (e *mockPolicyEngine) Evaluate(_ context.Context, input map[string]any) (*P
}, nil
}

// isTruthy returns true if v is a bool true, or a string "true"/"1"/"yes".
func isTruthy(v any) bool {
switch val := v.(type) {
case bool:
return val
case string:
return val == "true" || val == "1" || val == "yes"
}
return false
}

func containsString(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
func() bool {
Expand All @@ -195,104 +176,3 @@ func containsString(s, substr string) bool {
}())
}

// ─── OPA backend stub ────────────────────────────────────────────────────────

// opaPolicyEngine is a stub for OPA (Open Policy Agent) integration.
// Production: POST to the OPA REST API at <endpoint>/v1/data/<policy-path>.
type opaPolicyEngine struct {
endpoint string
allowStub bool
mu sync.RWMutex
policies map[string]string
}

func newOPAPolicyEngine(endpoint string, allowStub bool) *opaPolicyEngine {
if endpoint == "" {
endpoint = "http://localhost:8181"
}
return &opaPolicyEngine{endpoint: endpoint, allowStub: allowStub, policies: make(map[string]string)}
}

func (e *opaPolicyEngine) LoadPolicy(name, content string) error {
e.mu.Lock()
defer e.mu.Unlock()
// Production: PUT to <endpoint>/v1/policies/<name> with content as Rego source.
e.policies[name] = content
return nil
}

func (e *opaPolicyEngine) ListPolicies() []PolicyInfo {
e.mu.RLock()
defer e.mu.RUnlock()
out := make([]PolicyInfo, 0, len(e.policies))
for n, c := range e.policies {
out = append(out, PolicyInfo{Name: n, Backend: "opa", Content: c})
}
return out
}

func (e *opaPolicyEngine) Evaluate(_ context.Context, input map[string]any) (*PolicyDecision, error) {
// Production: POST {"input": input} to <endpoint>/v1/data/<default-policy>
// and parse the result body for {"result": {"allow": true}}.
if e.allowStub {
return &PolicyDecision{
Allowed: true,
Reasons: []string{"opa stub: allow_stub_backends enabled"},
Metadata: map[string]any{"backend": "opa", "endpoint": e.endpoint, "input": input},
}, nil
}
return &PolicyDecision{
Allowed: false,
Reasons: []string{"STUB IMPLEMENTATION - not connected to real backend - denied for safety"},
Metadata: map[string]any{"backend": "opa", "endpoint": e.endpoint, "input": input},
}, nil
}

// ─── Cedar backend stub ──────────────────────────────────────────────────────

// cedarPolicyEngine is a stub for Cedar policy language integration.
// Production: use the cedar-go library (github.com/cedar-policy/cedar-go).
type cedarPolicyEngine struct {
allowStub bool
mu sync.RWMutex
policies map[string]string
}

func newCedarPolicyEngine(allowStub bool) *cedarPolicyEngine {
return &cedarPolicyEngine{allowStub: allowStub, policies: make(map[string]string)}
}

func (e *cedarPolicyEngine) LoadPolicy(name, content string) error {
e.mu.Lock()
defer e.mu.Unlock()
// Production: parse and compile Cedar policy set via cedar-go.
e.policies[name] = content
return nil
}

func (e *cedarPolicyEngine) ListPolicies() []PolicyInfo {
e.mu.RLock()
defer e.mu.RUnlock()
out := make([]PolicyInfo, 0, len(e.policies))
for n, c := range e.policies {
out = append(out, PolicyInfo{Name: n, Backend: "cedar", Content: c})
}
return out
}

func (e *cedarPolicyEngine) Evaluate(_ context.Context, input map[string]any) (*PolicyDecision, error) {
// Production: build a cedar.Request from input (principal, action, resource, context)
// and call policySet.IsAuthorized(request).
if e.allowStub {
return &PolicyDecision{
Allowed: true,
Reasons: []string{"cedar stub: allow_stub_backends enabled"},
Metadata: map[string]any{"backend": "cedar", "input": input},
}, nil
}
return &PolicyDecision{
Allowed: false,
Reasons: []string{"STUB IMPLEMENTATION - not connected to real backend - denied for safety"},
Metadata: map[string]any{"backend": "cedar", "input": input},
}, nil
}
18 changes: 1 addition & 17 deletions plugin/rbac/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,23 +224,7 @@ func TestBuiltinProvider_WithPermissionManager(t *testing.T) {
}
}

func TestStubProviders_ReturnErrors(t *testing.T) {
ctx := context.Background()

permit := NewPermitProvider("key", "https://pdp.permit.io")
if permit.Name() != "permit" {
t.Errorf("expected name 'permit', got %q", permit.Name())
}
if _, err := permit.CheckPermission(ctx, "s", "r", "a"); err == nil {
t.Error("expected error from permit stub")
}
if _, err := permit.ListPermissions(ctx, "s"); err == nil {
t.Error("expected error from permit stub")
}
if err := permit.SyncRoles(ctx, nil); err == nil {
t.Error("expected error from permit stub")
}

func TestAWSIAMProvider_NameCheck(t *testing.T) {
aws := NewAWSIAMProviderWithClient("us-east-1", "arn:aws:iam::role/test", &mockIAMClient{})
if aws.Name() != "aws-iam" {
t.Errorf("expected name 'aws-iam', got %q", aws.Name())
Expand Down
39 changes: 0 additions & 39 deletions plugin/rbac/permit.go

This file was deleted.

54 changes: 10 additions & 44 deletions plugins/policy/plugin.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Package policy provides an EnginePlugin that registers policy engine module
// types (policy.opa, policy.cedar, policy.mock) and policy pipeline step types
// (step.policy_evaluate, step.policy_load, step.policy_list, step.policy_test).
// Package policy provides an EnginePlugin that registers the policy.mock module
// type and policy pipeline step types (step.policy_evaluate, step.policy_load,
// step.policy_list, step.policy_test). For OPA or Cedar backends, use the
// workflow-plugin-policy-opa or workflow-plugin-policy-cedar external plugins.
package policy

import (
Expand All @@ -23,17 +24,15 @@ func New() *Plugin {
BaseNativePlugin: plugin.BaseNativePlugin{
PluginName: "policy",
PluginVersion: "1.0.0",
PluginDescription: "External policy engine adapter supporting OPA (Open Policy Agent) and Cedar",
PluginDescription: "Policy engine plugin providing mock backend; OPA and Cedar via external plugins",
},
Manifest: plugin.PluginManifest{
Name: "policy",
Version: "1.0.0",
Author: "GoCodeAlone",
Description: "Policy engine adapter for OPA (Open Policy Agent) and Cedar authorization policies",
Description: "Policy engine plugin with mock backend for testing and development",
Tier: plugin.TierCore,
ModuleTypes: []string{
"policy.opa",
"policy.cedar",
"policy.mock",
},
StepTypes: []string{
Expand All @@ -55,28 +54,16 @@ func (p *Plugin) Capabilities() []capability.Contract {
return []capability.Contract{
{
Name: "policy-enforcement",
Description: "Policy evaluation using OPA (Rego), Cedar, or mock backends for authorization decisions",
Description: "Policy evaluation using mock backend for testing and development",
},
}
}

// ModuleFactories returns factories for policy.opa, policy.cedar, and policy.mock.
// ModuleFactories returns factories for policy.mock.
// For OPA or Cedar backends, use the workflow-plugin-policy-opa or
// workflow-plugin-policy-cedar external plugins.
func (p *Plugin) ModuleFactories() map[string]plugin.ModuleFactory {
return map[string]plugin.ModuleFactory{
"policy.opa": func(name string, cfg map[string]any) modular.Module {
if cfg == nil {
cfg = map[string]any{}
}
cfg["backend"] = "opa"
return module.NewPolicyEngineModule(name, cfg)
},
"policy.cedar": func(name string, cfg map[string]any) modular.Module {
if cfg == nil {
cfg = map[string]any{}
}
cfg["backend"] = "cedar"
return module.NewPolicyEngineModule(name, cfg)
},
"policy.mock": func(name string, cfg map[string]any) modular.Module {
if cfg == nil {
cfg = map[string]any{}
Expand Down Expand Up @@ -112,27 +99,6 @@ func (p *Plugin) ModuleSchemas() []*schema.ModuleSchema {
}

return []*schema.ModuleSchema{
{
Type: "policy.opa",
Label: "OPA Policy Engine",
Category: "security",
Description: "Open Policy Agent (OPA) policy engine. Evaluates Rego policies via OPA REST API.",
Inputs: []schema.ServiceIODef{{Name: "input", Type: "PolicyInput", Description: "JSON input document for policy evaluation"}},
Outputs: []schema.ServiceIODef{{Name: "decision", Type: "PolicyDecision", Description: "Policy decision: allowed/denied with reasons"}},
ConfigFields: append([]schema.ConfigFieldDef{
{Key: "endpoint", Label: "OPA Endpoint", Type: schema.FieldTypeString, DefaultValue: "http://localhost:8181", Description: "OPA REST API endpoint URL", Placeholder: "http://localhost:8181"},
}, sharedPolicyFields...),
DefaultConfig: map[string]any{"endpoint": "http://localhost:8181"},
},
{
Type: "policy.cedar",
Label: "Cedar Policy Engine",
Category: "security",
Description: "Cedar policy language engine for authorization. Uses the cedar-go library.",
Inputs: []schema.ServiceIODef{{Name: "input", Type: "PolicyInput", Description: "Cedar request: principal, action, resource, context"}},
Outputs: []schema.ServiceIODef{{Name: "decision", Type: "PolicyDecision", Description: "Policy decision: allowed/denied with reasons"}},
ConfigFields: sharedPolicyFields,
},
{
Type: "policy.mock",
Label: "Mock Policy Engine",
Expand Down
Loading