EC2 allows public and private keypairs to be associated with your AWS account as a KeyPair resource that is stored in the EC2 environment. The service will generate a named keypair on demand and will provide you with a once-only downloadable private key. The service keeps an internal copy of the public key corresponding to the private key. When you launch an instance in the service, you can ask the environment to provide the public key from one of your keypairs to the instance as contextual data. The instance can then use this public key as one half of the access credentials required to perform a secure login, so that only someone with access to the corresponding private key can access the instance.
This technique makes it possible to restrict access to EC2 instances to only the user who started a particular instance, despite the fact that many different users may use the same AMI as a starting point for their instance. The keypair access mechanism takes advantage of the EC2 environment’s ability to provide contextual data to an instance (see Instance Data” in Chapter 6), and it relies on the instance being configured to obtain the public key and apply it as a login credential (see Startup Scripts” in Chapter 6).
The publicly available AMIs provided by Amazon are configured to allow secure login based on a keypair that belongs to the user who launches an instance. Because we will be starting with these public AMIs to demonstrate the EC2 API, we must create a keypair before launching an instance.
If an AMI is configured to allow only keypair-based access, and you start an instance without assigning it a keypair of your own, you will be unable to log in to the instance to customize its content. You will be able to control the instance’s lifecycle and terminate it, but that will be the limit of your control.
Let us take a look at the EC2 API operations for working with keypairs.
The DescribeKeyPairs operation shown in Table 5-3 lists information about your keypairs, including their names and fingerprints. You can filter the listing by name to display the details of specific keypairs.
Table 5-3. DescribeKeyPairs request parameters
Parameter Name | Value | Required? |
---|---|---|
Action | DescribeKeyPairs | Yes |
KeyName (Indexed) | The names of specific keypairs to include in the listing. If this parameter is not provided, all your keypairs will be listed. | No |
Here is an XML document returned by the operation:
<DescribeKeyPairsResponse xmlns='http://ec2.amazonaws.com/doc/2007-08-29/'> <keySet> <item> <keyName>ec2-private-key</keyName> <keyFingerprint>5e:93:4c:dd:c3:cb:a6...:d6:4e:13:4d:85</keyFingerprint> </item> </keySet> </DescribeKeyPairsResponse>
The document includes a set of item elements inside the keySet element. As Table 5-4 shows, each item contains two elements with information about a keypair.
Table 5-4. XML elements that describe a keypair
Element Name | Description |
---|---|
keyName | The name of the keypair, as supplied by the user who created it |
keyFingerprint | The fingerprint of the keypair’s private key, computed as an SHA-1 digest of the private key data |
Example 5-2 defines a method that lists the keypairs you have created in your EC2 account as an array of hash dictionary objects.
Example 5-2. Describe keypairs: EC2.rb
def describe_keypairs(*keypair_names) parameters = build_query_params(API_VERSION, SIGNATURE_VERSION, { 'Action' => 'DescribeKeyPairs', },{ 'KeyName' => keypair_names }) response = do_query(HTTP_METHOD, ENDPOINT_URI, parameters) xml_doc = REXML::Document.new(response.body) keypairs = [] xml_doc.elements.each('//keySet/item') do |key| keypairs << { :name => key.elements['keyName'].text, :fingerprint => key.elements['keyFingerprint'].text } end return keypairs end
Because this is our first EC2 API implementation method, we
should take a close look at how it works. To prepare the request
parameter inputs we must provide to the service, we invoke the
build_query_params
method with two
hash variables. The first variable is the set of standard parameter
names and value mappings that will be included unmodified in the
request. The second variable is a set of indexed parameters. The
indexed parameters must be converted into multiple parameter
name-and-value pairs with a unique number suffix for each parameter
name, a task performed by the build_query_params
method (see Example 2-6).
The query execution performed by the AWS module’s do_query
method is quite straightforward; it
simply provides the the operation-specific parameters and performs the
request using the default HTTP method and EC2 endpoint URI constants
defined in the EC2 class. When the EC2
service sends an XML document in
response to a request, we interpret the document and store the
information in a Ruby data structure for easier access.
If you have not yet created your own keypairs, you will see the following result when you run the new method:
# Load the EC2 library for the first time and create an EC2 client object irb> require 'EC2' irb> ec2 = EC2.new # List your keypairs irb> ec2.describe_keypairs => []
That empty listing of keypairs is a sorry sight; we have no credentials to our name. We will remedy this straightaway by creating a new keypair.
The hard work of creating a keypair is performed by the CreateKeyPair operation, outlined in Table 5-5. This action instructs EC2 to generate a 2,048-bit public and private keypair using the RSA (Rivest-Shamir-Adleman) public-key cryptographic algorithm and to assign this pair a name. The public key component is stored in your EC2 environment and can be referenced using the name you assigned to the keypair. The private key is provided to you in the action’s response message. It is your responsibility to store the private key, keep it safe, and provide it when you log in to your instances.
Your private key will only be provided to you once, at the time the keypair is created. If you forget to save your private key data or lose it, you will not be able to use the keypair; you will have to create a new one.
Table 5-5. CreateKeyPair request parameters
Parameter Name | Value | Required? |
---|---|---|
Action | CreateKeyPair | Yes |
KeyName | A name for the new keypair; the name cannot match a keypair that already exists | Yes |
Here is an XML document returned by the operation:
<CreateKeyPairResponse xmlns='http://ec2.amazonaws.com/doc/2007-08-29/'> <keyName>ec2-private-key</keyName> <keyFingerprint>8a:e3:e4:9e:3b:4b:9f:32:02:56:c4:b2:0b:8d:2b:cb:f1:70:bf:22</keyFingerprint> <keyMaterial>—————BEGIN RSA PRIVATE KEY————— MIIEogIBAAKCAQEApDs8hK6g88TwTkn+kXYQIh9djl4DD3tYUeJ0oRc7nD+/FvYWLjqcYb3YcNAk r+mMog5fvvcagJhhEaglKpEytxho4/Rh4Y5N4iuHAR8mXPfwge6PdwR4gC9BHOJGakZUBLkEBAUf zDXKmmYip7Gu7EO7KtK+PJZ1BDtyaFjUdRWM3Bz+liMAijMwATCCQbnZ++mZe/41CA8cw2TLRamg ...YfwdpnslPpw/7SiWlGhJRyeBCB9GwuVXxnRlp9hQEkwBAoGAN+QSK4H22ULTSLUwAfigwXsnhQiS 8W836EP6GOlGcKBBNtmW+BE1u0xy40KKxRMNw994nVVhCKkFvj6ugmTepwMDISrnOG0EK/L/3aaV 2yN9ear1nkK4DQAu5zRcym0QK/qt66Dk2+tUIJSxxkrTzpCLWTs3Bz1V4n9jjORttFo= —————END RSA PRIVATE KEY—————</keyMaterial> </CreateKeyPairResponse>
The response document includes the name and fingerprint details of the newly created keypair. These are the same details we saw above in the discussion of the DescribeKeyPairs operation. However, the most important part of this message is the keyMaterial element.
The keyMaterial element contains the private key data for the new keypair, encoded as an unencrypted RSA key in the PEM format. You must store this data somewhere safe on your computer, so you can use the private key to access the instances you launch. If you are concerned about the security of your private key, as you should be, it would be wise to encrypt it so it can only be accessed with a password. We will demonstrate how to do this below.
Because your private key data is not encrypted in the XML result document sent by EC2, we highly recommend that you only perform the CreateKeyPair operation over a secure HTTPS connection.
Example 5-3 defines a method that
creates a new keypair in your EC2 account. If the method’s autosave
parameter is set to the value
true
, as it is by default, the
private key material returned by the service will be automatically
written to a file named after the keypair name.
Example 5-3. Create keypair: EC2.rb
def create_keypair(keyname, autosave=true) parameters = build_query_params(API_VERSION, SIGNATURE_VERSION, { 'Action' => 'CreateKeyPair', 'KeyName' => keyname, }) response = do_query(HTTP_METHOD, ENDPOINT_URI, parameters) xml_doc = REXML::Document.new(response.body) keypair = { :name => xml_doc.elements['//keyName'].text, :fingerprint => xml_doc.elements['//keyFingerprint'].text, :material => xml_doc.elements['//keyMaterial'].text } if autosave # Locate key material and save to a file named after the keyName File.open("#{keypair[:name]}.pem",'w') do |file| file.write(keypair[:material] + "\n") keypair[:file_name] = file.path end end return keypair end
We will run this method now to create a keypair that we can use later on to gain access to the EC2 instances we launch. In this book, we will use the name “ec2-private-key” for this keypair.
irb> keypair = ec2.create_keypair('ec2-private-key', true) => { :name=>"ec2-private-key", :fingerprint=>"99:e4:32:8d:5c:2c:52:22:99:a2:a6:27:ac:2f:78:27:26:61:88:e4", :file_name=>"ec2-private-key.pem", :material=>"—————BEGIN RSA PRIVATE KEY—————\n MIIEpAIBAAKCAQEAjW+Y70NBdp4Cw1lD9xTrUKYlp6iaOo1EuOcK793RAqBaaOV7I5eRUGQbgwb2\ . . . B2zgtZqfc7x2mZ+U+Jsp2W6KooInOiSI5tlW22DZ5zF3nkZtQstoA0jCcjGQ==\n —————END RSA PRIVATE KEY—————" }
Because we ran this method with the autosave
parameter set to true, the method
will have written the vital private key data to a file and returned
the name of this file in the hash item named :file_name
.
irb> keypair[:file_name] # => "ec2-private-key.pem"
If you wish to encrypt your private key file, as we
recommend you do, you can run the following OpenSSL
command at a command prompt to
encrypt the file using the DES3 algorithm.
$ openssl rsa -des3 -inform PEM -outform PEM \ -inec2-private-key.pem
-outec2-private-key.enc
When you run this command, you will be prompted to enter a
password, and the key will be encrypted and written to a new filename
you provide to the -out
option.
Once this is done, you will have to supply the correct password before
you will be able to access the encrypted key file. After you have
encrypted the file, be sure to delete the original, unencrypted file,
once you have confirmed that the new encrypted version works
properly.
To confirm that the new keypair is installed in our EC2 account, we will again list all our keypairs.
irb> ec2.describe_keypairs => [{ :name=>"ec2-private-key", :fingerprint=>"5e:93:4c:dd:c3:cb:a6:32:25:6a:aa:9a:2d:d7:42:d6:4e:13:4d:85" }]
Now that we have created a keypair and saved the private key component as a local file, we will be able to work with the access control mechanism used by the Amazon public AMIs.
The DeleteKeyPair operation shown in Table 5-6 removes a named keypair from your EC2 account. There will be times when you want to delete a keypair, such as if you lose your private key data file, or if you are security-conscious and disciplined enough to change your keys at regular intervals.
Table 5-6. DeleteKeyPair request parameters
Parameter Name | Value | Required? |
---|---|---|
Action | DeleteKeyPair | Yes |
KeyName | The name of the keypair to delete | Yes |
Here is an XML document returned by the operation. It is very simple, containing only a return element with a Boolean value that indicates whether or not the keypair was successfully deleted.
<DeleteKeyPairResponse xmlns='http://ec2.amazonaws.com/doc/2007-08-29/'> <return>true</return> </DeleteKeyPairResponse>
Example 5-4 defines a method that
deletes a keypair from your account. The method always returns
true
, unless the operation fails
with an error.
Example 5-4. Delete keypair: EC2.rb
def delete_keypair(keyname) parameters = build_query_params(API_VERSION, SIGNATURE_VERSION, { 'Action' => 'DeleteKeyPair', 'KeyName' => keyname, }) response = do_query(HTTP_METHOD, ENDPOINT_URI, parameters) return true end
Here is the command to delete a keypair that is no longer needed:
irb> ec2.delete_keypair('keypair-name') => true