mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-09-30 08:23:14 +00:00
fix(cdk): explicit Lambda LogGroups replace log_retention (INFRA-114) (#126)
Some checks are pending
Deploy / deploy (push) Waiting to run
Some checks are pending
Deploy / deploy (push) Waiting to run
First seahaven-prod deploy failed CREATE on the sender-auth MetricFilter: it imported /aws/lambda/<fn> by name, which pre-existed in mgmt but not in a fresh account. All 5 functions now get an explicit logs.LogGroup (TWO_MONTHS, RETAIN) via common.make_function_log_group, and the metric filter takes the construct so CFN orders it after the group exists. Also removes the deprecated LogRetention custom resource and its wildcard logs:PutRetentionPolicy role (CKV_AWS_111). Function roles keep AWSLambdaBasicExecutionRole (verified in the synthesized template), so log-write permissions are unchanged; cross-review's grant_write FIX was a false positive on that basis. Supersedes PR #84, which hardcoded the mgmt logs-CMK ARN and predates the common.py refactor. mgmt collision note: these CREATEs would collide with the pre-existing groups in mgmt; acceptable because the deploy secret now targets prod and mgmt is frozen pending decommission.
This commit is contained in:
parent
073201f633
commit
00d0d32337
3 changed files with 65 additions and 20 deletions
|
|
@ -73,7 +73,36 @@ def add_ddb_alarms(scope, id_prefix, table, alarm_name_prefix, alarm_topic):
|
|||
).add_alarm_action(cw_actions.SnsAction(alarm_topic))
|
||||
|
||||
|
||||
def add_sender_auth_rejected_alarm(scope, id_prefix, function_name, alarm_topic):
|
||||
def make_function_log_group(scope, id_prefix, function_name):
|
||||
"""Explicit log group for a Lambda, replacing the deprecated
|
||||
``log_retention`` prop (INFRA-114).
|
||||
|
||||
Making the group a real stack resource (a) drops the LogRetention custom
|
||||
resource whose role carried wildcard ``logs:PutRetentionPolicy`` (checkov
|
||||
CKV_AWS_111), and (b) makes the group an orderable CFN dependency:
|
||||
consumers like the sender-auth metric filter now deploy AFTER the group
|
||||
exists. The previous by-name import raced group creation in a fresh
|
||||
account and failed the first seahaven-prod deploy (the mgmt account
|
||||
masked this because its groups predated the filter).
|
||||
|
||||
RETAIN matches the repo convention for stateful resources and mirrors the
|
||||
old behavior (LogRetention never deleted groups on stack delete). NOTE:
|
||||
in an account where ``/aws/lambda/<fn>`` already exists out-of-band
|
||||
(mgmt), deploying this CREATE would collide -- acceptable because mgmt is
|
||||
frozen post-migration and never redeployed from main.
|
||||
"""
|
||||
return logs.LogGroup(
|
||||
scope,
|
||||
f"{id_prefix}LogGroup",
|
||||
log_group_name=f"/aws/lambda/{function_name}",
|
||||
retention=logs.RetentionDays.TWO_MONTHS,
|
||||
removal_policy=RemovalPolicy.RETAIN,
|
||||
)
|
||||
|
||||
|
||||
def add_sender_auth_rejected_alarm(
|
||||
scope, id_prefix, function_name, alarm_topic, log_group
|
||||
):
|
||||
"""Metric-filter + alarm on ``sender_auth_rejected`` warnings (INFRA-107).
|
||||
|
||||
A rejected inbound email is skipped without erroring the invocation, so it
|
||||
|
|
@ -84,21 +113,18 @@ def add_sender_auth_rejected_alarm(scope, id_prefix, function_name, alarm_topic)
|
|||
mail while the pipeline reports healthy.
|
||||
|
||||
ALARM-only SnsAction to site-alerts; no OK action. The metric filter reads
|
||||
the function's own log group (imported by the deterministic
|
||||
``/aws/lambda/<fn>`` name, created by the function's log_retention). A plain
|
||||
substring pattern is used because Lambda prefixes each line with its own
|
||||
level/timestamp/request-id, so the JSON payload is not a standalone JSON
|
||||
log event a `{$.event=...}` pattern could match.
|
||||
the function's own log group, passed in as the EXPLICIT LogGroup construct
|
||||
(from ``make_function_log_group``) so CFN orders the filter after the
|
||||
group exists -- a by-name import here failed the first fresh-account
|
||||
deploy. A plain substring pattern is used because Lambda prefixes each
|
||||
line with its own level/timestamp/request-id, so the JSON payload is not
|
||||
a standalone JSON log event a `{$.event=...}` pattern could match.
|
||||
"""
|
||||
metric_name = f"{function_name}-sender-auth-rejected"
|
||||
logs.MetricFilter(
|
||||
scope,
|
||||
f"{id_prefix}SenderAuthRejectedFilter",
|
||||
log_group=logs.LogGroup.from_log_group_name(
|
||||
scope,
|
||||
f"{id_prefix}LogGroup",
|
||||
f"/aws/lambda/{function_name}",
|
||||
),
|
||||
log_group=log_group,
|
||||
filter_pattern=logs.FilterPattern.literal('"sender_auth_rejected"'),
|
||||
metric_namespace=_SENDER_AUTH_METRIC_NAMESPACE,
|
||||
metric_name=metric_name,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ from aws_cdk import (
|
|||
aws_kms as kms,
|
||||
aws_lambda as lambda_,
|
||||
aws_lambda_event_sources as lambda_event_sources,
|
||||
aws_logs as logs,
|
||||
aws_s3 as s3,
|
||||
aws_s3_notifications as s3n,
|
||||
aws_ses as ses,
|
||||
|
|
@ -91,6 +90,9 @@ class PoIngestStack(Stack):
|
|||
email_processor_dlq = common.make_processor_dlq(self, "EmailProcessorDlq")
|
||||
|
||||
# --- Lambda function ---
|
||||
email_processor_log_group = common.make_function_log_group(
|
||||
self, "EmailProcessor", "po-email-processor"
|
||||
)
|
||||
email_processor = lambda_.Function(
|
||||
self,
|
||||
"EmailProcessor",
|
||||
|
|
@ -134,7 +136,7 @@ class PoIngestStack(Stack):
|
|||
),
|
||||
timeout=Duration.seconds(60),
|
||||
memory_size=256,
|
||||
log_retention=logs.RetentionDays.TWO_MONTHS,
|
||||
log_group=email_processor_log_group,
|
||||
dead_letter_queue=email_processor_dlq,
|
||||
environment={
|
||||
"PO_TABLE": "purchase-orders",
|
||||
|
|
@ -196,7 +198,11 @@ class PoIngestStack(Stack):
|
|||
# false-reject storm pages instead of vanishing. default_value=0 keeps the
|
||||
# series populated (alarm stays OK, never INSUFFICIENT_DATA) between events.
|
||||
common.add_sender_auth_rejected_alarm(
|
||||
self, "EmailProcessor", "po-email-processor", alarm_topic
|
||||
self,
|
||||
"EmailProcessor",
|
||||
"po-email-processor",
|
||||
alarm_topic,
|
||||
email_processor_log_group,
|
||||
)
|
||||
|
||||
# --- Template fallback-rate alarm: po-email-processor ---
|
||||
|
|
@ -372,6 +378,7 @@ class PoIngestStack(Stack):
|
|||
)
|
||||
|
||||
# --- Web UI Lambda ---
|
||||
web_ui_log_group = common.make_function_log_group(self, "WebUI", "po-web-ui")
|
||||
web_ui = lambda_.Function(
|
||||
self,
|
||||
"WebUI",
|
||||
|
|
@ -409,7 +416,7 @@ class PoIngestStack(Stack):
|
|||
),
|
||||
timeout=Duration.seconds(60),
|
||||
memory_size=256,
|
||||
log_retention=logs.RetentionDays.TWO_MONTHS,
|
||||
log_group=web_ui_log_group,
|
||||
environment={
|
||||
"PO_TABLE": "purchase-orders",
|
||||
# Defense-in-depth shared secret for the web UI handler. The
|
||||
|
|
@ -464,6 +471,9 @@ class PoIngestStack(Stack):
|
|||
# 518 WCU of write amplification. Re-add if a state-level query path ships.
|
||||
|
||||
# --- Site extractor Lambda (DynamoDB Streams → verified-sites) ---
|
||||
site_extractor_log_group = common.make_function_log_group(
|
||||
self, "SiteExtractor", "po-ingest-site-extractor"
|
||||
)
|
||||
site_extractor = lambda_.Function(
|
||||
self,
|
||||
"SiteExtractor",
|
||||
|
|
@ -483,7 +493,7 @@ class PoIngestStack(Stack):
|
|||
),
|
||||
timeout=Duration.seconds(60),
|
||||
memory_size=256,
|
||||
log_retention=logs.RetentionDays.TWO_MONTHS,
|
||||
log_group=site_extractor_log_group,
|
||||
environment={
|
||||
"VERIFIED_SITES_TABLE": verified_sites_table.table_name,
|
||||
"PENDING_REVIEW_TABLE": "pending-site-review",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from aws_cdk import (
|
|||
aws_cloudwatch_actions as cw_actions,
|
||||
aws_dynamodb as dynamodb,
|
||||
aws_lambda as lambda_,
|
||||
aws_logs as logs,
|
||||
aws_s3 as s3,
|
||||
aws_s3_notifications as s3n,
|
||||
aws_ses as ses,
|
||||
|
|
@ -89,6 +88,9 @@ class WorkorderIngestStack(Stack):
|
|||
email_processor_dlq = common.make_processor_dlq(self, "EmailProcessorDlq")
|
||||
|
||||
# --- Lambda function ---
|
||||
email_processor_log_group = common.make_function_log_group(
|
||||
self, "EmailProcessor", "workorder-email-processor"
|
||||
)
|
||||
email_processor = lambda_.Function(
|
||||
self,
|
||||
"EmailProcessor",
|
||||
|
|
@ -119,7 +121,7 @@ class WorkorderIngestStack(Stack):
|
|||
),
|
||||
timeout=Duration.seconds(60),
|
||||
memory_size=256,
|
||||
log_retention=logs.RetentionDays.TWO_MONTHS,
|
||||
log_group=email_processor_log_group,
|
||||
dead_letter_queue=email_processor_dlq,
|
||||
environment={
|
||||
"WORK_ORDERS_TABLE": work_orders_table.table_name,
|
||||
|
|
@ -194,7 +196,11 @@ class WorkorderIngestStack(Stack):
|
|||
# dropped. This metric filter + alarm turns those warnings into a paging
|
||||
# signal so a false-reject storm surfaces instead of a silent outage.
|
||||
common.add_sender_auth_rejected_alarm(
|
||||
self, "EmailProcessor", "workorder-email-processor", alarm_topic
|
||||
self,
|
||||
"EmailProcessor",
|
||||
"workorder-email-processor",
|
||||
alarm_topic,
|
||||
email_processor_log_group,
|
||||
)
|
||||
|
||||
# --- Template fallback-rate alarm: workorder-email-processor ---
|
||||
|
|
@ -304,6 +310,9 @@ class WorkorderIngestStack(Stack):
|
|||
)
|
||||
|
||||
# --- Web UI Lambda ---
|
||||
web_ui_log_group = common.make_function_log_group(
|
||||
self, "WebUI", "workorder-web-ui"
|
||||
)
|
||||
web_ui = lambda_.Function(
|
||||
self,
|
||||
"WebUI",
|
||||
|
|
@ -340,7 +349,7 @@ class WorkorderIngestStack(Stack):
|
|||
),
|
||||
timeout=Duration.seconds(15),
|
||||
memory_size=128,
|
||||
log_retention=logs.RetentionDays.TWO_MONTHS,
|
||||
log_group=web_ui_log_group,
|
||||
environment={
|
||||
"WORK_ORDERS_TABLE": work_orders_table.table_name,
|
||||
"COMMENTS_TABLE": comments_table.table_name,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue