Blocking IP addresses is a crucial security measure for protecting an organization’s infrastructure from various cyber threats, including DDoS attacks, brute-force login attempts, and unauthorized access. By blocking malicious or suspicious IPs, organizations can prevent system compromise, reduce unwanted traffic, and ensure that only trusted networks access critical resources. This helps maintain the performance and stability of the systems while ensuring compliance with security policies. Automating IP blocking using AWS Lambda and AWS Systems Manager (SSM) allows for a quick and efficient response to threats, securing environments with minimal manual intervention.
Managing IP access is an essential aspect of securing any application. This guide walks through the process of setting up a Lambda function in AWS to block or unblock IP addresses based on commands sent through Slack. By integrating AWS Lambda, Slack commands, AWS Systems Manager, and EC2 instances, you can automate IP blocking/unblocking. Below are the detailed steps for implementing this automation.
Step-by-Step Implementation
1. Create a New Lambda Function
- Access the AWS Lambda console and select the Ohio region (us-east-2).
- Choose “Create function”, and name it td-block-unblock-ip.
- Enable the Lambda Function URL and set the Auth type to None.
- Create the Lambda function and copy the Lambda function URL for later use.
This function will process Slack commands to block or unblock an IP.
2. Add the Code
Below is the Lambda function code that handles commands from Slack to block or unblock IP addresses:
import json import os import boto3 import hmac import base64 import time import hashlib import urllib.parse import ipaddress import re # Set up constants SLACK_CHANNEL = os.environ["SLACK_CHANNEL"] SLACK_HOOK = os.environ["SLACK_HOOK"] SLACK_SIGNING_KEY = os.environ["SIGNING_KEY"] EC2_INSTANCE_ID = os.environ.get("EC2_INSTANCE_ID") SSM_DOC_UNBLOCK_IP = os.environ.get("SSM_DOC_UNBLOCK_IP") SSM_DOC_BLOCK_IP = os.environ.get("SSM_DOC_BLOCK_IP") def lambda_handler(event, context): try: # Parse event data from Slack slack_signature = event['headers']["x-slack-signature"] slack_timestamp = event['headers']['x-slack-request-timestamp'] slack_payload = event['body'] payload = parse_slack_payload(slack_payload) from_channel = payload["channel_name"] # Check if request originates from a valid source from_valid_source = verify_source( signing_key=SLACK_SIGNING_KEY.encode('utf-8'), timestamp=slack_timestamp, body=base64.b64decode(slack_payload).decode('utf-8'), slack_signature=slack_signature, from_channel=from_channel ) # Parse the command and the IP from the payload command = urllib.parse.unquote(payload["command"]) ip_address = extract_ip(urllib.parse.unquote(payload["text"])) # Check if request IP is valid from_valid_ip = is_valid_ip(ip_str=(ip_address)) # Main Code if from_valid_source: if from_valid_ip: if command.startswith("/block-ip"): return process_command(ip_address, SSM_DOC_BLOCK_IP, SLACK_HOOK, EC2_INSTANCE_ID, "IP BLOCKING") elif command.startswith("/unblock-ip"): return process_command(ip_address, SSM_DOC_UNBLOCK_IP, SLACK_HOOK, EC2_INSTANCE_ID, "IP UNBLOCK") else: return "Invalid command" else: return f"Your input, \"{ip_address}\", is not a valid IPv4 address. Please provide a valid IPv4 address." else: return "You're not authorized to perform this operation" except Exception as e: print(e) return str(e) def parse_slack_payload(data): decoded_data_raw = base64.b64decode(data).decode('utf-8').split('&') decoded_data_formatted = {} for item in decoded_data_raw: data_object = item.split('=') decoded_data_formatted[data_object[0]] = data_object[1] return decoded_data_formatted def verify_source(signing_key, timestamp, body, slack_signature, from_channel): basestring = f'v0:{timestamp}:{body}' hmac_digest = hmac.new( key=signing_key, msg=basestring.encode('utf-8'), digestmod=hashlib.sha256 ).hexdigest() return hmac.compare_digest(slack_signature, f'v0={hmac_digest}') and SLACK_CHANNEL == from_channel def extract_ip(text): # Regular expression pattern to match IPv4 address pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b' match = re.search(pattern, text) if match: return match.group() else: return text def is_valid_ip(ip_str): try: ip = ipaddress.IPv4Address(ip_str) return True except ipaddress.AddressValueError: return False def execute_ssm_document(instance_id, ssm_document, parameters): try: ssm = boto3.client('ssm') response = ssm.send_command( InstanceIds=[instance_id], DocumentName=ssm_document, Parameters=parameters, TimeoutSeconds=30 ) # Convert datetime objects to strings if 'RequestedDateTime' in response: response['RequestedDateTime'] = response['RequestedDateTime'].isoformat() return response except Exception as e: print(e) return str(e) def process_command(ip_address, ssm_document, slack_hook, instance_id, message): parameters = {'ipAddress': [ip_address], 'slackHook': [slack_hook]} response = execute_ssm_document(instance_id, ssm_document, parameters) print('Command Parameters:', response.get('Command', {}).get('Parameters')) return { 'statusCode': 200, 'body': f'{message} request received and processing...' }
Set the following environment variables for the Lambda function to operate:
- EC2_INSTANCE_ID: The ID of the EC2 instance to manage.
- SIGNING_KEY: The signing key from Slack (Slack API > App Credentials).
- SLACK_CHANNEL: The name of the Slack channel where the Lambda function will operate.
- SLACK_HOOK: The Slack webhook URL for notifications.
- SSM_DOC_UNBLOCK_IP: The name of the SSM document for unblocking IP addresses.
- SSM_DOC_BLOCK_IP: The name of the SSM document for blocking IP addresses.
Navigate to Configuration > General configuration, and set the timeout to 30 seconds.
5. Assign IAM Role to Lambda
Assign the appropriate IAM role to allow Lambda access to AWS Systems Manager. The role needs permission to send commands to SSM:
{ "Effect": "Allow", "Action": "ssm:SendCommand", "Resource": "*" }
In the Slack API or Slack App Management Console, create two commands:
- /block-ip: For blocking an IP address
- /unblock-ip: For unblocking an IP address
Set the Request URL to the Lambda function URL you copied earlier.
7. Copy Needed Variables and
- From Slack, go to Settings > Basic Information > App Credentials > Signing Secret.
- Copy the
Signing Secret
text and paste it into the Lambda function environmental variableSIGNING_KEY
.
8. Create SSM Documents
Now, create two SSM documents in AWS Systems Manager for blocking and unblocking IPs.
SSM Document for Blocking IPs:
{ "schemaVersion": "2.2", "description": "Block IP address and notify Slack", "parameters": { "ipAddress": { "type": "String", "description": "(Required) IP address to block." }, "slackHook": { "type": "String", "description": "(Required) Slack Webhook URL for notifications." } }, "mainSteps": [ { "action": "aws:runShellScript", "name": "runShellScript", "inputs": { "runCommand": [ "#!/bin/bash", "ip_address='{{ ipAddress }}'", "hook='{{ slackHook }}'", " sleep 1", "iptables_file=\"PATH_TO_IPBlockList.txt"", "if sudo ipset list tdojoBlockedIPs | grep \"$ip_address\" && sudo grep \"$ip_address\" \"$iptables_file\" ; then", " echo 'IP address is already listed as blockedIP in the IPTABLES.'", " # Notify Slack Channel", " curl -H \"Content-type: application/json\" -d '{\"text\": \"IP address: '$ip_address' is ALREADY LISTED in the IPTABLES.\"}' -X POST \"$hook\"", "else", " sudo ipset add tdojoBlockedIPs \"$ip_address\"", " sudo echo \"$ip_address\" >> \"$iptables_file\"", " echo \"Done adding IP address: '$ip_address' to the IPTABLES and IPBlocklist.txt.\"", " # Notify Slack Channel", " curl -H \"Content-type: application/json\" -d '{\"text\": \"Done adding IP address: '$ip_address' to the IPTABLES.\"}' -X POST \"$hook\"", "fi" ] } } ] }
SSM Document for Unblocking IPs:
{ "schemaVersion": "2.2", "description": "Block IP address and notify Slack", "parameters": { "ipAddress": { "type": "String", "description": "(Required) IP address to unblock." }, "slackHook": { "type": "String", "description": "(Required) Slack Webhook URL for notifications." } }, "mainSteps": [ { "action": "aws:runShellScript", "name": "runShellScript", "inputs": { "runCommand": [ "#!/bin/bash", "ip_address='{{ ipAddress }}'", "hook='{{ slackHook }}'", "iptables_file=\"PATH_TO_IPBlockList.txt"", " sleep 1", "if sudo ipset list tdojoBlockedIPs | grep \"$ip_address\" && sudo grep \"$ip_address\" \"$iptables_file\" ; then", " sudo sed -i \"/$ip_address/d\" \"$iptables_file\"", " sudo ipset del tdojoBlockedIPs \"$ip_address\"", " echo \"IP address: '$ip_address' has been unblocked in IPBlockList.txt & tdojoBlockedIps\"", " # Notify Slack Channel", " curl -H \"Content-type: application/json\" -d '{\"text\": \"'$ip_address' has been UNBLOCKED in IPBlockList.txt & tdojoBlockedIps\"}' -X POST \"$hook\"", "else", " echo \"IP address'$ip_address' is not in the blockedIP list.\"", " # Notify Slack Channel", " curl -H \"Content-type: application/json\" -d '{\"text\": \"NO RECORD of IP address: '$ip_address'\"}' -X POST \"$hook\"", "fi" ] } } ] }
8. Testing
- Run Command in SSM: Execute the SSM documents manually by providing an IP address and the Slack webhook URL. Confirm the results by checking if the IP address is blocked or unblocked.
2. Use Slack Commands:
- Now, that the SSM is working it’s time to test the Lambda.
- Go to Slack and enter the Slack commands with the IP you want to block or unblock.
9. Monitor Logs
- Check for any issues or successful execution in CloudWatch Logs.
- SSH into EC2: Access the EC2 instance and verify the IPBlockList.txt file:
Conclusion
This implementation allows seamless IP blocking and unblocking using Slack commands integrated with AWS Lambda and AWS Systems Manager. By following this guide, you can efficiently automate and secure your infrastructure. Monitor CloudWatch Logs and test the setup thoroughly to guarantee it functions as expected.