From 49c6073de6c98dccfdcc5217455b418befb76b34 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 21 May 2025 07:02:13 -0500 Subject: [PATCH] Add ability to specify separator used in proxy headers (#18336) --- docs/docs/configuration/authentication.md | 3 ++- docs/docs/configuration/reference.md | 2 ++ frigate/api/auth.py | 11 +++++++++-- frigate/config/proxy.py | 13 ++++++++++++- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/docs/configuration/authentication.md b/docs/docs/configuration/authentication.md index 84204cd46..143c17e74 100644 --- a/docs/docs/configuration/authentication.md +++ b/docs/docs/configuration/authentication.md @@ -97,11 +97,12 @@ python3 -c 'import secrets; print(secrets.token_hex(64))' ### Header mapping -If you have disabled Frigate's authentication and your proxy supports passing a header with authenticated usernames and/or roles, you can use the `header_map` config to specify the header name so it is passed to Frigate. For example, the following will map the `X-Forwarded-User` and `X-Forwarded-Role` values. Header names are not case sensitive. Multiple values can be included in the role header, but they must be comma-separated. +If you have disabled Frigate's authentication and your proxy supports passing a header with authenticated usernames and/or roles, you can use the `header_map` config to specify the header name so it is passed to Frigate. For example, the following will map the `X-Forwarded-User` and `X-Forwarded-Role` values. Header names are not case sensitive. Multiple values can be included in the role header. Frigate expects that the character separating the roles is a comma, but this can be specified using the `separator` config entry. ```yaml proxy: ... + separator: "|" # This value defaults to a comma, but Authentik uses a pipe, for example. header_map: user: x-forwarded-user role: x-forwarded-role diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index 0b9428701..5f6644bdb 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -91,6 +91,8 @@ proxy: auth_secret: None # Optional: The default role to use for proxy auth. Must be "admin" or "viewer" default_role: viewer + # Optional: The character used to separate multiple values in the proxy headers. (default: shown below) + separator: "," # Optional: Authentication configuration auth: diff --git a/frigate/api/auth.py b/frigate/api/auth.py index 1a267b521..db12c04bb 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -202,9 +202,15 @@ async def get_current_user(request: Request): def require_role(required_roles: List[str]): async def role_checker(request: Request): + proxy_config: ProxyConfig = request.app.frigate_config.proxy + # Get role from header (could be comma-separated) role_header = request.headers.get("remote-role") - roles = [r.strip() for r in role_header.split(",")] if role_header else [] + roles = ( + [r.strip() for r in role_header.split(proxy_config.separator)] + if role_header + else [] + ) # Check if we have any roles if not roles: @@ -269,7 +275,8 @@ def auth(request: Request): # if comma-separated with "admin", use "admin", else use default role success_response.headers["remote-role"] = ( "admin" - if role and "admin" in [r.strip() for r in role.split(",")] + if role + and "admin" in [r.strip() for r in role.split(proxy_config.separator)] else proxy_config.default_role ) diff --git a/frigate/config/proxy.py b/frigate/config/proxy.py index ef1529f9e..68bd400e7 100644 --- a/frigate/config/proxy.py +++ b/frigate/config/proxy.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import Field +from pydantic import Field, field_validator from .base import FrigateBaseModel from .env import EnvString @@ -33,3 +33,14 @@ class ProxyConfig(FrigateBaseModel): default_role: Optional[str] = Field( default="viewer", title="Default role for proxy users." ) + separator: Optional[str] = Field( + default=",", + title="The character used to separate values in a mapped header.", + ) + + @field_validator("separator", mode="before") + @classmethod + def validate_separator_length(cls, v): + if v is not None and len(v) != 1: + raise ValueError("Separator must be exactly one character") + return v