feat: SHARED_SECRET_LEGACY migration escape hatch for plugin secret rollout
Accepts one legacy secret alongside the real one so existing clients keep registering while game machines migrate to websocket_secret.txt. Remove SHARED_SECRET_LEGACY from .env after the rollout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
15ae870117
commit
52bf9342df
2 changed files with 21 additions and 3 deletions
|
|
@ -26,6 +26,9 @@ services:
|
||||||
DB_MAX_SQL_VARIABLES: "${DB_MAX_SQL_VARIABLES}"
|
DB_MAX_SQL_VARIABLES: "${DB_MAX_SQL_VARIABLES}"
|
||||||
DB_WAL_AUTOCHECKPOINT_PAGES: "${DB_WAL_AUTOCHECKPOINT_PAGES}"
|
DB_WAL_AUTOCHECKPOINT_PAGES: "${DB_WAL_AUTOCHECKPOINT_PAGES}"
|
||||||
SHARED_SECRET: "${SHARED_SECRET}"
|
SHARED_SECRET: "${SHARED_SECRET}"
|
||||||
|
# Optional second secret accepted during plugin migration — remove
|
||||||
|
# from .env after rollout (see main.py SHARED_SECRET_LEGACY).
|
||||||
|
SHARED_SECRET_LEGACY: "${SHARED_SECRET_LEGACY:-}"
|
||||||
SECRET_KEY: "${SECRET_KEY}"
|
SECRET_KEY: "${SECRET_KEY}"
|
||||||
INVENTORY_SERVICE_URL: "http://inventory-service:8000"
|
INVENTORY_SERVICE_URL: "http://inventory-service:8000"
|
||||||
DISCORD_ACLOG_WEBHOOK: "${DISCORD_ACLOG_WEBHOOK:-}"
|
DISCORD_ACLOG_WEBHOOK: "${DISCORD_ACLOG_WEBHOOK:-}"
|
||||||
|
|
|
||||||
21
main.py
21
main.py
|
|
@ -1003,6 +1003,17 @@ if not _SHARED_SECRET_OK:
|
||||||
"SHARED_SECRET env var is unset or still the placeholder — "
|
"SHARED_SECRET env var is unset or still the placeholder — "
|
||||||
"refusing ALL plugin WebSocket connections until it is set in .env"
|
"refusing ALL plugin WebSocket connections until it is set in .env"
|
||||||
)
|
)
|
||||||
|
# Migration escape hatch: while game machines are being migrated to
|
||||||
|
# websocket_secret.txt, a second (legacy) secret can be accepted alongside
|
||||||
|
# the real one. REMOVE SHARED_SECRET_LEGACY from .env once the plugin
|
||||||
|
# rollout is complete — with the old placeholder in it, this re-opens the
|
||||||
|
# known-secret hole it exists to close.
|
||||||
|
SHARED_SECRET_LEGACY = os.getenv("SHARED_SECRET_LEGACY", "")
|
||||||
|
if SHARED_SECRET_LEGACY:
|
||||||
|
logger.warning(
|
||||||
|
"SHARED_SECRET_LEGACY is set — legacy plugin secret accepted during "
|
||||||
|
"migration; remove it from .env after the plugin rollout"
|
||||||
|
)
|
||||||
# Secret key for signing session cookies. Fail closed: running with a
|
# Secret key for signing session cookies. Fail closed: running with a
|
||||||
# publicly-known default would let anyone forge admin sessions.
|
# publicly-known default would let anyone forge admin sessions.
|
||||||
SECRET_KEY = os.getenv("SECRET_KEY", "")
|
SECRET_KEY = os.getenv("SECRET_KEY", "")
|
||||||
|
|
@ -2979,9 +2990,13 @@ async def ws_receive_snapshots(
|
||||||
# compare; refuse everything when the secret is not configured).
|
# compare; refuse everything when the secret is not configured).
|
||||||
key = secret or x_plugin_secret or ""
|
key = secret or x_plugin_secret or ""
|
||||||
# compare bytes: compare_digest(str, str) raises TypeError on non-ASCII
|
# compare bytes: compare_digest(str, str) raises TypeError on non-ASCII
|
||||||
if not _SHARED_SECRET_OK or not hmac.compare_digest(
|
key_b = key.encode("utf-8", "replace")
|
||||||
key.encode("utf-8", "replace"), SHARED_SECRET.encode("utf-8")
|
auth_ok = _SHARED_SECRET_OK and hmac.compare_digest(
|
||||||
):
|
key_b, SHARED_SECRET.encode("utf-8")
|
||||||
|
)
|
||||||
|
if not auth_ok and SHARED_SECRET_LEGACY:
|
||||||
|
auth_ok = hmac.compare_digest(key_b, SHARED_SECRET_LEGACY.encode("utf-8"))
|
||||||
|
if not auth_ok:
|
||||||
# Reject without completing the WebSocket handshake
|
# Reject without completing the WebSocket handshake
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Plugin WebSocket authentication failed from {websocket.client}"
|
f"Plugin WebSocket authentication failed from {websocket.client}"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue