MAQPNADocs

SIEM and audit forwarding

Stream audit ledger records to syslog, HTTP, Splunk HEC, Microsoft Sentinel, Google SecOps and CloudWatch Logs, keep a write-once copy in object storage, and import provider audit logs into the ledger.

The audit ledger is MAQPNA's append-only, hash-chained record of every decision, approval, revocation and result. Your security team usually wants those records in its SIEM as well. The gateway streams them there itself: each sink has a durable cursor on the audit volume and delivers records at least once. You can also ship sealed ledger segments to write-once storage (WORM), and pull provider audit logs (AWS CloudTrail, OpenAI, Anthropic) into the ledger.

flowchart LR
    G["MAQPNA gateway"] --> L[("audit ledger")]
    L -->|"cursor per sink"| S1["syslog<br/>tcp:// or tls://"]
    L --> S2["http<br/>OCSF, JSON"]
    L --> S3["splunk-hec"]
    L --> S4["sentinel"]
    L --> S5["chronicle"]
    L --> S6["cloudwatch"]
    L --> S7["jsonl file"]
    L -->|"sealed segments"| W[("WORM<br/>S3 Object Lock, GCS,<br/>Azure immutable Blob")]
    I["maqpna audit import<br/>CloudTrail, OpenAI, Anthropic"] -->|"decision observe"| L

When to use it#

  • Your SOC correlates agent activity with other security events.
  • Regulation or policy needs a copy of the evidence outside the cluster that no one can delete.
  • You want to see what happened at a model provider even when it did not pass the gateway.

For reading, verifying and exporting the ledger itself, see Read and verify the audit ledger.

Prerequisites#

  • MAQPNA installed with the Helm chart. Keep the ledger on a persistent volume (gateway.auditStorage.persistent: true) so cursors survive restarts.
  • Network access from the gateway to the SIEM endpoint. Every sink connection goes through the residency-checking dialer.
  • With sovereignty.enabled: sinks must use TLS (tls:// or https://) and their host must be in sovereignty.allowedEgressHosts. The chart refuses anything else, and the gateway checks it again at start-up.

Supported sinks#

type Destination Required fields Default format
syslog Any syslog receiver (RFC 5424 messages) address (tcp://host:port or tls://host:port) rfc5424-json
http Any HTTP(S) collector url ocsf
jsonl A local file path json
splunk-hec Splunk HTTP Event Collector (/services/collector/event) url (HEC base URL, https:// unless loopback), tokenSecret json
sentinel Microsoft Sentinel, through the Azure Monitor Logs Ingestion API url (data collection endpoint), dcrImmutableId (dcr-<32 hex>), streamName (Custom-<Table>) json
chronicle Google SecOps url (instance URL, https://<region>-chronicle.googleapis.com/v1alpha/projects/<p>/locations/<l>/instances/<id>), chronicleMode (logs or udm), logType for logs json
cloudwatch Amazon CloudWatch Logs (PutLogEvents); Security Lake through Firehose region, logGroup; logStream defaults to maqpna-gateway ocsf

Formats are json, rfc5424-json, cef and ocsf. On sentinel and chronicle, only json and ocsf are allowed. Syslog always sends RFC 5424 messages; format selects their MSG part.

Common optional fields: batchSize (default 100), pollIntervalMillis (default 1000), timeoutSeconds (default 15), facility (syslog, default 13), sdId (default maqpna@32473), serverName, maxBatchBytes, deadLetterPath, deadLetterMaxBytes (default 64 MiB). Sink names must match [a-z0-9][a-z0-9-]{0,39}.

Step 1: Store the credentials in Secrets#

The chart mounts each secret under /etc/maqpna/sinks/<name>/ and fills in the file paths for you:

Sink field Secret keys Used by
tlsSecret ca.crt, plus tls.crt and tls.key with mtls: true syslog over tls://, https:// collectors
headersSecret headers: one Name: value line per header http
tokenSecret token: the HEC token, or an Entra / Google OAuth bearer token splunk-hec, sentinel, chronicle
awsCredentialsSecret access-key, secret-key, optional session-token cloudwatch
credentials (no Secret) Keyless workload identity: aws (cloudwatch), azure (sentinel), gcp (chronicle) Bind it with gateway.serviceAccount.annotations

Example for Splunk HEC:

kubectl create secret generic maqpna-splunk-hec -n maqpna-system \
  --from-literal=token='<your-hec-token>'

Step 2: Add the sinks to the Helm values#

audit:
  sinks:
    - name: splunk
      type: splunk-hec
      url: https://splunk.example.eu:8088
      index: maqpna
      sourceType: maqpna:audit          # the default
      tokenSecret: maqpna-splunk-hec
    - name: soc-syslog
      type: syslog
      address: tls://siem.example.eu:6514
      format: rfc5424-json
      tlsSecret: maqpna-siem-tls
    - name: sentinel
      type: sentinel
      url: https://maqpna-dce-abcd.westeurope-1.ingest.monitor.azure.com
      dcrImmutableId: dcr-00000000000000000000000000000000
      streamName: Custom-MaqpnaAudit_CL
      credentials: azure                 # AKS Entra Workload ID
    - name: cloudwatch
      type: cloudwatch
      region: eu-central-1
      logGroup: /maqpna/audit
      credentials: aws                   # EKS Pod Identity or IRSA

Apply with helm upgrade. Keyless credentials use the gateway's cloud workload identity; see Cloud identity & keys and Cloud profiles.

Step 3: Check delivery#

maqpna audit sinks

Output from a local run with one jsonl sink:

ledger head seq 7
NAME       TYPE   FORMAT  CURSOR  LAG  DELIVERED  ERRORS  LAST ERROR
siem-file  jsonl  json    7       0    7          0

--max-lag N exits with status 3 when any sink lags more than N records or reports an error, so you can use it in a health check. The same data is on GET /v1/audit/sinks (admin API) and on the gateway's /metrics:

Metric Meaning
maqpna_gateway_audit_sink_lag{sink} Ledger records not yet delivered
maqpna_gateway_audit_sink_delivered_total{sink} Records delivered
maqpna_gateway_audit_sink_errors_total{sink} Delivery errors (retried)
maqpna_gateway_audit_sink_dead_lettered_total{sink} Records the collector rejected permanently

Authentication, throttling and server errors are retried. Records a collector rejects permanently go to <auditPath>.sinks/<name>.deadletter.jsonl instead of stalling the stream. When the dead-letter file is full, delivery stops rather than drop records.

Step 4: Backfill a sink#

To resend a range of records from a ledger file, run the sink offline with the gateway's configuration:

maqpna audit stream ledger.jsonl --config gateway.json --sink splunk --from-seq 1200

Keep a write-once copy (WORM)#

audit.sink: worm ships sealed ledger segments to object storage with retention locks. At start-up the gateway checks that the store enforces immutability, and refuses to start if it does not.

audit:
  sink: worm
  worm:
    provider: s3                 # s3 | gcs | azure
    endpoint: https://s3.eu-central-1.amazonaws.com
    bucket: maqpna-audit         # created with Object Lock
    region: eu-central-1
    objectLockDays: 365
    credentials: static          # or aws | gcp | azure (keyless)
    credentialsSecret: maqpna-audit-s3   # keys access-key, secret-key
provider Store Static credentials
s3 S3 Object Lock (AWS, MinIO, Ceph RGW) credentialsSecret
gcs GCS Bucket Lock or Object Retention Lock tokenSecret (key token)
azure Version-level immutable Blob storage tokenSecret or sasSecret (key sas)

Import provider audit logs#

An inline gateway sees only what passes through it. maqpna audit import pulls provider logs into a ledger file as evidence. Each event becomes a record with decision observe and server import/<source>, so it is never mistaken for a MAQPNA decision. Imports are incremental and skip events already recorded.

export OPENAI_ADMIN_KEY='<your-openai-admin-key>'
maqpna audit import --source openai --ledger imports.jsonl --since 72h
maqpna audit import --source cloudtrail --ledger imports.jsonl --region eu-central-1 --namespace team-a

Sources are cloudtrail, openai and anthropic. Use a dedicated ledger file, or one no running gateway writes. See Hyperscaler interop for the provider side.

Edge gateways that are offline forward sealed segments to a central gateway with maqpna audit export-segments and maqpna audit forward; see Offline edge gateways.

Verify#

  • maqpna audit sinks --max-lag 100 exits 0.
  • Search the SIEM for a session ID from maqpna audit tail; the record's seq and hash match.
  • With WORM: the gateway is running (it refuses to start against a store without immutability).

Troubleshooting#

Symptom Cause Fix
Helm fails: the sovereign profile requires a TLS endpoint A tcp:// or http:// sink with sovereignty.enabled Use tls:// or https://
Helm fails: SIEM host ... is outside sovereignty.allowedEgressHosts The SIEM host is not allowed Add it to sovereignty.allowedEgressHosts (it must be in-jurisdiction)
url ... the HEC token would be sent in cleartext splunk-hec with http:// to a remote host Use https://
format "cef" is not supported by type sentinel cef or rfc5424-json on a cloud sink Use json or ocsf
Lag grows, ERRORS count rises SIEM unreachable, bad token, or the dialer refused the host Read LAST ERROR; check the Secret and egress
dead_lettered_total grows The collector rejects records (schema, size) Inspect the dead-letter file; fix the DCR, parser or maxBatchBytes
credentials ... excludes tokenFile Both credentials and a token Secret set Use one or the other

Next steps#