Skip to content

v0.4.0 — Custom webhook authentication (Bearer + mTLS)

Choose a tag to compare

@anhtuank7c anhtuank7c released this 08 May 04:52
· 51 commits to main since this release

Minor release. Closes the long-standing gap in the custom-webhook authentication story: the channel now ships with two additive auth mechanisms — a single arbitrary HTTP header (Bearer / API key / HMAC-style; #7) and full mutual-TLS client authentication (cert + key + optional CA bundle; #8). Both flow through the same memfd-backed curl -K config the existing alert-credential isolation already used, so the curl child's argv is byte-identical regardless of which auth modes are configured — /proc/*/cmdline reveals nothing about which channel or which credentials are in play. No breaking changes.

What's new

webhook_auth_header — single HTTP header for the custom webhook (#7)

webhook_url = https://siem.example.com/ingest/pamsignal
webhook_auth_header = Authorization: Bearer <token>

Works with any single-header auth scheme: Bearer / OAuth, X-API-Key, Authorization: Splunk <token> (HEC), DD-API-KEY (Datadog), Wazuh API JWT, custom HMAC headers, etc. The header value is rendered into a memfd-backed curl -K config file — the secret never appears in argv, /proc/<pid>/cmdline, or any process listing. Validator at config load rejects CRLF / quote / backslash injection and refuses the key set without webhook_url.

Previously the only authenticated path was a reverse proxy in front of the receiver injecting the header — the bundled examples/nodejs-webhook/ reference receiver explicitly required this setup. The example README now documents direct configuration as the recommended path.

webhook_client_cert / webhook_client_key / webhook_ca_bundle — mTLS client auth (#8)

webhook_url = https://siem.internal.example.com/ingest
webhook_client_cert = /etc/pamsignal/webhook-client.crt
webhook_client_key  = /etc/pamsignal/webhook-client.key
# Optional, only if the receiver's CA isn't in the system trust store
webhook_ca_bundle   = /etc/pamsignal/internal-ca.pem

Targets the operator profile that already runs PKI (internal CA + cert-manager / SPIFFE / service-mesh issuance). Stronger than Bearer alone — the private key never travels over the wire, replay windows don't exist, credential rotation is delegated to the cert-management pipeline. Combines additively with webhook_auth_header — the corporate-SIEM / Wazuh pattern (mTLS for transport-layer service identity, Bearer for per-request authorization).

Validator at config load: O_NOFOLLOW open (symlinks rejected), regular-file required, ownership outside {root, daemon-uid} rejected, webhook_client_key additionally rejected if group- or world-readable. Path strings constrained to printable ASCII minus " and \ for unambiguous handling in the curl -K config. Cross-field consistency: cert without key (or key without cert) is a config-load error; any TLS key without webhook_url is rejected.

Documentation refresh

  • docs/configuration.md — full "Custom webhook authentication" reference covering both modes, combined-pattern examples, and the validation rules.
  • docs/architecture.md — new "Curl invocation (memfd-backed config)" subsection.
  • docs/alerts.md — "Authentication (optional)" + "Mutual TLS (optional, advanced)" subsections under "Custom webhook (ECS JSON)".
  • docs/deployment.md — cert/key file placement guidance.
  • docs/threat-model.md — attack #3 extended (covers the new auth surface), NS8 rewritten (Bearer + mTLS now ship; HMAC-signed payloads remain out of scope).
  • pamsignal.8.in — SECURITY + FILES sections updated.
  • examples/nodejs-webhook/ — adds an mTLS-enabled https.createServer variant.

Internal changes (no operator impact)

  • post_alert() and build_secrets_memfd() in src/notify.c swap from positional arguments to a curl_config_t struct that carries the optional auth header and TLS paths by name. The four legacy callers (Telegram, Slack/Teams/Discord, WhatsApp) pass struct-literal arguments and produce identical wire output to v0.3.x.
  • build_secrets_memfd buffer grows 2048 → 4096 to fit up to four config lines without truncation. Each snprintf append is bounds-checked.

Tests

CMocka test_config suite grew 33 → 53 (20 new cases): 9 for webhook_auth_header parse + validation, 11 for mTLS parse + perm validation including symlink rejection, world/group-readable key rejection, missing-cert-or-key, no-webhook_url, path-with-quote, and combined Bearer+mTLS load. All 4 suites pass under ASAN + UBSAN. clang-tidy clean. OWASP ASVS L1 audit zero FAIL findings.

Upgrading

sudo apt upgrade pamsignal   # Debian / Ubuntu
sudo dnf upgrade pamsignal   # Fedora / EL9

Existing v0.3.x configs continue to work unchanged. To enable the new auth options, see Configuration → Custom webhook authentication.

Closed issues

  • #7webhook_auth_header for the custom webhook channel
  • #8 — Optional mTLS client auth for the custom webhook channel

Full changelog: v0.3.4...v0.4.0