Skip to content

Commit 130ec1b

Browse files
authored
Updated to version v4.0.1
Updated to version v4.0.1
2 parents 43bf6bd + a3897f3 commit 130ec1b

File tree

6 files changed

+394
-1
lines changed

6 files changed

+394
-1
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,5 @@ six*
5353
urllib*
5454

5555
# Ignore lib folder within each lambada folder. Only include lib folder at upper level
56-
/source/**/lib
56+
/source/**/lib
57+
!/source/lib

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ 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.1] - 2023-05-19
8+
9+
### Fixed
10+
11+
- Updated gitignore files to resolve the issue for missing files [Github issue 244](https://github.com/aws-solutions/aws-waf-security-automations/issues/244) [Github issue 243](https://github.com/aws-solutions/aws-waf-security-automations/issues/243) [Github issue 245](https://github.com/aws-solutions/aws-waf-security-automations/issues)
12+
713
## [4.0.0] - 2023-05-11
814

915
### Added

source/lib/cfn_response.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
######################################################################################################################
13+
# !/bin/python
14+
15+
import requests
16+
import json
17+
18+
def send_response(log, event, context, response_status, response_data, resource_id, reason=None):
19+
"""
20+
Send a response to an AWS CloudFormation custom resource.
21+
Parameters:
22+
event: The fields in a custom resource request
23+
context: An object, specific to Lambda functions, that you can use to specify
24+
when the function and any callbacks have completed execution, or to
25+
access information from within the Lambda execution environment
26+
response_status: Whether the function successfully completed - SUCCESS or FAILED
27+
response_data: The Data field of a custom resource response object
28+
resource_id: The id of the custom resource that invoked the function
29+
reason: The error message if the function fails
30+
31+
Returns: None
32+
"""
33+
log.debug("[send_response] Start")
34+
35+
responseUrl = event['ResponseURL']
36+
cw_logs_url = "https://console.aws.amazon.com/cloudwatch/home?region=%s#logEventViewer:group=%s;stream=%s" % (
37+
context.invoked_function_arn.split(':')[3], context.log_group_name, context.log_stream_name)
38+
39+
log.info("[send_response] Sending cfn response url: %s", responseUrl)
40+
responseBody = {}
41+
responseBody['Status'] = response_status
42+
responseBody['Reason'] = reason or ('See the details in CloudWatch Logs: ' + cw_logs_url)
43+
responseBody['PhysicalResourceId'] = resource_id
44+
responseBody['StackId'] = event['StackId']
45+
responseBody['RequestId'] = event['RequestId']
46+
responseBody['LogicalResourceId'] = event['LogicalResourceId']
47+
responseBody['NoEcho'] = False
48+
responseBody['Data'] = response_data
49+
50+
json_responseBody = json.dumps(responseBody)
51+
log.debug("Response body:\n" + json_responseBody)
52+
53+
headers = {
54+
'content-type': '',
55+
'content-length': str(len(json_responseBody))
56+
}
57+
58+
try:
59+
response = requests.put(responseUrl,
60+
data=json_responseBody,
61+
headers=headers,
62+
timeout=10)
63+
log.info("[send_response] Sending cfn response status code: %s", response.reason)
64+
65+
except Exception as error:
66+
log.error("[send_response] Failed executing requests.put(..)")
67+
log.error(str(error))
68+
69+
log.debug("[send_response] End")

source/lib/cw_metrics_util.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
######################################################################################################################
13+
#!/bin/python
14+
15+
import datetime
16+
from os import environ
17+
from lib.boto3_util import create_client
18+
19+
class WAFCloudWatchMetrics(object):
20+
"""
21+
This class creates a wrapper function for cloudwatch get_metric_statistics API
22+
and another function to add the waf cw metric statistics to the anonymous usage
23+
data that the solution collects
24+
"""
25+
def __init__(self, log):
26+
self.log = log
27+
self.cw_client = create_client('cloudwatch')
28+
29+
def get_cw_metric_statistics(self, metric_name, period_seconds, waf_rule,
30+
namespace='AWS/WAFV2',
31+
statistics=['Sum'],
32+
start_time=datetime.datetime.utcnow(),
33+
end_time=datetime.datetime.utcnow(),
34+
web_acl='STACK_NAME'):
35+
"""
36+
Get a WAF CloudWatch metric given a WAF rule and metric name.
37+
Parameters:
38+
metric_name: string. The name of the metric. Optional.
39+
period_seconds: integer. The granularity, in seconds, of the returned data points.
40+
waf_rule: string. The name of the WAF rule.
41+
namespace: string. The namespace of the metric. Optional.
42+
statistics: list. The metric statistics, other than percentile. Optional.
43+
start_time: datetime. The time stamp that determines the first data point to return. Optional.
44+
end_time: datetime. The time stamp that determines the last data point to return. Optional.
45+
web_acl: string. The name of the WebACL. Optional
46+
47+
Returns: Metric data points if any, or None
48+
"""
49+
try:
50+
response = self.cw_client.get_metric_statistics(
51+
MetricName=metric_name,
52+
Namespace=namespace,
53+
Statistics=statistics,
54+
Period=period_seconds,
55+
StartTime=start_time - datetime.timedelta(seconds=period_seconds),
56+
EndTime=end_time,
57+
Dimensions=[
58+
{
59+
"Name": "Rule",
60+
"Value": waf_rule
61+
},
62+
{
63+
"Name": "WebACL",
64+
"Value": environ.get(web_acl)
65+
},
66+
{
67+
"Name": "Region",
68+
"Value": environ.get('AWS_REGION')
69+
}
70+
]
71+
)
72+
self.log.debug("[cw_metrics_util: get_cw_metric_statistics] response:\n{}".format(response))
73+
return response if len(response['Datapoints']) > 0 else None
74+
except Exception as e:
75+
self.log.error("[cw_metrics_util: get_cw_metric_statistics] Failed to get metric %s.", metric_name)
76+
self.log.error(e)
77+
return None
78+
79+
def add_waf_cw_metric_to_usage_data(self, metric_name, period_seconds, waf_rule,
80+
usage_data, usage_data_field_name, default_value):
81+
"""
82+
Get the CloudWatch metric statistics given a WAF rule and metric name, and
83+
add it to the anonymous usage data collected by the solution.
84+
Parameters:
85+
metric_name: string. The name of the metric. Optional.
86+
period_seconds: integer. The granularity, in seconds, of the returned data points.
87+
waf_rule: string. The name of the WAF rule.
88+
usage_data: JSON. Anonymous customer usage data of the solution
89+
usage_data_field_name: string. The field name in the usage data whose value will be
90+
replaced by the waf cloudwatch metric (if any)
91+
default_value: number. The default value of the field in the usage data
92+
93+
Returns: JSON. usage data.
94+
"""
95+
self.log.info("[cw_metrics_util: add_waf_cw_metric_to_usage_data] "
96+
+ "Get metric %s for waf rule %s." %(metric_name, waf_rule))
97+
98+
response = self.get_cw_metric_statistics(
99+
metric_name=metric_name,
100+
period_seconds=period_seconds,
101+
waf_rule=waf_rule
102+
)
103+
usage_data[usage_data_field_name] = \
104+
response['Datapoints'][0]['Sum'] if response is not None else default_value
105+
106+
self.log.info("[cw_metrics_util: add_waf_cw_metric_to_usage_data] "
107+
+ "%s - rule %s: %s"%(metric_name, waf_rule, str(usage_data[usage_data_field_name])))
108+
109+
return usage_data

source/lib/logging_util.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
######################################################################################################################
13+
#!/bin/python
14+
15+
import logging
16+
from os import environ
17+
18+
def set_log_level(default_log_level='ERROR'):
19+
default_log_level = logging.getLevelName(default_log_level.upper())
20+
log_level = str(environ['LOG_LEVEL'].upper()) \
21+
if 'LOG_LEVEL' in environ else default_log_level
22+
23+
log = logging.getLogger()
24+
25+
if log_level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
26+
log_level = 'ERROR'
27+
log.setLevel(log_level)
28+
29+
return log

0 commit comments

Comments
 (0)