Skip to content

Commit 8ff7be3

Browse files
authored
Merge pull request #231 from aws-solutions/release/v2.3.1
Release v2.3.1
2 parents 23999a4 + 7a16e4f commit 8ff7be3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2202
-222
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.3.1] - 2025-08-06
9+
10+
### Added
11+
12+
- AWS Lambda Powertools Logger & Tracer support for all services
13+
- Added the SNS topic name to the logs
14+
- Added missing ECR.1 remediation in SC list
15+
16+
### Fixed
17+
18+
- Remove tag for EventSourceMapping
19+
- Added missing condition on log group in Admin stack to skip creation on solution re-deployment
20+
821
## [2.3.0] - 2025-07-16
922

1023
### Added

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -444,8 +444,7 @@ For example:
444444
ParameterKey=UseCloudWatchMetrics,ParameterValue=yes \
445445
ParameterKey=UseCloudWatchMetricsAlarms,ParameterValue=yes \
446446
ParameterKey=RemediationFailureAlarmThreshold,ParameterValue=5 \
447-
ParameterKey=EnableEnhancedCloudWatchMetrics,ParameterValue=no \
448-
ParameterKey=TicketGenFunctionName,ParameterValue=
447+
ParameterKey=EnableEnhancedCloudWatchMetrics,ParameterValue=no
449448

450449
export NAMESPACE=$(date +%s | tail -c 9)
451450
export MEMBER_TEMPLATE_URL=https://$TEMPLATE_BUCKET_NAME.s3.amazonaws.com/$SOLUTION_NAME/$SOLUTION_VERSION/automated-security-response-member.template
@@ -464,7 +463,7 @@ For example:
464463
ParameterKey=CreateS3BucketForRedshiftAuditLogging,ParameterValue=no \
465464
ParameterKey=LogGroupName,ParameterValue=random-log-group-123456789012 \
466465
ParameterKey=Namespace,ParameterValue=$NAMESPACE \
467-
ParameterKey=SecHubAdminAccount,ParameterValue=123456789012
466+
ParameterKey=SecHubAdminAccount,ParameterValue={SecHubAdminAccount}
468467

469468
export MEMBER_ROLES_TEMPLATE_URL=https://$TEMPLATE_BUCKET_NAME.s3.amazonaws.com/$SOLUTION_NAME/$SOLUTION_VERSION/automated-security-response-member-roles.template
470469
aws cloudformation create-stack \
@@ -473,7 +472,7 @@ For example:
473472
--template-url $MEMBER_ROLES_TEMPLATE_URL \
474473
--parameters \
475474
ParameterKey=Namespace,ParameterValue=$NAMESPACE \
476-
ParameterKey=SecHubAdminAccount,ParameterValue=123456789012
475+
ParameterKey=SecHubAdminAccount,ParameterValue={SecHubAdminAccount}
477476
```
478477

479478
## Directory structure
@@ -487,6 +486,7 @@ For example:
487486
|-lib/ [ Solution CDK ]
488487
|-cdk-helper/ [ CDK helper functions ]
489488
|-member/ [ Member stack helper functions ]
489+
|-parameters/ [ Stack common parameters ]
490490
|-tags/ [ Resource tagging helper functions ]
491491
|-Orchestrator/ [ Orchestrator Step Function Lambda Functions ]
492492
|-playbooks/ [ Playbooks ]
@@ -499,6 +499,7 @@ For example:
499499
|-bin/ [ Playbook CDK App ]
500500
|-ssmdocs/ [ Control runbooks ]
501501
|-PCI321/ [ PCI-DSS v3.2.1 playbook ]
502+
|-NIST80053/ [ NIST80053 playbook ]
502503
|-SC/ [ Security Control playbook ]
503504
|-remediation_runbooks/ [ Shared remediation runbooks ]
504505
|-scripts/ [ Scripts used by remediation runbooks ]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "automated_security_response_on_aws"
3-
version = "2.3.0"
3+
version = "2.3.1"
44

55
[tool.setuptools]
66
package-dir = {"" = "source"}

solution-manifest.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
id: SO0111
22
name: automated-security-response-on-aws
3-
version: v2.3.0
3+
version: v2.3.1
44
cloudformation_templates:
55
- template: automated-security-response-admin.template
66
main_template: true

sonar-project.properties

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ sonar.python.coverage.reportPaths = deployment/test/coverage-reports/*.coverage.
3434

3535
sonar.javascript.lcov.reportPaths = source/coverage/lcov.info
3636

37+
sonar.cpd.exclusions= \
38+
source/playbooks/**/lib/*_remediations.ts, \
39+
source/playbooks/**/lib/*construct.ts, \
40+
source/playbooks/**/ssmdocs/**, \
41+
source/lib/*-stack.ts
42+
3743
sonar.issue.ignore.multicriteria = ts1
3844
sonar.issue.ignore.multicriteria.ts1.ruleKey = typescript:S1848
3945
sonar.issue.ignore.multicriteria.ts1.resourceKey = **/*.ts
46+

source/Orchestrator/check_ssm_doc_state.py

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,37 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3-
import os
3+
from typing import Any, Dict
44

55
import boto3
66
from botocore.exceptions import ClientError
7-
from layer import tracer_utils, utils
7+
from layer import utils
88
from layer.awsapi_cached_client import BotoSession
99
from layer.cloudwatch_metrics import CloudWatchMetrics
10-
from layer.logger import Logger
10+
from layer.powertools_logger import get_logger
1111
from layer.sechub_findings import Finding
12+
from layer.tracer_utils import init_tracer
1213

1314
ORCH_ROLE_NAME = "SO0111-ASR-Orchestrator-Member" # role to use for cross-account
1415

15-
# initialise loggers
16-
LOG_LEVEL = os.getenv("log_level", "info")
17-
LOGGER = Logger(loglevel=LOG_LEVEL)
16+
logger = get_logger("check_ssm_doc_state")
17+
tracer = init_tracer()
18+
1819
session = boto3.session.Session()
1920
AWS_REGION = session.region_name
2021

21-
tracer = tracer_utils.init_tracer()
22-
2322

2423
def _get_ssm_client(account, role, region=""):
2524
"""
2625
Create a client for ssm
2726
"""
2827
kwargs = {}
29-
3028
if region:
3129
kwargs["region_name"] = region
3230

3331
return BotoSession(account, f"{role}").client("ssm", **kwargs)
3432

3533

36-
def _add_doc_state_to_answer(doc, account, region, answer):
34+
def _add_doc_state_to_answer(doc: str, account: str, region: str, answer: Any) -> None:
3735
try:
3836
# Connect to APIs
3937
ssm = _get_ssm_client(account, ORCH_ROLE_NAME, region)
@@ -50,7 +48,7 @@ def _add_doc_state_to_answer(doc, account, region, answer):
5048
"message": 'Document Type is not "Automation": ' + str(doctype),
5149
}
5250
)
53-
LOGGER.error(answer.message)
51+
logger.error(answer.message)
5452

5553
docstate = docinfo.get("Status", "unknown")
5654
if docstate != "Active":
@@ -60,7 +58,7 @@ def _add_doc_state_to_answer(doc, account, region, answer):
6058
"message": 'Document Status is not "Active": ' + str(docstate),
6159
}
6260
)
63-
LOGGER.error(answer.message)
61+
logger.error(answer.message)
6462

6563
answer.update({"status": "ACTIVE"})
6664

@@ -70,15 +68,15 @@ def _add_doc_state_to_answer(doc, account, region, answer):
7068
answer.update(
7169
{"status": "NOTFOUND", "message": f"Document {doc} does not exist."}
7270
)
73-
LOGGER.error(answer.message)
71+
logger.error(answer.message)
7472
elif exception_type == "AccessDenied":
7573
answer.update(
7674
{
7775
"status": "ACCESSDENIED",
7876
"message": f"Could not assume role for {doc} in {account} in {region}",
7977
}
8078
)
81-
LOGGER.error(answer.message)
79+
logger.error(answer.message)
8280
try:
8381
cloudwatch_metrics = CloudWatchMetrics()
8482
cloudwatch_metric = {
@@ -88,33 +86,33 @@ def _add_doc_state_to_answer(doc, account, region, answer):
8886
}
8987
cloudwatch_metrics.send_metric(cloudwatch_metric)
9088
except Exception:
91-
LOGGER.debug("Did not send Cloudwatch metric")
89+
logger.debug("Did not send Cloudwatch metric")
9290
else:
9391
answer.update(
9492
{
9593
"status": "CLIENTERROR",
9694
"message": "An unhandled client error occurred: " + exception_type,
9795
}
9896
)
99-
LOGGER.error(answer.message)
97+
logger.error(answer.message)
10098

10199
except Exception as e:
102100
answer.update(
103101
{"status": "ERROR", "message": "An unhandled error occurred: " + str(e)}
104102
)
105-
LOGGER.error(answer.message)
103+
logger.error(answer.message)
106104

107105

108-
@tracer.capture_lambda_handler
109-
def lambda_handler(event, _):
110-
answer = utils.StepFunctionLambdaAnswer() # holds the response to the step function
111-
LOGGER.info(event)
106+
@tracer.capture_lambda_handler # type: ignore[misc]
107+
def lambda_handler(event: Dict[str, Any], _: Any) -> Dict[str, Any]:
108+
answer = utils.StepFunctionLambdaAnswer()
109+
logger.info("Processing SSM doc state check", **event)
112110
if "Finding" not in event or "EventType" not in event:
113111
answer.update(
114112
{"status": "ERROR", "message": "Missing required data in request"}
115113
)
116-
LOGGER.error(answer.message)
117-
return answer.json()
114+
logger.error(answer.message)
115+
return answer.json() # type: ignore[no-any-return]
118116

119117
product_name = (
120118
event["Finding"]
@@ -146,7 +144,7 @@ def lambda_handler(event, _):
146144
}
147145
)
148146
answer.update({"status": "ACTIVE"})
149-
return answer.json()
147+
return answer.json() # type: ignore[no-any-return]
150148

151149
finding = Finding(event["Finding"])
152150

@@ -174,7 +172,7 @@ def lambda_handler(event, _):
174172
"message": f'Security Standard is not enabled": "{finding.standard_name} version {finding.standard_version}"',
175173
}
176174
)
177-
return answer.json()
175+
return answer.json() # type: ignore[no-any-return]
178176

179177
# Is there alt workflow configuration?
180178
alt_workflow_doc = event.get("Workflow", {}).get("WorkflowDocument", None)
@@ -195,4 +193,4 @@ def lambda_handler(event, _):
195193
automation_docid, finding.account_id, finding.resource_region, answer
196194
)
197195

198-
return answer.json()
196+
return answer.json() # type: ignore[no-any-return]

source/Orchestrator/check_ssm_execution.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
import json
4-
import os
54
import re
65
from json.decoder import JSONDecodeError
7-
from typing import TYPE_CHECKING, Any, Optional
6+
from typing import TYPE_CHECKING, Any, Dict, Optional
87

9-
from layer import tracer_utils, utils
8+
from layer import utils
109
from layer.awsapi_cached_client import BotoSession
11-
from layer.logger import Logger
10+
from layer.powertools_logger import get_logger
11+
from layer.tracer_utils import init_tracer
1212

1313
if TYPE_CHECKING:
1414
from mypy_boto3_ssm.client import SSMClient
@@ -17,14 +17,11 @@
1717

1818
ORCH_ROLE_NAME = "SO0111-ASR-Orchestrator-Member" # role to use for cross-account
1919

20-
# initialise loggers
21-
LOG_LEVEL = os.getenv("log_level", "info")
22-
LOGGER = Logger(loglevel=LOG_LEVEL)
20+
logger = get_logger("check_ssm_execution")
21+
tracer = init_tracer()
2322

24-
tracer = tracer_utils.init_tracer()
2523

26-
27-
def _get_ssm_client(account: str, role: str, region: str = "") -> SSMClient:
24+
def _get_ssm_client(account: str, role: str, region: str = "") -> Any:
2825
"""
2926
Create a client for ssm
3027
"""
@@ -33,7 +30,7 @@ def _get_ssm_client(account: str, role: str, region: str = "") -> SSMClient:
3330
if region:
3431
kwargs["region_name"] = region
3532

36-
ssm: SSMClient = BotoSession(account, f"{role}").client("ssm", **kwargs)
33+
ssm = BotoSession(account, f"{role}").client("ssm", **kwargs)
3734
return ssm
3835

3936

@@ -78,7 +75,7 @@ def __init__(self, exec_id, account, role_base_name, region):
7875

7976
def get_execution_state(self):
8077
automation_exec_info = self._ssm_client.describe_automation_executions(
81-
Filters=[{"Key": "ExecutionId", "Values": [self.exec_id]}] # type: ignore[list-item]
78+
Filters=[{"Key": "ExecutionId", "Values": [self.exec_id]}]
8279
)
8380

8481
self.status = automation_exec_info["AutomationExecutionMetadataList"][0].get(
@@ -160,7 +157,7 @@ def get_remediation_message(response_data, remediation_status):
160157
return message
161158

162159

163-
def get_remediation_output(response_data: dict[str, str]) -> str:
160+
def get_remediation_output(response_data: Dict[str, str]) -> str:
164161
message_key = next((key for key in response_data if key.lower() == "message"), "")
165162
if message_key:
166163
response_data.pop(
@@ -186,8 +183,8 @@ def get_remediation_response(remediation_response_raw):
186183
return remediation_response
187184

188185

189-
@tracer.capture_lambda_handler
190-
def lambda_handler(event, _):
186+
@tracer.capture_lambda_handler # type: ignore[misc]
187+
def lambda_handler(event: Dict[str, Any], _: Any) -> Dict[str, Any]:
191188
answer = utils.StepFunctionLambdaAnswer()
192189
automation_doc = event["AutomationDocument"]
193190

@@ -199,8 +196,8 @@ def lambda_handler(event, _):
199196
+ json.dumps(automation_doc),
200197
}
201198
)
202-
LOGGER.error(answer.message)
203-
return answer.json()
199+
logger.error(answer.message)
200+
return answer.json() # type: ignore[no-any-return]
204201

205202
SSM_EXEC_ID = event["SSMExecution"]["ExecId"]
206203
SSM_ACCOUNT = event["SSMExecution"].get("Account")
@@ -216,7 +213,7 @@ def lambda_handler(event, _):
216213
SSM_EXEC_ID, SSM_ACCOUNT, ORCH_ROLE_NAME, SSM_REGION
217214
)
218215
except Exception as e:
219-
LOGGER.error(f"Unable to retrieve AutomationExecution data: {str(e)}")
216+
logger.error(f"Unable to retrieve AutomationExecution data: {str(e)}")
220217
raise e
221218

222219
# Terminal states - get log data from AutomationExecutionMetadataList
@@ -298,4 +295,4 @@ def lambda_handler(event, _):
298295
}
299296
)
300297

301-
return answer.json()
298+
return answer.json() # type: ignore[no-any-return]

0 commit comments

Comments
 (0)