CloudFront WAF IP Whitelisting with Lambda And Slack

Vinayak Pandey
5 min readJan 22, 2025

--

In this post, we’ll explore how to use AWS Lambda and Slack to whitelist an IP address in a CloudFront Web Application Firewall.

Pre-requisite: Create a CloudFront with S3 origin and associate a WAF with it. In WAF, add a rule named whitelist allows request coming from an IPSet.

Default action for this WAF is Block.

We also need AWS Chatbot configured with Slack integration.

Step 1: Create a Private Slack channel in your Slack workspace.

Step 2: Create a Slack Webhook URL. For this, sign in to your Slack account and select the workspace where you want to create the webhook.

Go to the Slack API website (https://api.slack.com/apps) and click on the “Create a Slack App” button.

Give your app a name and select the workspace where you want to install it. Click “Create App”.

On the next page, scroll down to the “Add features and functionality” section and click on “Incoming Webhooks”.

Activate the Incoming Webhooks feature by setting the toggle to “On”. Then click on the “Add New Webhook to Workspace” button.
Select the channel where you want to send the webhook messages and click “Authorize”.

You will be taken back to the Incoming Webhooks page. Scroll down to the “Webhook URLs for Your Workspace” section and copy the webhook URL.

Step 3: Create a Python3.11 lambda function named waf-update-validation with following code. Replace <SLACK_WEBHOOK_URL> with your webhook URL.

import requests
import json
import boto3
import re

SLACK_WEBHOOK_URL = '<SLACK_WEBHOOK_URL>'
cloudfront_client = boto3.client('cloudfront')
ipv4_regex='^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'

# Function to send message to Slack
def send_to_slack(ip,domain):
blocks = [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*WAF Update Request:*"
}
}
]

blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"IP Address: `{ip}`, Domain: `{domain}`"
},
"accessory": {
"type": "button",
"text": {"type": "plain_text", "text": "Approve"},
"style": "primary",
"action_id": f"approve_{ip}_{domain}"
}
})

blocks.append({"type": "divider"})

headers = {"Content-Type": "application/json"}
data = {
"text": "WAF Update Request",
"blocks": blocks
}

response = requests.post(SLACK_WEBHOOK_URL, headers=headers, data=json.dumps(data))
if response.status_code == 200:
print("Message sent to Slack successfully.")
else:
print(f"Failed to send message to Slack: {response.text}")


def lambda_handler(event,context):
ipv4_to_add=event['ipv4']
domain=event['domain']
distribution_id=None

response = cloudfront_client.list_distributions()
for distribution in response['DistributionList']['Items']:
if distribution['DomainName'] == domain+'.cloudfront.net':
distribution_id=distribution['Id']
break

if distribution_id is None:
return {'message': "No CloudFront distribution found for this domain name."}

match = re.search(ipv4_regex, ipv4_to_add)
if not match:
return {'message': "Invalid IPV4 address provided."}
else:
send_to_slack(ipv4_to_add,domain)
return {'message': "Your WAF whitelisting request has been sent to admin for approval."}

Set timeout to 30 seconds and add following permission to the IAM role

{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"cloudfront:ListDistributions",
"cloudfront:GetDistributionConfig"
],
"Effect": "Allow",
"Resource": "*",
"Sid": "ObjectAcc"
}
]
}

Since we are using requests module which is not available in Lambda, you can follow https://docs.aws.amazon.com/lambda/latest/dg/python-package.html to create a zip package. Required packages can be installed by running following commands:

pip3 install --target ./package -r requirements.txt

where requirements.txt

requests
urllib3==1.26

Step 4: Go to AWS Chatbot and configure this channel. Create following IAM policy

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:<AWS_ACCOUNT_ID>:function:waf-update-validation"
}
]
}

and associate it with Chatbot IAM role and use it as guardrail policy as well

This allows invoking this lambda from Slack channel.

Step 5: Follow Step 3 and create another Lambda named waf-update with following code.

import boto3
import re
import json
import base64
import urllib.parse
import requests

waf_client = boto3.client('wafv2',region_name='us-east-1')
cloudfront_client = boto3.client('cloudfront')
SLACK_WEBHOOK_URL='<SLACK_WEBHOOK_URL>'

def lambda_handler(event,context):
distribution_id=None
ipv4_rule_set=''
domain=''
ipv4_to_add=''

decoded_body = base64.b64decode(event["body"]).decode("utf-8")
print(urllib.parse.unquote(decoded_body))
input_string=urllib.parse.unquote(decoded_body)
json_string = input_string.replace("payload=", "").strip()


payload = json.loads(json_string)

if payload['user']['username'] == 'vinayak': #Your Slack username
blocks=payload['message']['blocks']
print(blocks)
for action in payload['actions']:
ipv4_to_add=action['action_id'].split('_')[1]
domain=action['action_id'].split('_')[2]

response = cloudfront_client.list_distributions()
for distribution in response['DistributionList']['Items']:
if distribution['DomainName'] == domain+'.cloudfront.net':
distribution_id=distribution['Id']
break

response = cloudfront_client.get_distribution_config(Id=distribution_id)
web_acl_arn=response['DistributionConfig']['WebACLId']
web_acl_id=web_acl_arn.split('/')[-1]
web_acl_name=web_acl_arn.split('/')[-2]

response = waf_client.get_web_acl(Name=web_acl_name,Scope='CLOUDFRONT',Id=web_acl_id)
for rule in response['WebACL']['Rules']:
if rule['Name'] == 'whitelist_ipv4' or re.search(r'\bwhitelist\b', rule['Name']):
ipv4_rule_set=str(rule['Statement']['IPSetReferenceStatement']['ARN'])
response = waf_client.get_ip_set(Name=ipv4_rule_set.split('/')[-2],Scope='CLOUDFRONT',Id=ipv4_rule_set.split('/')[-1])

ipsetv4_lock_token=response['LockToken']
current_ipv4_address=response['IPSet']['Addresses']
current_ipv4_address.append(ipv4_to_add+'/32')
try:
response = waf_client.update_ip_set(Name=ipv4_rule_set.split('/')[-2],Scope='CLOUDFRONT',Id=ipv4_rule_set.split('/')[-1],Addresses=current_ipv4_address,LockToken=ipsetv4_lock_token)
print("IPV4 Address Added")
message = f":white_check_mark: *WAF is updated* :rocket:\n> IP Address: `{ipv4_to_add}`\n> Domain: `{domain}`"
# Prepare the payload
data = {"text": message}
# Send the message to Slack
response = requests.post(SLACK_WEBHOOK_URL, headers={"Content-Type": "application/json"}, data=json.dumps(data))
# Check the response status
if response.status_code == 200:
print("Message sent to Slack successfully.")
else:
print(f"Failed to send message to Slack: {response.text}")
except botocore.exceptions.ClientError as error:
print("Error updating WAF. Please reach out to DevOps team")

print ("WAF is updated.")

Add following permission to this Lambda’s IAM role.

{
"Action": [
"cloudfront:ListDistributions",
"wafv2:GetIPSet",
"wafv2:ListIPSets",
"wafv2:GetWebACL",
"cloudfront:GetDistributionConfig",
"wafv2:UpdateIPSet"
],
"Effect": "Allow",
"Resource": "*",
"Sid": "ObjectAcc"
}

Also generate a Function URL for this Lambda. This is not a secure approach and we are using it for demo purpose only.

Now go back to your Slack App and select Interactivity & Shortcuts and Enable Interactivity and provide your Lambda function URL as Request URL.

Step 6: Now our setup is ready. As of now if we try to access our CloudFront URL, request will be blocked.

In slack channel, run following command. Replace <YOUR_IPV4_ADDRESS> with the IP address you want to whitelist.

@aws lambda invoke --payload {"domain": "d2k42oj5y2yi6", "ipv4":"<YOUR_IPV4_ADDRESS>"} --function-name waf-update-validation --region us-east-1

You’ll see following output

ExecutedVersion: $LATEST
StatusCode: 200
Payload:
{
"message" : "Your WAF whitelisting request has been sent to admin for approval."
}

and a message will be sent to Slack channel.

Step 7: Once a designated user clicks on Approve(if payload[‘user’][‘username’] == ‘vinayak’ in waf-update Lambda), Slack will receive following message

and IP will be added into the IPSet.

Here I have given a dummy IP address. If you specify your actual IP address, once it’s whitelisted, you can access your CloudFront URL.

--

--

Vinayak Pandey
Vinayak Pandey

Written by Vinayak Pandey

Experienced Cloud Engineer with a knack of automation. Linkedin profile: https://www.linkedin.com/in/vinayakpandeyit/

No responses yet