Skip to content

Commit 885146e

Browse files
authored
Merge pull request #273 from aws-solutions/release/v4.0.5
Release v4.0.5
2 parents 28b94cf + 0294e3b commit 885146e

Some content is hidden

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

53 files changed

+7100
-654
lines changed

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ source/**/.venv**
2222
source/**/test/__pycache__
2323
source/**/test/.pytest**
2424

25-
25+
# IDE specific config files
26+
.idea/
2627

2728

2829

2930
# Unit test / coverage reports
3031
**/coverage
31-
**/package
3232
*coverage
3333
source/test/coverage-reports/
3434
**/.venv-test
@@ -55,3 +55,6 @@ urllib*
5555
# Ignore lib folder within each lambada folder. Only include lib folder at upper level
5656
/source/**/lib
5757
!/source/lib
58+
59+
# Build script output from 'poetry export'
60+
requirements.txt

CHANGELOG.md

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

7+
## [4.0.5] - 2024-10-24
8+
9+
### Changed
10+
11+
- Add poetry.lock to pin dependency versions for Python code
12+
- Adapt build scripts to use Poetry for dependency management
13+
- Replace native Python logger with aws_lambda_powertools logger
14+
15+
## [4.0.4] - 2024-09-23
16+
17+
### Fixed
18+
- Patched dependency version of `requests` to `2.32.3` to mitigate [CVE-2024-3651](https://nvd.nist.gov/vuln/detail/CVE-2024-3651)
19+
- Pinned all dependencies to specific versions for reproducable builds and enable security scanning
20+
- Allow to install latest version of `urllib3` as transitive dependency
721

822
## [4.0.4] - 2024-09-23
923

deployment/build-s3-dist.sh

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#!/bin/bash
2-
# This assumes all of the OS-level configuration has been completed and git repo has already been cloned
2+
#
3+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
# This assumes all of the OS-level configuration has been completed and git repo has already been cloned
38
#
49
# This script should be run from the repo's deployment directory
510
# cd deployment
@@ -79,10 +84,21 @@ do
7984
done
8085

8186

87+
# Check if poetry is available in the shell
88+
if command -v poetry >/dev/null 2>&1; then
89+
POETRY_COMMAND="poetry"
90+
elif [ -n "$POETRY_HOME" ] && [ -x "$POETRY_HOME/bin/poetry" ]; then
91+
POETRY_COMMAND="$POETRY_HOME/bin/poetry"
92+
else
93+
echo "Poetry is not available. Aborting script." >&2
94+
exit 1
95+
fi
96+
8297
echo "------------------------------------------------------------------------------"
8398
echo "[Packing] Log Parser"
8499
echo "------------------------------------------------------------------------------"
85100
cd "$source_dir"/log_parser || exit 1
101+
"$POETRY_COMMAND" export --without dev -f requirements.txt --output requirements.txt --without-hashes
86102
pip3 install -r requirements.txt --target ./package
87103
cd "$source_dir"/log_parser/package || exit 1
88104
zip -q -r9 "$build_dist_dir"/log_parser.zip .
@@ -97,6 +113,7 @@ echo "--------------------------------------------------------------------------
97113
echo "[Packing] Access Handler"
98114
echo "------------------------------------------------------------------------------"
99115
cd "$source_dir"/access_handler || exit 1
116+
"$POETRY_COMMAND" export --without dev -f requirements.txt --output requirements.txt --without-hashes
100117
pip3 install -r requirements.txt --target ./package
101118
cd "$source_dir"/access_handler/package || exit 1
102119
zip -q -r9 "$build_dist_dir"/access_handler.zip .
@@ -111,6 +128,7 @@ echo "--------------------------------------------------------------------------
111128
echo "[Packing] IP Lists Parser"
112129
echo "------------------------------------------------------------------------------"
113130
cd "$source_dir"/reputation_lists_parser || exit 1
131+
"$POETRY_COMMAND" export --without dev -f requirements.txt --output requirements.txt --without-hashes
114132
pip3 install -r requirements.txt --target ./package
115133
cd "$source_dir"/reputation_lists_parser/package || exit 1
116134
zip -q -r9 "$build_dist_dir"/reputation_lists_parser.zip .
@@ -125,6 +143,7 @@ echo "--------------------------------------------------------------------------
125143
echo "[Packing] Custom Resource"
126144
echo "------------------------------------------------------------------------------"
127145
cd "$source_dir"/custom_resource || exit 1
146+
"$POETRY_COMMAND" export --without dev -f requirements.txt --output requirements.txt --without-hashes
128147
pip3 install -r requirements.txt --target ./package
129148
cd "$source_dir"/custom_resource/package || exit 1
130149
zip -q -r9 "$build_dist_dir"/custom_resource.zip .
@@ -139,6 +158,7 @@ echo "--------------------------------------------------------------------------
139158
echo "[Packing] Helper"
140159
echo "------------------------------------------------------------------------------"
141160
cd "$source_dir"/helper || exit 1
161+
"$POETRY_COMMAND" export --without dev -f requirements.txt --output requirements.txt --without-hashes
142162
pip3 install -r requirements.txt --target ./package
143163
cd "$source_dir"/helper/package || exit 1
144164
zip -q -r9 "$build_dist_dir"/helper.zip ./*
@@ -153,6 +173,7 @@ echo "--------------------------------------------------------------------------
153173
echo "[Packing] Timer"
154174
echo "------------------------------------------------------------------------------"
155175
cd "$source_dir"/timer || exit 1
176+
"$POETRY_COMMAND" export --without dev -f requirements.txt --output requirements.txt --without-hashes
156177
pip3 install -r requirements.txt --target ./package
157178
cd "$source_dir"/timer/package || exit 1
158179
zip -q -r9 "$build_dist_dir"/timer.zip ./*
@@ -169,6 +190,7 @@ echo "--------------------------------------------------------------------------
169190
echo "[Packing] IP Retention Handler"
170191
echo "------------------------------------------------------------------------------"
171192
cd "$source_dir"/ip_retention_handler || exit 1
193+
"$POETRY_COMMAND" export --without dev -f requirements.txt --output requirements.txt --without-hashes
172194
pip3 install -r requirements.txt --target ./package
173195
cd "$source_dir"/ip_retention_handler/package || exit 1
174196
zip -q -r9 "$build_dist_dir"/ip_retention_handler.zip ./*

deployment/run-unit-tests.sh

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,46 +16,41 @@ source_dir="$(cd $template_dir/../source; pwd -P)"
1616
echo "Current directory: $template_dir"
1717
echo "Source directory: $source_dir"
1818

19-
setup_python_env() {
20-
if [ -d "./.venv-test" ]; then
21-
echo "Reusing already setup python venv in ./.venv-test. Delete ./.venv-test if you want a fresh one created."
22-
return
23-
fi
24-
echo "Setting up python venv"
25-
python3 -m venv .venv-test
26-
echo "Initiating virtual environment"
27-
source .venv-test/bin/activate
28-
echo "Installing python packages"
29-
pip3 install -r requirements.txt --target .
30-
pip3 install -r requirements_dev.txt
31-
echo "deactivate virtual environment"
32-
deactivate
33-
}
34-
3519
run_python_lambda_test() {
3620
lambda_name=$1
3721
lambda_description=$2
3822
echo "------------------------------------------------------------------------------"
3923
echo "[Test] Python Unit Test: $lambda_description"
4024
echo "------------------------------------------------------------------------------"
4125

42-
cd $source_dir/$lambda_name
43-
echo "run_python_lambda_test: Current directory: $source_dir/$lambda_name"
26+
cd $source_dir/$lambda_name
27+
echo "run_python_lambda_test: Current directory: $source_dir/$lambda_name"
4428

45-
[ "${CLEAN:-true}" = "true" ] && rm -fr .venv-test
29+
echo "Installing python packages"
4630

47-
setup_python_env
31+
# Check if poetry is available in the shell
32+
if command -v poetry >/dev/null 2>&1; then
33+
POETRY_COMMAND="poetry"
34+
elif [ -n "$POETRY_HOME" ] && [ -x "$POETRY_HOME/bin/poetry" ]; then
35+
POETRY_COMMAND="$POETRY_HOME/bin/poetry"
36+
else
37+
echo "Poetry is not available. Aborting script." >&2
38+
exit 1
39+
fi
4840

49-
echo "Initiating virtual environment"
50-
source .venv-test/bin/activate
41+
# This creates a virtual environment based on the project name in pyproject.toml.
42+
"$POETRY_COMMAND" install
5143

52-
# Set coverage report path
44+
# Activate the virtual environment.
45+
source $("$POETRY_COMMAND" env info --path)/bin/activate
46+
47+
# Set coverage report path
5348
mkdir -p $source_dir/test/coverage-reports
5449
coverage_report_path=$source_dir/test/coverage-reports/$lambda_name.coverage.xml
5550
echo "coverage report path set to $coverage_report_path"
5651

57-
# Run unit tests with coverage
58-
python3 -m pytest --cov --cov-report=term-missing --cov-report "xml:$coverage_report_path"
52+
# Run unit tests with coverage
53+
python3 -m pytest --cov --cov-report=term-missing --cov-report "xml:$coverage_report_path"
5954

6055
if [ "$?" = "1" ]; then
6156
echo "(deployment/run-unit-tests.sh) ERROR: there is likely output above." 1>&2
@@ -67,11 +62,10 @@ run_python_lambda_test() {
6762
# absolute paths for source directories, this substitution is used to convert each absolute source directory
6863
# path to the corresponding project relative path. The $source_dir holds the absolute path for source directory.
6964
sed -i -e "s,<source>$source_dir,<source>source,g" $coverage_report_path
70-
echo "deactivate virtual environment"
71-
deactivate
65+
66+
deactivate
7267

7368
if [ "${CLEAN:-true}" = "true" ]; then
74-
rm -fr .venv-test
7569
# Note: leaving $source_dir/test/coverage-reports to allow further processing of coverage reports
7670
rm -fr coverage
7771
rm .coverage
Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1-
######################################################################################################################
2-
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. #
3-
# #
4-
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance #
5-
# with the License. A copy of the License is located at #
6-
# #
7-
# http://www.apache.org/licenses/LICENSE-2.0 #
8-
# #
9-
# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES #
10-
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions #
11-
# and limitations under the License. #
12-
######################################################################################################################
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
133

144
import os
15-
from ipaddress import ip_address
165
from ipaddress import IPv4Network
176
from ipaddress import IPv6Network
7+
from ipaddress import ip_address
188
from os import environ
19-
from lib.waflibv2 import WAFLIBv2
20-
from lib.solution_metrics import send_metrics
9+
10+
from aws_lambda_powertools import Logger
11+
2112
from lib.cw_metrics_util import WAFCloudWatchMetrics
22-
from lib.logging_util import set_log_level
13+
from lib.solution_metrics import send_metrics
14+
from lib.waflibv2 import WAFLIBv2
15+
16+
logger = Logger(
17+
level=os.getenv('LOG_LEVEL')
18+
)
2319

2420
waflib = WAFLIBv2()
2521
CW_METRIC_PERIOD_SECONDS = 12 * 3600 # Twelve hours in seconds
@@ -38,13 +34,13 @@ def initialize_usage_data():
3834
return usage_data
3935

4036

41-
def get_bad_bot_usage_data(log, scope, cw, ipset_name_v4, ipset_arn_v4, ipset_name_v6, ipset_arn_v6, usage_data):
42-
log.info("[get_bad_bot_usage_data] Get bad bot data")
37+
def get_bad_bot_usage_data(scope, cw, ipset_name_v4, ipset_arn_v4, ipset_name_v6, ipset_arn_v6, usage_data):
38+
logger.info("[get_bad_bot_usage_data] Get bad bot data")
4339

4440
if 'IP_SET_ID_BAD_BOTV4' in environ or 'IP_SET_ID_BAD_BOTV6' in environ:
4541
# Get the count of ipv4 and ipv6 in bad bot ip sets
46-
ipv4_count = waflib.get_ip_address_count(log, scope, ipset_name_v4, ipset_arn_v4)
47-
ipv6_count = waflib.get_ip_address_count(log, scope, ipset_name_v6, ipset_arn_v6)
42+
ipv4_count = waflib.get_ip_address_count(logger, scope, ipset_name_v4, ipset_arn_v4)
43+
ipv6_count = waflib.get_ip_address_count(logger, scope, ipset_name_v6, ipset_arn_v6)
4844
usage_data['bad_bot_ip_set_size'] = str(ipv4_count + ipv6_count)
4945

5046
# Get the count of blocked requests for the bad bot rule from cloudwatch metrics
@@ -59,14 +55,14 @@ def get_bad_bot_usage_data(log, scope, cw, ipset_name_v4, ipset_arn_v4, ipset_na
5955
return usage_data
6056

6157

62-
def send_anonymized_usage_data(log, scope, ipset_name_v4, ipset_arn_v4, ipset_name_v6, ipset_arn_v6):
58+
def send_anonymized_usage_data(scope, ipset_name_v4, ipset_arn_v4, ipset_name_v6, ipset_arn_v6):
6359
try:
6460
if 'SEND_ANONYMIZED_USAGE_DATA' not in environ or os.getenv('SEND_ANONYMIZED_USAGE_DATA').lower() != 'yes':
6561
return
6662

67-
log.info("[send_anonymized_usage_data] Start")
63+
logger.info("[send_anonymized_usage_data] Start")
6864

69-
cw = WAFCloudWatchMetrics(log)
65+
cw = WAFCloudWatchMetrics(logger)
7066
usage_data = initialize_usage_data()
7167

7268
# Get the count of allowed requests for all the waf rules from cloudwatch metrics
@@ -90,22 +86,22 @@ def send_anonymized_usage_data(log, scope, ipset_name_v4, ipset_arn_v4, ipset_na
9086
)
9187

9288
# Get bad bot specific usage data
93-
usage_data = get_bad_bot_usage_data(log, scope, cw, ipset_name_v4, ipset_arn_v4,
94-
ipset_name_v6, ipset_arn_v6, usage_data)
89+
usage_data = get_bad_bot_usage_data(scope, cw, ipset_name_v4, ipset_arn_v4, ipset_name_v6, ipset_arn_v6,
90+
usage_data)
9591

9692
# Send usage data
97-
log.info('[send_anonymized_usage_data] Send usage data: \n{}'.format(usage_data))
93+
logger.info('[send_anonymized_usage_data] Send usage data: \n{}'.format(usage_data))
9894
response = send_metrics(data=usage_data)
9995
response_code = response.status_code
100-
log.info('[send_anonymized_usage_data] Response Code: {}'.format(response_code))
101-
log.info("[send_anonymized_usage_data] End")
96+
logger.info('[send_anonymized_usage_data] Response Code: {}'.format(response_code))
97+
logger.info("[send_anonymized_usage_data] End")
10298

10399
except Exception as error:
104-
log.info("[send_anonymized_usage_data] Failed to Send Data")
105-
log.error(str(error))
100+
logger.info("[send_anonymized_usage_data] Failed to Send Data")
101+
logger.error(str(error))
106102

107103

108-
def add_ip_to_ip_set(log, scope, ip_type, source_ip, ipset_name, ipset_arn):
104+
def add_ip_to_ip_set(scope, ip_type, source_ip, ipset_name, ipset_arn):
109105
new_address = []
110106
output = None
111107

@@ -114,24 +110,24 @@ def add_ip_to_ip_set(log, scope, ip_type, source_ip, ipset_name, ipset_arn):
114110
elif ip_type == "IPV6":
115111
new_address.append(IPv6Network(source_ip).with_prefixlen)
116112

117-
ipset = waflib.get_ip_set(log, scope, ipset_name, ipset_arn)
113+
ipset = waflib.get_ip_set(logger, scope, ipset_name, ipset_arn)
118114
# merge old addresses with this one
119-
log.info(ipset)
115+
logger.info(ipset)
120116
current_list = ipset["IPSet"]["Addresses"]
121-
log.info(current_list)
117+
logger.info(current_list)
122118
new_list = list(set(current_list) | set(new_address))
123-
log.info(new_list)
124-
output = waflib.update_ip_set(log, scope, ipset_name, ipset_arn, new_list)
119+
logger.info(new_list)
120+
output = waflib.update_ip_set(logger, scope, ipset_name, ipset_arn, new_list)
125121

126122
return output
127123

128124

129125
# ======================================================================================================================
130126
# Lambda Entry Point
131127
# ======================================================================================================================
128+
@logger.inject_lambda_context
132129
def lambda_handler(event, _):
133-
log = set_log_level()
134-
log.info('[lambda_handler] Start')
130+
logger.info('[lambda_handler] Start')
135131

136132
# ----------------------------------------------------------
137133
# Read inputs parameters
@@ -144,30 +140,30 @@ def lambda_handler(event, _):
144140
ipset_arn_v6 = os.getenv('IP_SET_ID_BAD_BOTV6')
145141

146142
# Fixed as old line had security exposure based on user supplied IP address
147-
log.info("Event->%s<-", str(event))
143+
logger.info("Event->%s<-", str(event))
148144
if event['requestContext']['identity']['userAgent'] == 'Amazon CloudFront':
149145
source_ip = str(event['headers']['X-Forwarded-For'].split(',')[0].strip())
150146
else:
151147
source_ip = str(event['requestContext']['identity']['sourceIp'])
152148

153-
log.info("scope = %s", scope)
154-
log.info("ipset_name_v4 = %s", ipset_name_v4)
155-
log.info("ipset_name_v6 = %s", ipset_name_v6)
156-
log.info("IPARNV4 = %s", ipset_arn_v4)
157-
log.info("IPARNV6 = %s", ipset_arn_v6)
158-
log.info("source_ip = %s", source_ip)
149+
logger.info("scope = %s", scope)
150+
logger.info("ipset_name_v4 = %s", ipset_name_v4)
151+
logger.info("ipset_name_v6 = %s", ipset_name_v6)
152+
logger.info("IPARNV4 = %s", ipset_arn_v4)
153+
logger.info("IPARNV6 = %s", ipset_arn_v6)
154+
logger.info("source_ip = %s", source_ip)
159155

160156
ip_type = "IPV%s" % ip_address(source_ip).version
161157
output = None
162158
if ip_type == "IPV4":
163-
output = add_ip_to_ip_set(log, scope, ip_type, source_ip, ipset_name_v4, ipset_arn_v4)
159+
output = add_ip_to_ip_set(scope, ip_type, source_ip, ipset_name_v4, ipset_arn_v4)
164160
elif ip_type == "IPV6":
165-
output = add_ip_to_ip_set(log, scope, ip_type, source_ip, ipset_name_v6, ipset_arn_v6)
161+
output = add_ip_to_ip_set(scope, ip_type, source_ip, ipset_name_v6, ipset_arn_v6)
166162
except Exception as e:
167-
log.error(e)
163+
logger.error(e)
168164
raise
169165
finally:
170-
log.info("Output->%s<-", output)
166+
logger.info("Output->%s<-", output)
171167
message = "message: [%s] Thanks for the visit." % source_ip
172168
response = {
173169
'statusCode': 200,
@@ -176,7 +172,7 @@ def lambda_handler(event, _):
176172
}
177173

178174
if output is not None:
179-
send_anonymized_usage_data(log, scope, ipset_name_v4, ipset_arn_v4, ipset_name_v6, ipset_arn_v6)
180-
log.info('[lambda_handler] End')
175+
send_anonymized_usage_data(scope, ipset_name_v4, ipset_arn_v4, ipset_name_v6, ipset_arn_v6)
176+
logger.info('[lambda_handler] End')
181177

182178
return response

0 commit comments

Comments
 (0)