mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-11-08 06:33:01 +03:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06c23ab51b | ||
|
|
e04035dfa2 |
@@ -14,11 +14,17 @@ fi
|
|||||||
# Tell S6-Overlay not to restart this service
|
# Tell S6-Overlay not to restart this service
|
||||||
s6-svc -O .
|
s6-svc -O .
|
||||||
|
|
||||||
# Runs as root with root's HOME and LD_LIBRARY_PATH; -I skips user site-packages
|
# The helper runs as root (-I skips user site-packages). The probe runs as the
|
||||||
|
# service user when the service drops privileges; a root probe relies on
|
||||||
|
# resolve_ffmpeg_path refusing runtime-user-writable paths.
|
||||||
function set_libva_version() {
|
function set_libva_version() {
|
||||||
local ffmpeg_path
|
local ffmpeg_path
|
||||||
ffmpeg_path=$(python3 -I /usr/local/ffmpeg/get_ffmpeg_path.py)
|
local probe_as=()
|
||||||
LIBAVFORMAT_VERSION_MAJOR=$("$ffmpeg_path" -version | grep -Po "libavformat\W+\K\d+")
|
if [[ "$(id -u)" -eq 0 && "$runs_as_root" -eq 0 ]]; then
|
||||||
|
probe_as=(s6-setuidgid frigate)
|
||||||
|
fi
|
||||||
|
ffmpeg_path=$(python3 -I /usr/local/ffmpeg/get_ffmpeg_path.py frigate)
|
||||||
|
LIBAVFORMAT_VERSION_MAJOR=$("${probe_as[@]}" "$ffmpeg_path" -version | grep -Po "libavformat\W+\K\d+")
|
||||||
export LIBAVFORMAT_VERSION_MAJOR
|
export LIBAVFORMAT_VERSION_MAJOR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,19 @@ function get_ip_and_port_from_supervisor() {
|
|||||||
export FRIGATE_GO2RTC_WEBRTC_CANDIDATE_INTERNAL="${ip_address}:${webrtc_port}"
|
export FRIGATE_GO2RTC_WEBRTC_CANDIDATE_INTERNAL="${ip_address}:${webrtc_port}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The probe runs as the service user when the service drops privileges; a root
|
||||||
|
# probe relies on resolve_ffmpeg_path refusing runtime-user-writable paths
|
||||||
function set_libva_version() {
|
function set_libva_version() {
|
||||||
local ffmpeg_path
|
local ffmpeg_path
|
||||||
ffmpeg_path=$(python3 -I /usr/local/ffmpeg/get_ffmpeg_path.py)
|
local probe_as=()
|
||||||
LIBAVFORMAT_VERSION_MAJOR=$("$ffmpeg_path" -version | grep -Po "libavformat\W+\K\d+")
|
if [[ "$(id -u)" -eq 0 && "$runs_as_root" -eq 0 ]]; then
|
||||||
|
probe_as=(s6-setuidgid go2rtc)
|
||||||
|
fi
|
||||||
|
ffmpeg_path=$(python3 -I /usr/local/ffmpeg/get_ffmpeg_path.py go2rtc)
|
||||||
|
if ! LIBAVFORMAT_VERSION_MAJOR=$("${probe_as[@]}" "$ffmpeg_path" -version | grep -Po "libavformat\W+\K\d+"); then
|
||||||
|
echo "[WARN] Could not read the libavformat version from ${ffmpeg_path} as the go2rtc user; using the bundled build's version"
|
||||||
|
LIBAVFORMAT_VERSION_MAJOR=$("${probe_as[@]}" "/usr/lib/ffmpeg/${DEFAULT_FFMPEG_VERSION}/bin/ffmpeg" -version | grep -Po "libavformat\W+\K\d+")
|
||||||
|
fi
|
||||||
export LIBAVFORMAT_VERSION_MAJOR
|
export LIBAVFORMAT_VERSION_MAJOR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,4 +25,5 @@ except FileNotFoundError:
|
|||||||
config: dict[str, Any] = {}
|
config: dict[str, Any] = {}
|
||||||
|
|
||||||
path = config.get("ffmpeg", {}).get("path", "default")
|
path = config.get("ffmpeg", {}).get("path", "default")
|
||||||
print(resolve_ffmpeg_path(path, "ffmpeg"))
|
service = sys.argv[1] if len(sys.argv) > 1 else "frigate"
|
||||||
|
print(resolve_ffmpeg_path(path, "ffmpeg", service))
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ if not is_valid_ffmpeg_path(path):
|
|||||||
print(
|
print(
|
||||||
"[ERROR] ffmpeg path contains spaces, quotes or shell characters, using the default ffmpeg instead."
|
"[ERROR] ffmpeg path contains spaces, quotes or shell characters, using the default ffmpeg instead."
|
||||||
)
|
)
|
||||||
ffmpeg_path = resolve_ffmpeg_path(path, "ffmpeg")
|
ffmpeg_path = resolve_ffmpeg_path(path, "ffmpeg", "go2rtc")
|
||||||
|
|
||||||
for key in apply_go2rtc_ffmpeg_config(go2rtc_config, ffmpeg_path):
|
for key in apply_go2rtc_ffmpeg_config(go2rtc_config, ffmpeg_path):
|
||||||
print(
|
print(
|
||||||
|
|||||||
@@ -398,9 +398,9 @@ To do this:
|
|||||||
2. Update the `ffmpeg.path` in your Frigate config to `/config/custom-ffmpeg`.
|
2. Update the `ffmpeg.path` in your Frigate config to `/config/custom-ffmpeg`.
|
||||||
3. Restart Frigate and the custom version will be used if the steps above were done correctly.
|
3. Restart Frigate and the custom version will be used if the steps above were done correctly.
|
||||||
|
|
||||||
Both binaries have to be executable by Frigate's unprivileged runtime user, so `chmod 755` them after extracting. The startup ownership sweep runs only once, so anything you add to `/config` later keeps whatever ownership and mode you gave it.
|
Both binaries have to be executable by Frigate's unprivileged runtime user, so `chmod 755` them after extracting. go2rtc runs as its own user, so the build must be world-executable for go2rtc to use it (`ffmpeg:` sources, the birdseye restream and transcoded live streams). The startup ownership sweep runs only once, so anything you add to `/config` later keeps whatever ownership and mode you gave it.
|
||||||
|
|
||||||
There is one exception, and it only affects [`FRIGATE_ROOT_SERVICES`](/configuration/non_root#keeping-individual-services-root) listing `frigate`. That mode runs Frigate as root while still handing `/config` to the unprivileged runtime user, so anything running as that user could swap the binary and gain root. A build inside any of Frigate's writable volumes (`/config`, `/media/frigate`, the cache and shm dirs) is ignored there and the bundled one is used, with a warning in the log. Keep the build somewhere root-owned (any absolute `ffmpeg.path` works, so a read-only bind mount such as `/opt/custom-ffmpeg` is enough) if you need both. The default mode and `FRIGATE_RUN_AS_ROOT=true` are unaffected and behave exactly as they always have.
|
There is one exception, and it only affects [`FRIGATE_ROOT_SERVICES`](/configuration/non_root#keeping-individual-services-root) listing `frigate` or `go2rtc`. That mode runs the listed service as root while still handing `/config` to the unprivileged runtime user, so anything running as that user could swap the binary and gain root. A build inside any of Frigate's writable volumes (`/config`, `/media/frigate`, the cache and shm dirs) is ignored by the root service, which uses the bundled one and logs a warning. A service that is not listed keeps using the custom build. Keep the build somewhere root-owned (any absolute `ffmpeg.path` works, so a read-only bind mount such as `/opt/custom-ffmpeg` is enough) if you need both. The default mode and `FRIGATE_RUN_AS_ROOT=true` are unaffected and behave exactly as they always have.
|
||||||
|
|
||||||
### Custom go2rtc version
|
### Custom go2rtc version
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
"""Tests for custom ffmpeg path resolution and the root-mode guard."""
|
"""Tests for custom ffmpeg path resolution and the root-mode guard."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from frigate.const import DEFAULT_FFMPEG_VERSION
|
from frigate.const import DEFAULT_FFMPEG_VERSION
|
||||||
from frigate.util.config import (
|
from frigate.util.config import (
|
||||||
_warn_ignored_ffmpeg_path,
|
_warn_ignored_ffmpeg_path,
|
||||||
frigate_service_is_granular_root,
|
|
||||||
resolve_ffmpeg_path,
|
resolve_ffmpeg_path,
|
||||||
|
service_is_granular_root,
|
||||||
)
|
)
|
||||||
|
|
||||||
BUNDLED = f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffmpeg"
|
BUNDLED = f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffmpeg"
|
||||||
@@ -21,12 +23,20 @@ class TestConfigFfmpegRootGuard(unittest.TestCase):
|
|||||||
# the warning is memoized so it fires once per path, not per camera
|
# the warning is memoized so it fires once per path, not per camera
|
||||||
_warn_ignored_ffmpeg_path.cache_clear()
|
_warn_ignored_ffmpeg_path.cache_clear()
|
||||||
|
|
||||||
def _resolve(self, path: str, *, euid: int, env: dict, binary: str = "ffmpeg"):
|
def _resolve(
|
||||||
|
self,
|
||||||
|
path: str,
|
||||||
|
*,
|
||||||
|
euid: int,
|
||||||
|
env: dict,
|
||||||
|
binary: str = "ffmpeg",
|
||||||
|
service: str = "frigate",
|
||||||
|
):
|
||||||
with (
|
with (
|
||||||
patch("os.geteuid", return_value=euid),
|
patch("os.geteuid", return_value=euid),
|
||||||
patch.dict("os.environ", env, clear=True),
|
patch.dict("os.environ", env, clear=True),
|
||||||
):
|
):
|
||||||
return resolve_ffmpeg_path(path, binary)
|
return resolve_ffmpeg_path(path, binary, service)
|
||||||
|
|
||||||
def test_custom_path_used_when_service_is_unprivileged(self) -> None:
|
def test_custom_path_used_when_service_is_unprivileged(self) -> None:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -65,6 +75,59 @@ class TestConfigFfmpegRootGuard(unittest.TestCase):
|
|||||||
f"{CUSTOM}/bin/ffmpeg",
|
f"{CUSTOM}/bin/ffmpeg",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_custom_path_ignored_for_go2rtc_when_go2rtc_is_a_root_service(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
self.assertEqual(
|
||||||
|
self._resolve(
|
||||||
|
CUSTOM,
|
||||||
|
euid=0,
|
||||||
|
env={"FRIGATE_ROOT_SERVICES": "go2rtc"},
|
||||||
|
service="go2rtc",
|
||||||
|
),
|
||||||
|
BUNDLED,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_go2rtc_keeps_custom_path_when_only_frigate_is_root(self) -> None:
|
||||||
|
self.assertEqual(
|
||||||
|
self._resolve(
|
||||||
|
CUSTOM,
|
||||||
|
euid=0,
|
||||||
|
env={"FRIGATE_ROOT_SERVICES": "frigate"},
|
||||||
|
service="go2rtc",
|
||||||
|
),
|
||||||
|
f"{CUSTOM}/bin/ffmpeg",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_go2rtc_custom_path_used_in_default_drop_mode(self) -> None:
|
||||||
|
# the run scripts resolve as root but probe as the service user
|
||||||
|
self.assertEqual(
|
||||||
|
self._resolve(CUSTOM, euid=0, env={}, service="go2rtc"),
|
||||||
|
f"{CUSTOM}/bin/ffmpeg",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_escape_hatch_keeps_go2rtc_custom_path(self) -> None:
|
||||||
|
self.assertEqual(
|
||||||
|
self._resolve(
|
||||||
|
CUSTOM,
|
||||||
|
euid=0,
|
||||||
|
env={"FRIGATE_RUN_AS_ROOT": "true", "FRIGATE_ROOT_SERVICES": "go2rtc"},
|
||||||
|
service="go2rtc",
|
||||||
|
),
|
||||||
|
f"{CUSTOM}/bin/ffmpeg",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_go2rtc_root_owned_path_is_left_alone(self) -> None:
|
||||||
|
self.assertEqual(
|
||||||
|
self._resolve(
|
||||||
|
"/opt/custom-ffmpeg",
|
||||||
|
euid=0,
|
||||||
|
env={"FRIGATE_ROOT_SERVICES": "go2rtc"},
|
||||||
|
service="go2rtc",
|
||||||
|
),
|
||||||
|
"/opt/custom-ffmpeg/bin/ffmpeg",
|
||||||
|
)
|
||||||
|
|
||||||
def test_root_services_has_no_effect_under_docker_user(self) -> None:
|
def test_root_services_has_no_effect_under_docker_user(self) -> None:
|
||||||
# docker's own user: means the service never had root to keep, which is
|
# docker's own user: means the service never had root to keep, which is
|
||||||
# also the case for get_ffmpeg_path.py in a --user container
|
# also the case for get_ffmpeg_path.py in a --user container
|
||||||
@@ -115,15 +178,65 @@ class TestConfigFfmpegRootGuard(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestFrigateServiceIsGranularRoot(unittest.TestCase):
|
class TestConfigFfmpegRootGuardSymlinks(unittest.TestCase):
|
||||||
|
"""A root service gets the resolved path, so a link cannot be retargeted."""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
_warn_ignored_ffmpeg_path.cache_clear()
|
||||||
|
tmp = tempfile.TemporaryDirectory()
|
||||||
|
self.addCleanup(tmp.cleanup)
|
||||||
|
root = os.path.realpath(tmp.name)
|
||||||
|
self.writable = os.path.join(root, "config")
|
||||||
|
self.root_owned = os.path.join(root, "opt", "ffmpeg")
|
||||||
|
os.makedirs(self.writable)
|
||||||
|
os.makedirs(self.root_owned)
|
||||||
|
self.link = os.path.join(self.writable, "link")
|
||||||
|
dirs = patch("frigate.util.config.RUNTIME_USER_WRITABLE_DIRS", (self.writable,))
|
||||||
|
dirs.start()
|
||||||
|
self.addCleanup(dirs.stop)
|
||||||
|
|
||||||
|
def _resolve(self, env: dict, service: str = "frigate") -> str:
|
||||||
|
with (
|
||||||
|
patch("os.geteuid", return_value=0),
|
||||||
|
patch.dict("os.environ", env, clear=True),
|
||||||
|
):
|
||||||
|
return resolve_ffmpeg_path(self.link, "ffmpeg", service)
|
||||||
|
|
||||||
|
def test_link_to_a_root_owned_tree_resolves_to_its_target(self) -> None:
|
||||||
|
os.symlink(self.root_owned, self.link)
|
||||||
|
for service in ("frigate", "go2rtc"):
|
||||||
|
self.assertEqual(
|
||||||
|
self._resolve({"FRIGATE_ROOT_SERVICES": service}, service),
|
||||||
|
f"{self.root_owned}/bin/ffmpeg",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_link_to_a_writable_tree_is_refused(self) -> None:
|
||||||
|
evil = os.path.join(self.writable, "evil")
|
||||||
|
os.makedirs(evil)
|
||||||
|
os.symlink(evil, self.link)
|
||||||
|
self.assertEqual(self._resolve({"FRIGATE_ROOT_SERVICES": "frigate"}), BUNDLED)
|
||||||
|
|
||||||
|
def test_bin_linked_into_a_writable_tree_is_refused(self) -> None:
|
||||||
|
evil = os.path.join(self.writable, "evil")
|
||||||
|
os.makedirs(evil)
|
||||||
|
os.symlink(evil, os.path.join(self.root_owned, "bin"))
|
||||||
|
os.symlink(self.root_owned, self.link)
|
||||||
|
self.assertEqual(self._resolve({"FRIGATE_ROOT_SERVICES": "frigate"}), BUNDLED)
|
||||||
|
|
||||||
|
def test_unprivileged_service_keeps_the_path_as_written(self) -> None:
|
||||||
|
os.symlink(self.root_owned, self.link)
|
||||||
|
self.assertEqual(self._resolve({}), f"{self.link}/bin/ffmpeg")
|
||||||
|
|
||||||
|
|
||||||
|
class TestServiceIsGranularRoot(unittest.TestCase):
|
||||||
"""Root via FRIGATE_ROOT_SERVICES only, never via the escape hatch."""
|
"""Root via FRIGATE_ROOT_SERVICES only, never via the escape hatch."""
|
||||||
|
|
||||||
def _check(self, *, euid: int, env: dict) -> bool:
|
def _check(self, *, euid: int, env: dict, service: str = "frigate") -> bool:
|
||||||
with (
|
with (
|
||||||
patch("os.geteuid", return_value=euid),
|
patch("os.geteuid", return_value=euid),
|
||||||
patch.dict("os.environ", env, clear=True),
|
patch.dict("os.environ", env, clear=True),
|
||||||
):
|
):
|
||||||
return frigate_service_is_granular_root()
|
return service_is_granular_root(service)
|
||||||
|
|
||||||
def test_false_without_any_root_signal(self) -> None:
|
def test_false_without_any_root_signal(self) -> None:
|
||||||
self.assertFalse(self._check(euid=0, env={}))
|
self.assertFalse(self._check(euid=0, env={}))
|
||||||
@@ -146,6 +259,12 @@ class TestFrigateServiceIsGranularRoot(unittest.TestCase):
|
|||||||
self._check(euid=0, env={"FRIGATE_ROOT_SERVICES": "go2rtc,nginx"})
|
self._check(euid=0, env={"FRIGATE_ROOT_SERVICES": "go2rtc,nginx"})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_matches_the_named_service(self) -> None:
|
||||||
|
env = {"FRIGATE_ROOT_SERVICES": "go2rtc"}
|
||||||
|
self.assertTrue(self._check(euid=0, env=env, service="go2rtc"))
|
||||||
|
self.assertFalse(self._check(euid=0, env=env, service="frigate"))
|
||||||
|
self.assertFalse(self._check(euid=1000, env=env, service="go2rtc"))
|
||||||
|
|
||||||
def test_substring_of_a_service_name_does_not_match(self) -> None:
|
def test_substring_of_a_service_name_does_not_match(self) -> None:
|
||||||
self.assertFalse(self._check(euid=0, env={"FRIGATE_ROOT_SERVICES": "frigatee"}))
|
self.assertFalse(self._check(euid=0, env={"FRIGATE_ROOT_SERVICES": "frigatee"}))
|
||||||
|
|
||||||
|
|||||||
+24
-15
@@ -46,14 +46,14 @@ DROPPED_DETECTOR_OPTIONS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Trees the unprivileged runtime user can write. A root frigate service must
|
# Trees the unprivileged runtime user can write. A root service must not
|
||||||
# not execute a binary from any of them; a compromised uid-1000 process could
|
# execute a binary from any of them; a compromised uid-1000 process could
|
||||||
# plant one and be root after the next restart.
|
# plant one and be root after the next restart.
|
||||||
RUNTIME_USER_WRITABLE_DIRS = (CONFIG_DIR, BASE_DIR, CACHE_DIR, "/dev/shm", "/tmp")
|
RUNTIME_USER_WRITABLE_DIRS = (CONFIG_DIR, BASE_DIR, CACHE_DIR, "/dev/shm", "/tmp")
|
||||||
|
|
||||||
|
|
||||||
def frigate_service_is_granular_root() -> bool:
|
def service_is_granular_root(service: str) -> bool:
|
||||||
"""Report whether FRIGATE_ROOT_SERVICES runs frigate as root.
|
"""Report whether FRIGATE_ROOT_SERVICES runs the given service as root.
|
||||||
|
|
||||||
The escape hatch is excluded: it never sweeps /config and leaves no
|
The escape hatch is excluded: it never sweeps /config and leaves no
|
||||||
unprivileged service running, so custom binaries stay as safe as they
|
unprivileged service running, so custom binaries stay as safe as they
|
||||||
@@ -66,7 +66,7 @@ def frigate_service_is_granular_root() -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
entries = os.environ.get("FRIGATE_ROOT_SERVICES", "").split(",")
|
entries = os.environ.get("FRIGATE_ROOT_SERVICES", "").split(",")
|
||||||
return any("".join(entry.split()) == "frigate" for entry in entries)
|
return any("".join(entry.split()) == service for entry in entries)
|
||||||
|
|
||||||
|
|
||||||
def is_runtime_user_writable(path: str) -> bool:
|
def is_runtime_user_writable(path: str) -> bool:
|
||||||
@@ -79,11 +79,12 @@ def is_runtime_user_writable(path: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def _warn_ignored_ffmpeg_path(path: str) -> None:
|
def _warn_ignored_ffmpeg_path(path: str, service: str) -> None:
|
||||||
"""Warn once per path; resolution runs per camera and per binary."""
|
"""Warn once per path; resolution runs per camera and per binary."""
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Ignoring ffmpeg.path %s because FRIGATE_ROOT_SERVICES runs frigate as root and that location is writable by the unprivileged user; using the bundled build",
|
"Ignoring ffmpeg.path %s because FRIGATE_ROOT_SERVICES runs %s as root and that location is writable by the unprivileged user; using the bundled build",
|
||||||
path,
|
path,
|
||||||
|
service,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -97,16 +98,19 @@ def is_valid_ffmpeg_path(path: str) -> bool:
|
|||||||
return not path.startswith("/") or _CUSTOM_FFMPEG_PATH.fullmatch(path) is not None
|
return not path.startswith("/") or _CUSTOM_FFMPEG_PATH.fullmatch(path) is not None
|
||||||
|
|
||||||
|
|
||||||
def resolve_ffmpeg_path(path: str, binary: str = "ffmpeg") -> str:
|
def resolve_ffmpeg_path(
|
||||||
|
path: str, binary: str = "ffmpeg", service: str = "frigate"
|
||||||
|
) -> str:
|
||||||
"""Resolve an ffmpeg version alias or custom path to a binary path.
|
"""Resolve an ffmpeg version alias or custom path to a binary path.
|
||||||
|
|
||||||
A bare version alias that is no longer bundled (for example one that was
|
A bare version alias that is no longer bundled (for example one that was
|
||||||
dropped when the default version changed) falls back to the default
|
dropped when the default version changed) falls back to the default
|
||||||
bundled version so existing configs keep working across an upgrade or a
|
bundled version so existing configs keep working across an upgrade or a
|
||||||
revert. Custom install paths (anything absolute) are used as-is, unless
|
revert. Custom install paths (anything absolute) are used as-is. While
|
||||||
they fail `is_valid_ffmpeg_path` (which falls back to the default), or
|
FRIGATE_ROOT_SERVICES makes the service that execs it root, a custom path
|
||||||
they are in a runtime-user-writable tree while FRIGATE_ROOT_SERVICES
|
is resolved to its real location and refused inside a runtime-user-writable
|
||||||
makes frigate root; see RUNTIME_USER_WRITABLE_DIRS.
|
tree; see RUNTIME_USER_WRITABLE_DIRS. Paths that fail
|
||||||
|
`is_valid_ffmpeg_path` also fall back to the default.
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
path == "default"
|
path == "default"
|
||||||
@@ -116,11 +120,16 @@ def resolve_ffmpeg_path(path: str, binary: str = "ffmpeg") -> str:
|
|||||||
version = DEFAULT_FFMPEG_VERSION
|
version = DEFAULT_FFMPEG_VERSION
|
||||||
elif path in INCLUDED_FFMPEG_VERSIONS:
|
elif path in INCLUDED_FFMPEG_VERSIONS:
|
||||||
version = path
|
version = path
|
||||||
else:
|
elif not service_is_granular_root(service):
|
||||||
if not (frigate_service_is_granular_root() and is_runtime_user_writable(path)):
|
|
||||||
return f"{path}/bin/{binary}"
|
return f"{path}/bin/{binary}"
|
||||||
|
else:
|
||||||
|
# return the resolved form so a link in the path cannot be retargeted
|
||||||
|
# after the check
|
||||||
|
real = os.path.realpath(f"{path}/bin/{binary}")
|
||||||
|
if is_valid_ffmpeg_path(real) and not is_runtime_user_writable(real):
|
||||||
|
return real
|
||||||
|
|
||||||
_warn_ignored_ffmpeg_path(path)
|
_warn_ignored_ffmpeg_path(path, service)
|
||||||
version = DEFAULT_FFMPEG_VERSION
|
version = DEFAULT_FFMPEG_VERSION
|
||||||
|
|
||||||
return f"/usr/lib/ffmpeg/{version}/bin/{binary}"
|
return f"/usr/lib/ffmpeg/{version}/bin/{binary}"
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
from frigate.const import MODEL_CACHE_DIR
|
from frigate.const import MODEL_CACHE_DIR
|
||||||
from frigate.util.config import (
|
from frigate.util.config import (
|
||||||
frigate_service_is_granular_root,
|
|
||||||
is_runtime_user_writable,
|
is_runtime_user_writable,
|
||||||
|
service_is_granular_root,
|
||||||
)
|
)
|
||||||
from frigate.util.file import FileLock
|
from frigate.util.file import FileLock
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ def _usable_reason() -> str | None:
|
|||||||
|
|
||||||
base = user_base()
|
base = user_base()
|
||||||
|
|
||||||
if frigate_service_is_granular_root() and is_runtime_user_writable(str(base)):
|
if service_is_granular_root("frigate") and is_runtime_user_writable(str(base)):
|
||||||
return (
|
return (
|
||||||
f"{base} is writable by the unprivileged user while "
|
f"{base} is writable by the unprivileged user while "
|
||||||
"FRIGATE_ROOT_SERVICES runs frigate as root"
|
"FRIGATE_ROOT_SERVICES runs frigate as root"
|
||||||
|
|||||||
Reference in New Issue
Block a user