MAQPNADocs

Sovereignty, confidential tiers and customer-held keys

Keep agents, data and keys in your jurisdiction with a sovereignty policy, check manifests offline, run sessions in attested confidential VMs, and hold every signing key yourself.

flowchart TB
  P[Sovereignty policy<br/>jurisdiction · registries · egress · attestation] --> C[maqpna sovereignty check<br/>offline, in CI]
  P --> O[Operator admission<br/>Agent + trust tier]
  P --> G[Gateway residency dialer<br/>MCP servers · models · SIEM · WORM]
  O --> T{Trust tier}
  T -->|tier-0 gVisor<br/>tier-1 microVM| S[Session token from Secret]
  T -->|tier-2 confidential VM| A[maqpna-attest<br/>attestation via Trustee]
  A -->|measurement approved| R[Token sealed to the TEE]
  K[Keys you hold<br/>file · PKCS#11 · KMS] --> I[Identity broker · audit checkpoints · usage reports]

Goal#

Run MAQPNA so that agents, their data and the keys that sign their identities and evidence stay under your control and in your jurisdiction:

  1. Declare the rules in a sovereignty policy and check manifests against it before they reach the cluster.
  2. Install with the sovereign profile.
  3. Run sensitive sessions on tier-2 (confidential VM) with attestation, so the infrastructure operator cannot read agent memory.
  4. Hold the identity, audit-checkpoint and tenant signing keys yourself.

A sovereignty policy (SovereigntyPolicy) is the cluster-wide set of rules on jurisdiction, registries, egress and attestation that every session is admitted against. A trust tier is the isolation level a session runs at: tier-0 (gVisor), tier-1 (Kata Containers with a Firecracker microVM) or tier-2 (confidential VM with attestation).

Prerequisites#

  • The maqpna CLI. For step 1 nothing else: the checks run offline.
  • For steps 2–5: a Kubernetes cluster you install MAQPNA on (see production install with Helm).
  • For tier-2: nodes with AMD SEV-SNP or Intel TDX, Kata Containers with Confidential Containers, and a Trustee attestation service that you run.
  • The standalone maqpna-sovereign binary for image, URL and bundle checks (it ships with each release).

Steps#

1. Write a sovereignty policy and check agents offline#

This is the sample policy from config/samples/sovereigntypolicy-eu.yaml. The operator enforces the policy named default:

apiVersion: maqpna.com/v1alpha1
kind: SovereigntyPolicy
metadata:
  name: default
spec:
  jurisdiction: EU
  allowedJurisdictions: ["EU"]        # admits EU, EU-DE, EU-FR, ...
  allowedRegistries:
  - registry.acme.eu/
  allowedEgressHosts:
  - "*.svc"
  - "*.svc.cluster.local"
  - "*.acme.eu"
  allowedEgressCIDRs:
  - 10.0.0.0/8
  requireAttestationFor: ["confidential"]
  keyCustody: kms
  telemetry: "off"
  auditRetentionDays: 365
  enforcement: enforce
Field Meaning
jurisdiction, allowedJurisdictions Hierarchical codes: EU admits EU-DE and EU-FR; EU-DE admits only EU-DE and its sub-codes such as EU-DE-BY. Each trust tier must declare a jurisdiction
allowedRegistries Image registry or repository prefixes. nginx is read as docker.io/library/nginx
allowedEgressHosts, allowedEgressCIDRs Where model endpoints, MCP servers, SIEM and WORM may be
requireAttestationFor Isolation levels (gvisor, microvm, confidential) whose tiers must set attestationRequired
keyCustody Where you hold signing keys (file, pkcs11, kms). It records your custody model; it does not configure the keys
auditRetentionDays Below 183 days (the EU AI Act six-month minimum) the gateway logs a warning and the policy reports RetentionBelowAIAct=True
enforcement enforce refuses violations; audit reports them without blocking, for a dry run

Check an agent manifest against the policy. The trust tier is looked up by spec.tier in the -f and --tier files:

maqpna sovereignty check -f config/samples/agent-coder.yaml \
  --policy config/samples/sovereigntypolicy-eu.yaml --tier config/samples/
COMPLIANT      Agent team-a/coder (config/samples/agent-coder.yaml)

The same agent with its image moved to ghcr.io (output from a local run):

NON-COMPLIANT  Agent team-a/coder-us (agent-us.yaml): 1 violation(s)
  - [registry-not-allowed] spec.image: image "ghcr.io/agents/coder:1.4.0" (registry ghcr.io) is not from an allowed registry [registry.acme.eu/]

It exits 3 when an agent would be refused, so add it to CI. maqpna validate -f DIR --sovereignty POLICY runs the same checks together with schema validation.

Check single images and URLs with maqpna-sovereign. It takes the policy as JSON (here examples/sovereign/policy-eu.json), the same format as the gateway's sovereigntyPolicyFile. That format has two switches the SovereigntyPolicy resource does not: requireDigest (images must be pinned by @sha256: digest) and requireTLSExternal (no plain http:// outside the cluster):

maqpna-sovereign check-url -policy examples/sovereign/policy-eu.json https://api.openai.com/v1 https://llm.acme.eu/v1
maqpna-sovereign check-image -policy examples/sovereign/policy-eu.json docker.io/library/nginx:1.27
DENY  https://api.openai.com/v1: [egress-not-allowed] host "api.openai.com" is not in allowedEgressHosts/allowedEgressCIDRs
OK    https://llm.acme.eu/v1
DENY  docker.io/library/nginx:1.27: [registry-not-allowed] image "docker.io/library/nginx:1.27" (registry docker.io) is not from an allowed registry [registry.acme.eu ghcr.io/maqpna]
DENY  docker.io/library/nginx:1.27: [digest-required] image "docker.io/library/nginx:1.27" must be pinned by digest (@sha256:...)

Both exit 1 when anything is denied.

2. Install with the sovereign profile#

The chart ships values-sovereign-eu.yaml. It turns on: an air-gapped registry, the sovereignty policy, PostgreSQL state, attestation with a Trustee verifier, WORM audit storage and a TLS syslog SIEM sink, Ed25519-signed checkpoints, the eu-strict DLP profile, an identity key from your own Secret, auditFailurePolicy: closed and three gateway replicas. Every value marked CHANGE-ME must be replaced. The key parts:

sovereignty:
  enabled: true
  jurisdiction: "EU"
  allowedJurisdictions: ["EU", "EU-DE", "EU-FR", "EU-NL"]
  allowedRegistries: ["registry.eu.internal:5000/"]          # CHANGE-ME
  allowedEgressHosts: ["*.svc", "*.svc.cluster.local", "*.tools.eu.internal"]  # CHANGE-ME
  allowedEgressCIDRs: ["10.20.0.0/16"]                       # CHANGE-ME
  requireAttestationFor: ["confidential"]
  auditRetentionDays: 3650
  enforcement: enforce
identity:
  key:
    mode: existingSecret
    existingSecret: maqpna-identity-key
audit:
  checkpointSigning:
    enabled: true
    secretName: maqpna-audit-signing

Check your values and the cluster first, then install:

maqpna values validate -f my-values.yaml --profile sovereign
maqpna preflight --profile sovereign-eu -f my-values.yaml
maqpna install --profile sovereign-eu -f my-values.yaml --wait

The sovereign profile also renders a ValidatingAdmissionPolicy (Kubernetes 1.30 or later). For air-gapped clusters, see air-gapped install; maqpna-sovereign verify-bundle --require-signature DIR checks a bundle's checksums and signature offline.

At runtime the gateway sends every upstream connection (MCP servers, model routes, SIEM and WORM) through a residency-checking dialer. It resolves the host once, refuses unless every address is allowed, and dials the checked address, which defends against DNS rebinding. Link-local and cloud metadata ranges are blocked unless a CIDR allows them.

3. Choose trust tiers#

The chart creates three trust tiers:

Tier RuntimeClass Isolation Use for
tier-0 gvisor gVisor user-space kernel Untrusted, generated code; the default
tier-1 kata-fc Kata Containers with a Firecracker microVM Hardware-virtualised isolation
tier-2 kata-qemu-snp Confidential VM (AMD SEV-SNP or Intel TDX), attestationRequired: true Sessions whose memory the infrastructure operator must not read

All three default to egress: gateway-only. A tier can pin nodes and a jurisdiction, as in config/samples/tier-2-confidential.yaml:

apiVersion: maqpna.com/v1alpha1
kind: TrustTier
metadata:
  name: tier-2-confidential
spec:
  runtimeClassName: kata-qemu-snp
  isolation: confidential
  attestationRequired: true
  jurisdiction: EU
  egress: gateway-only
  allowedNodeLabels:
    topology.kubernetes.io/region: eu-central-1
    maqpna.com/confidential-computing: sev-snp
  maxSessionTTL: 2h

maqpna tier list and maqpna tier get tier-2 show the tiers in a cluster. An agent selects one with spec.tier.

4. Turn on attestation for tier-2#

For a tier with attestationRequired, the session token is never written to a Kubernetes Secret. The operator registers a single-use release with maqpna-attest. An init container inside the confidential VM gets a hardware report (through Linux configfs-tsm) bound to a fresh nonce and an ephemeral key. maqpna-attest sends it to your Trustee attestation service and appraises the result against reference values you approved: TEE type, launch measurement, workload measurements, debug off and a minimum security version. Only then does it mint the token and seal it to the key inside the VM. Any failure returns 403 {reason}, consumes the release, and the session fails with AttestationFailed.

Configure it in your values:

attestation:
  enabled: true
  replicas: 2                       # >= 2 needs state.backend=postgres
  verifier: trustee
  trusteeURL: https://kbs.attest.eu.internal:8080
  trusteeJWKS:
    existingSecret: maqpna-trustee-jwks
    key: jwks.json
  referenceValues:
    allowedMeasurements: ["<launch measurement>"]   # empty rejects every report
    snpHostData: ["<host data>"]                     # or tdxMrConfigId / tdxRtmr for TDX
    allowDebug: false
    minSvn: 0
    teeTypes: ["snp", "tdx"]

Compute the reference measurements from your exact guest firmware, kernel, initrd and command line (for example with sev-snp-measure) and update them on every image or firmware change; MAQPNA does not compute them. A TDX or SNP report is rejected when no workload reference value is set, unless you set allowLaunchMeasurementOnly: true.

Sign attest responses and serve maqpna-attest over TLS, so a host on the network path cannot substitute a sealed token: set -response-signing-key on maqpna-attest and give its public key to the operator, and use -tls-cert and -tls-key.

To see the full flow without TEE hardware, run examples/sovereign/demo-attest.sh from a source checkout. It shows a compliant and a non-compliant residency check, one release, three rejections (wrong measurement, replayed release, stale nonce) and token renewal, all with the sample verifier.

Troubleshoot a failed attestation with maqpna session describe S -n NS (condition AttestationFailed with a reason) and the metric maqpna_attest_failure_total{reason}.

5. Hold your own keys#

MAQPNA never holds or escrows your keys. Each signing key is yours:

Key Where it is set Format
Identity signing key (session tokens) identity.key.mode: existingSecret with a Secret holding key.pem Ed25519 PKCS#8 PEM
Audit-checkpoint key (also signs usage reports) audit.checkpointSigning.secretName (key audit-signing.pem) or audit.checkpointSigning.keyURI Ed25519 PKCS#8 PEM, or a key URI
Tenant signing keys Tenant.spec.signingKeySecretRef Ed25519 PKCS#8 PEM

Key URIs take three forms:

file:///etc/maqpna/keys/identity.pem
pkcs11:token=maqpna;object=identity-signing?module-path=/usr/lib/softhsm/libsofthsm2.so&pin-source=file:/run/pin
kms://vault/transit/keys/maqpna-identity

A file key must be mode 0600 or 0400 (0440 is accepted for Secret volumes with fsGroup). PKCS#11 URIs follow RFC 7512; pin-value is refused, use pin-source.

Generate an Ed25519 key with maqpna keygen -out keys --name identity. Rotate keys with maqpna keys rotate identity, audit-checkpoint, vault or attest (see security hardening).

Verify#

  • maqpna sovereignty check -f agents/ --policy sovereignty.yaml exits 0 for every agent you deploy.
  • kubectl get sovereigntypolicy default -o yaml shows no violations and, if retention is short, the RetentionBelowAIAct condition.
  • A tier-2 session reaches Running, and maqpna_attest_success_total increases.
  • maqpna audit verify FILE --jwks jwks.json verifies checkpoints signed with your key (see audit ledger and evidence).

Troubleshooting#

Symptom Cause Fix
[registry-not-allowed], [digest-required], [egress-not-allowed] The manifest breaks the policy Move the image to an allowed registry, pin it by digest, or change the endpoint
A tier is refused for its jurisdiction The tier declares no jurisdiction, or one outside allowedJurisdictions Set spec.jurisdiction on the TrustTier
Session Failed with AttestationFailed Measurement not in the reference values (often after a firmware or image update), debug on, or a stale nonce Update referenceValues through your approval process; never switch to the sample verifier
The gateway refuses to start with a SIEM or WORM endpoint The endpoint is outside the allow-lists, or uses plain TCP with requireTLSExternal Use tls:// or https:// and list the host
backend not configured for a pkcs11: or kms:// key No HSM or KMS backend in this build Use a file key from a Secret synced from your HSM or KMS

Next steps#