Fix leftover __OP_* dialplan variables from the openphone rename (all readers use SP_*). Record the reverse-proxy decision in 00 §10.
89 lines
5.9 KiB
Markdown
89 lines
5.9 KiB
Markdown
# 08 — Security, operations, legal
|
||
|
||
## 1. Threat model (what attacks a PBX on the internet)
|
||
|
||
| Threat | Example | Controls |
|
||
|---|---|---|
|
||
| SIP scanners / password guessing | "friendly-scanner", sipvicious hitting 5060 | Generated 24-char SIP passwords; no anonymous endpoint; fail2ban/CrowdSec on `security.log`; optional phone ACL (§2.2); firewall allow-list |
|
||
| **Toll fraud** (the expensive one) | Stolen SIP creds used to call premium international numbers | International route disabled by default; per-user outbound class; `max_channels`; voip.ms-side international block + balance limits (06 §1); forward targets checked against class |
|
||
| Web credential stuffing | Login brute force | argon2id, rate limits, audit log, TOTP for admins (M11) |
|
||
| Voicemail PIN guessing | Calling *98 and trying PINs | 6-digit default PIN, `maxlogins=3`, reject trivial PINs (`000000`, `123456`, the extension number repeated) |
|
||
| Provisioning leaks | Fetching `/prov/<mac>.cfg` to steal SIP passwords | Per-device Basic auth, 404 on unknown MAC, rate limit + temporary IP block, optional LAN-only |
|
||
| Dialplan injection | Name `x); System(...)` | Sanitizers (03 §2), no `System()` anywhere |
|
||
| SSRF via webhooks | Webhook to `http://127.0.0.1:5038` | Private IP block by default (04 §9) |
|
||
| Stolen backup | Backup file copied | Backups exclude generated plaintext configs; DB secrets are encrypted with `APP_SECRET` (kept in `.env`, not in the backup); mode 0600 |
|
||
| AMI exposure | AMI reachable from network | Bound to 127.0.0.1, `permit` 127.0.0.1 only, random secret |
|
||
|
||
## 2. Required controls
|
||
|
||
### 2.1 Network
|
||
- Expose only 443/tcp, 5060/udp+tcp (5061/tcp if TLS), 10000–20000/udp.
|
||
- Admin guide includes an nftables example that limits 5060/5061 to voip.ms
|
||
server IPs (from the voip.ms wiki list, **verify**) plus `sip.extraAllowedNets`, for setups with no remote phones.
|
||
|
||
### 2.2 Phone ACL (setting `sip.restrictPhonesToLocalNets`)
|
||
When true, the generator adds:
|
||
```ini
|
||
[sp-phone-acl]
|
||
type = acl
|
||
contact_deny = 0.0.0.0/0.0.0.0
|
||
contact_permit = 192.168.0.0/16 ; each network.localNets + sip.extraAllowedNets
|
||
```
|
||
and `contact_acl = sp-phone-acl` on every extension endpoint (not trunks). Golden test required.
|
||
|
||
### 2.3 fail2ban / CrowdSec
|
||
- `logger.conf` writes `security.log` (01 §3.3).
|
||
- Admin guide: fail2ban `asterisk` jail pointed at `./data/asterisk-logs/security.log`, or the CrowdSec
|
||
Asterisk collection with the same log path **(verify collection name)**.
|
||
- The api's own auth failures go to stdout JSON with `event: "auth.failed"` and `ip`, so they can be parsed too.
|
||
|
||
### 2.4 Web
|
||
- Security headers on every api response:
|
||
`Content-Security-Policy: default-src 'self'; connect-src 'self' wss://{host}; media-src 'self' blob:; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'`,
|
||
`X-Content-Type-Options: nosniff`, `Referrer-Policy: same-origin`,
|
||
`Permissions-Policy: microphone=(self), camera=()`.
|
||
- HSTS is set by the reverse proxy (Caddy does it by default; nginx example adds `add_header Strict-Transport-Security "max-age=31536000" always;`).
|
||
- `TRUST_PROXY=true` → Fastify `trustProxy: '127.0.0.1'` only.
|
||
- Admin TOTP (M11): RFC 6238, 30 s, 6 digits, ±1 window, 10 single-use recovery codes (stored hashed).
|
||
|
||
### 2.5 Files and permissions
|
||
- `/etc/asterisk/selfphoneng/*` mode 0640, owner app UID.
|
||
- `.env` mode 0600 (install.sh sets it).
|
||
- Recordings and voicemail are served only through authenticated api routes, never as static files.
|
||
|
||
## 3. Operations
|
||
|
||
### 3.1 Install / update
|
||
- `scripts/install.sh` (01 §1.3).
|
||
- `scripts/update.sh`: `git pull --ff-only`, `docker compose build`, `docker compose up -d`. Migrations run automatically on api start. Print the new version.
|
||
- Version: `package.json` version at the root, exposed at `/api/system/status`. Semver. `CHANGELOG.md` kept by hand.
|
||
- Asterisk security releases: bump `ASTERISK_VERSION` in the Dockerfile and rebuild. The admin guide explains how.
|
||
|
||
### 3.2 Monitoring
|
||
- `GET /healthz` for an external monitor (Uptime Kuma etc.): 200 / 503 + JSON reasons.
|
||
- Emails to admins: trunk down > 5 min, disk < 10%, public IP changed, backup failed.
|
||
- Logs: `docker compose logs api`, `./data/asterisk-logs/messages.log`.
|
||
|
||
### 3.3 Troubleshooting page in the admin guide (M11)
|
||
Must cover: one-way audio (NAT/publicIp/SIP ALG/RTP ports), trunk won't register (credentials, server,
|
||
IP whitelist when IP auth, firewall), inbound calls don't arrive (DID routing + POP), browser softphone won't
|
||
register (WSS proxy path `/ws`, certificate, microphone permission), voicemail emails not sent (SMTP test), and
|
||
how to use `/admin/system` CLI views and `pjsip set logger on` (via `docker compose exec asterisk asterisk -rx`).
|
||
|
||
## 4. Licensing compliance
|
||
- Our code: AGPL-3.0-or-later. Add `LICENSE`, SPDX headers, and an About page (`/admin/system` → About) listing
|
||
the license and a link to the source repository (AGPL requires offering source to network users).
|
||
- Asterisk: GPL-2.0, runs as a separate program. We don't link to it, so there's no license conflict.
|
||
- Asterisk core/extra sounds and opsound MOH: CC-BY-SA. Credit them on the About page.
|
||
- Ringtone: CC0, documented in `apps/web/public/sounds/SOUNDS-LICENSE.md`.
|
||
- No closed-source codecs (AGENTS rule 4).
|
||
|
||
## 5. Legal features (not legal advice; the admin guide says to check local law)
|
||
- **Call recording consent:** US federal law and most states are one-party consent, but several states
|
||
(e.g. California, Florida, Illinois, Maryland, Massachusetts, Montana, New Hampshire, Pennsylvania, Washington)
|
||
require all parties to consent. When any recording is enabled, the Recording settings tab shows this notice and
|
||
strongly suggests setting `recordings.announcementSoundId`. The setup wizard doesn't turn recording on.
|
||
- **E911 / Kari's Law / RAY BAUM'S Act:** see 06 §6.
|
||
- **Data retention:** retention settings for recordings and voicemail (04 §8). Deleting a user offers
|
||
"delete their voicemail and recordings too".
|