Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ deployment/global-s3-assets/
deployment/regional-s3-assets/
deployment/viperlight
deployment/staging/
deployment/open-source

### macOS ###
# General
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.4.4] - 2025-09-24

### Security

- Updated axios from `1.7.7` to `1.12.1` to mitigate [CVE-2025-58754](https://avd.aquasec.com/nvd/cve-2025-58754), a DoS vulnerability.
- Updated Python Lambda base image from `public.ecr.aws/lambda/python:3.12.2025.09.02.19` to `public.ecr.aws/lambda/python:3.12.2025.09.22.12` to address [CVE-2025-24528](https://avd.aquasec.com/nvd/cve-2025-24528), [CVE-2025-3576](https://avd.aquasec.com/nvd/cve-2025-3576), [CVE-2025-7425](https://avd.aquasec.com/nvd/cve-2025-7425), and [CVE-2025-8058](https://avd.aquasec.com/nvd/cve-2025-8058).
- Removed deprecated NPM package "fs" has been identified as potentially vulnerable to package takeover.

### Fixed

- Fixed solution CloudFormation template deployment failures in AWS China partition by implementing partition-aware S3 URL generation [Issue #338](https://github.com/aws-solutions/centralized-logging-with-opensearch/issues/338)
- Fixed timeout issue with Get Agent status API by optimizing retry logic to work within AppSync's 30 second timeout limit

## [2.4.3] - 2025-09-03

### Security
Expand Down
12 changes: 12 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1777,6 +1777,18 @@ napi-postinstall under the MIT license.
@unrs/resolver-binding-linux-x64-musl under the MIT license.
exit-x under the MIT license.
pygments under the 0BSD license.
set-proto under the MIT license.
side-channel-map under the MIT license.
safe-push-apply under the MIT license.
async-function under the MIT license.
side-channel-list under the MIT license.
call-bound under the MIT license.
side-channel-weakmap under the MIT license.
own-keys under the MIT license.
@babel/plugin-transform-explicit-resource-management under the MIT license.
wsl-utils under the MIT license.
baseline-browser-mapping under the Apache-2.0 license.
@babel/helper-globals under the Apache-2.0 license.

********************
OPEN SOURCE LICENSES
Expand Down
6 changes: 0 additions & 6 deletions deployment/cdk-solution-helper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,5 @@
"name": "Amazon Web Services",
"url": "https://aws.amazon.com/solutions",
"organization": true
},
"devDependencies": {
"fs": "0.0.1-security"
},
"dependencies": {
"fs": "0.0.1-security"
}
}
4 changes: 2 additions & 2 deletions deployment/ecr/clo-s3-list-objects/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM public.ecr.aws/lambda/python:3.12.2025.09.02.19 AS builder
FROM public.ecr.aws/lambda/python:3.12.2025.09.22.12 AS builder

WORKDIR /build

Expand All @@ -14,7 +14,7 @@ RUN python -m venv .venv && \
cd common-lib && \
poetry build

FROM public.ecr.aws/lambda/python:3.12.2025.09.02.19
FROM public.ecr.aws/lambda/python:3.12.2025.09.22.12

WORKDIR /ws

Expand Down
21 changes: 11 additions & 10 deletions source/constructs/lambda/api/log_agent_status/lambda_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,22 +190,22 @@ def list_command_invocations(ssm_client, command_id, details=True, maxResults=50


def handle_command_invocations(
ssm_client, command_id, max_retries=10, retry_delay=0.25
ssm_client, command_id, max_retries=13, retry_delay=2.0
):
"""
Handles the list of command invocations for the specified AWS Systems Manager command.
If any of the invocations are in progress, it will retry the command until all invocations are completed or the maximum number of retries is reached.

Uses fixed retry delay to stay within AppSync's 30-second timeout.
Args:
ssm_client (boto3.client): An AWS Systems Manager client.
command_id (str): The ID of the command to handle.
max_retries (int, optional): The maximum number of times to retry the command. Defaults to 5.
retry_delay (float, optional): The number of seconds to wait between retries. Defaults to 0.5.
max_retries (int, optional): Maximum retries (default: 13)
retry_delay (float, optional): Delay between retries (default: 2.0 seconds)

Returns:
list: The list of command invocations.
"""
for _ in range(max_retries):
for attempt in range(max_retries):
command_invocations = list(list_command_invocations(ssm_client, command_id))
in_progress_count = len(
list(
Expand All @@ -216,13 +216,14 @@ def handle_command_invocations(
return command_invocations

logger.info(
f"Retrying command {command_id} ({in_progress_count}/{len(command_invocations)} invocations in progress)"
f"Retrying command {command_id} ({in_progress_count}/{len(command_invocations)} invocations in progress) - attempt {attempt + 1}/{max_retries}"
)
time.sleep(retry_delay)
retry_delay *= 2 # Exponential backoff

if attempt < max_retries - 1:
time.sleep(retry_delay)

raise TimeoutError(
f"Command {command_id} did not complete within {sum(2 ** i * retry_delay for i in range(max_retries))} seconds."
f"Command {command_id} did not complete within {max_retries * retry_delay} seconds."
)


Expand Down
2 changes: 2 additions & 0 deletions source/constructs/lambda/custom-resource/lambda_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
solution_version = os.environ.get("SOLUTION_VERSION")
solution_name = os.environ.get("SOLUTION_NAME")
template_bucket = os.environ.get("TEMPLATE_OUTPUT_BUCKET")
template_base_url = os.environ.get("TEMPLATE_BASE_URL")
bucket_name = os.environ.get("WEB_BUCKET_NAME")
api_endpoint = os.environ.get("API_ENDPOINT")
user_pool_id = os.environ.get("USER_POOL_ID")
Expand Down Expand Up @@ -121,6 +122,7 @@ def get_config_str():
"solution_name": solution_name,
"sns_email_topic_arn": SNS_EMAIL_TOPIC_ARN,
"template_bucket": template_bucket,
"template_base_url": template_base_url,
}

return json.dumps(export_json)
Expand Down
4 changes: 2 additions & 2 deletions source/constructs/lambda/main/cfnHelper/lambda_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

default_region = os.environ.get("AWS_REGION")

template_output_bucket = os.environ.get("TEMPLATE_OUTPUT_BUCKET", "aws-gcr-solutions")
template_base_url = os.environ.get("TEMPLATE_BASE_URL")
solution_name = os.environ.get("SOLUTION_NAME", "clo")
template_prefix = f"https://{template_output_bucket}.s3.amazonaws.com/{solution_name}/{solution_version}"
template_prefix = f"{template_base_url}/{solution_name}/{solution_version}"

sts = boto3.client("sts", config=default_config)
account_id = sts.get_caller_identity()["Account"]
Expand Down
2 changes: 1 addition & 1 deletion source/constructs/lambda/main/cfnHelper/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ def default_environment_variables():
os.environ["SOLUTION_VERSION"] = "v1.0.0"
os.environ["SOLUTION_ID"] = "SOXXXX"

os.environ["TEMPLATE_OUTPUT_BUCKET"] = "solution-bucket"
os.environ["TEMPLATE_BASE_URL"] = "https://solution-bucket.s3.amazonaws.com"
os.environ["SUB_ACCOUNT_LINK_TABLE_NAME"] = "mocked-sub-account-link-table-name"
os.environ["SUB_ACCOUNT_LINK_TABLE"] = "mocked-sub-account-link-table-name"
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,18 @@ def s3_client():
key = f"{solution_name}/{version}/AlarmForOpenSearch.template"

s3 = boto3.resource("s3", region_name=region)
# Create the bucket
template_bucket = os.environ.get("TEMPLATE_OUTPUT_BUCKET")
s3.create_bucket(Bucket=template_bucket)
# Create the bucket - extract bucket name from TEMPLATE_BASE_URL
template_base_url = os.environ.get("TEMPLATE_BASE_URL")
if template_base_url:
bucket_name = template_base_url.replace("https://", "").split(".s3.")[0]
else:
bucket_name = "solution-bucket"

s3.create_bucket(Bucket=bucket_name)

# upload template file
data = open("./test/template/test.template", "rb")
s3.Bucket(template_bucket).put_object(Key=key, Body=data)
s3.Bucket(bucket_name).put_object(Key=key, Body=data)

yield

Expand Down
9 changes: 7 additions & 2 deletions source/constructs/lib/api/api-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ export interface APIProps {
readonly snsEmailTopic: sns.Topic;
readonly sendAnonymizedUsageData: string;
readonly solutionUuid: string;

readonly s3Endpoint: string;
readonly templateBucketName: string;
readonly templateBaseUrl: string;
}

/**
Expand All @@ -136,15 +140,15 @@ export class APIStack extends Construct {
});

apiStack.graphqlApi
.addHttpDataSource('LatestVersionDS', 'https://s3.amazonaws.com')
.addHttpDataSource('LatestVersionDS', props.s3Endpoint)
.createResolver('LatestVersionResolver', {
typeName: 'Query',
fieldName: 'latestVersion',
requestMappingTemplate: appsync.MappingTemplate.fromString(
JSON.stringify({
version: '2018-05-29',
method: 'GET',
resourcePath: `/${TEMPLATE_OUTPUT_BUCKET}/centralized-logging-with-opensearch/latest/version`,
resourcePath: `/${props.templateBucketName}/centralized-logging-with-opensearch/latest/version`,
params: {
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -183,6 +187,7 @@ export class APIStack extends Construct {
stackPrefix: props.stackPrefix,
solutionId: props.solutionId,
microBatchStack: props.microBatchStack,
templateBaseUrl: props.templateBaseUrl,
});
NagSuppressions.addResourceSuppressions(
cfnFlow,
Expand Down
51 changes: 45 additions & 6 deletions source/constructs/lib/main-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
aws_s3 as s3,
aws_sqs as sqs,
Stack,
StackProps
StackProps,
} from 'aws-cdk-lib';
import { Vpc } from 'aws-cdk-lib/aws-ec2';
import { NagSuppressions } from 'cdk-nag';
Expand Down Expand Up @@ -154,6 +154,34 @@ export class MainStack extends Stack {
''
);

// Create China partition condition and resolve template URLs
const isChinaCondition = new CfnCondition(this, 'IsChinaPartition', {
expression: Fn.conditionEquals(Aws.PARTITION, 'aws-cn'),
});

const templateBucketBase =
process.env.TEMPLATE_OUTPUT_BUCKET || 'aws-gcr-solutions';
const globalTemplateBucket = templateBucketBase;
const chinaTemplateBucket = `${templateBucketBase}-cn`;

const templateBaseUrl = Fn.conditionIf(
isChinaCondition.logicalId,
`https://${chinaTemplateBucket}.s3.cn-north-1.amazonaws.com.cn`,
`https://${globalTemplateBucket}.s3.amazonaws.com`
).toString();

const s3Endpoint = Fn.conditionIf(
isChinaCondition.logicalId,
'https://s3.cn-north-1.amazonaws.com.cn',
'https://s3.amazonaws.com'
).toString();

const templateBucketName = Fn.conditionIf(
isChinaCondition.logicalId,
chinaTemplateBucket,
globalTemplateBucket
).toString();

if (this.authType === AuthType.OIDC) {
oidcProvider = new CfnParameter(this, 'OidcProvider', {
type: 'String',
Expand Down Expand Up @@ -633,6 +661,9 @@ export class MainStack extends Stack {
'SendAnonymizedUsageData',
'Data'
),
s3Endpoint: s3Endpoint,
templateBucketName: templateBucketName,
templateBaseUrl: templateBaseUrl,
});
NagSuppressions.addResourceSuppressions(
apiStack,
Expand Down Expand Up @@ -709,6 +740,8 @@ export class MainStack extends Stack {
authenticationType: this.authType,
webUILoggingBucket: portalStack.webUILoggingBucket.bucketName,
snsEmailTopic: microBatchStack.microBatchSNSStack.SNSSendEmailTopic,
templateBucketName: templateBucketName,
templateBaseUrl: templateBaseUrl,
});

// Allow init config function to put aws-exports.json to portal bucket
Expand All @@ -732,10 +765,16 @@ export class MainStack extends Stack {
value: portalStack.portalUrl,
}).overrideLogicalId('WebConsoleUrl');

Aspects.of(this).add(new CfnGuardSuppressResourceList({
"AWS::IAM::Role": ["IAM_NO_INLINE_POLICY_CHECK", "IAM_POLICYDOCUMENT_NO_WILDCARD_RESOURCE", "CFN_NO_EXPLICIT_RESOURCE_NAMES"], // Explicit role names required for cross account assumption
"AWS::Logs::LogGroup": ["CLOUDWATCH_LOG_GROUP_ENCRYPTED"], // Using service default encryption https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/data-protection.html
}))
Aspects.of(this).add(
new CfnGuardSuppressResourceList({
'AWS::IAM::Role': [
'IAM_NO_INLINE_POLICY_CHECK',
'IAM_POLICYDOCUMENT_NO_WILDCARD_RESOURCE',
'CFN_NO_EXPLICIT_RESOURCE_NAMES',
], // Explicit role names required for cross account assumption
'AWS::Logs::LogGroup': ['CLOUDWATCH_LOG_GROUP_ENCRYPTED'], // Using service default encryption https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/data-protection.html
})
);
}

private addToParamGroups(label: string, ...param: string[]) {
Expand All @@ -749,7 +788,7 @@ export class MainStack extends Stack {
this.paramLabels[param] = {
default: label,
};
}
}
}

class AddS3BucketNotificationsDependency implements IAspect {
Expand Down
6 changes: 3 additions & 3 deletions source/constructs/lib/main/cfn-flow-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface CfnFlowProps {
readonly solutionId: string;

readonly microBatchStack: MicroBatchStack;

readonly templateBaseUrl: string;
}

/**
Expand All @@ -52,8 +54,6 @@ export class CfnFlowStack extends Construct {

const stackArn = `arn:${Aws.PARTITION}:cloudformation:${Aws.REGION}:${Aws.ACCOUNT_ID}:stack/${props.stackPrefix}*`;

const templateBucket =
process.env.TEMPLATE_OUTPUT_BUCKET || 'aws-gcr-solutions';
const solutionName = process.env.SOLUTION_TRADEMARKEDNAME || 'log-hub'; // Old name

// Create a Lambda to handle all the cloudformation related tasks.
Expand All @@ -67,7 +67,7 @@ export class CfnFlowStack extends Construct {
memorySize: 128,
layers: [SharedPythonLayer.getInstance(this)],
environment: {
TEMPLATE_OUTPUT_BUCKET: templateBucket,
TEMPLATE_BASE_URL: props.templateBaseUrl,
SOLUTION_NAME: solutionName,
SOLUTION_ID: props.solutionId,
SUB_ACCOUNT_LINK_TABLE_NAME: props.subAccountLinkTable.tableName,
Expand Down
Loading
Loading