diff --git a/docker/main/rootfs/usr/local/nginx/conf/auth_location.conf b/docker/main/rootfs/usr/local/nginx/conf/auth_location.conf index 75cd6a620..285a3d81b 100644 --- a/docker/main/rootfs/usr/local/nginx/conf/auth_location.conf +++ b/docker/main/rootfs/usr/local/nginx/conf/auth_location.conf @@ -22,6 +22,9 @@ location /auth { proxy_set_header Cookie $http_cookie; proxy_set_header X-CSRF-TOKEN "1"; + # include headers from common auth proxies + include proxy_trusted_headers.conf; + ## Basic Proxy Configuration 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 diff --git a/docker/main/rootfs/usr/local/nginx/conf/proxy_trusted_headers.conf b/docker/main/rootfs/usr/local/nginx/conf/proxy_trusted_headers.conf new file mode 100644 index 000000000..28b885154 --- /dev/null +++ b/docker/main/rootfs/usr/local/nginx/conf/proxy_trusted_headers.conf @@ -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; \ No newline at end of file diff --git a/frigate/api/app.py b/frigate/api/app.py index bd2357be6..a11705fcf 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -21,7 +21,7 @@ from frigate.api.export import ExportBp from frigate.api.media import MediaBp from frigate.api.preview import PreviewBp 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.events.external import ExternalEventProcessor from frigate.models import Event, Timeline @@ -86,7 +86,9 @@ def create_app( app.plus_api = plus_api app.camera_error_image = None 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 app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1) # initialize the rate limiter for the login endpoint diff --git a/frigate/api/auth.py b/frigate/api/auth.py index 5b998d04c..144c71d03 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -17,6 +17,7 @@ from flask_limiter import Limiter from joserfc import jwt from peewee import DoesNotExist +from frigate.config import AuthModeEnum from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM from frigate.models import User @@ -175,12 +176,24 @@ 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: + # 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.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_REFRESH = current_app.frigate_config.auth.refresh_time JWT_SESSION_LENGTH = current_app.frigate_config.auth.session_length diff --git a/frigate/app.py b/frigate/app.py index 0e00f4ba1..9bc5c0146 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -27,7 +27,7 @@ from frigate.comms.dispatcher import Communicator, Dispatcher from frigate.comms.inter_process import InterProcessCommunicator from frigate.comms.mqtt import MqttClient from frigate.comms.ws import WebSocketClient -from frigate.config import FrigateConfig +from frigate.config import AuthModeEnum, FrigateConfig from frigate.const import ( CACHE_DIR, CLIPS_DIR, @@ -592,7 +592,7 @@ class FrigateApp: ) def init_auth(self) -> None: - if self.config.auth.enabled: + if self.config.auth.mode == AuthModeEnum.native: if User.select().count() == 0: password = secrets.token_hex(16) password_hash = hash_password( diff --git a/frigate/config.py b/frigate/config.py index dca5bd4e0..a6fd0280c 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -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): - enabled: bool = Field(default=False, title="Enable authentication") + mode: AuthModeEnum = Field(default=AuthModeEnum.native, title="Authentication mode") reset_admin_password: bool = Field( 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", 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( default="1/second;5/minute;20/hour", title="Rate limits for failed login attempts.",