From ac3ca9fa1160a6b895c40a0679b72844daea8fe8 Mon Sep 17 00:00:00 2001 From: kenzhang Date: Mon, 3 Aug 2020 14:57:00 +0800 Subject: [PATCH] Add Lambda to update the WAFv2 IPSet --- .DS_Store | Bin 0 -> 10244 bytes README.md | 6 ++ update_wafv2_ipset_lambda/README.md | 107 +++++++++++++++++++++++++++ update_wafv2_ipset_lambda/lambda.py | 111 ++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+) create mode 100644 .DS_Store create mode 100644 update_wafv2_ipset_lambda/README.md create mode 100644 update_wafv2_ipset_lambda/lambda.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0a13387b1a280a815d80976ec57b002f5639db9b GIT binary patch literal 10244 zcmeHMU2GIp6h5adFf&k@A_%kmbknBNK!IHX#X`mH4@d<9VOv^CVVT_-+6l8W>(1;J zT577sp9h2SC-DJ{3Hnx*Clckw#Q2wB{E3Z!Q6KchMVp8MTzW_s=d0JfK`BtRSh6uOwy7g2SS!u0IAp;SaYDoG@NfDmlRz%ZC_@&;?! zArX)WNCYGT5&?<8{{aE|X0xI!6jIg_0f~S_U>X7Teu&e>WGa&5Lh@G!RXhbCSw-Ev zP@D1q?h}t>Dw5+ua#tEtWDgj*Vw7T_a3_77nUhRKa$HE^4k+9KqnR|0~esOLN zm=aRf5&?<8jR>&Z-GCAlz@n^uX8vA=&6LYKqzNdi52@{vMIN%uGCLd;vaX*c@2lB@ z>jYsU@fB87R?VBgKvC(hS~->;@`ux*8J2=}vv`!<9W=wCY}D@deRsNTW)C~|a7JI+ z;d`O!cy^IHJC;o*1HF!C`NQpg$+H4JPFg6~JZeUdjgD?<+1eCOCR&nXP4UrWBGJ+m zZ{B*}*qEv;Z%E$Xbs~ReWc2LVBj?5XF?xr<;#qiHm>U>YkaM%N95&h71W0 z%m`*KqXgv+u_F~PP3_3n(>tOqG`b$r_GR=XWdiy@A#{8%IBXXKk{Nw*K%3K*^Sy&b zo9TOVzFW$BK}N61nQpG+nxWn1x@l*`4*Ej-bjVu$fnN+exM4=uOM!jJCOfBWcan9` z=g@G>Ohy^Gz)_J((glG61YibQ`z@kw0`xcwU z(l0A2*VNY<#!;r}DB4t8io(0sHX2%Yz#=vy!=??I)_sb|rw0@Q3p}}5GkRPzYrDFo z3b*EMn$cSpNa}l|&K<3q(MNllrzHk8*c5fA+6?13n|YrZga-v01%<2GXHc5KC6xEuFi7csRPd$1S#u%DP} zai%(0!~jcJCdNLBj}c#=#HS<1zKAd5MSOJ@Kf7k}^Yyv$Gw*kHMxR0@@mIL^-hcisB; z|2uA7GGK{-MBwiufK{F8&UUiD)qnZytUXTG3A$KecH=^F7pj;E>*vSubi +Updating from https://ip-ranges.amazonaws.com/ip-ranges.json +MD5 Mismatch: got 2e967e943cf98ae998efeec05d4f351c expected 7fd59f5c7f5cf643036cbd4443ad3e4b: Exception +Traceback (most recent call last): + File "/var/task/lambda_function.py", line 29, in lambda_handler + ip_ranges = json.loads(get_ip_groups_json(message['url'], message['md5'])) + File "/var/task/lambda_function.py", line 50, in get_ip_groups_json + raise Exception('MD5 Missmatch: got ' + hash + ' expected ' + expected_hash) +Exception: MD5 Mismatch: got 2e967e943cf98ae998efeec05d4f351c expected 7fd59f5c7f5cf643036cbd4443ad3e4b + +You will see a message indicating there was a hash mismatch. Normally, a real SNS notification from the IP Ranges SNS topic will include the right hash, but because our sample event is a test case representing the event, you will need to update the sample event manually to have the expected hash. + +4. Edit the sample event again, and this time change the md5 hash **that is bold** to be the first hash provided in the log output. In this example, we would update the sample event with the hash “2e967e943cf98ae998efeec05d4f351c”. + + +5. Click Save and test, and your Lambda function will be invoked. + +This time, you should see output indicating your security group was properly updated. If you go back to the EC2 console and view the security group you created, you will now see all the CloudFront IP ranges added as allowed points of ingress. If your log output is different, it should help you identify the issue. + +## Configure your Lambda function’s trigger +After you have validated that your function is executing properly, it’s time to connect it to the SNS topic for IP changes. To do this, use the AWS Command Line Interface (CLI). Enter the following command, making sure to replace with the Amazon Resource Name (ARN) of your Lambda function. You will find this ARN at the top right when viewing the configuration of your Lambda function. + +`aws sns subscribe --topic-arn arn:aws:sns:us-east-1:806199016981:AmazonIpSpaceChanged --protocol lambda --notification-endpoint ` + +You should receive an ARN of your Lambda function’s SNS subscription. Your Lambda function will now be invoked whenever AWS publishes new IP ranges! +*** + +Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at + + http://aws.amazon.com/apache2.0/ + +or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/update_wafv2_ipset_lambda/lambda.py b/update_wafv2_ipset_lambda/lambda.py new file mode 100644 index 0000000..5e89cff --- /dev/null +++ b/update_wafv2_ipset_lambda/lambda.py @@ -0,0 +1,111 @@ +import boto3 +import hashlib +import json +import logging +import urllib.request, urllib.error, urllib.parse +import os + +def lambda_handler(event, context): + # Set up logging + if len(logging.getLogger().handlers) > 0: + logging.getLogger().setLevel(logging.ERROR) + else: + logging.basicConfig(level=logging.DEBUG) + + # Set the environment variable DEBUG to 'true' if you want verbose debug details in CloudWatch Logs. + try: + if os.environ['DEBUG'] == 'true': + logging.getLogger().setLevel(logging.INFO) + except KeyError: + pass + + # If you want a different service, set the SERVICE environment variable. + # It defaults to CLOUDFRONT. Using 'jq' and 'curl' get the list of possible + # services like this: + # curl -s 'https://ip-ranges.amazonaws.com/ip-ranges.json' | jq -r '.prefixes[] | .service' ip-ranges.json | sort -u + SERVICE = os.getenv( 'SERVICE', "CLOUDFRONT") + + message = json.loads(event['Records'][0]['Sns']['Message']) + + # Load the ip ranges from the url + ip_ranges = json.loads(get_ip_groups_json(message['url'], message['md5'])) + + # Extract the service ranges + global_cf_ranges = get_ranges_for_service(ip_ranges, SERVICE, "GLOBAL") + region_cf_ranges = get_ranges_for_service(ip_ranges, SERVICE, "REGION") + cf_ranges = global_cf_ranges + region_cf_ranges + + # Update the WAF IPSet groups + result = update_ipset(cf_ranges) + + return result + +def update_ipset(ip_ranges): + client = boto3.client('wafv2') + target_ip_sets = {} + token = [] + for i in client.list_ip_sets(Scope='REGIONAL')['IPSets']: + if 'cf-auto-update' in i['Name']: + target_ip_sets.update({i['Name']:i['Id']}) + token = i['LockToken'] + + for k, v in target_ip_sets.items(): + client.update_ip_set( + Name=k, + Scope='REGIONAL', + Id=v, + Addresses=ip_ranges, + LockToken=token + ) + +def get_ranges_for_service(ranges, service, subset): + + service_ranges = list() + for prefix in ranges['prefixes']: + if prefix['service'] == service and ((subset == prefix['region'] and subset == "GLOBAL") or (subset != 'GLOBAL' and prefix['region'] != 'GLOBAL')): + logging.info(('Found ' + service + ' region: ' + prefix['region'] + ' range: ' + prefix['ip_prefix'])) + service_ranges.append(prefix['ip_prefix']) + + return service_ranges + +def get_ip_groups_json(url, expected_hash): + + logging.debug("Updating from " + url) + + response = urllib.request.urlopen(url) + ip_json = response.read() + + m = hashlib.md5() + m.update(ip_json) + hash = m.hexdigest() + + if hash != expected_hash: + raise Exception('MD5 Mismatch: got ' + hash + ' expected ' + expected_hash) + + return ip_json + +# This is a handy test event you can use when testing your lambda function. +''' +Sample Event From SNS: +{ + "Records": [ + { + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "EventSource": "aws:sns", + "Sns": { + "SignatureVersion": "1", + "Timestamp": "1970-01-01T00:00:00.000Z", + "Signature": "EXAMPLE", + "SigningCertUrl": "EXAMPLE", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "Message": "{\"create-time\": \"yyyy-mm-ddThh:mm:ss+00:00\", \"synctoken\": \"0123456789\", \"md5\": \"45be1ba64fe83acb7ef247bccbc45704\", \"url\": \"https://ip-ranges.amazonaws.com/ip-ranges.json\"}", + "Type": "Notification", + "UnsubscribeUrl": "EXAMPLE", + "TopicArn": "arn:aws:sns:EXAMPLE", + "Subject": "TestInvoke" + } + } + ] +} +''' \ No newline at end of file