Files
2026-11-05 12:52:32 +01:00

62 lines
3.5 KiB
Markdown

# Security
## Model
- **Devices** authenticate with Basic Auth (required by the protocol) - always behind TLS (reverse proxy).
The password is verified by logging in to IMAP and is passed to SMTP/DAV only within the same request. It is
never stored. The successful login cache keeps only HMAC-SHA256(login, password) with a random key generated
at process start.
- **The IMAP pool** keeps authenticated connections keyed by the same HMAC - a connection is only reused for a
request with identical credentials.
- **The panel** is a separate listener (loopback by default; another address requires explicit consent).
Administrator passwords: argon2id. Session: random 256-bit token in an `HttpOnly; SameSite=Strict` cookie
(`Secure` over HTTPS); only its SHA-256 is stored. Every modifying request needs the CSRF header. The first
administrator can only be created with a one-time token from the CLI (valid 60 minutes).
- **Panel headers:** strict CSP (`script-src 'self'`, no inline code), `X-Frame-Options: DENY`, `nosniff`,
`Referrer-Policy: no-referrer`, COOP.
## Abuse protection
- Lockout after `auth_fail_max` failures per account and `3x` per IP within the window; HTTP 429 with
`Retry-After`. Other accounts from the same IP keep working until the IP limit is reached.
- fail2ban friendly log line:
`authentication failed user=jan@example.com ip=203.0.113.7 service=activesync`
```ini
# /etc/fail2ban/filter.d/active-push.conf
[Definition]
failregex = authentication failed user=\S+ ip=<HOST> service=
journalmatch = _SYSTEMD_UNIT=active-push.service
```
- Panel: 5 failed logins in 10 minutes lock the IP for 15 minutes; constant verification cost even for unknown
accounts.
- Limits: request size (`sync.max_request_mb`), attachments, messages, WBXML depth and element count, IMAP
response size, timeouts on every backend operation, HTTP header read timeout.
- Unknown ActiveSync command names are counted as `Unknown` (no unbounded metric labels).
- XML (Autodiscover, WebDAV) is parsed without DTD/entities (XXE, billion laughs).
- The DAV client never follows `href`s to another host (SSRF).
- Logins, device ids and device types are validated; SQL parameters are always bound.
- `smtp.enforce_from` rewrites `From` to the account address; `Bcc` is stripped from the transmitted copy.
## Device control
- Provisioning policies (password, encryption, camera, maximum mail age ...). Changing a policy invalidates
keys - devices must accept the policy again.
- Remote wipe (confirmed by typing the device id), device block, approval of new devices, mailbox block,
blocked device types/User-Agents.
- Every administrative action is written to the audit log (who, what, when, from where).
## systemd
The service runs as `active-push` without privileges or capabilities, with `ProtectSystem=strict`,
`ProtectHome`, `PrivateTmp`, `PrivateDevices`, `MemoryDenyWriteExecute`, a system call filter and write access
only to `/var/lib/active-push`. The database is `0600`, the directory `0700`, `UMask=0077`.
## Recommendations
1. TLS only at the reverse proxy, with HSTS; ActivePush on `127.0.0.1`.
2. Panel through an SSH tunnel or VPN. If exposed - TLS + `panel.allowed_networks`.
3. SMTP submission with `reject_sender_login_mismatch`.
4. Enable `security.require_device_approval` in company environments.
5. Set a Prometheus token only if you scrape metrics; the endpoint then lives on the panel port.
6. `logging.log_wbxml` contains message content - only for short diagnosis.