Deploys openpolicyagent/opa:0.68.0 as a single-replica ClusterIP
service in the scobleclaw namespace, with a ConfigMap-mounted
policy bundle under /policies.
Policies (package scobleclaw):
_lib.rego shared deny rules (external email, forgejo
write outside marcus/engineering, payment
kamelets) + helpers (is_external_email,
is_payment_kamelet, is_forgejo_write_outside_allowed)
main-agent.rego 10-kamelet allowlist for main-agent
demo-concierge.rego read-only 4-kamelet allowlist
demo-watchdog.rego 4-kamelet allowlist (ob1 + discord)
L0 contract:
POST /v1/data/scobleclaw/allow -> {allow: bool}
POST /v1/data/scobleclaw/needs_l1 -> {needs_l1: bool}
POST /v1/data/scobleclaw/deny -> [reason, ...]
Verified end-to-end against the running OPA pod:
allow=true for main-agent + kamelet:source/forgejo-search
deny=[reason] for external email, forgejo write to stranger/not-mine,
payment kamelet (stripe-charge), demo-concierge forgejo-create
needs_l1=true for unknown kamelet (e.g. some-new-kamelet) on main-agent
needs_l1=false for payment kamelet (L1 cannot override L0 deny)
Out of scope (separate tickets): judge-integration Camel-K binding,
llm-classifier-integration (L1), discord-escalation-integration (L2),
audit-sink OPA log tailing.
137 lines
3.8 KiB
YAML
137 lines
3.8 KiB
YAML
---
|
|
# ConfigMap containing the Rego policy bundle. Mounted at /policies in the
|
|
# OPA pod. Policies share the `scobleclaw` package; per-agent files add
|
|
# their own allow + agent-specific deny rules on top of _lib's base denies.
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: opa-policies
|
|
namespace: scobleclaw
|
|
labels:
|
|
app: opa
|
|
component: l0-judge
|
|
data:
|
|
_lib.rego: |
|
|
package scobleclaw
|
|
|
|
internal_email_domain = "scoble.me"
|
|
|
|
payment_kamelets = {
|
|
"kamelet:source/stripe-charge",
|
|
"kamelet:source/paypal-transfer",
|
|
"kamelet:source/ach-debit",
|
|
"kamelet:sink/wire-transfer",
|
|
}
|
|
|
|
is_external_email(domain) {
|
|
domain != internal_email_domain
|
|
domain != ""
|
|
}
|
|
|
|
is_payment_kamelet(ref) {
|
|
payment_kamelets[ref]
|
|
}
|
|
|
|
is_forgejo_write_outside_allowed(params) {
|
|
params.owner != "marcus"
|
|
}
|
|
|
|
is_forgejo_write_outside_allowed(params) {
|
|
params.owner == "marcus"
|
|
params.repo != "engineering"
|
|
}
|
|
|
|
deny[reason] {
|
|
input.kamelet_ref != ""
|
|
input.params.to != ""
|
|
is_external_email(input.params.domain)
|
|
reason := sprintf("external email not permitted (domain=%v, to=%v)", [input.params.domain, input.params.to])
|
|
}
|
|
|
|
deny[reason] {
|
|
is_forgejo_write_outside_allowed(input.params)
|
|
reason := sprintf(
|
|
"forgejo writes restricted to marcus/engineering (got owner=%v repo=%v)",
|
|
[input.params.owner, input.params.repo],
|
|
)
|
|
}
|
|
|
|
deny[reason] {
|
|
is_payment_kamelet(input.kamelet_ref)
|
|
reason := sprintf("payment kamelet blocked at L0: %v", [input.kamelet_ref])
|
|
}
|
|
main-agent.rego: |
|
|
package scobleclaw
|
|
|
|
main_agent_allowed_kamelets = {
|
|
"kamelet:source/forgejo-search",
|
|
"kamelet:source/forgejo-read",
|
|
"kamelet:source/forgejo-create-issue",
|
|
"kamelet:source/forgejo-create-pr",
|
|
"kamelet:source/forgejo-comment",
|
|
"kamelet:source/ob1-capture",
|
|
"kamelet:source/ob1-search",
|
|
"kamelet:source/kamelet-dispatch",
|
|
"kamelet:source/kubectl-exec",
|
|
"kamelet:source/age-decrypt",
|
|
}
|
|
|
|
allow {
|
|
input.agent_id == "main-agent"
|
|
main_agent_allowed_kamelets[input.kamelet_ref]
|
|
count(deny) == 0
|
|
}
|
|
|
|
needs_l1 {
|
|
input.agent_id == "main-agent"
|
|
not main_agent_allowed_kamelets[input.kamelet_ref]
|
|
not payment_kamelets[input.kamelet_ref]
|
|
}
|
|
demo-concierge.rego: |
|
|
package scobleclaw
|
|
|
|
demo_concierge_allowed_kamelets = {
|
|
"kamelet:source/forgejo-search",
|
|
"kamelet:source/forgejo-read",
|
|
"kamelet:source/ob1-search",
|
|
"kamelet:source/kamelet-dispatch",
|
|
}
|
|
|
|
allow {
|
|
input.agent_id == "demo-concierge"
|
|
demo_concierge_allowed_kamelets[input.kamelet_ref]
|
|
count(deny) == 0
|
|
}
|
|
|
|
needs_l1 {
|
|
input.agent_id == "demo-concierge"
|
|
not demo_concierge_allowed_kamelets[input.kamelet_ref]
|
|
not payment_kamelets[input.kamelet_ref]
|
|
}
|
|
|
|
deny[reason] {
|
|
input.agent_id == "demo-concierge"
|
|
startswith(input.kamelet_ref, "kamelet:source/forgejo-create")
|
|
reason := sprintf("demo-concierge is read-only; %v not permitted", [input.kamelet_ref])
|
|
}
|
|
demo-watchdog.rego: |
|
|
package scobleclaw
|
|
|
|
demo_watchdog_allowed_kamelets = {
|
|
"kamelet:source/ob1-search",
|
|
"kamelet:source/ob1-list",
|
|
"kamelet:source/discord-post",
|
|
"kamelet:source/kamelet-dispatch",
|
|
}
|
|
|
|
allow {
|
|
input.agent_id == "demo-watchdog"
|
|
demo_watchdog_allowed_kamelets[input.kamelet_ref]
|
|
count(deny) == 0
|
|
}
|
|
|
|
needs_l1 {
|
|
input.agent_id == "demo-watchdog"
|
|
not demo_watchdog_allowed_kamelets[input.kamelet_ref]
|
|
not payment_kamelets[input.kamelet_ref]
|
|
}
|