From 2c2cfcc5380e48f87fc27a8a2acf28186dbdfa60 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Wed, 15 Jul 2026 19:43:36 -0400 Subject: [PATCH] Log Secrets Manager failures in web UI auth token fetch The web UI auth gate correctly fails closed when the shared token cannot be read, but _get_auth_token() swallowed every exception silently. A Secrets Manager permission or config error then made every request 401 with no operational signal, leaving an outage indistinguishable from ordinary unauthenticated traffic. Add a module-level logger to both web_ui handlers and log the fetch failure with logger.exception() in the except block before returning None. Behavior is unchanged (still fails closed); the failure is now visible in CloudWatch. The secret value is never logged. The two handlers stay byte-consistent in the mirrored _get_auth_token() region. The companion finding on the CDK import of the shared procurement-ingest/web-ui-auth-token secret was evaluated and left as-is: the token is a single secret shared by both the PO and WO stacks, so from_secret_name_v2 (which scopes grant_read via the standard 6-char suffix wildcard) is correct; making it a CDK-managed Secret in both stacks would collide the two stacks on the same explicit secret name at deploy time. Refs: INFRA-74 --- lambdas/po/web_ui/handler.py | 12 ++++++++++++ lambdas/wo/web_ui/handler.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lambdas/po/web_ui/handler.py b/lambdas/po/web_ui/handler.py index 9e8d48f..e229a0d 100644 --- a/lambdas/po/web_ui/handler.py +++ b/lambdas/po/web_ui/handler.py @@ -7,6 +7,7 @@ Accessed via Lambda Function URL. import hmac import json +import logging import os import time from decimal import Decimal @@ -14,6 +15,9 @@ from html import escape as esc import boto3 +logger = logging.getLogger() +logger.setLevel(logging.INFO) + dynamodb = boto3.resource("dynamodb") PO_TABLE = os.environ.get("PO_TABLE", "purchase-orders") @@ -58,6 +62,14 @@ def _get_auth_token() -> str | None: _auth_token_cached_at = now return _auth_token_cache except Exception: + # Fail closed (return None -> caller 401s) but surface the failure: a + # Secrets Manager permission/config error would otherwise make every + # request 401 with no operational signal. The secret value is never + # logged. + logger.exception( + "Failed to fetch web UI auth token from Secrets Manager; " + "denying request (failing closed)" + ) return None diff --git a/lambdas/wo/web_ui/handler.py b/lambdas/wo/web_ui/handler.py index 21f64aa..3e3c849 100644 --- a/lambdas/wo/web_ui/handler.py +++ b/lambdas/wo/web_ui/handler.py @@ -7,12 +7,16 @@ Accessed via Lambda Function URL. import hmac import json +import logging import os import time from html import escape as esc import boto3 +logger = logging.getLogger() +logger.setLevel(logging.INFO) + dynamodb = boto3.resource("dynamodb") WORK_ORDERS_TABLE = os.environ.get("WORK_ORDERS_TABLE", "WorkOrders") @@ -57,6 +61,14 @@ def _get_auth_token() -> str | None: _auth_token_cached_at = now return _auth_token_cache except Exception: + # Fail closed (return None -> caller 401s) but surface the failure: a + # Secrets Manager permission/config error would otherwise make every + # request 401 with no operational signal. The secret value is never + # logged. + logger.exception( + "Failed to fetch web UI auth token from Secrets Manager; " + "denying request (failing closed)" + ) return None