Skip to content

Commit

Permalink
TLS support (#11678)
Browse files Browse the repository at this point in the history
* implement self signed cert and monitor/reload

* move go2rtc upstream to separate file

* add directory for ACME challenges

* make certsync more resilient

* add TLS docs

* add jwt secret info to docs
  • Loading branch information
blakeblackshear authored Jun 1, 2024
1 parent 8418b65 commit bccffe6
Show file tree
Hide file tree
Showing 25 changed files with 633 additions and 282 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
certsync
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
certsync-pipeline
4 changes: 4 additions & 0 deletions docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync-log/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/command/with-contenv bash
# shellcheck shell=bash

exec logutil-service /dev/shm/logs/certsync
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
longrun
Empty file.
30 changes: 30 additions & 0 deletions docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/finish
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/command/with-contenv bash
# shellcheck shell=bash
# Take down the S6 supervision tree when the service fails

set -o errexit -o nounset -o pipefail

# Logs should be sent to stdout so that s6 can collect them

declare exit_code_container
exit_code_container=$(cat /run/s6-linux-init-container-results/exitcode)
readonly exit_code_container
readonly exit_code_service="${1}"
readonly exit_code_signal="${2}"
readonly service="CERTSYNC"

echo "[INFO] Service ${service} exited with code ${exit_code_service} (by signal ${exit_code_signal})"

if [[ "${exit_code_service}" -eq 256 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo $((128 + exit_code_signal)) >/run/s6-linux-init-container-results/exitcode
fi
if [[ "${exit_code_signal}" -eq 15 ]]; then
exec /run/s6/basedir/bin/halt
fi
elif [[ "${exit_code_service}" -ne 0 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo "${exit_code_service}" >/run/s6-linux-init-container-results/exitcode
fi
exec /run/s6/basedir/bin/halt
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
certsync-log
53 changes: 53 additions & 0 deletions docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/command/with-contenv bash
# shellcheck shell=bash
# Start the CERTSYNC service

set -o errexit -o nounset -o pipefail

# Logs should be sent to stdout so that s6 can collect them

echo "[INFO] Starting certsync..."

lefile="/etc/letsencrypt/live/frigate/fullchain.pem"


while true
do

if [ ! -e $lefile ]
then
echo "[ERROR] TLS certificate does not exist: $lefile"
fi

leprint=`openssl x509 -in $lefile -fingerprint -noout || echo 'failed'`

case "$leprint" in
*Fingerprint*)
;;
*)
echo "[ERROR] Missing fingerprint from $lefile"
;;
esac

liveprint=`echo | openssl s_client -showcerts -connect 127.0.0.1:443 2>&1 | openssl x509 -fingerprint | grep -i fingerprint || echo 'failed'`

case "$liveprint" in
*Fingerprint*)
;;
*)
echo "[ERROR] Missing fingerprint from current nginx TLS cert"
;;
esac

if [[ "$leprint" != "failed" && "$liveprint" != "failed" && "$leprint" != "$liveprint" ]]
then
echo "[INFO] Reloading nginx to refresh TLS certificate"
echo "$lefile: $leprint"
/usr/local/nginx/sbin/nginx -s reload
fi

sleep 60

done

exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
30000
1 change: 1 addition & 0 deletions docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/type
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
longrun
2 changes: 1 addition & 1 deletion docker/main/rootfs/etc/s6-overlay/s6-rc.d/log-prepare/run
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

set -o errexit -o nounset -o pipefail

dirs=(/dev/shm/logs/frigate /dev/shm/logs/go2rtc /dev/shm/logs/nginx)
dirs=(/dev/shm/logs/frigate /dev/shm/logs/go2rtc /dev/shm/logs/nginx /dev/shm/logs/certsync)

mkdir -p "${dirs[@]}"
chown nobody:nogroup "${dirs[@]}"
Expand Down
5 changes: 5 additions & 0 deletions docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/data/check
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e

# Wait for PID file to exist.
while ! test -f /run/nginx.pid; do sleep 1; done
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3
18 changes: 17 additions & 1 deletion docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/run
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ function set_worker_processes() {

set_worker_processes

# ensure the directory for ACME challenges exists
mkdir -p /etc/letsencrypt/www

# Create self signed certs if needed
letsencrypt_path=/etc/letsencrypt/live/frigate
mkdir -p $letsencrypt_path

if [ ! \( -f "$letsencrypt_path/privkey.pem" -a -f "$letsencrypt_path/fullchain.pem" \) ]; then
echo "[INFO] No TLS certificate found. Generating a self signed certificate..."
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
-subj "/O=FRIGATE DEFAULT CERT/CN=*" \
-keyout "$letsencrypt_path/privkey.pem" -out "$letsencrypt_path/fullchain.pem"
fi

# Replace the bash process with the NGINX process, redirecting stderr to stdout
exec 2>&1
exec nginx
exec \
s6-notifyoncheck -t 30000 -n 1 \
nginx
Empty file.
4 changes: 4 additions & 0 deletions docker/main/rootfs/usr/local/nginx/conf/go2rtc_upstream.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
upstream go2rtc {
server 127.0.0.1:1984;
keepalive 1024;
}
13 changes: 10 additions & 3 deletions docker/main/rootfs/usr/local/nginx/conf/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ http {
keepalive 1024;
}

upstream go2rtc {
server 127.0.0.1:1984;
keepalive 1024;
include go2rtc_upstream.conf;

server {
listen [::]:80 ipv6only=off default_server;

location / {
return 301 https://$host$request_uri;
}
}

server {
Expand All @@ -67,6 +72,8 @@ http {
# intended for internal traffic, not protected by auth
listen [::]:5000 ipv6only=off;

include tls.conf;

# vod settings
vod_base_url '';
vod_segments_base_url '';
Expand Down
24 changes: 24 additions & 0 deletions docker/main/rootfs/usr/local/nginx/conf/tls.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
keepalive_timeout 70;
listen [::]:443 ipv6only=off default_server ssl;

ssl_certificate /etc/letsencrypt/live/frigate/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/frigate/privkey.pem;

# generated 2024-06-01, Mozilla Guideline v5.7, nginx 1.25.3, OpenSSL 1.1.1w, modern configuration, no OCSP
# https://ssl-config.mozilla.org/#server=nginx&version=1.25.3&config=modern&openssl=1.1.1w&ocsp=false&guideline=5.7
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;

# modern configuration
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;

# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;

# ACME challenge location
location /.well-known/acme-challenge/ {
default_type "text/plain";
root /etc/letsencrypt/www;
}
21 changes: 21 additions & 0 deletions docs/docs/configuration/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,27 @@ auth:
- 172.18.0.0/16 # <---- this is the subnet for the internal docker compose network
```
#### JWT Token Secret
The JWT token secret needs to be kept secure. Anyone with this secret can generate valid JWT tokens to authenticate with Frigate. This should be a cryptographically random string of at least 64 characters.
You can generate a token using the Python secret library with the following command:
```shell
python3 -c 'import secrets; print(secrets.token_hex(64))'
```

Frigate looks for a JWT token secret in the following order:

1. An environment variable named `FRIGATE_JWT_SECRET`
2. A docker secret named `FRIGATE_JWT_SECRET` in `/run/secrets/`
3. A `jwt_secret` option from the Home Assistant Addon options
4. A `.jwt_secret` file in the config directory

If no secret is found on startup, Frigate generates one and stores it in a `.jwt_secret` file in the config directory.

Changing the secret will invalidate current tokens.

### Proxy mode

Proxy mode is designed to complement common upstream authentication proxies such as Authelia, Authentik, oauth2_proxy, or traefik-forward-auth.
Expand Down
34 changes: 34 additions & 0 deletions docs/docs/configuration/tls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
id: tls
title: TLS
---

# TLS

Frigate's integrated NGINX server supports TLS certificates. By default Frigate will generate a self signed certificate that will be used for port 443. Frigate is designed to make it easy to use whatever tool you prefer to manage certificates.

Frigate is often running behind a reverse proxy that manages TLS certificates for multiple services. However, if you are running on a device that's separate from your proxy or if you expose Frigate directly to the internet, you may want to configure TLS.

## Certificates

TLS certificates can be mounted at `/etc/letsencrypt/live/frigate` using a bind mount or docker volume.

```yaml
frigate:
...
volumes:
- /path/to/your/certificate_folder:/etc/letsencrypt/live/frigate
...
```

Within the folder, the private key is expected to be named `privkey.pem` and the certificate is expected to be named `fullchain.pem`.

Frigate automatically compares the fingerprint of the certificate at `/etc/letsencrypt/live/frigate/fullchain.pem` against the fingerprint of the TLS cert in NGINX every minute. If these differ, the NGINX config is reloaded to pick up the updated certificate.

## ACME Challenge

Frigate also supports hosting the acme challenge files for the HTTP challenge method if needed. The challenge files should be mounted at `/etc/letsencrypt/www`.

## Advanced customization

If you would like to customize the TLS configuration, you can do so by using a bind mount to override `/usr/local/nginx/conf/tls.conf`. Check the source code for the default configuration and modify from there.
4 changes: 2 additions & 2 deletions docs/docs/frigate/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ The following ports are used by Frigate and can be mapped via docker as required

| Port | Description |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `8080` | Authenticated UI and API access. Reverse proxies should use this port. |
| `8080` | Authenticated UI and API access without TLS. Reverse proxies should use this port. |
| `443` | Authenticated UI and API access with TLS. See the [TLS configuration](/configuration/tls) for more details. |
| `5000` | Internal unauthenticated UI and API access. Access to this port should be limited. Intended to be used within the docker network for services that integrate with Frigate. |
| `8554` | RTSP restreaming. By default, these streams are unauthenticated. Authentication can be configured in go2rtc section of config. |
| `8555` | WebRTC connections for low latency live views. |
Expand All @@ -44,7 +45,6 @@ The following ports are used by Frigate and can be mapped via docker as required
Writing to a local disk or external USB drive:

```yaml
version: "3.9"
services:
frigate:
...
Expand Down
Loading

0 comments on commit bccffe6

Please sign in to comment.