◆ ForgeVis
Skip to content

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:

  1. IP/CIDR allow-list (ips)
  2. Permission (action, optional path)
  3. 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:

PrefixFormatWhen 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.

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: read

SHA256 Example (legacy) ​

bash
echo -n "mypass" | openssl dgst -binary -sha256 | openssl base64
yaml
security:
  auth_method: internal
  users:
    - user: viewer
      pass: sha256:6nHCWnpgIka0w5gkuFVniJSpb0O7m3ExnDlwCh4EUiI=
      ips: []
      permissions:
        - action: read

Note: 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: read

Migration Strategy ​

Previous releases accepted unprefixed values as plaintext (silent catch-all). That fallback is removed; rehash existing credentials:

  1. Generate new argon2: (or sha256:) values for every user.
  2. Update config, restart.
  3. 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 to forgevis: chown forgevis:forgevis <directory>. The default, /var/lib/forgevis/recordings, already does.
  • Hooks (runOnSegmentCreate, runOnSegmentComplete, runPeriodically) run as forgevis under the same isolation: they cannot write to /etc, /usr, /boot or home directories.
  • TLS keys in /etc/forgevis/tls/ have to be readable by the forgevis group: chown root:forgevis node.key && chmod 640 node.key.
  • forgevis --retention from 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 forgevis

TLS 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.key

That 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.key

The 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, and metrics actions.
  • Restrict privileged users with ips CIDR rules.

Proprietary software.