mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-19 13:12:48 +03:00
redact go2rtc rtsp, api and webrtc credentials in /config
/config cleaned only go2rtc.streams, so go2rtc.rtsp and go2rtc.api usernames and passwords and the WebRTC ICE server credentials went back verbatim to any authenticated user. They now use the redaction sentinel like the MQTT and ONVIF passwords. Config save already dropped a field equal to the sentinel, but lists are leaf values after flattening, so an ice_servers list echoed back from /config would have written the sentinel to disk. Save now drops any value that contains it.
This commit is contained in:
+20
-4
@@ -403,6 +403,19 @@ def config(request: Request):
|
||||
|
||||
config["go2rtc"]["streams"][stream_name] = cleaned
|
||||
|
||||
for section in ("rtsp", "api"):
|
||||
go2rtc_section = config["go2rtc"].get(section)
|
||||
if isinstance(go2rtc_section, dict):
|
||||
redact_credential(go2rtc_section, "username")
|
||||
redact_credential(go2rtc_section, "password")
|
||||
|
||||
webrtc = config["go2rtc"].get("webrtc")
|
||||
if isinstance(webrtc, dict):
|
||||
for ice_server in webrtc.get("ice_servers") or []:
|
||||
if isinstance(ice_server, dict):
|
||||
redact_credential(ice_server, "username")
|
||||
redact_credential(ice_server, "credential")
|
||||
|
||||
config["plus"] = {"enabled": request.app.frigate_config.plus_api.is_active()}
|
||||
|
||||
for index, model in enumerate(config_obj.models):
|
||||
@@ -788,9 +801,11 @@ def _config_set_in_memory(request: Request, body: AppConfigSetBody) -> JSONRespo
|
||||
_restore_masked_camera_paths(body.config_data, request.app.frigate_config)
|
||||
updates = flatten_config_data(body.config_data)
|
||||
updates = {k: ("" if v is None else v) for k, v in updates.items()}
|
||||
# Drop any field whose value is still the redaction sentinel
|
||||
# Drop any field whose value still holds the redaction sentinel
|
||||
updates = {
|
||||
k: v for k, v in updates.items() if v != REDACTED_CREDENTIAL_SENTINEL
|
||||
k: v
|
||||
for k, v in updates.items()
|
||||
if REDACTED_CREDENTIAL_SENTINEL not in str(v)
|
||||
}
|
||||
|
||||
if not updates:
|
||||
@@ -948,11 +963,12 @@ def config_set(request: Request, body: AppConfigSetBody):
|
||||
# Convert None values to empty strings for deletion (e.g., when deleting masks)
|
||||
updates = {k: ("" if v is None else v) for k, v in updates.items()}
|
||||
# Drop sentinel-valued fields so untouched credential
|
||||
# placeholders don't clobber the saved YAML value.
|
||||
# placeholders don't clobber the saved YAML value. Lists
|
||||
# are leaf values, so check for a sentinel nested inside.
|
||||
updates = {
|
||||
k: v
|
||||
for k, v in updates.items()
|
||||
if v != REDACTED_CREDENTIAL_SENTINEL
|
||||
if REDACTED_CREDENTIAL_SENTINEL not in str(v)
|
||||
}
|
||||
|
||||
if not updates:
|
||||
|
||||
@@ -14,6 +14,7 @@ from frigate.config.camera.updater import (
|
||||
CameraConfigUpdateTopic,
|
||||
)
|
||||
from frigate.config.holder import ConfigHolder
|
||||
from frigate.const import REDACTED_CREDENTIAL_SENTINEL
|
||||
from frigate.models import Event, Recordings, ReviewSegment
|
||||
from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp
|
||||
|
||||
@@ -568,6 +569,46 @@ class TestConfigSetWildcardPropagation(BaseTestHttp):
|
||||
finally:
|
||||
os.unlink(config_path)
|
||||
|
||||
@patch("frigate.api.app.find_config_file")
|
||||
def test_go2rtc_credentials_redacted_and_round_trip(self, mock_find_config):
|
||||
self._add_credentials()
|
||||
config_path = self._write_config_file()
|
||||
mock_find_config.return_value = config_path
|
||||
|
||||
try:
|
||||
app, _ = self._create_app_with_publisher()
|
||||
|
||||
with AuthTestClient(app) as client:
|
||||
go2rtc = client.get("/config").json()["go2rtc"]
|
||||
ice_server = go2rtc["webrtc"]["ice_servers"][0]
|
||||
|
||||
for section in ("rtsp", "api"):
|
||||
self.assertEqual(
|
||||
go2rtc[section]["username"], REDACTED_CREDENTIAL_SENTINEL
|
||||
)
|
||||
self.assertEqual(
|
||||
go2rtc[section]["password"], REDACTED_CREDENTIAL_SENTINEL
|
||||
)
|
||||
self.assertEqual(ice_server["credential"], REDACTED_CREDENTIAL_SENTINEL)
|
||||
self.assertEqual(ice_server["username"], REDACTED_CREDENTIAL_SENTINEL)
|
||||
self.assertEqual(go2rtc["webrtc"]["candidates"], ["10.0.0.5:8555"])
|
||||
|
||||
resp = client.put(
|
||||
"/config/set",
|
||||
json={"config_data": {"go2rtc": go2rtc}, "requires_restart": 0},
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
raw, saved = self._read_config_file(config_path)
|
||||
self.assertNotIn(REDACTED_CREDENTIAL_SENTINEL, raw)
|
||||
self.assertEqual(saved["go2rtc"]["rtsp"]["password"], "rtsppass")
|
||||
self.assertEqual(saved["go2rtc"]["api"]["password"], "apipass")
|
||||
self.assertEqual(
|
||||
saved["go2rtc"]["webrtc"]["ice_servers"][0]["credential"], "turnpass"
|
||||
)
|
||||
finally:
|
||||
os.unlink(config_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user