Cloud profiles
Per-cloud Helm profiles for EKS, GKE, AKS, OpenShift and RKE2, cloud-native WORM and SIEM sinks, FQDN egress policies, and the CERT-In, DPDP and EU AI Act compliance profiles.
The chart ships one values profile per platform. Each profile maps the trust tiers to the RuntimeClass names the
platform really ships, holds placeholders for cloud workload identity, puts the audit ledger on the platform's
default storage class, and shows the cloud's own WORM and SIEM sinks. Layer a profile after
values-production.yaml:
helm upgrade --install maqpna deploy/helm/maqpna -n maqpna-system --create-namespace \
-f deploy/helm/maqpna/values-production.yaml -f deploy/helm/maqpna/values-gke.yaml
maqpna values validate -f deploy/helm/maqpna/values-production.yaml -f deploy/helm/maqpna/values-gke.yaml --profile production
| Profile | tier-0 | tier-1 | tier-2 | Storage class | FQDN egress |
|---|---|---|---|---|---|
values-eks.yaml |
gvisor (chart RuntimeClass, gVisor node group) |
kata-qemu (kata-deploy on C8i/M8i/R8i nested virtualization) |
not created | gp3 |
eks |
values-gke.yaml |
gvisor (GKE Sandbox) |
kata-qemu (kata-deploy, not supported by Google) |
not created | standard-rwo |
gke |
values-aks.yaml |
kata-vm-isolation (Pod Sandboxing) |
kata-vm-isolation |
not created | managed-csi |
cilium (ACNS) |
values-openshift.yaml |
kata (sandboxed containers) |
kata |
kata-remote, commented |
cluster default | none (OVN NetworkPolicy) |
values-rke2.yaml |
gvisor (chart RuntimeClass) |
kata-qemu (kata-deploy) |
opt-in | cluster default | cilium (with RKE2's Cilium) |
Sandbox runtimes per cloud#
- AKS. Pod Sandboxing runs each pod in a Kata VM on Cloud Hypervisor, on Azure Linux node pools created with
--workload-runtime KataVmIsolation. Pods opt in withruntimeClassName: kata-vm-isolation. Each pod VM gets memory equal to its limit and whole vCPUs, Arm64 and FIPS pools cannot run Kata, and Defender for Containers does not assess Kata pods. Confidential Containers (kata-cc-isolation) are still preview, and their Azure Linux 2.0 images are removed on 2026-10-31. Confidential VM node pools are GA since 2026-06-19, but protect the node, not the pod, so the profile creates no tier-2. - EKS. EC2 nested virtualization on C8i, M8i and R8i is GA since 2026-02-16, so Kata no longer needs
*.metalinstances. gVisor still needs a node group whose launch template or AMI installsrunsc. Fargate cannot host sandboxes. - GKE. GKE Sandbox and GKE Agent Sandbox create the
gvisorRuntimeClass, and MAQPNA renders sessions into the same upstreamSandboxobjects. For pod snapshots setoperator.sandboxSnapshotApi: podsnapshot.gke.io/v1. - OpenShift. OpenShift sandboxed containers provide
kata(andkata-remotefor peer pods); confidential containers with the Red Hat build of Trustee back tier-2 (maqpna-attest --verifier trustee). - RKE2. RKE2 ships neither runtime: install
runscthrough RKE2's containerd template and Kata with kata-deploy.
Workload identity#
Every component's ServiceAccount takes annotations, and every pod takes extra labels:
| Cloud | Mechanism | Values |
|---|---|---|
| EKS | Pod Identity (default) | none: create a pod identity association for maqpna-gateway |
| EKS | IRSA (AWS Marketplace metering, Fargate, hybrid nodes) | gateway.serviceAccount.annotations: {eks.amazonaws.com/role-arn: arn:aws:iam::<account>:role/<role>} |
| GKE | Workload Identity Federation | none for a direct principal:// IAM binding, or iam.gke.io/gcp-service-account: <gsa>@<project>.iam.gserviceaccount.com |
| AKS | Entra Workload ID | gateway.serviceAccount.annotations: {azure.workload.identity/client-id: <uuid>} and gateway.podLabels: {azure.workload.identity/use: "true"} |
The keys are gateway, identity and operator .serviceAccount.annotations, and .podLabels on those plus
attestation. The identity broker's ServiceAccount exists when identity.bootstrap.enabled (the default).
The gateway exchanges this identity for cloud credentials itself (pkg/cloudcreds; see
Cloud identity & keys). With audit.worm.credentials: aws|gcp|azure the WORM
sink, with credentials: aws|azure|gcp a CloudWatch, Sentinel or SecOps SIEM sink, and with no explicit credential a
Bedrock, Vertex AI, Azure OpenAI or OCI Generative AI model route use it, and no Secret is mounted.
gateway.cloudCredentials tunes the providers (region, IMDS fallback, partition DNS suffix, metadata host).
WORM audit storage#
audit.sink: worm ships sealed ledger segments to a write-once store. audit.worm.provider picks it:
| Provider | Store | Immutability the gateway requires at startup | Credentials |
|---|---|---|---|
s3 |
S3 Object Lock (AWS, MinIO, Ceph RGW, Scaleway, OVHcloud) | GetObjectLockConfiguration returns ObjectLockEnabled: Enabled; every object is written in COMPLIANCE mode with its own retain-until date |
credentials: aws (EKS Pod Identity / IRSA), or credentialsSecret: access-key, secret-key, optional session-token |
gcs |
Google Cloud Storage | a locked retention policy at least as long as the retention (Bucket Lock), or Object Retention Lock (then every object gets a Locked retention) | credentials: gcp (GKE Workload Identity Federation), or tokenSecret: OAuth token (devstorage.read_write) |
azure |
Azure Blob Storage | version-level immutability support on the container (x-ms-immutable-storage-with-versioning-enabled); every blob gets a Locked x-ms-immutability-policy-until-date |
credentials: azure (Entra Workload ID), or tokenSecret (Entra token for https://storage.azure.com/.default) or sasSecret |
When the check fails the gateway does not start (fail closed). Uploads never replace an object: GCS writes use
ifGenerationMatch=0 and Azure writes If-None-Match: *; a crashed leader's identical re-upload is recognised by its
MD5 and accepted. audit.worm.objectLockDays sets the retention (default: sovereignty.auditRetentionDays).
# AWS: a bucket with Object Lock
aws s3api create-bucket --bucket acme-maqpna-audit --region eu-central-1 \
--create-bucket-configuration LocationConstraint=eu-central-1 --object-lock-enabled-for-bucket
# Google Cloud: Bucket Lock (locking is irreversible)
gcloud storage buckets create gs://acme-maqpna-audit --location=europe-west3 --retention-period=3650d
gcloud storage buckets update gs://acme-maqpna-audit --lock-retention-period
# Azure: a container with version-level immutability support
az storage container-rm create --storage-account acmeaudit -n maqpna-audit --enable-vlw
SIEM sinks#
audit.sinks adds cloud-native types next to syslog, http and jsonl:
| Type | Destination | Required fields |
|---|---|---|
cloudwatch |
CloudWatch Logs PutLogEvents (OCSF messages by default) |
region, logGroup, optional logStream (created if missing), credentials: aws or awsCredentialsSecret |
sentinel |
Microsoft Sentinel through the Azure Monitor Logs Ingestion API | url (data collection endpoint), dcrImmutableId, streamName (Custom-<Table>), credentials: azure or tokenSecret |
chronicle |
Google SecOps: unstructured logs:import or UDM events:import |
url (instance URL), chronicleMode (logs with logType, or udm), credentials: gcp or tokenSecret |
splunk-hec |
Splunk HTTP Event Collector | url (HEC base URL; must be https://, http:// only to a loopback collector, since the HEC token is sent in the Authorization header), tokenSecret, optional index, source, sourceType |
Batches are split at each collector's byte and count limits. Authentication errors, throttling (Retry-After) and
server errors are retried with exponential backoff without dropping anything; records the collector rejects as
malformed or out of range go to <auditPath>.sinks/<name>.deadletter.jsonl (bounded by deadLetterMaxBytes, 64 MiB by
default; when it is full, delivery stops instead). maqpna_gateway_audit_sink_dead_lettered_total{sink} counts them.
Amazon Security Lake. MAQPNA does not write Parquet itself. Ship OCSF to CloudWatch Logs with the cloudwatch sink,
then add a subscription filter to an Amazon Data Firehose stream that converts the records to Parquet into a Security
Lake custom source (OCSF class 6003, API Activity). This keeps the gateway free of a columnar writer and lets Security
Lake own partitioning and retention.
Microsoft Sentinel. Create a custom table (for example MaqpnaAudit_CL) and a data collection rule whose stream
declares the columns of the json record (TimeGenerated, seq, time, session, namespace, agent, user,
server, tool, decision, rule, reason, hash, ext) and grant the identity Monitoring Metrics Publisher on
the DCR.
FQDN egress policies#
The gateway is the only component that calls tools, models, WORM and SIEM endpoints; agent sandboxes reach only the
gateway. networkPolicy.fqdnEgress mirrors the gateway's own allowlist (sovereignty.allowedEgressHosts and
allowedEgressCIDRs, plus extraHosts) into the CNI, so a host the gateway refuses is also dropped in the kernel:
provider |
Objects | Clusters |
|---|---|---|
cilium |
CiliumNetworkPolicy with toFQDNs (DNS proxy on kube-dns, the whole cluster and the API server allowed) |
AKS with ACNS, RKE2 with Cilium, any Cilium cluster |
gke |
FQDNNetworkPolicy plus a base NetworkPolicy (DNS, cluster pods, clusterCIDRs) |
GKE Dataplane V2 |
eks |
ApplicationNetworkPolicy with domainNames plus the same base NetworkPolicy |
EKS Auto Mode, or VPC CNI 1.21.1+ with network policies |
In-cluster names (*.svc, *.cluster.local) are skipped because they resolve to cluster IPs. On gke and eks the
gateway's egress becomes default-deny, so clusterCIDRs must list the API server endpoint (the chart refuses to render
without it while gateway.configSync or gateway.activity is on). Cilium's *.example.com matches one label;
list deeper subdomains explicitly.
OpenShift: restricted-v2#
values-openshift.yaml removes the pinned UID/GID/fsGroup 65532 from every pod and sets
operator.sandboxRunAsUID: -1 (operator flag --sandbox-run-as-uid=-1), so OpenShift assigns UIDs from the namespace
range and every pod, sandboxes included, runs under the default restricted-v2 SCC. No nonroot-v2 grant is needed.
RKE2 and FIPS#
RKE2 can run FIPS 140-3 validated Kubernetes components, but MAQPNA's images are not FIPS builds and contain no validated cryptographic module; in a FIPS cluster record MAQPNA as a non-FIPS workload. ingress-nginx in RKE2 reached end of life in March 2026; the chart creates no Ingress.
Compliance profiles#
compliance.profiles declares the regulatory profiles an installation is meant to meet. The chart refuses to render
when a profile's retention floor is not met, maqpna values validate --profile <name> reports the same rules plus
warnings, and every pod carries the annotation maqpna.com/compliance-profiles. These are configuration checks, not
legal advice.
| Profile | Checks |
|---|---|
cert-in |
sovereignty.auditRetentionDays and audit.worm.objectLockDays of at least 180 days; a ledger that outlives the pod (WORM, a persistent volume or a SIEM sink); warnings for the time sources and a non-Indian jurisdiction |
dpdp |
sovereignty enabled; a warning below 365 days of retention |
eu-ai-act |
at least 183 days (six months, Art. 19 and 26(6)); values-sovereign-eu.yaml declares it |
values-cert-in.yaml is a ready India profile: jurisdiction IN, 365 days, S3 Object Lock in ap-south-1.
CERT-In. The Directions of 28 April 2022 require incident reports within 6 hours, ICT logs kept for 180 days, and
clocks synchronised to the NTP servers of NIC or NPL. MAQPNA timestamps every ledger record with the gateway's clock in
UTC, so synchronise the nodes: point chrony or systemd-timesyncd at samay1.nic.in, samay2.nic.in and
time.nplindia.org (or sources traceable to them) and declare them in compliance.timeSources. For the 6-hour
report, export the timeline:
maqpna incident export --gateway "$GW" --since 6h --detected 2026-10-10T07:45:00Z --format md --out incident.md
The export verifies the hash chain over the window, lists denials, approvals, kill-switch and revocation events, DLP
and taint hits, and maps them to the fields of the CERT-In report with the deadline. See
maqpna incident export.
DPDP. maqpna memory erase --user <subject> deletes a person's entries from every memory store of the gateway's
memory backend (the file store, or the PostgreSQL state backend with HA) and returns a signed erasure certificate
anchored in the ledger. It does not reach the PostgreSQL/pgvector and OpenSearch memory adapters (documented stubs that
store nothing today), the audit ledger itself (records hold the subject identifier and argument hashes, not argument
values, and stay immutable by design), copies already shipped to WORM or SIEM, data inside sandboxes or upstream tools,
or prompts sent to model providers. Cover those in your retention and erasure procedures.
Reference clusters and the smoke test#
hack/cloud/{eks,gke,aks} are OpenTofu modules for small test clusters with the sandbox node pools, a NetworkPolicy
dataplane and workload identity wired to the gateway's ServiceAccount. hack/cloud-smoke.sh <eks|gke|aks> installs
MAQPNA with the matching profile and checks that the gateway pod gets cloud credentials with no Secret mounted, that a
delete in the WORM bucket is refused by the provider, and that egress to a host outside the allowlist is blocked.