mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-19 21:22:50 +03:00
set the secure cookie flag when the request arrived over tls
The session cookie only got the Secure flag from auth.cookie_secure, which defaults to false even though TLS is on by default, so browsers would send it over plain HTTP. The cookie is now Secure whenever nginx reports an https scheme, and nginx passes that scheme to the /auth subrequest so refreshed cookies match. cookie_secure still forces the flag for a reverse proxy that terminates TLS in front of plain-HTTP Frigate.
This commit is contained in:
@@ -19,6 +19,7 @@ location /auth {
|
|||||||
# this cannot be spoofed
|
# this cannot be spoofed
|
||||||
proxy_set_header X-Go2rtc-Stream-Proxy $go2rtc_stream_proxy;
|
proxy_set_header X-Go2rtc-Stream-Proxy $go2rtc_stream_proxy;
|
||||||
proxy_set_header X-Server-Port $server_port;
|
proxy_set_header X-Server-Port $server_port;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header Content-Length "";
|
proxy_set_header Content-Length "";
|
||||||
# Pass along auth related info
|
# Pass along auth related info
|
||||||
proxy_set_header Authorization $http_authorization;
|
proxy_set_header Authorization $http_authorization;
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ auth:
|
|||||||
# Optional: Cookie to store the JWT token for native auth (default: shown below)
|
# Optional: Cookie to store the JWT token for native auth (default: shown below)
|
||||||
cookie_name: frigate_token
|
cookie_name: frigate_token
|
||||||
# Optional: Set secure flag on cookie. (default: shown below)
|
# Optional: Set secure flag on cookie. (default: shown below)
|
||||||
# NOTE: This should be set to True if you are using TLS
|
# NOTE: Frigate sets the flag automatically when serving TLS. Set this to True
|
||||||
|
# when a reverse proxy terminates TLS in front of Frigate
|
||||||
cookie_secure: False
|
cookie_secure: False
|
||||||
# Optional: Session length in seconds (default: shown below)
|
# Optional: Session length in seconds (default: shown below)
|
||||||
session_length: 86400 # 24 hours
|
session_length: 86400 # 24 hours
|
||||||
|
|||||||
+11
-4
@@ -490,8 +490,15 @@ def refresh_user_session(request: Request, username: str) -> None:
|
|||||||
request.app.user_sessions[username] = _user_session(user)
|
request.app.user_sessions[username] = _user_session(user)
|
||||||
|
|
||||||
|
|
||||||
|
def is_secure_request(request: Request) -> bool:
|
||||||
|
# nginx sets X-Forwarded-Proto to its own scheme, replacing any client value
|
||||||
|
return (
|
||||||
|
request.app.frigate_config.auth.cookie_secure
|
||||||
|
or request.headers.get("x-forwarded-proto") == "https"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def set_jwt_cookie(response: Response, cookie_name, encoded_jwt, max_age, secure):
|
def set_jwt_cookie(response: Response, cookie_name, encoded_jwt, max_age, secure):
|
||||||
# TODO: ideally this would set secure as well, but that requires TLS
|
|
||||||
# Starlette sets SameSite=Lax by default. The cookie is still sent to
|
# Starlette sets SameSite=Lax by default. The cookie is still sent to
|
||||||
# same-site iframes (e.g. Home Assistant on the same host or domain), but
|
# same-site iframes (e.g. Home Assistant on the same host or domain), but
|
||||||
# not to cross-site ones. CSRF is also mitigated by requiring a custom
|
# not to cross-site ones. CSRF is also mitigated by requiring a custom
|
||||||
@@ -844,7 +851,7 @@ def auth(request: Request):
|
|||||||
JWT_COOKIE_NAME,
|
JWT_COOKIE_NAME,
|
||||||
new_encoded_jwt,
|
new_encoded_jwt,
|
||||||
JWT_SESSION_LENGTH,
|
JWT_SESSION_LENGTH,
|
||||||
request.app.frigate_config.auth.cookie_secure,
|
is_secure_request(request),
|
||||||
)
|
)
|
||||||
|
|
||||||
success_response.headers["remote-user"] = user
|
success_response.headers["remote-user"] = user
|
||||||
@@ -963,7 +970,7 @@ def login(request: Request, body: AppPostLoginBody):
|
|||||||
JWT_COOKIE_NAME,
|
JWT_COOKIE_NAME,
|
||||||
encoded_jwt,
|
encoded_jwt,
|
||||||
JWT_SESSION_LENGTH,
|
JWT_SESSION_LENGTH,
|
||||||
request.app.frigate_config.auth.cookie_secure,
|
is_secure_request(request),
|
||||||
)
|
)
|
||||||
# Clear admin_first_time_login flag after successful admin login so the
|
# Clear admin_first_time_login flag after successful admin login so the
|
||||||
# UI stops showing the first-time login documentation link.
|
# UI stops showing the first-time login documentation link.
|
||||||
@@ -1138,7 +1145,7 @@ async def update_password(
|
|||||||
JWT_COOKIE_NAME,
|
JWT_COOKIE_NAME,
|
||||||
encoded_jwt,
|
encoded_jwt,
|
||||||
JWT_SESSION_LENGTH,
|
JWT_SESSION_LENGTH,
|
||||||
request.app.frigate_config.auth.cookie_secure,
|
is_secure_request(request),
|
||||||
)
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class AuthConfig(FrigateBaseModel):
|
|||||||
cookie_secure: bool = Field(
|
cookie_secure: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
title="Secure cookie flag",
|
title="Secure cookie flag",
|
||||||
description="Set the secure flag on the auth cookie; should be true when using TLS.",
|
description="Always set the secure flag on the auth cookie. Frigate sets it automatically when serving TLS; enable this when a reverse proxy terminates TLS in front of Frigate.",
|
||||||
)
|
)
|
||||||
session_length: int = Field(
|
session_length: int = Field(
|
||||||
default=86400,
|
default=86400,
|
||||||
|
|||||||
@@ -229,3 +229,45 @@ class TestAuthJwtRole(BaseTestHttp):
|
|||||||
|
|
||||||
self.assertEqual(resp.status_code, 202)
|
self.assertEqual(resp.status_code, 202)
|
||||||
self.assertIn("frigate_token=", resp.headers["set-cookie"])
|
self.assertIn("frigate_token=", resp.headers["set-cookie"])
|
||||||
|
|
||||||
|
def _login_cookie(self, headers: dict | None = None) -> str:
|
||||||
|
with AuthTestClient(self._create_app()) as client:
|
||||||
|
resp = client.post(
|
||||||
|
"/login",
|
||||||
|
json={"user": "bob", "password": PASSWORD},
|
||||||
|
headers=headers or {},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
return resp.headers["set-cookie"].lower()
|
||||||
|
|
||||||
|
def test_login_cookie_is_secure_over_tls(self):
|
||||||
|
cookie = self._login_cookie({"x-forwarded-proto": "https"})
|
||||||
|
self.assertIn("; secure", cookie)
|
||||||
|
|
||||||
|
def test_login_cookie_is_not_secure_over_http(self):
|
||||||
|
cookie = self._login_cookie({"x-forwarded-proto": "http"})
|
||||||
|
self.assertNotIn("; secure", cookie)
|
||||||
|
|
||||||
|
def test_cookie_secure_config_forces_secure(self):
|
||||||
|
self.minimal_config["auth"]["cookie_secure"] = True
|
||||||
|
cookie = self._login_cookie()
|
||||||
|
self.assertIn("; secure", cookie)
|
||||||
|
|
||||||
|
def test_refreshed_cookie_is_secure_over_tls(self):
|
||||||
|
app = self._create_app()
|
||||||
|
token = jwt.encode(
|
||||||
|
{"alg": "HS256"},
|
||||||
|
{"sub": "bob", "exp": int(time.time()) + 60, "iat": int(time.time())},
|
||||||
|
app.jwt_token,
|
||||||
|
)
|
||||||
|
|
||||||
|
with AuthTestClient(app) as client:
|
||||||
|
client.cookies.set("frigate_token", token)
|
||||||
|
resp = client.get(
|
||||||
|
"/auth",
|
||||||
|
headers={"x-server-port": "8971", "x-forwarded-proto": "https"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 202)
|
||||||
|
self.assertIn("; secure", resp.headers["set-cookie"].lower())
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"cookie_secure": {
|
"cookie_secure": {
|
||||||
"label": "Secure cookie flag",
|
"label": "Secure cookie flag",
|
||||||
"description": "Set the secure flag on the auth cookie; should be true when using TLS."
|
"description": "Always set the secure flag on the auth cookie. Frigate sets it automatically when serving TLS; enable this when a reverse proxy terminates TLS in front of Frigate."
|
||||||
},
|
},
|
||||||
"session_length": {
|
"session_length": {
|
||||||
"label": "Session length",
|
"label": "Session length",
|
||||||
|
|||||||
Reference in New Issue
Block a user