Skip to content

Commit bccffe6

Browse files
TLS support (#11678)
* 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
1 parent 8418b65 commit bccffe6

File tree

25 files changed

+633
-282
lines changed

25 files changed

+633
-282
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
certsync

docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync-log/dependencies.d/log-prepare

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
certsync-pipeline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/command/with-contenv bash
2+
# shellcheck shell=bash
3+
4+
exec logutil-service /dev/shm/logs/certsync
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
longrun

docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/dependencies.d/nginx

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/command/with-contenv bash
2+
# shellcheck shell=bash
3+
# Take down the S6 supervision tree when the service fails
4+
5+
set -o errexit -o nounset -o pipefail
6+
7+
# Logs should be sent to stdout so that s6 can collect them
8+
9+
declare exit_code_container
10+
exit_code_container=$(cat /run/s6-linux-init-container-results/exitcode)
11+
readonly exit_code_container
12+
readonly exit_code_service="${1}"
13+
readonly exit_code_signal="${2}"
14+
readonly service="CERTSYNC"
15+
16+
echo "[INFO] Service ${service} exited with code ${exit_code_service} (by signal ${exit_code_signal})"
17+
18+
if [[ "${exit_code_service}" -eq 256 ]]; then
19+
if [[ "${exit_code_container}" -eq 0 ]]; then
20+
echo $((128 + exit_code_signal)) >/run/s6-linux-init-container-results/exitcode
21+
fi
22+
if [[ "${exit_code_signal}" -eq 15 ]]; then
23+
exec /run/s6/basedir/bin/halt
24+
fi
25+
elif [[ "${exit_code_service}" -ne 0 ]]; then
26+
if [[ "${exit_code_container}" -eq 0 ]]; then
27+
echo "${exit_code_service}" >/run/s6-linux-init-container-results/exitcode
28+
fi
29+
exec /run/s6/basedir/bin/halt
30+
fi
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
certsync-log
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/command/with-contenv bash
2+
# shellcheck shell=bash
3+
# Start the CERTSYNC service
4+
5+
set -o errexit -o nounset -o pipefail
6+
7+
# Logs should be sent to stdout so that s6 can collect them
8+
9+
echo "[INFO] Starting certsync..."
10+
11+
lefile="/etc/letsencrypt/live/frigate/fullchain.pem"
12+
13+
14+
while true
15+
do
16+
17+
if [ ! -e $lefile ]
18+
then
19+
echo "[ERROR] TLS certificate does not exist: $lefile"
20+
fi
21+
22+
leprint=`openssl x509 -in $lefile -fingerprint -noout || echo 'failed'`
23+
24+
case "$leprint" in
25+
*Fingerprint*)
26+
;;
27+
*)
28+
echo "[ERROR] Missing fingerprint from $lefile"
29+
;;
30+
esac
31+
32+
liveprint=`echo | openssl s_client -showcerts -connect 127.0.0.1:443 2>&1 | openssl x509 -fingerprint | grep -i fingerprint || echo 'failed'`
33+
34+
case "$liveprint" in
35+
*Fingerprint*)
36+
;;
37+
*)
38+
echo "[ERROR] Missing fingerprint from current nginx TLS cert"
39+
;;
40+
esac
41+
42+
if [[ "$leprint" != "failed" && "$liveprint" != "failed" && "$leprint" != "$liveprint" ]]
43+
then
44+
echo "[INFO] Reloading nginx to refresh TLS certificate"
45+
echo "$lefile: $leprint"
46+
/usr/local/nginx/sbin/nginx -s reload
47+
fi
48+
49+
sleep 60
50+
51+
done
52+
53+
exit 0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
30000
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
longrun

docker/main/rootfs/etc/s6-overlay/s6-rc.d/log-prepare/run

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
set -o errexit -o nounset -o pipefail
66

7-
dirs=(/dev/shm/logs/frigate /dev/shm/logs/go2rtc /dev/shm/logs/nginx)
7+
dirs=(/dev/shm/logs/frigate /dev/shm/logs/go2rtc /dev/shm/logs/nginx /dev/shm/logs/certsync)
88

99
mkdir -p "${dirs[@]}"
1010
chown nobody:nogroup "${dirs[@]}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
# Wait for PID file to exist.
5+
while ! test -f /run/nginx.pid; do sleep 1; done
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3

docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/run

+17-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@ function set_worker_processes() {
2222

2323
set_worker_processes
2424

25+
# ensure the directory for ACME challenges exists
26+
mkdir -p /etc/letsencrypt/www
27+
28+
# Create self signed certs if needed
29+
letsencrypt_path=/etc/letsencrypt/live/frigate
30+
mkdir -p $letsencrypt_path
31+
32+
if [ ! \( -f "$letsencrypt_path/privkey.pem" -a -f "$letsencrypt_path/fullchain.pem" \) ]; then
33+
echo "[INFO] No TLS certificate found. Generating a self signed certificate..."
34+
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
35+
-subj "/O=FRIGATE DEFAULT CERT/CN=*" \
36+
-keyout "$letsencrypt_path/privkey.pem" -out "$letsencrypt_path/fullchain.pem"
37+
fi
38+
2539
# Replace the bash process with the NGINX process, redirecting stderr to stdout
2640
exec 2>&1
27-
exec nginx
41+
exec \
42+
s6-notifyoncheck -t 30000 -n 1 \
43+
nginx

docker/main/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/certsync-pipeline

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
upstream go2rtc {
2+
server 127.0.0.1:1984;
3+
keepalive 1024;
4+
}

docker/main/rootfs/usr/local/nginx/conf/nginx.conf

+10-3
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,14 @@ http {
5656
keepalive 1024;
5757
}
5858

59-
upstream go2rtc {
60-
server 127.0.0.1:1984;
61-
keepalive 1024;
59+
include go2rtc_upstream.conf;
60+
61+
server {
62+
listen [::]:80 ipv6only=off default_server;
63+
64+
location / {
65+
return 301 https://$host$request_uri;
66+
}
6267
}
6368

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

75+
include tls.conf;
76+
7077
# vod settings
7178
vod_base_url '';
7279
vod_segments_base_url '';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
keepalive_timeout 70;
2+
listen [::]:443 ipv6only=off default_server ssl;
3+
4+
ssl_certificate /etc/letsencrypt/live/frigate/fullchain.pem;
5+
ssl_certificate_key /etc/letsencrypt/live/frigate/privkey.pem;
6+
7+
# generated 2024-06-01, Mozilla Guideline v5.7, nginx 1.25.3, OpenSSL 1.1.1w, modern configuration, no OCSP
8+
# https://ssl-config.mozilla.org/#server=nginx&version=1.25.3&config=modern&openssl=1.1.1w&ocsp=false&guideline=5.7
9+
ssl_session_timeout 1d;
10+
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
11+
ssl_session_tickets off;
12+
13+
# modern configuration
14+
ssl_protocols TLSv1.3;
15+
ssl_prefer_server_ciphers off;
16+
17+
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
18+
add_header Strict-Transport-Security "max-age=63072000" always;
19+
20+
# ACME challenge location
21+
location /.well-known/acme-challenge/ {
22+
default_type "text/plain";
23+
root /etc/letsencrypt/www;
24+
}

docs/docs/configuration/authentication.md

+21
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,27 @@ auth:
5252
- 172.18.0.0/16 # <---- this is the subnet for the internal docker compose network
5353
```
5454
55+
#### JWT Token Secret
56+
57+
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.
58+
59+
You can generate a token using the Python secret library with the following command:
60+
61+
```shell
62+
python3 -c 'import secrets; print(secrets.token_hex(64))'
63+
```
64+
65+
Frigate looks for a JWT token secret in the following order:
66+
67+
1. An environment variable named `FRIGATE_JWT_SECRET`
68+
2. A docker secret named `FRIGATE_JWT_SECRET` in `/run/secrets/`
69+
3. A `jwt_secret` option from the Home Assistant Addon options
70+
4. A `.jwt_secret` file in the config directory
71+
72+
If no secret is found on startup, Frigate generates one and stores it in a `.jwt_secret` file in the config directory.
73+
74+
Changing the secret will invalidate current tokens.
75+
5576
### Proxy mode
5677

5778
Proxy mode is designed to complement common upstream authentication proxies such as Authelia, Authentik, oauth2_proxy, or traefik-forward-auth.

docs/docs/configuration/tls.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
id: tls
3+
title: TLS
4+
---
5+
6+
# TLS
7+
8+
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.
9+
10+
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.
11+
12+
## Certificates
13+
14+
TLS certificates can be mounted at `/etc/letsencrypt/live/frigate` using a bind mount or docker volume.
15+
16+
```yaml
17+
frigate:
18+
...
19+
volumes:
20+
- /path/to/your/certificate_folder:/etc/letsencrypt/live/frigate
21+
...
22+
```
23+
24+
Within the folder, the private key is expected to be named `privkey.pem` and the certificate is expected to be named `fullchain.pem`.
25+
26+
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.
27+
28+
## ACME Challenge
29+
30+
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`.
31+
32+
## Advanced customization
33+
34+
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.

docs/docs/frigate/installation.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ The following ports are used by Frigate and can be mapped via docker as required
3434

3535
| Port | Description |
3636
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
37-
| `8080` | Authenticated UI and API access. Reverse proxies should use this port. |
37+
| `8080` | Authenticated UI and API access without TLS. Reverse proxies should use this port. |
38+
| `443` | Authenticated UI and API access with TLS. See the [TLS configuration](/configuration/tls) for more details. |
3839
| `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. |
3940
| `8554` | RTSP restreaming. By default, these streams are unauthenticated. Authentication can be configured in go2rtc section of config. |
4041
| `8555` | WebRTC connections for low latency live views. |
@@ -44,7 +45,6 @@ The following ports are used by Frigate and can be mapped via docker as required
4445
Writing to a local disk or external USB drive:
4546

4647
```yaml
47-
version: "3.9"
4848
services:
4949
frigate:
5050
...

0 commit comments

Comments
 (0)