Appearance
Security & Authentication
ForgeVis uses internal Basic authentication configured in security.users.
How It Works
When security.users is empty, requests are allowed (backward-compatible mode). When at least one user is configured, access is checked by:
- IP/CIDR allow-list (
ips) - Permission (
action, optionalpath) - Credentials (
user/pass)
For HLS, WebSocket and WebRTC, path is the camera id: a user with path: cam1 sees only cam1 through any of them. Watching (WHEP) takes read, publishing (WHIP) takes publish.
Changes to security.users apply without a restart, with the rest of the configuration: a removed user or a changed password stops working at once.
Password guessing. After security.lockout.attempts failed sign-ins in a row (10 by default) the user is locked out from that address for duration_sec seconds (300 by default) — on every server at once, RTSP included. What is counted is the address and the user name together: an operator mistyping a password behind a shared NAT does not lock out the others. Requests without a password (how every client starts) and a right password refused a camera or an action are not counted. A lockout shows in the log as a WARN line and in the auth_lockouts_total metric.
Credential Formats
Every user / pass value must carry an explicit method prefix. Unknown or unprefixed values are rejected (fail-closed) — this prevents a typo in the prefix from silently degrading a hashed credential to plaintext.
Supported prefixes:
| Prefix | Format | When to use |
|---|---|---|
argon2: | PHC string: $argon2id$v=19$m=…,t=…,p=…$<salt>$<hash> | Recommended — salted, slow-by-design |
sha256: | <base64-of-raw-sha256-digest> | Legacy / interoperability with existing tools |
plain: | <plaintext> | Development / behind a private network only |
All credential comparisons run in constant time, so the result does not leak information about how many characters of a wrong password matched.
Argon2id Example (recommended)
Generate a hash with any standard argon2 CLI (e.g. argon2 from passlib, or argon2-cli):
bash
echo -n "mypass" | argon2 "$(openssl rand -hex 16)" -id -t 2 -m 16 -p 1 -e
# → $argon2id$v=19$m=65536,t=2,p=1$<salt>$<hash>yaml
security:
auth_method: internal
users:
- user: viewer
pass: "argon2:$argon2id$v=19$m=65536,t=2,p=1$c2FsdHNhbHQ$<hash>"
ips: []
permissions:
- action: readSHA256 Example (legacy)
bash
echo -n "mypass" | openssl dgst -binary -sha256 | openssl base64yaml
security:
auth_method: internal
users:
- user: viewer
pass: sha256:6nHCWnpgIka0w5gkuFVniJSpb0O7m3ExnDlwCh4EUiI=
ips: []
permissions:
- action: readNote: SHA256 here is unsalted and fast — fine against accidental log exposure, weak against offline attacker iteration. Prefer argon2: for new deployments.
Plain Example (development only)
yaml
security:
users:
- user: viewer
pass: "plain:change-me"
ips: []
permissions:
- action: readMigration Strategy
Previous releases accepted unprefixed values as plaintext (silent catch-all). That fallback is removed; rehash existing credentials:
- Generate new
argon2:(orsha256:) values for every user. - Update config, restart.
- For mixed-fleet rollouts, migrate one user at a time and verify access before moving on.
Service User and Isolation
The packaged service runs as the system user forgevis, not as root, and under systemd isolation: /usr, /boot and /etc are read-only to it (except /etc/forgevis), home directories are out of reach, and it holds no root capabilities. A flaw in parsing what a camera sends does not give root on the host.
What follows from it:
- The archive directory (
record_path) has to belong toforgevis:chown forgevis:forgevis <directory>. The default,/var/lib/forgevis/recordings, already does. - Hooks (
runOnSegmentCreate,runOnSegmentComplete,runPeriodically) run asforgevisunder the same isolation: they cannot write to/etc,/usr,/bootor home directories. - TLS keys in
/etc/forgevis/tls/have to be readable by theforgevisgroup:chown root:forgevis node.key && chmod 640 node.key. forgevis --retentionfrom cron still runs as root.
An Installation That Ran as Root
On upgrading an installation that ran as root, the package leaves it on root: its archive, state and logs belong to root, and handing terabytes to another owner is work of its own, not a package upgrade. It does so with /etc/systemd/system/forgevis.service.d/10-run-as-root.conf. The isolation applies all the same.
Moving such an installation to forgevis takes a maintenance window:
bash
sudo systemctl stop forgevis
sudo chown -R forgevis:forgevis <archive directory> /var/lib/forgevis /var/log/forgevis
sudo chown forgevis:forgevis /etc/forgevis/instance_id
sudo chown forgevis:forgevis /etc/forgevis/forgevis.lic # if a licence is installed
sudo rm /etc/systemd/system/forgevis.service.d/10-run-as-root.conf
sudo systemctl daemon-reload && sudo systemctl start forgevisTLS for the HTTP servers
Credentials travel over HTTP Basic, which means in the clear. Without TLS, anyone listening on the network between the client and the node reads them.
One setting turns it on for the whole node:
yaml
tls:
enabled: true
cert: /etc/forgevis/tls/node.pem
key: /etc/forgevis/tls/node.keyThat covers every HTTP listener at once: the management API, HLS, WebRTC signalling, archive playback and metrics.
The single switch is deliberate. A browser that loaded the interface over https will not fetch an HLS playlist or a WHEP endpoint over http — the mixed-content rule blocks the request. Per-listener settings would let you express a combination that cannot work, and the symptom would read as broken HLS rather than as a configuration mistake.
enabled is explicit rather than inferred from the paths being present, so the paths can stay in the file while TLS is switched off for an investigation. true without a readable certificate and key is a startup failure, not a quiet fallback to plain HTTP.
The certificate is read at startup, so replacing it needs a restart.
What this buys, and what it does not
Traffic is encrypted, so credentials stop crossing the wire in the clear. That is what the setting is for.
Server authentication is a separate matter and depends on the certificate. A self-signed one, or one issued by your own internal authority, is unknown to a browser: it shows a warning, and curl needs -k. For a client to verify who it is talking to, your certificate authority has to reach that client — which is the installation's job.
A self-signed certificate for a local network:
bash
openssl req -x509 -newkey rsa:2048 -nodes -days 825 \
-keyout node.key -out node.pem -subj "/CN=fv1.example.local" \
-addext "subjectAltName = DNS:fv1.example.local, IP:192.168.1.10"
chmod 600 node.keyThe SAN must carry the name or address the node is actually reached by — what goes into the address bar.
Not the same as cluster.tls
These are different things. tls is how a client verifies the node, and it can be switched off. cluster.tls is how nodes verify each other: it additionally needs clientAuth, and it cannot be switched off. See Clustering.
WebRTC media is always encrypted
This setting governs only the signalling HTTP (WHEP/WHIP). The stream itself is protected by DTLS-SRTP, which the protocol makes mandatory.
Security Recommendations
- Turn on
tls, or keep the nodes on a private network or VPN. - Prefer
argon2:for password storage. - Use separate users for
read,api, andmetricsactions. - Restrict privileged users with
ipsCIDR rules.