MAQPNADocs

Deployment topologies#

MAQPNA is one set of binaries with several deployment shapes. The shape is chosen by how you start it (maqpna dev up or the Helm chart) and by the chart profile: values.yaml (default), values-dev.yaml, values-production.yaml or values-sovereign-eu.yaml (maqpna install --profile dev|prod|sovereign-eu).

Topology Started with Kubernetes State backend Replicas Sandboxes Typical use
Local dev maqpna dev up None Files in ./.maqpna 1 process each None: your agent runs as a local process Building and testing an agent
Single cluster maqpna install (default values) 1.29+ with agent-sandbox file 1 each tier-0, tier-1 (and tier-2 if enabled) Pilots, non-production
HA --profile prod Same postgres gateway 3, identity 2, attest 2, operator 2 Same Production
Multi-tenant / service provider Any cluster profile plus Tenant objects Same postgres recommended As HA Same, per tenant namespace Hosting agents for customers
Air-gapped Air-gap bundle + maqpna airgap push + install Same, no internet Either Either Same Disconnected and regulated sites
Confidential (attested) --profile sovereign-eu Same, plus confidential-VM nodes postgres As HA tier-2 confidential VMs Sovereign and high-assurance workloads

The profiles combine: values-sovereign-eu.yaml is HA, air-gapped and confidential at once.

Local dev#

maqpna dev up starts a local MAQPNA without Kubernetes, Docker or kind: plain processes bound to 127.0.0.1.

flowchart LR
    subgraph Laptop["Your laptop (127.0.0.1)"]
      A["Your agent<br/>maqpna dev run -- CMD"]
      GW["maqpna-gateway :8080"]
      IB["maqpna-identity :8081"]
      ECHO["mcp-echo :8090"]
      STUB["stub-llm :8000<br/>(--stub-llm)"]
      L[(".maqpna/audit.jsonl")]
    end
    A -- "MCP and model calls<br/>Bearer session token" --> GW
    GW -- JWKS --> IB
    GW --> ECHO
    GW --> STUB
    GW --> L
    CLI["maqpna dev run"] -- "POST /v1/token" --> IB
  1. maqpna dev up writes policies.json and gateway.json into the state directory (./.maqpna), with a file ledger and a 2-second policy reload.
  2. It starts the test MCP server (mcp-echo, built into the CLI), the stub model server with --stub-llm, the identity broker with a generated key, and the gateway, waiting for each to be healthy.
  3. maqpna dev run -- CMD mints a session token for namespace dev, agent coder, user you@localhost, and runs your command with MAQPNA_GATEWAY_URL, MAQPNA_TOKEN and MAQPNA_TOOL_<NAME>_URL set.

There is no sandbox, trust tier, operator or NetworkPolicy here; the gateway pipeline, policy, DLP, approvals and ledger are the real ones. See developer loop.

Single cluster#

The default chart installs one replica of each control-plane component into maqpna-system, with file-backed state.

flowchart TB
    subgraph CP["Namespace maqpna-system"]
      OP["maqpna-operator (1)"]
      GW["maqpna-gateway (1)<br/>file ledger + journals"]
      IB["maqpna-identity (1)<br/>:8081, bootstrap :8083"]
      EX["maqpna-mcp-echo (example)"]
    end
    subgraph AG["Namespace maqpna-agents<br/>default-deny NetworkPolicy"]
      S1["Sandbox tier-0<br/>gVisor"]
      S2["Sandbox tier-1<br/>Kata + Firecracker"]
    end
    K[("Kubernetes API<br/>CRDs maqpna.com")]
    OP <--> K
    OP -- "mint token" --> IB
    S1 & S2 -- "only egress" --> GW
    GW --> EX
  • Workloads: maqpna-operator, maqpna-gateway, maqpna-identity (Deployments), maqpna-mcp-echo (example, on by default), maqpna-attest only with attestation.enabled.
  • RuntimeClasses gvisor, kata-fc and kata-qemu-snp and trust tiers tier-0, tier-1, tier-2 are created by the chart.
  • Prerequisite not installed by the chart: kubernetes-sigs/agent-sandbox v1.0.4.
  • With state.backend: file, gateway.replicas > 1 refuses to render unless guards.allowIndependentGatewayReplicas is set, because each replica would have its own approvals and ledger.
  • The admin API uses the static admin token (adminAuth.mode: token) by default.

HA#

values-production.yaml runs every stateful component on shared PostgreSQL state.

flowchart TB
    LB["Service maqpna-gateway"] --> G1 & G2 & G3
    subgraph GWs["maqpna-gateway x3, PDB minAvailable 2"]
      G1["replica A<br/>leader: siem/siem"]
      G2["replica B<br/>leader: worm/audit"]
      G3["replica C<br/>standby"]
    end
    subgraph Others["Other components"]
      I1["maqpna-identity x2<br/>key from existingSecret"]
      A1["maqpna-attest x2"]
      O1["maqpna-operator x2<br/>Lease maqpna-operator.maqpna.com"]
    end
    PG[("PostgreSQL<br/>schema maqpna:<br/>state_events, audit_chain,<br/>state_blobs, counters")]
    G1 & G2 & G3 <--> PG
    A1 <--> PG
Setting in values-production.yaml Value
state.backend postgres (DSN from Secret maqpna-state)
gateway.replicas, gateway.pdb.minAvailable 3, 2
gateway.auditFailurePolicy closed: an intent record is written before every allowed call
gateway.auditStorage.persistent true (one PVC per replica, StatefulSet)
gateway.config.adminListen :9090: the admin API, metrics and pprof move off the data listener
identity.replicas, identity.key.mode 2, existingSecret
attestation.replicas, attestation.verifier 2, trustee, with signed responses
operator.replicas 2 (the chart adds --leader-elect when replicas > 1)
adminAuth.mode oidc, break-glass token disabled
audit.sink, audit.checkpointSigning.enabled worm, true

Gateway replicas share approvals, revocations, taint, pins, the notification outbox, FinOps state and one audit hash chain. Background jobs (SIEM and WORM shipping, tenant ledger feed, retention sweeps) run on one replica each, elected with PostgreSQL advisory locks. See state backend and HA and failure handling.

Multi-tenant / service provider#

A service provider (a neocloud, colocation provider, telco or ISV) runs one installation for several customers. Each customer is a Tenant (cluster-scoped) that owns namespaces.

flowchart LR
    subgraph Inst["One installation"]
      GW["Gateway<br/>key binding per tenant"]
      IB["Identity broker<br/>platform key + tenant keys"]
      subgraph TA["Tenant acme"]
        NA["ns acme-prod<br/>label maqpna.com/tenant=acme"]
      end
      subgraph TB["Tenant globex"]
        NB["ns globex-dev"]
      end
    end
    NA -- "tokens spiffe://acme.maqpna.local/..." --> GW
    NB -- "tokens spiffe://globex.maqpna.local/..." --> GW
    GW --> LA[("ledger tenant/acme<br/>WORM prefix tenants/acme/audit")]
    GW --> LB2[("ledger tenant/globex")]
    GW --> LP[("platform ledger")]

What each tenant gets:

Isolation Mechanism
Namespaces spec.namespaces, labelled maqpna.com/tenant=<name>; the oldest Tenant wins a contested namespace
Trust domain and signing key <tenant>.<--tenant-trust-domain-suffix> (default suffix maqpna.local); Ed25519 key in Secret maqpna-tenant-keys, generated or copied
Token binding The gateway refuses a tenant namespace token not signed with that tenant's kid and trust domain (tenant_mismatch), and a tenant key for any other namespace
Ledger An independent hash chain per tenant (tenant/<name>), verifiable on its own
WORM Its own key prefix (auditWormPrefix, default tenants/<name>/audit)
Admin login admin.oidcIssuer / oidcAudience; mapped roles become namespace roles in the tenant's namespaces only
Quotas quotas render the BudgetPolicy tenant-<name>
MCP servers Shared across tenants only with the owner's allowSharingWith (cross_tenant_server otherwise)

For billing, the operator counts billable nodes (nodes running at least one pod labelled maqpna.com/session) every minute into ConfigMap maqpna-usage-nodes, and reports each session's sandbox time to the gateway, which records it in the ledger as usage. maqpna usage buckets shows hourly usage per tenant and maqpna usage report builds a signed usage report. See tenants and usage metering.

Air-gapped#

flowchart LR
    subgraph Online["Connected build host"]
      B["maqpna airgap bundle<br/>images, chart, SBOMs,<br/>SHA256SUMS + cosign signature"]
    end
    B -- "removable media" --> V
    subgraph Site["Disconnected site"]
      V["maqpna airgap verify DIR<br/>--require-signature"] --> P["maqpna airgap push DIR<br/>--registry registry.eu.internal:5000"]
      P --> R[("Private registry")]
      R --> I["maqpna install<br/>airgap.enabled, airgap.imageRegistry"]
      LIC["Licence file<br/>verified offline"] --> I
    end
  1. On a connected host, maqpna airgap bundle builds the offline bundle: the 11 images, chart, upstream manifests, SBOMs, SHA256SUMS and a cosign signature (key-based or keyless).
  2. maqpna airgap verify DIR checks every checksum, the signature (--trusted-root for keyless verification offline) and that all 11 images are present; it exits 3 on any failure.
  3. maqpna airgap push DIR --registry REG copies the images into your registry (with skopeo, crane or docker).
  4. Install with airgap.enabled: true and airgap.imageRegistry pointing at the mirror.

Nothing at runtime needs the internet: no telemetry, no update checks, and the licence is a signed file verified against a key embedded in the binary.

Confidential (attested)#

tier-2 sandboxes run as confidential VMs (Kata Containers with AMD SEV-SNP or Intel TDX). Their session token is released only after remote attestation.

flowchart LR
    subgraph Node["Confidential-VM node<br/>maqpna.com/confidential=true"]
      subgraph CVM["tier-2 sandbox (Kata, kata-qemu-snp)"]
        INIT["maqpna-attest init container"]
        REN["maqpna-attest-renewer sidecar"]
        AG["agent container<br/>token on tmpfs"]
      end
    end
    OP["Operator"] -- "POST /v1/releases" --> AT["maqpna-attest x2"]
    INIT -- "TEE report via configfs-tsm" --> AT
    AT -- "verify evidence" --> TR["Trustee attestation service<br/>(customer-run)"]
    AT -- "appraise vs reference values" --> RV[("reference values<br/>measurements, HOST_DATA,<br/>MRCONFIGID, RTMR")]
    AT -- "mint" --> IB["Identity broker"]
    AT -- "token sealed to the VM key" --> INIT
    AG -- "only egress" --> GW["Gateway"]

values-sovereign-eu.yaml configures this shape: attestation.enabled, two replicas on Postgres, verifier: trustee with a customer Trustee URL and JWKS, reference values that are empty by default (empty rejects every VM), requireAttestationFor: [confidential], tier-2 with attestationRequired: true and EU node affinity, audit.sink: worm with 3650-day retention, an EU DLP profile, OIDC admin auth and principalBinding.mode: requester.

See attestation-gated secrets and attestation service.