split out proxy from auth

This commit is contained in:
Blake Blackshear 2024-06-14 17:22:06 -05:00
parent b49cda274d
commit 2f89f5226e
5 changed files with 67 additions and 33 deletions

View File

@ -66,13 +66,28 @@ database:
# Optional: TLS configuration
tls:
# Optional: Enable TLS for port 8080 (default: shown below)
enabled: true
enabled: True
# Optional: Proxy configuration
proxy:
# Optional: Mapping for headers from upstream proxies. Only used if Frigate's auth
# is disabled.
# NOTE: Many authentication proxies pass a header downstream with the authenticated
# user name. Not all values are supported. It must be a whitelisted header.
# See the docs for more info.
header_map:
user: x-forwarded-user
# Optional: Url for logging out a user. This sets the location of the logout url in
# the UI.
logout_url: /api/logout
# Optional: Auth secret that is checked against the X-Proxy-Secret header sent from
# the proxy. If not set, all requests are trusted regardless of origin.
auth_secret: None
# Optional: Authentication configuration
auth:
# Optional: Authentication mode (default: shown below)
# Valid values are: native, proxy
mode: native
# Optional: Enable authentication
enabled: True
# Optional: Reset the admin user password on startup (default: shown below)
# New password is printed in the logs
reset_admin_password: False
@ -87,23 +102,14 @@ auth:
# When the session is going to expire in less time than this setting,
# it will be refreshed back to the session_length.
refresh_time: 43200 # 12 hours
# Optional: Mapping for headers from upstream proxies. Only used in proxy auth mode.
# NOTE: Many authentication proxies pass a header downstream with the authenticated
# user name. Not all values are supported. It must be a whitelisted header.
# See the docs for more info.
header_map:
user: x-forwarded-user
# Optional: Rate limiting for login failures to help prevent brute force
# login attacks (default: shown below)
# See the docs for more information on valid values
failed_login_rate_limit: None
# Optional: Trusted proxies for determining IP address to rate limit
# NOTE: This is only used for rate limiting login attempts and does not bypass
# authentication in any way
# authentication. See the authentication docs for more details.
trusted_proxies: []
# Optional: Url for logging out a user. This only needs to be set if you are using
# proxy mode.
logout_url: /api/logout
# Optional: Number of hashing iterations for user passwords
# As of Feb 2023, OWASP recommends 600000 iterations for PBKDF2-SHA256
# NOTE: changing this value will not automatically update password hashes, you

View File

@ -176,6 +176,9 @@ def config():
# remove the mqtt password
config["mqtt"].pop("password", None)
# remove the proxy secret
config["proxy"].pop("auth_secret", None)
for camera_name, camera in current_app.frigate_config.cameras.items():
camera_dict = config["cameras"][camera_name]

View File

@ -17,7 +17,7 @@ from flask_limiter import Limiter
from joserfc import jwt
from peewee import DoesNotExist
from frigate.config import AuthConfig, AuthModeEnum
from frigate.config import AuthConfig, ProxyConfig
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
from frigate.models import User
@ -166,6 +166,9 @@ def set_jwt_cookie(response, cookie_name, encoded_jwt, expiration, secure):
# Endpoint for use with nginx auth_request
@AuthBp.route("/auth")
def auth():
auth_config: AuthConfig = current_app.frigate_config.auth
proxy_config: ProxyConfig = current_app.frigate_config.proxy
success_response = make_response({}, 202)
# dont require auth if the request is on the internal port
@ -173,11 +176,22 @@ def auth():
if request.headers.get("x-server-port", 0, type=int) == 5000:
return success_response
# if proxy auth mode
if current_app.frigate_config.auth.mode == AuthModeEnum.proxy:
fail_response = make_response({}, 401)
# ensure the proxy secret matches if configured
if (
proxy_config.auth_secret is not None
and request.headers.get("x-proxy-secret", "", type=str)
!= proxy_config.auth_secret
):
logger.debug("X-Proxy-Secret header does not match configured secret value")
return fail_response
# if auth is disabled, just apply the proxy header map and return success
if not auth_config.enabled:
# pass the user header value from the upstream proxy if a mapping is specified
# or use anonymous if none are specified
if current_app.frigate_config.auth.header_map.user is not None:
if proxy_config.header_map.user is not None:
upstream_user_header_value = request.headers.get(
current_app.frigate_config.auth.header_map.user,
type=str,
@ -188,7 +202,7 @@ def auth():
success_response.headers["remote-user"] = "anonymous"
return success_response
fail_response = make_response({}, 401)
# now apply authentication
fail_response.headers["location"] = "/login"
JWT_COOKIE_NAME = current_app.frigate_config.auth.cookie_name

View File

@ -119,19 +119,28 @@ class TlsConfig(FrigateBaseModel):
enabled: bool = Field(default=True, title="Enable TLS for port 8080")
class AuthModeEnum(str, Enum):
native = "native"
proxy = "proxy"
class HeaderMappingConfig(FrigateBaseModel):
user: str = Field(
default=None, title="Header name from upstream proxy to identify user."
)
class ProxyConfig(FrigateBaseModel):
header_map: HeaderMappingConfig = Field(
default_factory=HeaderMappingConfig,
title="Header mapping definitions for proxy user passing.",
)
logout_url: Optional[str] = Field(
default=None, title="Redirect url for logging out with proxy."
)
auth_secret: Optional[str] = Field(
default=None,
title="Secret value for proxy authentication.",
)
class AuthConfig(FrigateBaseModel):
mode: AuthModeEnum = Field(default=AuthModeEnum.native, title="Authentication mode")
enabled: bool = Field(default=True, title="Enable authentication")
reset_admin_password: bool = Field(
default=False, title="Reset the admin password on startup"
)
@ -147,10 +156,6 @@ class AuthConfig(FrigateBaseModel):
title="Refresh the session if it is going to expire in this many seconds",
ge=30,
)
header_map: HeaderMappingConfig = Field(
default_factory=HeaderMappingConfig,
title="Header mapping definitions for proxy auth mode.",
)
failed_login_rate_limit: Optional[str] = Field(
default=None,
title="Rate limits for failed login attempts.",
@ -159,9 +164,6 @@ class AuthConfig(FrigateBaseModel):
default=[],
title="Trusted proxies for determining IP address to rate limit",
)
logout_url: Optional[str] = Field(
default=None, title="Redirect url for logging out in proxy mode."
)
# As of Feb 2023, OWASP recommends 600000 iterations for PBKDF2-SHA256
hash_iterations: int = Field(default=600000, title="Password hash iterations")
@ -1308,6 +1310,9 @@ class FrigateConfig(FrigateBaseModel):
default_factory=DatabaseConfig, title="Database configuration."
)
tls: TlsConfig = Field(default_factory=TlsConfig, title="TLS configuration.")
proxy: ProxyConfig = Field(
default_factory=ProxyConfig, title="Proxy configuration."
)
auth: AuthConfig = Field(default_factory=AuthConfig, title="Auth configuration.")
environment_vars: Dict[str, str] = Field(
default_factory=dict, title="Frigate environment variables."
@ -1373,6 +1378,12 @@ class FrigateConfig(FrigateBaseModel):
"""Merge camera config with globals."""
config = self.model_copy(deep=True)
# Proxy secret substitution
if config.proxy.auth_secret:
config.proxy.auth_secret = config.proxy.auth_secret.format(
**FRIGATE_ENV_VARS
)
# MQTT user/password substitutions
if config.mqtt.user or config.mqtt.password:
config.mqtt.user = config.mqtt.user.format(**FRIGATE_ENV_VARS)

View File

@ -26,7 +26,7 @@ type AccountSettingsProps = {
export default function AccountSettings({ className }: AccountSettingsProps) {
const { data: profile } = useSWR("profile");
const { data: config } = useSWR("config");
const logoutUrl = config?.auth.logout_url || "/api/logout";
const logoutUrl = config?.proxy.logout_url || "/api/logout";
const Container = isDesktop ? DropdownMenu : Drawer;
const Trigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger;