mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-15 03:02:49 +03:00
Resolve go2rtc stream placeholders the same way everywhere
The go2rtc startup script expanded stream sources with str.format(), which also supports attribute and index access, while the config API check used substitute_frigate_vars and fell back to the raw string when a placeholder could not be resolved. A placeholder the API check could not resolve could therefore expand into a restricted source at startup. The startup script now uses substitute_frigate_vars, like the rest of the config, and the API check rejects a stream source whose placeholders cannot be resolved. The startup script's own copy of the FRIGATE_* env var map is removed; the shared one also honors CREDENTIALS_DIRECTORY. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
21f5ab5a1e
commit
ac0f39268c
@@ -3,7 +3,6 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
@@ -25,15 +24,6 @@ sys.path.remove("/opt/frigate")
|
||||
|
||||
yaml = YAML()
|
||||
|
||||
FRIGATE_ENV_VARS = {k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")}
|
||||
# read docker secret files as env vars too
|
||||
if os.path.isdir("/run/secrets"):
|
||||
for secret_file in os.listdir("/run/secrets"):
|
||||
if secret_file.startswith("FRIGATE_"):
|
||||
FRIGATE_ENV_VARS[secret_file] = (
|
||||
Path(os.path.join("/run/secrets", secret_file)).read_text().strip()
|
||||
)
|
||||
|
||||
config_file = find_config_file()
|
||||
|
||||
try:
|
||||
@@ -113,7 +103,7 @@ for name in list(go2rtc_config.get("streams", {})):
|
||||
|
||||
if isinstance(stream, str):
|
||||
try:
|
||||
formatted_stream = stream.format(**FRIGATE_ENV_VARS)
|
||||
formatted_stream = substitute_frigate_vars(stream)
|
||||
if is_restricted_go2rtc_source(formatted_stream):
|
||||
print(
|
||||
f"[ERROR] Stream '{name}' uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
||||
@@ -122,7 +112,7 @@ for name in list(go2rtc_config.get("streams", {})):
|
||||
del go2rtc_config["streams"][name]
|
||||
continue
|
||||
go2rtc_config["streams"][name] = formatted_stream
|
||||
except KeyError as e:
|
||||
except (KeyError, ValueError) as e:
|
||||
print(
|
||||
"[ERROR] Invalid substitution found, see https://docs.frigate.video/configuration/restream#advanced-restream-configurations for more info."
|
||||
)
|
||||
@@ -132,7 +122,7 @@ for name in list(go2rtc_config.get("streams", {})):
|
||||
filtered_streams = []
|
||||
for i, stream_item in enumerate(stream):
|
||||
try:
|
||||
formatted_stream = stream_item.format(**FRIGATE_ENV_VARS)
|
||||
formatted_stream = substitute_frigate_vars(stream_item)
|
||||
if is_restricted_go2rtc_source(formatted_stream):
|
||||
print(
|
||||
f"[ERROR] Stream '{name}' item {i + 1} uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
||||
@@ -141,7 +131,7 @@ for name in list(go2rtc_config.get("streams", {})):
|
||||
continue
|
||||
|
||||
filtered_streams.append(formatted_stream)
|
||||
except KeyError as e:
|
||||
except (KeyError, ValueError) as e:
|
||||
print(
|
||||
"[ERROR] Invalid substitution found, see https://docs.frigate.video/configuration/restream#advanced-restream-configurations for more info."
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ from unittest.mock import patch
|
||||
|
||||
import ruamel.yaml
|
||||
|
||||
from frigate.config.env import FRIGATE_ENV_VARS
|
||||
from frigate.models import Event, Recordings, ReviewSegment
|
||||
from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp
|
||||
|
||||
@@ -167,3 +168,24 @@ class TestConfigGo2rtcStreamRestrictions(BaseTestHttp):
|
||||
assert resp.status_code == 400
|
||||
assert "Restricted go2rtc stream source" in resp.json()["message"]
|
||||
assert self._read_config() == before
|
||||
|
||||
# --- {FRIGATE_*} placeholders resolve the same way as at go2rtc startup ---
|
||||
|
||||
def test_set_rejects_unresolvable_placeholder(self):
|
||||
before = self._read_config()
|
||||
with patch.dict(FRIGATE_ENV_VARS, {"FRIGATE_RTSP_USER": "user"}):
|
||||
for src in (
|
||||
"{FRIGATE_RTSP_USER!r}fmpeg:front_door#raw=-vn",
|
||||
"{FRIGATE_RTSP_USER:>5}",
|
||||
"rtsp://{FRIGATE_UNDEFINED}@10.0.0.2:554/video",
|
||||
):
|
||||
resp = self._set_streams({"new_stream": [src]})
|
||||
assert resp.status_code == 400, src
|
||||
assert self._read_config() == before
|
||||
|
||||
def test_set_allows_resolvable_placeholder(self):
|
||||
src = "rtsp://{FRIGATE_RTSP_USER}@10.0.0.2:554/video"
|
||||
with patch.dict(FRIGATE_ENV_VARS, {"FRIGATE_RTSP_USER": "user"}):
|
||||
resp = self._set_streams({"new_stream": [src]})
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert src in self._read_config()
|
||||
|
||||
@@ -1073,10 +1073,13 @@ def find_restricted_go2rtc_stream_changes(
|
||||
restricted.append(name)
|
||||
break
|
||||
|
||||
# go2rtc startup resolves sources with the same substitution, and
|
||||
# refuses to start on one it cannot resolve
|
||||
try:
|
||||
resolved_source = substitute_frigate_vars(source)
|
||||
except (KeyError, ValueError):
|
||||
resolved_source = source
|
||||
restricted.append(name)
|
||||
break
|
||||
|
||||
if is_restricted_go2rtc_api_source(
|
||||
source
|
||||
|
||||
Reference in New Issue
Block a user