add support for user passthru from upstream proxies

This commit is contained in:
Blake Blackshear 2024-05-17 18:52:33 -05:00
parent 46d60e4a39
commit dd730bf6c7
6 changed files with 63 additions and 8 deletions

View File

@ -22,6 +22,9 @@ location /auth {
proxy_set_header Cookie $http_cookie; proxy_set_header Cookie $http_cookie;
proxy_set_header X-CSRF-TOKEN "1"; proxy_set_header X-CSRF-TOKEN "1";
# include headers from common auth proxies
include proxy_trusted_headers.conf;
## Basic Proxy Configuration ## Basic Proxy Configuration
proxy_pass_request_body off; proxy_pass_request_body off;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; # Timeout if the real server is dead proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; # Timeout if the real server is dead

View File

@ -0,0 +1,22 @@
# these headers will be copied to the /auth request and are available
# to be mapped in the config to Frigate's remote-user header
# List of headers sent by common authentication proxies:
# - Authelia
# - Traefik forward auth
# - oauth2_proxy
# - Authentik
proxy_set_header Remote-User $http_remote_user;
proxy_set_header Remote-Groups $http_remote_groups;
proxy_set_header Remote-Email $http_remote_email;
proxy_set_header Remote-Name $http_remote_name;
proxy_set_header X-Forwarded-User $http_x_forwarded_user;
proxy_set_header X-Forwarded-Groups $http_x_forwarded_groups;
proxy_set_header X-Forwarded-Email $http_x_forwarded_email;
proxy_set_header X-Forwarded-Preferred-Username $http_x_forwarded_preferred_username;
proxy_set_header X-authentik-username $http_x_authentik_username;
proxy_set_header X-authentik-groups $http_x_authentik_groups;
proxy_set_header X-authentik-email $http_x_authentik_email;
proxy_set_header X-authentik-name $http_x_authentik_name;
proxy_set_header X-authentik-uid $http_x_authentik_uid;

View File

@ -21,7 +21,7 @@ from frigate.api.export import ExportBp
from frigate.api.media import MediaBp from frigate.api.media import MediaBp
from frigate.api.preview import PreviewBp from frigate.api.preview import PreviewBp
from frigate.api.review import ReviewBp from frigate.api.review import ReviewBp
from frigate.config import FrigateConfig from frigate.config import AuthModeEnum, FrigateConfig
from frigate.const import CONFIG_DIR from frigate.const import CONFIG_DIR
from frigate.events.external import ExternalEventProcessor from frigate.events.external import ExternalEventProcessor
from frigate.models import Event, Timeline from frigate.models import Event, Timeline
@ -86,7 +86,9 @@ def create_app(
app.plus_api = plus_api app.plus_api = plus_api
app.camera_error_image = None app.camera_error_image = None
app.stats_emitter = stats_emitter app.stats_emitter = stats_emitter
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled else None app.jwt_token = (
get_jwt_secret() if frigate_config.auth.mode == AuthModeEnum.native else None
)
# update the request_address with the x-forwarded-for header from nginx # update the request_address with the x-forwarded-for header from nginx
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1) app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1)
# initialize the rate limiter for the login endpoint # initialize the rate limiter for the login endpoint

View File

@ -17,6 +17,7 @@ from flask_limiter import Limiter
from joserfc import jwt from joserfc import jwt
from peewee import DoesNotExist from peewee import DoesNotExist
from frigate.config import AuthModeEnum
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
from frigate.models import User from frigate.models import User
@ -175,12 +176,24 @@ def auth():
if request.headers.get("x-server-port", 0, type=int) == 5000: if request.headers.get("x-server-port", 0, type=int) == 5000:
return success_response return success_response
# if proxy auth mode
if current_app.frigate_config.auth.mode == AuthModeEnum.proxy:
# 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:
upstream_user_header_value = request.headers.get(
current_app.frigate_config.auth.header_map.user,
type=str,
default="anonymous",
)
success_response.headers["remote-user"] = upstream_user_header_value
else:
success_response.headers["remote-user"] = "anonymous"
return success_response
fail_response = make_response({}, 401) fail_response = make_response({}, 401)
fail_response.headers["location"] = "/login" fail_response.headers["location"] = "/login"
if not current_app.frigate_config.auth.enabled:
return success_response
JWT_COOKIE_NAME = current_app.frigate_config.auth.cookie_name JWT_COOKIE_NAME = current_app.frigate_config.auth.cookie_name
JWT_REFRESH = current_app.frigate_config.auth.refresh_time JWT_REFRESH = current_app.frigate_config.auth.refresh_time
JWT_SESSION_LENGTH = current_app.frigate_config.auth.session_length JWT_SESSION_LENGTH = current_app.frigate_config.auth.session_length

View File

@ -27,7 +27,7 @@ from frigate.comms.dispatcher import Communicator, Dispatcher
from frigate.comms.inter_process import InterProcessCommunicator from frigate.comms.inter_process import InterProcessCommunicator
from frigate.comms.mqtt import MqttClient from frigate.comms.mqtt import MqttClient
from frigate.comms.ws import WebSocketClient from frigate.comms.ws import WebSocketClient
from frigate.config import FrigateConfig from frigate.config import AuthModeEnum, FrigateConfig
from frigate.const import ( from frigate.const import (
CACHE_DIR, CACHE_DIR,
CLIPS_DIR, CLIPS_DIR,
@ -592,7 +592,7 @@ class FrigateApp:
) )
def init_auth(self) -> None: def init_auth(self) -> None:
if self.config.auth.enabled: if self.config.auth.mode == AuthModeEnum.native:
if User.select().count() == 0: if User.select().count() == 0:
password = secrets.token_hex(16) password = secrets.token_hex(16)
password_hash = hash_password( password_hash = hash_password(

View File

@ -116,8 +116,19 @@ class UIConfig(FrigateBaseModel):
) )
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 AuthConfig(FrigateBaseModel): class AuthConfig(FrigateBaseModel):
enabled: bool = Field(default=False, title="Enable authentication") mode: AuthModeEnum = Field(default=AuthModeEnum.native, title="Authentication mode")
reset_admin_password: bool = Field( reset_admin_password: bool = Field(
default=False, title="Reset the admin password on startup" default=False, title="Reset the admin password on startup"
) )
@ -132,6 +143,10 @@ class AuthConfig(FrigateBaseModel):
title="Refresh the session if it is going to expire in this many seconds", title="Refresh the session if it is going to expire in this many seconds",
ge=30, ge=30,
) )
header_map: Optional[HeaderMappingConfig] = Field(
default_factory=HeaderMappingConfig,
title="Header mapping definitions for proxy auth mode.",
)
failed_login_rate_limit: Optional[str] = Field( failed_login_rate_limit: Optional[str] = Field(
default="1/second;5/minute;20/hour", default="1/second;5/minute;20/hour",
title="Rate limits for failed login attempts.", title="Rate limits for failed login attempts.",