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:
- Declare the rules in a sovereignty policy and check manifests against it before they reach the cluster.
- Install with the sovereign profile.
- Run sensitive sessions on tier-2 (confidential VM) with attestation, so the infrastructure operator cannot read agent memory.
- 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
maqpnaCLI. 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-sovereignbinary 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.yamlexits 0 for every agent you deploy.kubectl get sovereigntypolicy default -o yamlshows no violations and, if retention is short, theRetentionBelowAIActcondition.- A tier-2 session reaches
Running, andmaqpna_attest_success_totalincreases. maqpna audit verify FILE --jwks jwks.jsonverifies 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#
- Air-gapped install and verifying releases.
- DLP profiles: the
eu-strictprofile. - Multi-tenant setup for service providers: per-tenant keys and trust domains.
- Command reference:
maqpna sovereignty check,maqpna validate,maqpna keys rotate identity.