mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-10-07 16:08:54 +00:00
Add TTL to web UI auth token cache for rotation
The web-ui handlers cached the Secrets Manager auth token at module level with no expiry, so a rotated secret was only picked up when the warm container recycled — an emergency rotation could take hours to take effect. Cache the fetched value for a 5-minute TTL instead, so a rotated token propagates within the TTL while still avoiding a Secrets Manager call on every request. Still fails closed when the secret is unset or unreadable. Refs: INFRA-74
This commit is contained in:
parent
5b39f1dad1
commit
2802d04368
2 changed files with 32 additions and 8 deletions
|
|
@ -8,6 +8,7 @@ Accessed via Lambda Function URL.
|
||||||
import hmac
|
import hmac
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from html import escape as esc
|
from html import escape as esc
|
||||||
|
|
||||||
|
|
@ -26,17 +27,27 @@ PO_TABLE = os.environ.get("PO_TABLE", "purchase-orders")
|
||||||
# ARN is unset or the secret is missing, the handler fails closed and denies every
|
# ARN is unset or the secret is missing, the handler fails closed and denies every
|
||||||
# request.
|
# request.
|
||||||
_WEB_UI_AUTH_TOKEN_SECRET_ARN = os.environ.get("WEB_UI_AUTH_TOKEN_SECRET_ARN")
|
_WEB_UI_AUTH_TOKEN_SECRET_ARN = os.environ.get("WEB_UI_AUTH_TOKEN_SECRET_ARN")
|
||||||
|
# Refresh the cached token this often so a rotated secret propagates without
|
||||||
|
# waiting for the execution environment to recycle (emergency-rotation path).
|
||||||
|
_AUTH_TOKEN_CACHE_TTL_SECONDS = 300
|
||||||
_auth_token_cache = None
|
_auth_token_cache = None
|
||||||
|
_auth_token_cached_at = 0.0
|
||||||
|
|
||||||
|
|
||||||
def _get_auth_token() -> str | None:
|
def _get_auth_token() -> str | None:
|
||||||
"""Fetch the shared web UI auth token from Secrets Manager.
|
"""Fetch the shared web UI auth token from Secrets Manager.
|
||||||
|
|
||||||
Cached at module level across warm invocations so we don't hit Secrets
|
Cached in the warm container for a short TTL so we don't hit Secrets Manager
|
||||||
Manager on every request. Returns None when not configured.
|
on every request, while still picking up a rotated secret within the TTL
|
||||||
|
rather than only when the execution environment recycles. Returns None when
|
||||||
|
not configured or unreadable (the caller then fails closed).
|
||||||
"""
|
"""
|
||||||
global _auth_token_cache
|
global _auth_token_cache, _auth_token_cached_at
|
||||||
if _auth_token_cache is not None:
|
now = time.monotonic()
|
||||||
|
if (
|
||||||
|
_auth_token_cache is not None
|
||||||
|
and now - _auth_token_cached_at < _AUTH_TOKEN_CACHE_TTL_SECONDS
|
||||||
|
):
|
||||||
return _auth_token_cache
|
return _auth_token_cache
|
||||||
if not _WEB_UI_AUTH_TOKEN_SECRET_ARN:
|
if not _WEB_UI_AUTH_TOKEN_SECRET_ARN:
|
||||||
return None
|
return None
|
||||||
|
|
@ -44,6 +55,7 @@ def _get_auth_token() -> str | None:
|
||||||
try:
|
try:
|
||||||
secret = secrets.get_secret_value(SecretId=_WEB_UI_AUTH_TOKEN_SECRET_ARN)
|
secret = secrets.get_secret_value(SecretId=_WEB_UI_AUTH_TOKEN_SECRET_ARN)
|
||||||
_auth_token_cache = secret["SecretString"]
|
_auth_token_cache = secret["SecretString"]
|
||||||
|
_auth_token_cached_at = now
|
||||||
return _auth_token_cache
|
return _auth_token_cache
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ Accessed via Lambda Function URL.
|
||||||
import hmac
|
import hmac
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
from html import escape as esc
|
from html import escape as esc
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
|
@ -25,17 +26,27 @@ COMMENTS_TABLE = os.environ.get("COMMENTS_TABLE", "WorkOrderComments")
|
||||||
# templates and Lambda env vars. If the ARN is unset or the secret is missing, the
|
# templates and Lambda env vars. If the ARN is unset or the secret is missing, the
|
||||||
# handler fails closed and denies every request.
|
# handler fails closed and denies every request.
|
||||||
_WEB_UI_AUTH_TOKEN_SECRET_ARN = os.environ.get("WEB_UI_AUTH_TOKEN_SECRET_ARN")
|
_WEB_UI_AUTH_TOKEN_SECRET_ARN = os.environ.get("WEB_UI_AUTH_TOKEN_SECRET_ARN")
|
||||||
|
# Refresh the cached token this often so a rotated secret propagates without
|
||||||
|
# waiting for the execution environment to recycle (emergency-rotation path).
|
||||||
|
_AUTH_TOKEN_CACHE_TTL_SECONDS = 300
|
||||||
_auth_token_cache = None
|
_auth_token_cache = None
|
||||||
|
_auth_token_cached_at = 0.0
|
||||||
|
|
||||||
|
|
||||||
def _get_auth_token() -> str | None:
|
def _get_auth_token() -> str | None:
|
||||||
"""Fetch the shared web UI auth token from Secrets Manager.
|
"""Fetch the shared web UI auth token from Secrets Manager.
|
||||||
|
|
||||||
Cached at module level across warm invocations so we don't hit Secrets
|
Cached in the warm container for a short TTL so we don't hit Secrets Manager
|
||||||
Manager on every request. Returns None when not configured.
|
on every request, while still picking up a rotated secret within the TTL
|
||||||
|
rather than only when the execution environment recycles. Returns None when
|
||||||
|
not configured or unreadable (the caller then fails closed).
|
||||||
"""
|
"""
|
||||||
global _auth_token_cache
|
global _auth_token_cache, _auth_token_cached_at
|
||||||
if _auth_token_cache is not None:
|
now = time.monotonic()
|
||||||
|
if (
|
||||||
|
_auth_token_cache is not None
|
||||||
|
and now - _auth_token_cached_at < _AUTH_TOKEN_CACHE_TTL_SECONDS
|
||||||
|
):
|
||||||
return _auth_token_cache
|
return _auth_token_cache
|
||||||
if not _WEB_UI_AUTH_TOKEN_SECRET_ARN:
|
if not _WEB_UI_AUTH_TOKEN_SECRET_ARN:
|
||||||
return None
|
return None
|
||||||
|
|
@ -43,6 +54,7 @@ def _get_auth_token() -> str | None:
|
||||||
try:
|
try:
|
||||||
secret = secrets.get_secret_value(SecretId=_WEB_UI_AUTH_TOKEN_SECRET_ARN)
|
secret = secrets.get_secret_value(SecretId=_WEB_UI_AUTH_TOKEN_SECRET_ARN)
|
||||||
_auth_token_cache = secret["SecretString"]
|
_auth_token_cache = secret["SecretString"]
|
||||||
|
_auth_token_cached_at = now
|
||||||
return _auth_token_cache
|
return _auth_token_cache
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue