Skip to content

Commit 72a5248

Browse files
authored
Merge pull request #1 from rrakhmanto/develop
waf regex match and pattern sets
2 parents e5812af + c9d8b59 commit 72a5248

File tree

5 files changed

+282
-0
lines changed

5 files changed

+282
-0
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,17 @@ Required parameters:
4949
- `Destination` - Destination bucket and prefix in `s3://bucket-name/destination-prefix` format
5050
- `CannedAcl` - Canned ACL for created objects in destination
5151
No optional parameters.
52+
53+
### Create Regex Waf Rules
54+
55+
This custom resource allows create/update/delete match regex rules.
56+
57+
handler: `waf_regex/handler.lambda_handler`
58+
runtime: `python3.6`
59+
60+
Required parameters:
61+
62+
- `RegexPatterns` - List format, regex pattern to match.
63+
- `Type` - The part of the web request that you want AWS WAF to search for a specified string
64+
- `Data` - Data such as when the value of Type is HEADER , enter the name of the header that you want AWS WAF to search, for example, User-Agent or Referer
65+
- `Transform` - Text transformations eliminate some of the unusual formatting that attackers use in web requests in an effort to bypass AWS WAF.

waf_regex/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# package marker

waf_regex/cr_response.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import logging
2+
from urllib.request import urlopen, Request, HTTPError, URLError
3+
import json
4+
5+
logger = logging.getLogger()
6+
logger.setLevel(logging.INFO)
7+
8+
class CustomResourceResponse:
9+
def __init__(self, request_payload):
10+
self.payload = request_payload
11+
self.response = {
12+
"StackId": request_payload["StackId"],
13+
"RequestId": request_payload["RequestId"],
14+
"LogicalResourceId": request_payload["LogicalResourceId"],
15+
"Status": 'SUCCESS',
16+
}
17+
18+
19+
def respond_error(self, message):
20+
self.response['Status'] = 'FAILED'
21+
self.response['Reason'] = message
22+
self.respond()
23+
24+
def respond(self, data):
25+
event = self.payload
26+
response = self.response
27+
####
28+
#### copied from https://github.com/ryansb/cfn-wrapper-python/blob/master/cfn_resource.py
29+
####
30+
31+
if event.get("PhysicalResourceId", False):
32+
response["PhysicalResourceId"] = event["PhysicalResourceId"]
33+
34+
logger.debug("Received %s request with event: %s" %
35+
(event['RequestType'], json.dumps(event)))
36+
37+
response["Data"] = data
38+
39+
serialized = json.dumps(response)
40+
41+
logger.info(f"Responding to {event['RequestType']} request with: {serialized}")
42+
req_data = serialized.encode('utf-8')
43+
44+
req = Request(
45+
event['ResponseURL'],
46+
data=req_data,
47+
headers={'Content-Length': len(req_data), 'Content-Type': ''}
48+
)
49+
req.get_method = lambda: 'PUT'
50+
51+
try:
52+
urlopen(req)
53+
logger.debug("Request to CFN API succeeded, nothing to do here")
54+
except HTTPError as e:
55+
logger.error("Callback to CFN API failed with status %d" % e.code)
56+
logger.error("Response: %s" % e.reason)
57+
except URLError as e:
58+
logger.error("Failed to reach the server - %s" % e.reason)

waf_regex/handler.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import sys
2+
import os
3+
import re
4+
import random
5+
6+
sys.path.append(f"{os.environ['LAMBDA_TASK_ROOT']}/lib")
7+
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
8+
9+
import cr_response
10+
from logic import WafRegexLogic
11+
import json
12+
13+
def lambda_handler(event, context):
14+
15+
print(f"Received event:{json.dumps(event)}")
16+
17+
lambda_response = cr_response.CustomResourceResponse(event)
18+
cr_params = event['ResourceProperties']
19+
match_name = event['LogicalResourceId']
20+
waf_logic = WafRegexLogic(match_name , cr_params)
21+
try:
22+
# if create request, generate physical id, both for create/update copy files
23+
if event['RequestType'] == 'Create':
24+
print("Create request")
25+
event['PhysicalResourceId'] = waf_logic.new_match_set()
26+
data = {
27+
"MatchID" : event['PhysicalResourceId']
28+
}
29+
lambda_response.respond(data)
30+
31+
elif event['RequestType'] == 'Update':
32+
print("Update request")
33+
waf_logic.update_match_set(event['PhysicalResourceId'])
34+
data = {
35+
"MatchID" : event['PhysicalResourceId']
36+
}
37+
lambda_response.respond(data)
38+
39+
elif event['RequestType'] == 'Delete':
40+
print(event['PhysicalResourceId'])
41+
waf_logic.remove_match_set(event['PhysicalResourceId'])
42+
print("Delete request")
43+
data = { }
44+
lambda_response.respond(data)
45+
46+
except Exception as e:
47+
message = str(e)
48+
lambda_response.respond_error(message)
49+
50+
return 'OK'

waf_regex/logic.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import boto3
2+
import os
3+
import glob
4+
import logging
5+
import pprint
6+
7+
8+
logger = logging.getLogger()
9+
logger.setLevel(logging.INFO)
10+
11+
12+
class WafRegexLogic:
13+
14+
def __init__(self, name, resource_properties):
15+
self.regex_patterns = resource_properties['RegexPatterns']
16+
self.match_type = resource_properties['Type']
17+
self.match_data = resource_properties['Data']
18+
self.transform = resource_properties['Transform']
19+
self.match_name = name
20+
self.pattern_name = f"{name}-pattern"
21+
self.client = boto3.client('waf-regional')
22+
23+
def new_pattern_set(self):
24+
changeToken = self.client.get_change_token()
25+
response_create_pattern_set = self.client.create_regex_pattern_set(
26+
Name=self.pattern_name,
27+
ChangeToken=changeToken['ChangeToken']
28+
)
29+
for pattern in self.regex_patterns:
30+
self.insert_pattern_set(response_create_pattern_set['RegexPatternSet']['RegexPatternSetId'], pattern)
31+
return response_create_pattern_set['RegexPatternSet']['RegexPatternSetId']
32+
33+
def remove_pattern_set(self,pattern_set_id):
34+
pattern_set_object = self.get_pattern_set(pattern_set_id)
35+
for pattern_set_string in pattern_set_object['RegexPatternSet']['RegexPatternStrings']:
36+
self.delete_pattern_set(pattern_set_id, pattern_set_string)
37+
changeToken = self.client.get_change_token()
38+
response = self.client.delete_regex_pattern_set(
39+
RegexPatternSetId=pattern_set_id,
40+
ChangeToken=changeToken['ChangeToken']
41+
)
42+
43+
def get_pattern_set(self,pattern_set_id):
44+
pattern_set_object = self.client.get_regex_pattern_set(
45+
RegexPatternSetId=pattern_set_id
46+
)
47+
return pattern_set_object
48+
49+
50+
def update_pattern_set(self,pattern_set_id):
51+
pattern_set_object = self.get_pattern_set(pattern_set_id)
52+
#delete existing and add a new one
53+
for pattern_set_string in pattern_set_object['RegexPatternSet']['RegexPatternStrings']:
54+
self.delete_pattern_set(pattern_set_id, pattern_set_string)
55+
for pattern in self.regex_patterns:
56+
self.insert_pattern_set(pattern_set_id, pattern)
57+
58+
59+
def insert_pattern_set(self,pattern_set_id, pattern_set_string):
60+
changeToken = self.client.get_change_token()
61+
update_regex_patternset = self.client.update_regex_pattern_set(
62+
RegexPatternSetId=pattern_set_id,
63+
Updates=[
64+
{
65+
'Action': 'INSERT',
66+
'RegexPatternString': pattern_set_string
67+
},
68+
],
69+
ChangeToken=changeToken['ChangeToken']
70+
)
71+
72+
def delete_pattern_set(self,pattern_set_id, pattern_set_string):
73+
changeToken = self.client.get_change_token()
74+
update_regex_patternset = self.client.update_regex_pattern_set(
75+
RegexPatternSetId=pattern_set_id,
76+
Updates=[
77+
{
78+
'Action': 'DELETE',
79+
'RegexPatternString': pattern_set_string
80+
},
81+
],
82+
ChangeToken=changeToken['ChangeToken']
83+
)
84+
85+
#
86+
# Match Sets
87+
#
88+
89+
def new_match_set(self):
90+
changeToken = self.client.get_change_token()
91+
#create match set
92+
response_create_match_set = self.client.create_regex_match_set(
93+
Name=self.match_name,
94+
ChangeToken=changeToken['ChangeToken']
95+
)
96+
#create pattern set
97+
pattern_set_id = self.new_pattern_set()
98+
self.insert_match_set(response_create_match_set['RegexMatchSet']['RegexMatchSetId'], pattern_set_id)
99+
return response_create_match_set['RegexMatchSet']['RegexMatchSetId']
100+
101+
def insert_match_set(self, match_set_id, pattern_set_id):
102+
changeToken = self.client.get_change_token()
103+
update_regex_matchset = self.client.update_regex_match_set(
104+
RegexMatchSetId=match_set_id,
105+
Updates=[
106+
{
107+
'Action': 'INSERT',
108+
'RegexMatchTuple': {
109+
'FieldToMatch': {
110+
'Type': self.match_type,
111+
'Data': self.match_data
112+
},
113+
'TextTransformation': self.transform,
114+
'RegexPatternSetId': pattern_set_id
115+
}
116+
},
117+
],
118+
ChangeToken=changeToken['ChangeToken']
119+
)
120+
121+
def update_match_set(self,match_set_id):
122+
match_set_object = self.get_match_set(match_set_id)
123+
for match_tuple in match_set_object['RegexMatchSet']['RegexMatchTuples']:
124+
self.update_pattern_set(match_tuple['RegexPatternSetId'])
125+
self.delete_match_set(match_set_id,match_tuple)
126+
self.insert_match_set(match_set_id,match_tuple['RegexPatternSetId'])
127+
128+
129+
def get_match_set(self,match_set_id):
130+
match_set_object = self.client.get_regex_match_set(
131+
RegexMatchSetId=match_set_id
132+
)
133+
return match_set_object
134+
135+
def remove_match_set(self,match_set_id):
136+
match_set_object = self.get_match_set(match_set_id)
137+
138+
for match_tuple in match_set_object['RegexMatchSet']['RegexMatchTuples']:
139+
self.delete_match_set(match_set_id,match_tuple)
140+
self.remove_pattern_set(match_tuple['RegexPatternSetId'])
141+
142+
changeToken = self.client.get_change_token()
143+
response = self.client.delete_regex_match_set(
144+
RegexMatchSetId=match_set_id,
145+
ChangeToken=changeToken['ChangeToken']
146+
)
147+
148+
def delete_match_set(self,match_set_id,match_tuple):
149+
changeToken = self.client.get_change_token()
150+
update_regex_matchset = self.client.update_regex_match_set(
151+
RegexMatchSetId=match_set_id,
152+
Updates=[
153+
{
154+
'Action': 'DELETE',
155+
'RegexMatchTuple': match_tuple
156+
},
157+
],
158+
ChangeToken=changeToken['ChangeToken']
159+
)

0 commit comments

Comments
 (0)