Skip to content

Commit

Permalink
Merge pull request #36 from aws-solutions/release/v1.0.14
Browse files Browse the repository at this point in the history
Release v1.0.14
  • Loading branch information
tbelmega authored Oct 17, 2024
2 parents a49f15a + caed1a8 commit f2a448e
Show file tree
Hide file tree
Showing 16 changed files with 3,528 additions and 1,077 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ 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).

## [1.0.14] - 2024-10

### Changed

- Remove dependencies `bootstrap` and `datefns`
- Allow backend to accept uppercase http headers, to prevent errors when receiving uppercase `Content-type`
- Replace pip3/requirements.txt dependency management with Poetry

### Added

- Add poetry.lock file to support reproducible builds, improve vulnerability scanning

## [1.0.13] - 2024-9

- Upgrade `rollup` to mitigate [CVE-2024-47068](https://nvd.nist.gov/vuln/detail/CVE-2024-47068)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The default deployment of solution pre-packaged template deploys following infra

### Setup

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

Clone the repository and make desired code changes.
Expand Down
8 changes: 5 additions & 3 deletions source/lambda/assessment_runner/assessment_runner.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

import os
import traceback
from abc import ABC, abstractmethod
from datetime import datetime
from typing import List, Dict, Optional
Expand Down Expand Up @@ -61,11 +62,12 @@ def run_assessment(
return new_job
except Exception as e:
self.logger.error('Failed to scan/write in Job ' + new_job['JobId'])
self.logger.error(traceback.format_exc())
self.logger.error(e)
if isinstance(e, ClientException):
new_job['Error'] = e.error + " " + e.message
self._finish_job(new_job, JobStatus.FAILED)
raise e
raise

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

Expand Down
2,683 changes: 2,683 additions & 0 deletions source/lambda/poetry.lock

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions source/lambda/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[tool.poetry]
name = "account-assessment-for-aws-organizations"
package-mode = false

[tool.poetry.dependencies]
python = "^3.12"
aws-lambda-powertools = "~3.1.0"
aws-xray-sdk = "~2.14.0"
botocore = "~1.35.41"
boto3 = "~1.35.41"
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" }
cfnresponse = "~1.1.5"
requests = "~2.32.3"
pyparsing = "~3.2.0"
pydantic = "~2.9.2"

[tool.poetry.group.dev.dependencies]
python = "^3.12"
mock = "~5.1.0"
boto3-stubs = { extras = ["essential"], version = "~1.35.41" }
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"}
pytest = "~8.3.3"
pytest-mock = "~3.14.0"
pytest-runner = "~6.0.1"
pytest-describe = "~2.2.0"
pytest-cov = "~5.0.0"
openapi-spec-validator = "~0.7.1"
docker = "~7.1.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
38 changes: 0 additions & 38 deletions source/lambda/requirements.txt

This file was deleted.

38 changes: 0 additions & 38 deletions source/lambda/testing_requirements.txt

This file was deleted.

43 changes: 41 additions & 2 deletions source/lambda/tests/test_utils/test_api_gateway_lambda_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

from aws_lambda_powertools.utilities.typing import LambdaContext

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


def test_accepts_content_type_case_insensitive():
# ARRANGE
def handler_function(_event, _context):
return {"foo": "bar"}

event = {
'body': '{}',
'headers': {
'Content-type': 'application/json; charset=UTF-8'
}
}

# ACT
response = GenericApiGatewayEventHandler().handle_and_create_response(event, LambdaContext(), handler_function)

# ASSERT
assert response['statusCode'] == 200
assert response['body'] == '{"foo": "bar"}'


def test_returns_415_on_missing_header():
# ARRANGE
def handler_function(_event, _context):
raise IndexError()

event = {
'body': '{invalid_syntax}',
'headers': {
# content-type header is missing
}
}

# ACT
response = GenericApiGatewayEventHandler().handle_and_create_response(event, LambdaContext(), handler_function)

# ASSERT
assert response['statusCode'] == 415


def test_wraps_handler_error_in_http_response():
# ARRANGE
def handler_function(_event, _context):
Expand Down
47 changes: 34 additions & 13 deletions source/lambda/utils/api_gateway_lambda_handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

import json
import traceback
from json import JSONDecodeError
from os import getenv
from typing import TypedDict, List
Expand Down Expand Up @@ -44,6 +45,33 @@ def __init__(self, error: str, message: str, status_code: int = 400):
self.status_code = status_code


def validate_content_type(event: dict):
allowed_content_types = [
'application/json',
'application/json; charset=utf-8'
]
content_type: str | None = None

# Iterate through the headers and find the 'Content-Type' header (case-insensitive)
for header_key, header_value in event["headers"].items():
if header_key.lower() == 'content-type':
content_type = header_value.lower()
break

if not content_type in allowed_content_types:
raise ClientException(
error='Invalid content-type',
message=f'Accepting: {", ".join(allowed_content_types)}',
status_code=415
)


def validate_body(event: dict):
if event.get("body"):
validate_content_type(event)
json.loads(event["body"])


class GenericApiGatewayEventHandler:

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

self.validate_body(event)
validate_body(event)

event = APIGatewayProxyEvent(event)

Expand All @@ -85,6 +113,7 @@ def handle_and_create_response(self,
}
except ClientException as error:
self.logger.error(f"Error: {error}")
self.logger.error(traceback.format_exc())
body = json.dumps({
'Error': error.error,
'Message': error.message
Expand All @@ -96,6 +125,7 @@ def handle_and_create_response(self,
}
except JSONDecodeError as error:
self.logger.error(f"Error: {error}")
self.logger.error(traceback.format_exc())
body = json.dumps({
'Error': 'JSONDecodeError',
'Message': error.msg
Expand All @@ -107,6 +137,7 @@ def handle_and_create_response(self,
}
except Exception as error:
self.logger.error(f"Error: {error}")
self.logger.error(traceback.format_exc())
error_type = type(error).__name__
body = {
"Error": error_type,
Expand All @@ -117,13 +148,3 @@ def handle_and_create_response(self,
'body': json.dumps(body),
'headers': default_headers,
}

def validate_body(self, event):
if event.get("body"):
if event["headers"].get("content-type") != 'application/json' and \
event["headers"].get("content-type") != 'application/json; charset=UTF-8':
raise ClientException(error='Invalid content-type',
message='Accepting application/json or application/json; charset=UTF-8',
status_code=415)
else:
json.loads(event["body"])
12 changes: 6 additions & 6 deletions source/lib/app-register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import {
Tags,
} from "aws-cdk-lib";
import * as appreg from "@aws-cdk/aws-servicecatalogappregistry-alpha";
import { CfnApplication } from "aws-cdk-lib/aws-applicationinsights";
import { CfnAttributeGroupAssociation, CfnResourceAssociation, } from "aws-cdk-lib/aws-servicecatalogappregistry";
import { CfnResourceShare } from "aws-cdk-lib/aws-ram";
import { IConstruct } from "constructs";
import {CfnApplication} from "aws-cdk-lib/aws-applicationinsights";
import {CfnAttributeGroupAssociation, CfnResourceAssociation,} from "aws-cdk-lib/aws-servicecatalogappregistry";
import {CfnResourceShare} from "aws-cdk-lib/aws-ram";
import {IConstruct} from "constructs";

export interface AppRegisterProps {
solutionId: string;
Expand Down Expand Up @@ -139,7 +139,7 @@ export class AppRegister {
) {
const orgId = new CfnParameter(hubStack, "OrganizationID", {
description:
"Organization ID to support multi account deployment. Leave blank for single account deployments.",
"Organization ID to support multi account deployment.",
type: "String",
allowedPattern: "^$|^o-[a-z0-9]{10,32}$",
default: "",
Expand All @@ -150,7 +150,7 @@ export class AppRegister {
"ManagementAccountId",
{
description:
"Account ID for the management account of the Organization. Leave blank for single account deployments.",
"Account ID for the management account of the Organization.",
type: "String",
default: "",
}
Expand Down
4 changes: 2 additions & 2 deletions source/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion source/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "account-assessment-for-aws-organizations",
"version": "1.0.13",
"version": "1.0.14",
"description": "Account Assessment for AWS Organizations (SO0217)",
"license": "Apache-2.0",
"author": {
Expand Down
3 changes: 1 addition & 2 deletions source/run-all-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ setup_python_env() {

echo "Installing python packages"
# install test dependencies in the python virtual environment
pip3 install -r testing_requirements.txt
pip3 install -r requirements.txt
"$POETRY_HOME"/bin/poetry install

echo "deactivate virtual environment"
deactivate
Expand Down
Loading

0 comments on commit f2a448e

Please sign in to comment.