forked from Github/frigate
split out proxy from auth (#11963)
* split out proxy from auth * update documentation * fixup auth mode check
This commit is contained in:
@@ -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 AuthModeEnum, FrigateConfig
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import CONFIG_DIR
|
||||
from frigate.events.external import ExternalEventProcessor
|
||||
from frigate.models import Event, Timeline
|
||||
@@ -86,9 +86,7 @@ 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.mode == AuthModeEnum.native else None
|
||||
)
|
||||
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled 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
|
||||
@@ -176,6 +174,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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 AuthModeEnum, FrigateConfig
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import (
|
||||
CACHE_DIR,
|
||||
CLIPS_DIR,
|
||||
@@ -593,7 +593,7 @@ class FrigateApp:
|
||||
)
|
||||
|
||||
def init_auth(self) -> None:
|
||||
if self.config.auth.mode == AuthModeEnum.native:
|
||||
if self.config.auth.enabled:
|
||||
if User.select().count() == 0:
|
||||
password = secrets.token_hex(16)
|
||||
password_hash = hash_password(
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user