IP rotation from EC2 machine
Last updated
Last updated
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeAddresses",
"ec2:AllocateAddress",
"ec2:AssociateAddress",
"ec2:ReleaseAddress"
],
"Resource": "*"
}
]
}sudo apt install awscli
aws --version#!/usr/bin/env bash
# -------------------------------------------------------------------
# Rotate the Elastic IP attached to a specific EC2 network interface.
#
# Flow:
# 1. Discover the instance and target network interface via IMDS
# 2. Detect the currently associated Elastic IP (if any)
# 3. Allocate a new Elastic IP
# 4. Associate the new IP to the interface
# 5. Release the old Elastic IP
#
# Compatible with IMDSv1 and IMDSv2
# -------------------------------------------------------------------
# --- Configuration --------------------------------------------------
# Device number of the target Elastic Network Interface (ENI).
# Usually:
# 0 = primary interface (eth0, ens5, etc.)
TARGET_DEVICE_NUMBER="0"
# TTL for IMDSv2 session token (6 hours)
TOKEN_TTL_SECONDS=21600
# Enable verbose/debug output if needed
DEBUG_MODE="false"
# --- ANSI Color Codes (for readable output) -------------------------
RED='\033[0;31m' # Errors
GREEN='\033[0;32m' # Success
YELLOW='\033[0;33m' # Warnings
CYAN='\033[0;36m' # Debug
NC='\033[0m' # Reset color
# --- Functions ------------------------------------------------------
# get_metadata
# Retrieves EC2 instance metadata.
# Automatically uses IMDSv2 if available, otherwise falls back to IMDSv1.
#
# Usage:
# get_metadata "instance-id"
# get_metadata "network/interfaces/macs/"
get_metadata() {
local path="$1"
local token
# Request IMDSv2 token
token=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: $TOKEN_TTL_SECONDS")
# Use token if available (IMDSv2), otherwise fall back to IMDSv1
if [ -n "$token" ]; then
curl -s -H "X-aws-ec2-metadata-token: $token" \
"http://169.254.169.254/latest/meta-data/${path}"
else
curl -s "http://169.254.169.254/latest/meta-data/${path}"
fi
}
# --- Main Logic -----------------------------------------------------
echo "Starting Elastic IP rotation..."
# Retrieve the EC2 instance ID to confirm metadata access
INSTANCE_ID=$(get_metadata "instance-id")
if [ -z "$INSTANCE_ID" ]; then
echo -e "${RED}Error: Unable to retrieve instance ID${NC}"
exit 1
fi
echo "Instance ID: $INSTANCE_ID"
# -------------------------------------------------------------------
# Discover the network interface (ENI) matching TARGET_DEVICE_NUMBER
#
# EC2 metadata returns one directory per ENI MAC address.
# Each MAC directory exposes:
# - device-number
# - interface-id
# -------------------------------------------------------------------
NETWORK_INTERFACE_ID=""
for mac in $(get_metadata "network/interfaces/macs/"); do
# Read the device-number for this interface
device_num=$(get_metadata "network/interfaces/macs/${mac}device-number")
# Match against the configured target device number
if [ "$device_num" = "$TARGET_DEVICE_NUMBER" ]; then
# Retrieve the ENI ID for the matched interface
NETWORK_INTERFACE_ID=$(get_metadata "network/interfaces/macs/${mac}interface-id")
break
fi
done
# Fail if the ENI could not be identified
if [ -z "$NETWORK_INTERFACE_ID" ]; then
echo -e "${RED}Error: ENI with device-number $TARGET_DEVICE_NUMBER not found${NC}"
exit 1
fi
echo "Target ENI: $NETWORK_INTERFACE_ID"
# -------------------------------------------------------------------
# Retrieve the currently associated Elastic IP (if any)
# -------------------------------------------------------------------
OLD_IP=$(aws ec2 describe-network-interfaces \
--network-interface-ids "$NETWORK_INTERFACE_ID" \
--query 'NetworkInterfaces[0].Association.PublicIp' \
--output text 2>/dev/null)
OLD_ALLOCATION_ID=""
# If an Elastic IP is already attached, retrieve its Allocation ID
if [ -n "$OLD_IP" ] && [ "$OLD_IP" != "None" ]; then
OLD_ALLOCATION_ID=$(aws ec2 describe-addresses \
--public-ips "$OLD_IP" \
--query 'Addresses[0].AllocationId' \
--output text 2>/dev/null)
echo -e "Old IP: ${YELLOW}$OLD_IP${NC}"
else
echo -e "${YELLOW}No existing Elastic IP found${NC}"
fi
# -------------------------------------------------------------------
# Allocate a brand new Elastic IP in the VPC scope
# -------------------------------------------------------------------
echo "Allocating new Elastic IP..."
ALLOC=$(aws ec2 allocate-address \
--domain vpc \
--query '[PublicIp,AllocationId]' \
--output text 2>/dev/null)
# Parse allocation result
NEW_IP=$(echo "$ALLOC" | awk '{print $1}')
NEW_ALLOCATION_ID=$(echo "$ALLOC" | awk '{print $2}')
# Ensure allocation succeeded
if [ -z "$NEW_IP" ] || [ -z "$NEW_ALLOCATION_ID" ]; then
echo -e "${RED}Failed to allocate new Elastic IP${NC}"
exit 1
fi
echo -e "New IP: ${GREEN}$NEW_IP${NC}"
# -------------------------------------------------------------------
# Associate the new Elastic IP with the target ENI
# -------------------------------------------------------------------
ASSOCIATION_ID=$(aws ec2 associate-address \
--network-interface-id "$NETWORK_INTERFACE_ID" \
--allocation-id "$NEW_ALLOCATION_ID" \
--allow-reassociation \
--query AssociationId \
--output text 2>/dev/null)
# If association fails, release the newly allocated IP to avoid leaks
if [ -z "$ASSOCIATION_ID" ]; then
echo -e "${RED}Failed to associate new Elastic IP${NC}"
aws ec2 release-address --allocation-id "$NEW_ALLOCATION_ID" 2>/dev/null
exit 1
fi
echo -e "${GREEN}New IP associated successfully${NC}"
# -------------------------------------------------------------------
# Release the previously associated Elastic IP (if one existed)
# -------------------------------------------------------------------
if [ -n "$OLD_ALLOCATION_ID" ]; then
echo "Releasing old Elastic IP..."
aws ec2 release-address --allocation-id "$OLD_ALLOCATION_ID" 2>/dev/null
echo -e "${GREEN}Old IP released${NC}"
fi
echo -e "${GREEN}Elastic IP rotation completed successfully${NC}"
exit 0chmod +x ip_rotate.sh
./ip_rotate.sh