Export policies to Cedar, CEL, Rego and AgentCore
Compile MAQPNA policies to Cedar, CEL, Rego (OPA) or AWS Bedrock AgentCore Policy with maqpna policy compile, so other platforms enforce the same decisions.
You write a policy once, as a ToolPolicy, and other systems enforce it. maqpna policy compile translates MAQPNA policies into four targets:
| Target | --to |
What you get | Who runs it |
|---|---|---|---|
| Cedar | cedar |
Cedar policies against MAQPNA's schema, config/cedar/maqpna.cedarschema |
Any Cedar engine, or the gateway's own Cedar adapter |
| CEL | cel |
One expression over a request map that returns allow, require_approval or deny |
Hosts that embed CEL: Kubernetes admission policies, Envoy, agentgateway |
| Rego | rego |
One Rego v1 module whose decision is the object MAQPNA's own Rego backend reads |
OPA on its own, or as the gateway's OPA sidecar |
| AgentCore | agentcore |
Cedar against AWS Bedrock AgentCore's schema, one statement per AgentCore policy | An AgentCore Gateway in front of the same tools |
The rule behind every target: exact, or refused. A compiled policy decides exactly as the MAQPNA gateway does, for every request. If the target cannot express a construct, the command fails and names the policy, rule and field. It never writes a weaker translation.
flowchart LR
P["ToolPolicy YAML<br/>or policies.json"] --> C["maqpna policy compile"]
C -->|"--to cedar"| CE["maqpna.cedar"]
C -->|"--to cel"| CL["CEL expression"]
C -->|"--to rego"| RG["policy.rego<br/>+ OPA bundle"]
C -->|"--to agentcore"| AC["AgentCore policies<br/>+ schema"]
C -->|"unsupported construct"| X["error, exit 1<br/>names policy and rule"]
RG --> OPA["OPA / gateway sidecar"]
AC --> PUSH["maqpna policy push"]
When to use it#
- You run tools behind another enforcement point (an API gateway, an admission controller, an AgentCore Gateway) and want it to apply the same rules as MAQPNA.
- You want OPA as a second, independent check next to the gateway.
- You need the policy as a reviewable artefact in another language.
If you need the whole policy language outside the gateway (schedules, when conditions, sinks, rate limits), use the policy engine as a Wasm module instead. A translation covers only part of the language.
Prerequisites#
- The
maqpnaCLI (install). - Policy files:
ToolPolicyresources (YAML or JSON, several per file, or aList) or an operator-renderedpolicies.jsonbundle. A file or a directory both work. - For
--to agentcore: a gateway manifest that lists the AgentCore Gateway ARN and its tools (see AgentCore). - For Rego: OPA if you want to run the result.
Step 1: Compile#
Pick a target. The examples use the sample policy in the repository, config/samples/toolpolicy-coder.yaml.
maqpna policy compile --to cedar config/samples/toolpolicy-coder.yaml --out maqpna.cedar
maqpna policy compile --to cel config/samples/toolpolicy-coder.yaml -o json
maqpna policy compile --to rego config/samples/toolpolicy-coder.yaml --bundle-out opa-bundle/
maqpna policy compile --to agentcore \
--manifest examples/integrations/agentcore/gateway-manifest.json \
config/samples/toolpolicy-coder.yaml -o json --schema-out agentcore.cedarschema
The Cedar run prints where it wrote the file, then warnings for what the target does not enforce:
compiled 1 policies to cedar: maqpna.cedar
warning: policy "coder-policy" rule "github-read": maxCallsPerMinute is not enforced (rate limits are state in the gateway)
warning: policy "coder-policy" rule "k8s-read": maxCallsPerMinute is not enforced (rate limits are state in the gateway)
warning: argMatch: a non-string value at an argMatch key, or a non-record on a dotted key's path, is a Cedar evaluation error (MAQPNA's evaluator denies; other Cedar engines skip the policy)
Each Cedar statement carries an @id of <policy>/<rule>, and @reason and @action("require_approval") where they apply. With -o json, CEL output is an object with target, policies, text and warnings.
Flags#
| Flag | Target | Meaning |
|---|---|---|
--to |
all | cedar, cel, rego or agentcore |
--out FILE |
all | Write the result to a file (default: stdout) |
--namespace NS |
all | Namespace for ToolPolicy resources without metadata.namespace (default default) |
--package |
rego | The module's package (default maqpna.authz; query data.<package>.decision) |
--bundle-out DIR |
rego | Also write an OPA bundle: .manifest and <package path>/policy.rego |
--manifest FILE |
agentcore | The gateway manifest (ARN, targets and their MCP tools) |
--schema-out FILE |
agentcore | Also write the Cedar schema AgentCore derives from the manifest |
-o json |
all | Machine-readable output |
Full reference: maqpna policy compile.
Step 2: Read the refusals#
A construct the target cannot express is an error, and all of them are reported at once. The command exits with status 1. For example, the sample toolpolicy-v2-guardrails.yaml uses state that only the gateway has:
maqpna policy compile --to cedar config/samples/toolpolicy-v2-guardrails.yaml
✗ policyexport: not expressible exactly for cedar: policy "coder-v2-guardrails": toolLabels (tool taint and sink labels need the gateway's label state)
policyexport: not expressible exactly for cedar: policy "coder-v2-guardrails": toolPins (tool definition pinning is enforced by the gateway)
policyexport: not expressible exactly for cedar: policy "coder-v2-guardrails" rule "no-exfil-after-untrusted": sinks (tool labels)
policyexport: not expressible exactly for cedar: policy "coder-v2-guardrails" rule "big-transfer-four-eyes": when conditions
policyexport: not expressible exactly for cedar: policy "coder-v2-guardrails" rule "deploy-freeze": schedule
Keep those rules in the gateway, or move the policy to the Wasm module, which runs all of them.
What each target supports#
| Construct | Cedar | CEL | Rego |
|---|---|---|---|
namespace, agents, servers, tools, principals.users / notUsers |
* globs (no ?) |
globs | globs, and character classes that cannot match / |
principals.groups / notGroups, sessionTaints.any / all |
exact labels, or a lone * |
globs | globs |
substrate: isolation, attestation, checks and their not forms |
yes | yes | yes |
substrate jurisdiction (providers, regions, countries and their not forms) |
refused | refused | refused |
argMatch |
strings | strings, bools, numbers, lists | strings, bools, numbers, nested lists |
defaultAction, dryRun + dryRunFallback |
yes | yes | yes |
require_approval with minApprovers / approverGroups |
yes (annotations) | the action only (warning) | yes (the decision's approval) |
maxCallsPerMinute |
not enforced (warning) | not enforced (warning) | not enforced (warning) |
when, schedule, sinks, scopes, toolLabels, toolPins, embedded Cedar |
refused | refused | refused |
The semantics carried over to every target are the gateway's: a policy applies by namespace and agent globs; rules are ordered and the first match decides; with no match, defaultAction decides (deny when unset); across policies the strictest decision wins; no applicable policy means deny. Cedar has no rule order, so rule n compiles to "this rule matches and no earlier rule does". Cedar output therefore grows with the square of the number of rules.
argMatch caveats#
- Cedar is typed. A non-string value at an
argMatchkey is a Cedar evaluation error. MAQPNA's Cedar adapter turns that into deny; other Cedar engines skip the policy, which can skip aforbid. The compiler warns wheneverargMatchis used. - CEL and Rego compare a bool as
"true"/"false"and an integral number (up to 2^53) as its decimal form, as the gateway does. A glob never matches a non-integral number (a warning).
Step 3: Run the result#
Rego with OPA on its own#
opa run --server --bundle opa-bundle/
curl -s localhost:8181/v1/data/maqpna/authz/decision -d '{"input": {"request": {"namespace": "team-a",
"agent": "coder", "server": "github", "tool": "get_issue"}}}'
The input is the document the gateway sends its OPA sidecar: request with namespace, agent, server, tool, user, groups, sessionTaints, args and substrate. The decision is always defined and has the same fields as the gateway's decision: action, policy, rule, reason and, for approvals, approval. When no policy applies it is {"action": "deny", "reason": "no_applicable_policy"}.
The bundle's .manifest has roots: ["maqpna/authz"], rego_version: 1 and the module's SHA-256 as revision. The module also has import rego.v1, so OPA 0.59 and later load it in v0 mode too.
Rego as the gateway's OPA sidecar#
The gateway can ask OPA on every call and apply the stricter of its own decision and OPA's. Load the compiled module into the chart's OPA sidecar:
kubectl -n maqpna-system create configmap maqpna-opa-policies \
--from-file=policy.rego=opa-bundle/maqpna/authz/policy.rego
# values-opa.yaml
gateway:
policyAdapters:
rego:
enabled: true
timeoutMs: 250
enforcement: enforce # or dryRun: audited, never enforced
sidecar:
enabled: true
policiesConfigMap: maqpna-opa-policies
maqpna upgrade -f values-opa.yaml
The sidecar setup, its failure modes and metrics are on Policy engines and Wasm.
AgentCore#
AgentCore Policy evaluates Cedar against a schema it builds from the gateway's tool manifest. A tool is an action there, so the compiler needs the tool list and decides every servers and tools condition per tool at compile time. A shortened manifest, from examples/integrations/agentcore/gateway-manifest.json:
{
"gatewayArn": "arn:aws:bedrock-agentcore:eu-west-1:123456789012:gateway/maqpna-coder",
"approvalAsDeny": true,
"principal": {"namespaceTag": "maqpna_ns", "agentTag": "maqpna_agent", "groupsTag": "maqpna_groups"},
"targets": [
{"server": "kubernetes", "target": "Kubernetes", "tools": [
{"name": "scale", "inputSchema": {"type": "object",
"properties": {"name": {"type": "string"}, "namespace": {"type": "string"}, "replicas": {"type": "integer"}},
"required": ["name", "replicas"]}}
]}
]
}
| Manifest field | Meaning |
|---|---|
gatewayArn |
The AgentCore Gateway, used as the Cedar resource |
targets[].server / target |
The MAQPNA server name and its AgentCore target name |
targets[].tools[] |
Each MCP tool with its JSON input schema |
principal.namespaceTag, agentTag, groupsTag |
JWT claims for namespace, agent and groups (defaults maqpna_ns, maqpna_agent, maqpna_groups) |
principal.userTag |
The claim that holds the user, needed for principals.users rules |
approvalAsDeny |
Compile require_approval rules as forbid |
| AgentCore | Compiled from |
|---|---|
principal AgentCore::OAuthUser, JWT claims as tags |
the principal tags above (groups space-separated) |
action AgentCore::Action::"<target>___<tool>" |
servers and tools |
resource AgentCore::Gateway::"<gateway ARN>" |
the manifest's gatewayArn |
context.input |
argMatch, typed by the tool's input schema |
AgentCore has no approvals, taints or substrate. require_approval is refused unless the manifest sets approvalAsDeny: true; then it compiles to forbid with a warning. sessionTaints and substrate are refused.
To load the compiled policies into an AgentCore policy engine and keep them reconciled, use maqpna policy push --to agentcore. It plans creates, updates and deletes for the policies MAQPNA owns (name prefix maqpna_ and an owner marker in the description) and never changes others. See Hyperscaler interop.
Verify#
-
Test the source policy with the native engine, so you know the expected decisions:
maqpna policy test --policy config/samples/toolpolicy-coder.yaml --ns team-a --agent coder --server github --tool create_pull_request -
Ask the target the same question. For Rego, the
curlabove must return the sameaction,policyandrule. - For AgentCore, validate the statements against the schema written by
--schema-outwith a Cedar validator before pushing.
Limits#
- Rate limits (
maxCallsPerMinute) are gateway state and are never enforced by a compiled policy. - CEL carries the approval action, not
minApproversorapproverGroups. - A compiled policy decides; it does not audit. Approvals, the audit ledger, budgets and taint stay in the gateway.
- A policy with a
when,schedule,sinks,scopes,toolLabelsortoolPinscannot be exported at all.
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
not expressible exactly for <target> and exit 1 |
The policy uses a construct the target cannot express | Remove or move that rule, or use the Wasm module |
maxCallsPerMinute is not enforced warning |
Rate limits live in the gateway | Expected; keep the gateway in the path for rate limits |
| Another Cedar engine allows a call MAQPNA denies | A non-string argMatch value made Cedar skip a forbid |
Constrain the tool's argument types, or evaluate with MAQPNA's Cedar adapter |
| OPA returns an empty result | Wrong query path | Query data.<package>.decision; with the default package that is /v1/data/maqpna/authz/decision |
--to agentcore refuses require_approval |
AgentCore cannot hold a call for a human | Set "approvalAsDeny": true in the manifest, or keep the rule in MAQPNA |