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
This commit is contained in:
Blake Blackshear 2024-06-01 10:29:46 -05:00 committed by GitHub
parent 8418b65f34
commit bccffe6670
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 623 additions and 272 deletions

View File

@ -0,0 +1 @@
certsync

View File

@ -0,0 +1 @@
certsync-pipeline

View File

@ -0,0 +1,4 @@
#!/command/with-contenv bash
# shellcheck shell=bash
exec logutil-service /dev/shm/logs/certsync

View File

@ -0,0 +1 @@
longrun

View File

@ -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

View File

@ -0,0 +1 @@
certsync-log

View File

@ -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

View File

@ -0,0 +1 @@
30000

View File

@ -0,0 +1 @@
longrun

View File

@ -4,7 +4,7 @@
set -o errexit -o nounset -o pipefail 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[@]}" mkdir -p "${dirs[@]}"
chown nobody:nogroup "${dirs[@]}" chown nobody:nogroup "${dirs[@]}"

View File

@ -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

View File

@ -0,0 +1 @@
3

View File

@ -22,6 +22,22 @@ function set_worker_processes() {
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 # Replace the bash process with the NGINX process, redirecting stderr to stdout
exec 2>&1 exec 2>&1
exec nginx exec \
s6-notifyoncheck -t 30000 -n 1 \
nginx

View File

@ -0,0 +1,4 @@
upstream go2rtc {
server 127.0.0.1:1984;
keepalive 1024;
}

View File

@ -56,9 +56,14 @@ http {
keepalive 1024; keepalive 1024;
} }
upstream go2rtc { include go2rtc_upstream.conf;
server 127.0.0.1:1984;
keepalive 1024; server {
listen [::]:80 ipv6only=off default_server;
location / {
return 301 https://$host$request_uri;
}
} }
server { server {
@ -67,6 +72,8 @@ http {
# intended for internal traffic, not protected by auth # intended for internal traffic, not protected by auth
listen [::]:5000 ipv6only=off; listen [::]:5000 ipv6only=off;
include tls.conf;
# vod settings # vod settings
vod_base_url ''; vod_base_url '';
vod_segments_base_url ''; vod_segments_base_url '';

View File

@ -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;
}

View File

@ -52,6 +52,27 @@ auth:
- 172.18.0.0/16 # <---- this is the subnet for the internal docker compose network - 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
Proxy mode is designed to complement common upstream authentication proxies such as Authelia, Authentik, oauth2_proxy, or traefik-forward-auth. Proxy mode is designed to complement common upstream authentication proxies such as Authelia, Authentik, oauth2_proxy, or traefik-forward-auth.

View File

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

View File

@ -34,7 +34,8 @@ The following ports are used by Frigate and can be mapped via docker as required
| Port | Description | | 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. | | `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. | | `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. | | `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
Writing to a local disk or external USB drive: Writing to a local disk or external USB drive:
```yaml ```yaml
version: "3.9"
services: services:
frigate: frigate:
... ...

665
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,9 @@
"write-heading-ids": "docusaurus write-heading-ids" "write-heading-ids": "docusaurus write-heading-ids"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "^3.3.2", "@docusaurus/core": "^3.4.0",
"@docusaurus/preset-classic": "^3.3.2", "@docusaurus/preset-classic": "^3.4.0",
"@docusaurus/theme-mermaid": "^3.3.2", "@docusaurus/theme-mermaid": "^3.4.0",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"prism-react-renderer": "^2.1.0", "prism-react-renderer": "^2.1.0",
@ -37,8 +37,8 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "^3.3.2", "@docusaurus/module-type-aliases": "^3.4.0",
"@docusaurus/types": "^3.3.2", "@docusaurus/types": "^3.4.0",
"@types/react": "^18.2.79" "@types/react": "^18.2.79"
}, },
"engines": { "engines": {

View File

@ -52,6 +52,7 @@ module.exports = {
"configuration/authentication", "configuration/authentication",
"configuration/hardware_acceleration", "configuration/hardware_acceleration",
"configuration/ffmpeg_presets", "configuration/ffmpeg_presets",
"configuration/tls",
"configuration/advanced", "configuration/advanced",
], ],
}, },