Skip to content

Commit f2a448e

Browse files
authored
Merge pull request #36 from aws-solutions/release/v1.0.14
Release v1.0.14
2 parents a49f15a + caed1a8 commit f2a448e

16 files changed

+3528
-1077
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ 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+
## [1.0.14] - 2024-10
9+
10+
### Changed
11+
12+
- Remove dependencies `bootstrap` and `datefns`
13+
- Allow backend to accept uppercase http headers, to prevent errors when receiving uppercase `Content-type`
14+
- Replace pip3/requirements.txt dependency management with Poetry
15+
16+
### Added
17+
18+
- Add poetry.lock file to support reproducible builds, improve vulnerability scanning
19+
820
## [1.0.13] - 2024-9
921

1022
- Upgrade `rollup` to mitigate [CVE-2024-47068](https://nvd.nist.gov/vuln/detail/CVE-2024-47068)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ The default deployment of solution pre-packaged template deploys following infra
4040

4141
### Setup
4242

43-
- Python Prerequisite: python=3.9 | pip3=21.3.1
43+
- Python Prerequisite: python=3.12 | pip3=21.3.1
4444
- Javascript Prerequisite: node=v18.10.0 | npm=8.19.2
4545

4646
Clone the repository and make desired code changes.

source/lambda/assessment_runner/assessment_runner.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2-
# SPDX-License-Identifier: Apache-2.0
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
33

44
import os
5+
import traceback
56
from abc import ABC, abstractmethod
67
from datetime import datetime
78
from typing import List, Dict, Optional
@@ -61,11 +62,12 @@ def run_assessment(
6162
return new_job
6263
except Exception as e:
6364
self.logger.error('Failed to scan/write in Job ' + new_job['JobId'])
65+
self.logger.error(traceback.format_exc())
6466
self.logger.error(e)
6567
if isinstance(e, ClientException):
6668
new_job['Error'] = e.error + " " + e.message
6769
self._finish_job(new_job, JobStatus.FAILED)
68-
raise e
70+
raise
6971

7072
def _create_job_entry_in_ddb(self, event: APIGatewayProxyEvent) -> JobModel:
7173

source/lambda/poetry.lock

Lines changed: 2683 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/lambda/pyproject.toml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[tool.poetry]
2+
name = "account-assessment-for-aws-organizations"
3+
package-mode = false
4+
5+
[tool.poetry.dependencies]
6+
python = "^3.12"
7+
aws-lambda-powertools = "~3.1.0"
8+
aws-xray-sdk = "~2.14.0"
9+
botocore = "~1.35.41"
10+
boto3 = "~1.35.41"
11+
boto3-stubs = { extras = ["essential", "sts", "organizations", "stepfunctions", "s3", "glacier", "iam", "sns", "sqs", "lambda", "efs", "secretsmanager", "iot", "kms", "apigateway", "events", "sesv2", "ecr", "config", "ssm-incidents", "opensearch", "cloudformation", "glue", "serverlessrepo", "backup", "codeartifact", "codebuild", "mediastore", "ec2"], version = "~1.35.41" }
12+
cfnresponse = "~1.1.5"
13+
requests = "~2.32.3"
14+
pyparsing = "~3.2.0"
15+
pydantic = "~2.9.2"
16+
17+
[tool.poetry.group.dev.dependencies]
18+
python = "^3.12"
19+
mock = "~5.1.0"
20+
boto3-stubs = { extras = ["essential"], version = "~1.35.41" }
21+
moto = {extras = ["sts", "organizations", "stepfunctions", "s3", "glacier", "iam", "sns", "sqs", "lambda", "efs", "secretsmanager", "iot", "kms", "apigateway", "events", "sesv2", "ecr", "config", "ssm-incidents", "opensearch", "cloudformation", "glue", "serverlessrepo", "backup", "codeartifact", "codebuild", "mediastore", "ec2"], version = "~5.0.17"}
22+
pytest = "~8.3.3"
23+
pytest-mock = "~3.14.0"
24+
pytest-runner = "~6.0.1"
25+
pytest-describe = "~2.2.0"
26+
pytest-cov = "~5.0.0"
27+
openapi-spec-validator = "~0.7.1"
28+
docker = "~7.1.0"
29+
30+
31+
[build-system]
32+
requires = ["poetry-core"]
33+
build-backend = "poetry.core.masonry.api"

source/lambda/requirements.txt

Lines changed: 0 additions & 38 deletions
This file was deleted.

source/lambda/testing_requirements.txt

Lines changed: 0 additions & 38 deletions
This file was deleted.

source/lambda/tests/test_utils/test_api_gateway_lambda_handler.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2-
# SPDX-License-Identifier: Apache-2.0
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
33

44
from aws_lambda_powertools.utilities.typing import LambdaContext
55

@@ -26,6 +26,45 @@ def handler_function(_event, _context):
2626
assert response['body'] == '{"foo": "bar"}'
2727

2828

29+
def test_accepts_content_type_case_insensitive():
30+
# ARRANGE
31+
def handler_function(_event, _context):
32+
return {"foo": "bar"}
33+
34+
event = {
35+
'body': '{}',
36+
'headers': {
37+
'Content-type': 'application/json; charset=UTF-8'
38+
}
39+
}
40+
41+
# ACT
42+
response = GenericApiGatewayEventHandler().handle_and_create_response(event, LambdaContext(), handler_function)
43+
44+
# ASSERT
45+
assert response['statusCode'] == 200
46+
assert response['body'] == '{"foo": "bar"}'
47+
48+
49+
def test_returns_415_on_missing_header():
50+
# ARRANGE
51+
def handler_function(_event, _context):
52+
raise IndexError()
53+
54+
event = {
55+
'body': '{invalid_syntax}',
56+
'headers': {
57+
# content-type header is missing
58+
}
59+
}
60+
61+
# ACT
62+
response = GenericApiGatewayEventHandler().handle_and_create_response(event, LambdaContext(), handler_function)
63+
64+
# ASSERT
65+
assert response['statusCode'] == 415
66+
67+
2968
def test_wraps_handler_error_in_http_response():
3069
# ARRANGE
3170
def handler_function(_event, _context):

source/lambda/utils/api_gateway_lambda_handler.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2-
# SPDX-License-Identifier: Apache-2.0
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
33

44
import json
5+
import traceback
56
from json import JSONDecodeError
67
from os import getenv
78
from typing import TypedDict, List
@@ -44,6 +45,33 @@ def __init__(self, error: str, message: str, status_code: int = 400):
4445
self.status_code = status_code
4546

4647

48+
def validate_content_type(event: dict):
49+
allowed_content_types = [
50+
'application/json',
51+
'application/json; charset=utf-8'
52+
]
53+
content_type: str | None = None
54+
55+
# Iterate through the headers and find the 'Content-Type' header (case-insensitive)
56+
for header_key, header_value in event["headers"].items():
57+
if header_key.lower() == 'content-type':
58+
content_type = header_value.lower()
59+
break
60+
61+
if not content_type in allowed_content_types:
62+
raise ClientException(
63+
error='Invalid content-type',
64+
message=f'Accepting: {", ".join(allowed_content_types)}',
65+
status_code=415
66+
)
67+
68+
69+
def validate_body(event: dict):
70+
if event.get("body"):
71+
validate_content_type(event)
72+
json.loads(event["body"])
73+
74+
4775
class GenericApiGatewayEventHandler:
4876

4977
def __init__(self):
@@ -64,7 +92,7 @@ def handle_and_create_response(self,
6492
self.logger.debug(f"Event: {str(event)}")
6593
self.logger.debug(f"Context: {str(context)}")
6694

67-
self.validate_body(event)
95+
validate_body(event)
6896

6997
event = APIGatewayProxyEvent(event)
7098

@@ -85,6 +113,7 @@ def handle_and_create_response(self,
85113
}
86114
except ClientException as error:
87115
self.logger.error(f"Error: {error}")
116+
self.logger.error(traceback.format_exc())
88117
body = json.dumps({
89118
'Error': error.error,
90119
'Message': error.message
@@ -96,6 +125,7 @@ def handle_and_create_response(self,
96125
}
97126
except JSONDecodeError as error:
98127
self.logger.error(f"Error: {error}")
128+
self.logger.error(traceback.format_exc())
99129
body = json.dumps({
100130
'Error': 'JSONDecodeError',
101131
'Message': error.msg
@@ -107,6 +137,7 @@ def handle_and_create_response(self,
107137
}
108138
except Exception as error:
109139
self.logger.error(f"Error: {error}")
140+
self.logger.error(traceback.format_exc())
110141
error_type = type(error).__name__
111142
body = {
112143
"Error": error_type,
@@ -117,13 +148,3 @@ def handle_and_create_response(self,
117148
'body': json.dumps(body),
118149
'headers': default_headers,
119150
}
120-
121-
def validate_body(self, event):
122-
if event.get("body"):
123-
if event["headers"].get("content-type") != 'application/json' and \
124-
event["headers"].get("content-type") != 'application/json; charset=UTF-8':
125-
raise ClientException(error='Invalid content-type',
126-
message='Accepting application/json or application/json; charset=UTF-8',
127-
status_code=415)
128-
else:
129-
json.loads(event["body"])

source/lib/app-register.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ import {
1515
Tags,
1616
} from "aws-cdk-lib";
1717
import * as appreg from "@aws-cdk/aws-servicecatalogappregistry-alpha";
18-
import { CfnApplication } from "aws-cdk-lib/aws-applicationinsights";
19-
import { CfnAttributeGroupAssociation, CfnResourceAssociation, } from "aws-cdk-lib/aws-servicecatalogappregistry";
20-
import { CfnResourceShare } from "aws-cdk-lib/aws-ram";
21-
import { IConstruct } from "constructs";
18+
import {CfnApplication} from "aws-cdk-lib/aws-applicationinsights";
19+
import {CfnAttributeGroupAssociation, CfnResourceAssociation,} from "aws-cdk-lib/aws-servicecatalogappregistry";
20+
import {CfnResourceShare} from "aws-cdk-lib/aws-ram";
21+
import {IConstruct} from "constructs";
2222

2323
export interface AppRegisterProps {
2424
solutionId: string;
@@ -139,7 +139,7 @@ export class AppRegister {
139139
) {
140140
const orgId = new CfnParameter(hubStack, "OrganizationID", {
141141
description:
142-
"Organization ID to support multi account deployment. Leave blank for single account deployments.",
142+
"Organization ID to support multi account deployment.",
143143
type: "String",
144144
allowedPattern: "^$|^o-[a-z0-9]{10,32}$",
145145
default: "",
@@ -150,7 +150,7 @@ export class AppRegister {
150150
"ManagementAccountId",
151151
{
152152
description:
153-
"Account ID for the management account of the Organization. Leave blank for single account deployments.",
153+
"Account ID for the management account of the Organization.",
154154
type: "String",
155155
default: "",
156156
}

0 commit comments

Comments
 (0)