mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
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:
parent
8418b65f34
commit
bccffe6670
@ -0,0 +1 @@
|
|||||||
|
certsync
|
@ -0,0 +1 @@
|
|||||||
|
certsync-pipeline
|
4
docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync-log/run
Executable file
4
docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync-log/run
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/command/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
exec logutil-service /dev/shm/logs/certsync
|
@ -0,0 +1 @@
|
|||||||
|
longrun
|
30
docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/finish
Executable file
30
docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/finish
Executable 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
|
@ -0,0 +1 @@
|
|||||||
|
certsync-log
|
53
docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/run
Executable file
53
docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/run
Executable 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
|
@ -0,0 +1 @@
|
|||||||
|
30000
|
1
docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/type
Normal file
1
docker/main/rootfs/etc/s6-overlay/s6-rc.d/certsync/type
Normal file
@ -0,0 +1 @@
|
|||||||
|
longrun
|
@ -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[@]}"
|
||||||
|
5
docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/data/check
Executable file
5
docker/main/rootfs/etc/s6-overlay/s6-rc.d/nginx/data/check
Executable 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
|
@ -0,0 +1 @@
|
|||||||
|
3
|
@ -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
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
upstream go2rtc {
|
||||||
|
server 127.0.0.1:1984;
|
||||||
|
keepalive 1024;
|
||||||
|
}
|
@ -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 '';
|
||||||
|
24
docker/main/rootfs/usr/local/nginx/conf/tls.conf
Normal file
24
docker/main/rootfs/usr/local/nginx/conf/tls.conf
Normal 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;
|
||||||
|
}
|
@ -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.
|
||||||
|
34
docs/docs/configuration/tls.md
Normal file
34
docs/docs/configuration/tls.md
Normal 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.
|
@ -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
665
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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": {
|
||||||
|
@ -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",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user