feat(6838): read the client address and HTTPS from the trusted proxy hop
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 37s
CI & Build / Python tests (push) Failing after 1m20s
CI & Build / integration (push) Successful in 2m27s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 37s
CI & Build / Python tests (push) Failing after 1m20s
CI & Build / integration (push) Successful in 2m27s
CI & Build / Build & push image (push) Skipped
grimoire/proxy.py is the one reader of X-Forwarded-For and X-Forwarded-Proto. It takes the entry TRUSTED_PROXY_HOPS in from the right (default 1; a shorter header reads its leftmost entry; 0 trusts no proxy), so an entry a client puts left of the proxy's is never believed. Before this, with TRUST_PROXY_HEADERS on, the leftmost X-Forwarded-For entry keyed the login, register, forgot and reset rate limits and the audit log, and a client could choose it. The session cookie is Secure, and HSTS (max-age=31536000) is sent, exactly when the request is HTTPS by the trusted proxy's word. Plain HTTP is untouched and nothing redirects. SECURE_COOKIES is gone (a startup warning says so when it is still set); TRUST_PROXY_HEADERS=false still maps to 0 hops, with a warning to move to TRUSTED_PROXY_HOPS. Admin audit lines now use the same client address instead of the socket peer. The placeholder-key refusal keyed on SECURE_COOKIES, so it now refuses either placeholder key outright; the instance makes its own key when none is set. Family idea #5004 (IsHTTPS, trusted-hop X-Forwarded-Proto), canon v2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+13
-10
@@ -6,7 +6,8 @@ from pathlib import Path
|
||||
|
||||
from quart import Quart, g, jsonify, make_response, request, send_from_directory
|
||||
|
||||
from grimoire.config import PLACEHOLDER_SECRET_KEYS, SECRET_KEY_FILENAME, Config
|
||||
from grimoire.config import SECRET_KEY_FILENAME, Config
|
||||
from grimoire.proxy import PerRequestSecureSession, apply_https_headers
|
||||
from grimoire.routes.admin import admin_bp
|
||||
from grimoire.routes.api import api
|
||||
from grimoire.routes.auth import auth_bp
|
||||
@@ -61,13 +62,13 @@ def create_app() -> Quart:
|
||||
app.secret_key = Config.SECRET_KEY
|
||||
app.config["SESSION_COOKIE_HTTPONLY"] = True
|
||||
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
||||
app.config["SESSION_COOKIE_SECURE"] = Config.SECURE_COOKIES
|
||||
# Secure is decided per request, from the trusted proxy's X-Forwarded-Proto.
|
||||
app.session_interface = PerRequestSecureSession()
|
||||
|
||||
if Config.SECRET_KEY in PLACEHOLDER_SECRET_KEYS:
|
||||
if os.environ.get("SECURE_COOKIES"):
|
||||
logger.warning(
|
||||
"SECRET_KEY is set to a public placeholder, so session cookies are insecure. "
|
||||
"Remove it to let the instance keep its own key in %s, or set a real one.",
|
||||
Config.DATA_DIR,
|
||||
"SECURE_COOKIES is not used: the session cookie is Secure on every request "
|
||||
"the trusted proxy reports as HTTPS. Remove it from the environment."
|
||||
)
|
||||
if Config.SECRET_KEY_SOURCE == "ephemeral":
|
||||
logger.warning(
|
||||
@@ -82,11 +83,12 @@ def create_app() -> Quart:
|
||||
Config.DATA_DIR, SECRET_KEY_FILENAME,
|
||||
)
|
||||
|
||||
if not Config.TRUST_PROXY_HEADERS:
|
||||
if os.environ.get("TRUST_PROXY_HEADERS") and not os.environ.get("TRUSTED_PROXY_HOPS"):
|
||||
logger.warning(
|
||||
"TRUST_PROXY_HEADERS is not set. If this instance is behind a reverse proxy "
|
||||
"(nginx, Caddy, Traefik) set TRUST_PROXY_HEADERS=true so rate limiting uses "
|
||||
"real client IPs rather than the proxy IP."
|
||||
"TRUST_PROXY_HEADERS is replaced by TRUSTED_PROXY_HOPS (now %d). Set "
|
||||
"TRUSTED_PROXY_HOPS to the number of proxies in front of Grimoire and "
|
||||
"remove TRUST_PROXY_HEADERS.",
|
||||
Config.TRUSTED_PROXY_HOPS,
|
||||
)
|
||||
|
||||
app.register_blueprint(admin_bp)
|
||||
@@ -159,6 +161,7 @@ def create_app() -> Quart:
|
||||
except Exception:
|
||||
logger.debug("Failed to log usage", exc_info=True)
|
||||
|
||||
apply_https_headers(response)
|
||||
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
||||
response.headers.setdefault("X-Frame-Options", "DENY")
|
||||
response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
|
||||
Reference in New Issue
Block a user