mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-21 03:41:55 +03:00
redact credentials in config endpoint with sentinel
This commit is contained in:
parent
8ea46e7c6c
commit
3c9fe6fa0f
@ -43,6 +43,7 @@ from frigate.config.camera.updater import (
|
||||
CameraConfigUpdateEnum,
|
||||
CameraConfigUpdateTopic,
|
||||
)
|
||||
from frigate.const import REDACTED_CREDENTIAL_SENTINEL
|
||||
from frigate.ffmpeg_presets import FFMPEG_HWACCEL_VAAPI, _gpu_selector
|
||||
from frigate.genai import PROVIDERS, load_providers
|
||||
from frigate.jobs.media_sync import (
|
||||
@ -61,7 +62,11 @@ from frigate.util.builtin import (
|
||||
process_config_query_string,
|
||||
update_yaml_file_bulk,
|
||||
)
|
||||
from frigate.util.config import apply_section_update, find_config_file
|
||||
from frigate.util.config import (
|
||||
apply_section_update,
|
||||
find_config_file,
|
||||
redact_credential,
|
||||
)
|
||||
from frigate.util.schema import get_config_schema
|
||||
from frigate.util.services import (
|
||||
get_nvidia_driver_info,
|
||||
@ -284,26 +289,24 @@ def config(request: Request):
|
||||
if request.headers.get("remote-role") != "admin":
|
||||
config.pop("environment_vars", None)
|
||||
|
||||
# remove mqtt credentials
|
||||
config["mqtt"].pop("password", None)
|
||||
config["mqtt"].pop("user", None)
|
||||
# redact mqtt credentials
|
||||
redact_credential(config["mqtt"], "password")
|
||||
|
||||
# remove the proxy secret
|
||||
config["proxy"].pop("auth_secret", None)
|
||||
# redact proxy secret
|
||||
redact_credential(config["proxy"], "auth_secret")
|
||||
|
||||
# remove genai api keys
|
||||
for genai_name, genai_cfg in config.get("genai", {}).items():
|
||||
# redact genai api keys
|
||||
for _genai_name, genai_cfg in config.get("genai", {}).items():
|
||||
if isinstance(genai_cfg, dict):
|
||||
genai_cfg.pop("api_key", None)
|
||||
redact_credential(genai_cfg, "api_key")
|
||||
|
||||
for camera_name, camera in request.app.frigate_config.cameras.items():
|
||||
camera_dict = config["cameras"][camera_name]
|
||||
|
||||
# remove onvif credentials
|
||||
# redact onvif credentials
|
||||
onvif_dict = camera_dict.get("onvif", {})
|
||||
if onvif_dict:
|
||||
onvif_dict.pop("user", None)
|
||||
onvif_dict.pop("password", None)
|
||||
redact_credential(onvif_dict, "password")
|
||||
|
||||
# clean paths
|
||||
for input in camera_dict.get("ffmpeg", {}).get("inputs", []):
|
||||
@ -680,6 +683,10 @@ 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
|
||||
updates = {
|
||||
k: v for k, v in updates.items() if v != REDACTED_CREDENTIAL_SENTINEL
|
||||
}
|
||||
|
||||
if not updates:
|
||||
return JSONResponse(
|
||||
@ -790,6 +797,13 @@ def config_set(request: Request, body: AppConfigSetBody):
|
||||
updates = flatten_config_data(body.config_data)
|
||||
# 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.
|
||||
updates = {
|
||||
k: v
|
||||
for k, v in updates.items()
|
||||
if v != REDACTED_CREDENTIAL_SENTINEL
|
||||
}
|
||||
|
||||
if not updates:
|
||||
return JSONResponse(
|
||||
|
||||
@ -21,6 +21,8 @@ PLUS_API_HOST = "https://api.frigate.video"
|
||||
|
||||
SHM_FRAMES_VAR = "SHM_MAX_FRAMES"
|
||||
|
||||
REDACTED_CREDENTIAL_SENTINEL = "__FRIGATE_SAVED_CREDENTIAL__"
|
||||
|
||||
# Attribute & Object constants
|
||||
|
||||
DEFAULT_ATTRIBUTE_LABEL_MAP = {
|
||||
|
||||
@ -8,7 +8,7 @@ from typing import Any, Optional, Union
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from frigate.const import CONFIG_DIR, EXPORT_DIR
|
||||
from frigate.const import CONFIG_DIR, EXPORT_DIR, REDACTED_CREDENTIAL_SENTINEL
|
||||
from frigate.util.builtin import deep_merge
|
||||
from frigate.util.services import get_video_properties
|
||||
|
||||
@ -18,6 +18,21 @@ CURRENT_CONFIG_VERSION = "0.18-0"
|
||||
DEFAULT_CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yml")
|
||||
|
||||
|
||||
def redact_credential(obj: dict[str, Any], key: str) -> None:
|
||||
"""Replace obj[key] with the redaction sentinel if a value is saved, else drop.
|
||||
|
||||
Used when shaping the /config response so saved credentials never leave
|
||||
the server. The frontend recognizes REDACTED_CREDENTIAL_SENTINEL, renders
|
||||
the field as empty with a "saved — leave blank to keep" placeholder, and
|
||||
/config/set strips it from any incoming payload so the YAML value is
|
||||
preserved when the user doesn't touch the field.
|
||||
"""
|
||||
if obj.get(key):
|
||||
obj[key] = REDACTED_CREDENTIAL_SENTINEL
|
||||
else:
|
||||
obj.pop(key, None)
|
||||
|
||||
|
||||
def find_config_file() -> str:
|
||||
config_path = os.environ.get("CONFIG_FILE", DEFAULT_CONFIG_FILE)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user