Auto association of ec2 elastic IPs on instance launch

What a pain is to have to associate elastic IPs when launching Amazon AWS ec2 instances. Think about 15-20 machines which need static (elastic) ip. I solved this problem with a bit of Perl code:

First thing You need to do is to have ec2-api-tools installed in /usr/local/ec2/ec2-api-tools and your cert-…pem and pk-….pem file in /root/.ec2/. Then You need to install ec2-metadata API tool to be able to get the user data passed on launch of the instance:

user@computer:$
cd /usr/local/bin
wget http://s3.amazonaws.com/ec2metadata/ec2-metadata
chmod +x ec2-metadata

Now You should be ready to write a script that will associate elastic IP:

You have to make sure perl script can run API tools by adding ENV vars.:

$ENV{EC2_HOME} = '/usr/local/ec2/ec2-api-tools';
$ENV{PATH} = '$EC2_HOME/bin:'.$ENV{PATH};
$ENV{JAVA_HOME} = "/usr";
$ENV{EC2_PRIVATE_KEY} = '/root/.ec2/pk-XXX.pem';
$ENV{EC2_CERT} = '/root/.ec2/cert-XXX.pem';
$ENV{EC2_REGION} = 'eu-west-1';
$ENV{EC2_URL} = 'https://eu-west-1.ec2.amazonaws.com';

this bit will get the elastic_ip from user data:

my @params = qx(/usr/local/bin/ec2-metadata -d | cut -d ':' -f 2 | awk '{sub\(/^[ \t]+/, ""\); print}' | tr '|' '\n');
chomp @params;
my %options;
my $params_check = scalar @params;
if ($params_check == 0)
{
        print "No user-data defined !\n";
        print "Removing certificate and key\n";
        system("rm /root/.ec2/pk-XXX.pem; rm /root/.ec2/cert-XXX.pem");
        exit;   
}

User data is supplied in following format: key=value|key2=value|key3=value|… but can be also supplied as single variable: key=value

associate Elastic IP:

if ($options{'elastic_ip'})
{
        my $elastic_ip = $options{'elastic_ip'};
        print "Associating Elastic IP: $elastic_ip\n";
        my $instance_id = qx(/usr/local/bin/ec2-metadata -i | cut -d ':' -f 2 | awk '{sub\(/^[ \t]+/, ""\); print}');
        chomp $instance_id;
        system("/usr/local/ec2/ec2-api-tools/bin/ec2-associate-address $elastic_ip -i $instance_id");
}
else
{
        print "Removing certificate and key\n";
        system("rm /root/.ec2/pk-XXX.pem; rm /root/.ec2/cert-XXX.pem");
}

and wait for Elastic IP to be associated:

while (my $external_ip = qx(/usr/local/bin/ec2-metadata -v | cut -d ':' -f 2 | awk '{sub\(/^[ \t]+/, ""\); print}'))
{
        chomp $external_ip;
        if ($external_ip eq $options{'elastic_ip'})
        {
                print "Elastic IP attached\n";
                print "Removing certificate and key\n";
                system("rm /root/.ec2/pk-XXX.pem; rm /root/.ec2/cert-XXX.pem");
                last;
        }
        else
        {
                print "Waiting for Elastic IP\n";
                sleep 5;
        }
}

yes, this is endless loop but it will make sure that script will not exit until elastic IP is associated. This is useful when some services depend on it.

The complete script can look something like this:

#!/usr/bin/perl -w

use strict;

# Set variables for ec2
$ENV{EC2_HOME} = '/usr/local/ec2/ec2-api-tools';
$ENV{PATH} = '$EC2_HOME/bin:'.$ENV{PATH};
$ENV{JAVA_HOME} = "/usr";
$ENV{EC2_PRIVATE_KEY} = '/root/.ec2/pk-XXX.pem';
$ENV{EC2_CERT} = '/root/.ec2/cert-XXX.pem';
$ENV{EC2_REGION} = 'eu-west-1';
$ENV{EC2_URL} = 'https://eu-west-1.ec2.amazonaws.com';

# Get user data parameters provided on instance launch
# User data needs to be supplied in following format: key=value|key2=value|key3=value|...
print "Getting parameters\n";
my @params = qx(/usr/local/bin/ec2-metadata -d | cut -d ':' -f 2 | awk '{sub\(/^[ \t]+/, ""\); print}' | tr '|' '\n');
chomp @params;
my %options;

my $params_check = scalar @params;

if ($params_check == 0)
{
        print "No user-data defined !\n";
        print "Removing certificate and key\n";
        system("rm /root/.ec2/pk-XXX.pem; rm /root/.ec2/cert-XXX.pem");
        exit;
}

# Print out all user data to screen (helps when getting system log from instance)
print "User data:\n";
foreach (@params)
{
        my ($key, $value) = split(/\=/, $_);
        print "\t$key: $value\n";
        $options{$key} = $value;
}

# Exit if disable_boot_script option is set, this option should be used only when updating AMI as it will leave cert file and key on the instance
if ($options{'disable_boot_script'})
{
        print "Booting script disabled, exiting\nBye!\n";
        exit;
}

# Associate Elastic IP
if ($options{'elastic_ip'})
{
        my $elastic_ip = $options{'elastic_ip'};
        print "Associating Elastic IP: $elastic_ip\n";
        my $instance_id = qx(/usr/local/bin/ec2-metadata -i | cut -d ':' -f 2 | awk '{sub\(/^[ \t]+/, ""\); print}');
        chomp $instance_id;
        system("/usr/local/ec2/ec2-api-tools/bin/ec2-associate-address $elastic_ip -i $instance_id");
}
else
{
        print "Removing certificate and key\n";
        system("rm /root/.ec2/pk-XXX.pem; rm /root/.ec2/cert-XXX.pem");
}

# Wait for Elastic IP to be associated (Yes, this is endless loop !)
while (my $external_ip = qx(/usr/local/bin/ec2-metadata -v | cut -d ':' -f 2 | awk '{sub\(/^[ \t]+/, ""\); print}'))
{
        chomp $external_ip;
        if ($external_ip eq $options{'elastic_ip'})
        {
                print "Elastic IP attached\n";
                print "Removing certificate and key\n";
                system("rm /root/.ec2/pk-XXX.pem; rm /root/.ec2/cert-XXX.pem");
                last;
        }
        else
        {
                print "Waiting for Elastic IP\n";
                sleep 5;
        }
}

# Set hostname
my $hostname = $options{'hostname'};
print "Setting up hostname to $hostname\n";
system("hostname $hostname");

print "All done here\n";

now add this script to /etc/rc.local, problem solved :)

Of course this makes only sense on private AMIs. Please let me know what You think !

  • Liraz Siri

    Putting your private key and cert on an image is a bad idea. If one machine gets compromised the attacker now has access to your entire EC2 infrastructure.

    The correct solution is to invoke the API to do what you want from whatever interface you are using.

    Part of this comment was removed as it was adverting ec2 related services.

    • http://www.krzywanski.net/ Artur

      Well, it’s a compromise of not using third party tools and doing everything on instance rather than using some sort of interface to handle it. I’ve added lines that will remove this key when IP is attached or the option is missing just for sake of security.

      • Someone

        Artur, could you please post the lines that remove the certificate and security key

        • artkrz

          They are in the updated post.

  • Andy

    Exactly what I was looking for – THANK YOU!