MAQPNADocs

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 maqpna CLI (install).
  • Policy files: ToolPolicy resources (YAML or JSON, several per file, or a List) or an operator-rendered policies.json bundle. 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 argMatch key is a Cedar evaluation error. MAQPNA's Cedar adapter turns that into deny; other Cedar engines skip the policy, which can skip a forbid. The compiler warns whenever argMatch is 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#

  1. 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
    
  2. Ask the target the same question. For Rego, the curl above must return the same action, policy and rule.

  3. For AgentCore, validate the statements against the schema written by --schema-out with 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 minApprovers or approverGroups.
  • 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, toolLabels or toolPins cannot 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

Next steps#