Skip to content

Instantly share code, notes, and snippets.

@davidedg
Last active April 6, 2024 03:39
Show Gist options
  • Save davidedg/c29c478ee9c15a804a99cbd1de364647 to your computer and use it in GitHub Desktop.
Save davidedg/c29c478ee9c15a804a99cbd1de364647 to your computer and use it in GitHub Desktop.
AWS NAT Instance HA
#!/bin/bash
#https://gist.github.com/davidedg/c29c478ee9c15a804a99cbd1de364647#file-userdata
# Intended to run together with AMI amzn-ami-vpc-nat-hvm-*, with ASG min=max=desired=1
# Tested with amzn-ami-vpc-nat-hvm-2018.03.0.20180811-x86_64-ebs (ami-0ea87e2bfa81ca08a)
# Expected TAGS:
# - Backend Subnets: Name=InternetNAT Values=AvailabilityZone ( eg: eu-west-1 ) where Frontend NAT Instance is (this allows for multiple NAT instances to serve differenze AZ subnets
# - Backend Subnets + ASG: Name=Environment, Values=EnvironmentLabel ( eg: "production", "staging" ... ) (this allows for multiple environments in same VPC, served by different NAT instances)
# - ASG: Name=EIP Values=EIP-allocation-id ( eg eipalloc-abcdef12 )
PATH="/usr/sbin:/sbin:/usr/bin:/bin"
function log { logger -t "NAT-instance" -- $1; }
function die {
[[ -n "$1" ]] && log "$1"
log "NAT Instance Configuration failed"
exit 1
}
stop_and_disable_services() {
## Clean up image
log "Stopping unneeded services"
initctl stop amazon-ssm-agent >/dev/null 2>/dev/null &
service atd stop &
service nfs stop &
service lvm2-lvmetad stop &
service lvm2-lvmpolld stop &
service lvm2-monitor stop &
service mdmonitor stop &
service nfslock stop &
service rpcbind stop &
service rpcgssd stop &
service sendmail stop &
wait
log "Uninstalling/Disabling unneeded services"
yum -y erase amazon-ssm-agent &
chkconfig atd off >/dev/null &
chkconfig nfs off >/dev/null &
chkconfig lvm2-lvmetad off >/dev/null &
chkconfig lvm2-lvmpolld off >/dev/null &
chkconfig lvm2-monitor off >/dev/null &
chkconfig mdmonitor off >/dev/null &
chkconfig nfslock off >/dev/null &
chkconfig rpcbind off >/dev/null &
chkconfig rpcgssd off >/dev/null &
chkconfig sendmail off >/dev/null &
wait
}
get_aws_data() {
## Get AWS details
export AWS_DEFAULT_OUTPUT="text"
export INSTANCE_ID=$(curl --retry 3 --silent --fail http://169.254.169.254/latest/meta-data/instance-id)
export MAC=$(curl --retry 3 --silent --fail http://169.254.169.254/latest/meta-data/mac)
export ENI=$(curl --retry 3 --silent --fail http://169.254.169.254/latest/meta-data/network/interfaces/macs/$MAC/interface-id)
AWS_IIDOC=$(curl --retry 3 --silent --fail http://169.254.169.254/latest/dynamic/instance-identity/document)
export AWS_DEFAULT_REGION=$(echo $AWS_IIDOC | python -c "import json,sys; print json.loads(sys.stdin.read())['region']") #"
}
apply_updates() {
log "Applying Security Updates"
yum -y update --security
}
##iptables_active_ftp() {
##log "Adding support for Active FTP"
##cat << EOF | sudo tee /etc/sysconfig/modules/nat_ftp.modules
##!/bin/sh
## Support for Active FTP initiated from INSIDE
##/sbin/modprobe nf_nat_ftp >/dev/null 2>&1
##iptables -A PREROUTING -t raw -m rpfilter --invert -j DROP >/dev/null 2>&1
##iptables -A PREROUTING -t raw -p tcp --sport 1024: --dport 21 -j CT --helper ftp >/dev/null 2>&1
##EOF
##chmod +x /etc/sysconfig/modules/nat_ftp.modules
##/etc/sysconfig/modules/nat_ftp.modules
##}
##
attach_eip() {
EIPTAG=$(aws ec2 describe-instances --filters "Name=instance-id,Values=$INSTANCE_ID" --query 'Reservations[].Instances[].Tags[?Key==`EIP`].Value')
log "Attaching EIP $EIPTAG to $INSTANCE_ID"
aws ec2 associate-address --allow-reassociation --instance-id $INSTANCE_ID --allocation-id "$EIPTAG"
}
update_routes() {
log "Updating Routing Tables"
VPC_ID=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --query 'Reservations[*].Instances[*].VpcId') || die "Unable to determine VPC ID for instance."
ENVTAG=$(aws ec2 describe-instances --filters "Name=instance-id,Values=$INSTANCE_ID" --query 'Reservations[].Instances[].Tags[?Key==`Environment`].Value')
PRIVATE_SUBNETS="$(aws ec2 describe-subnets --query 'Subnets[*].SubnetId' --filters Name=vpc-id,Values=$VPC_ID Name=tag:InternetNAT,Values=1 Name=tag:Environment,Values=$ENVTAG Name=state,Values=available)"
[[ -z "$PRIVATE_SUBNETS" ]] && die "No private subnets found"
aws ec2 modify-instance-attribute --instance-id $INSTANCE_ID --no-source-dest-check
for subnet in $PRIVATE_SUBNETS; do
ROUTE_TABLE_ID=$(aws ec2 describe-route-tables --query 'RouteTables[*].RouteTableId' --filters Name=association.subnet-id,Values=$subnet)
if [[ -z "$ROUTE_TABLE_ID" ]]; then
log "$subnet is not associated with a Route Table. Skipping this subnet."
else # create or replace route
aws ec2 create-route --route-table-id $ROUTE_TABLE_ID --destination-cidr-block 0.0.0.0/0 --network-interface-id $ENI &
aws ec2 replace-route --route-table-id $ROUTE_TABLE_ID --destination-cidr-block 0.0.0.0/0 --network-interface-id $ENI &
wait # backgrounding everything is too heavy for t2.nano - you might skip this wait with larger instances
fi
done
}
function script_full_path {
prg=$0
if [ ! -e "$prg" ]; then
case $prg in
(*/*) exit 1;;
(*) prg=$(command -v -- "$prg") || exit;;
esac
fi
dir=$(
cd -P -- "$(dirname -- "$prg")" && pwd -P
) || exit
prg=$dir/$(basename -- "$prg") || exit
printf '%s' "$prg"
}
update_rc_local() {
p=$(script_full_path)
printf '\n%s on-reboot\n' "$p" >> /etc/rc.local
}
case "$1" in
attach-eip)
get_aws_data
attach_eip
;;
update-routes)
get_aws_data
update_routes
;;
on-reboot)
get_aws_data
attach_eip
update_routes
;;
*) # very first boot
stop_and_disable_services
get_aws_data
apply_updates
update_rc_local
needs-restarting -r
if [[ $? -ne 0 ]]; then
log "A Reboot is required"
reboot
else
bash $(script_full_path) on-reboot
fi
;;
esac
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment