Cloud computing with PHP, Part 2: Using Amazon EC2 with the Zend Framework

Using virtual machines with the Zend Framework

The Zend Framework contains several classes that make using cloud-based storage services easy. Part 1 of this "Cloud computing with PHP" series looks at using Zend classes with Amazon's S3 cloud storage service. This article covers the Zend classes that make it easy to work with virtual machines in Amazon's Elastic Compute Cloud (EC2).

Doug Tidwell, Cloud Computing Evangelist, Software Group Strategy, EMC

Doug TidwellDoug Tidwell is a senior software engineer in IBM’s Emerging Technology group. He was a speaker at the first XML conference in 1997 and has been working with markup languages, Web services and SOA technologies for many years. His job as a technology evangelist is to explain the standards and technologies behind cloud computing and to help customers integrate them into their overall business architectures and strategies. He is the author of many articles here at developerWorks and is the author of O’Reilly’s book on XSLT (ISBN 0-596-52721-7), a copy of which makes a thoughtful gift for all occasions. He lives in Chapel Hill, N.C., with his wife, daughter, and dog.


developerWorks Contributing author
        level

13 October 2009

Also available in Russian Portuguese

One of the most exciting aspects of cloud computing is the ability to run virtual machines in a cloud provider's data center. Amazon's EC2 gives architects and developers the ability to create virtual-machine images and run them in the cloud. You can create custom images that contain the software and data your organization needs, then you can run as many (or as few) copies of those virtual machines as you need. This article looks at some of the classes in the Zend Framework that make it easy to work with EC2 images.

Because EC2 is a sophisticated product, the EC2 features are split into two articles. This article covers the basics of working with images, starting and stopping instances of those images, and the key pairs and security groups that let you secure the instances. Topics such as Elastic IP addresses, Amazon CloudWatch monitoring and Elastic Block Storage are covered in another article.

Getting started

In this article, you'll see how to do several important EC2 tasks:

  • Find the Amazon Machine Images (AMIs) that belong to you
  • Work with key pairs, including finding the ones that belong to you, creating new ones and deleting old ones
  • Work with security groups, including controlling access to the TCP/IP ports of your virtual machine
  • Start an instance of an AMI
  • Reboot or terminate an instance of an AMI

As with all of the articles in this series, you'll get the most out of the examples if you have the Zend Framework installed before you begin. If you don't have the Zend Framework installed already, download and install the full package from zend.com/community/downloads. This installs the Zend Framework, PHP, and the Apache Web server on your machine. When the install is complete, go to http://localhost/ZendServer/ in your browser. See the Zend Framework installation documentation for all the details. If you can log in to the ZendServer console, you're all set.

You'll also need to set up an account with Amazon (see Resources).

The examples use the PHP Credentials class created in Part 1. That class manages the credentials for your Amazon account. The credentials are stored in an .ini file.

Listing 1. Storing credentials in a PHP .ini file
; Configuration file to hold secret keys, account numbers and other useful
; strings for Amazon and other cloud accounts.

[amazon]
accessKey=0123456789ABCDEFGHIJ
secretKey=0123456789abcdefghiABCDEFGHI1234567890AB
ownerId=123456789012

In this article, the ownerId value is crucial. There are thousands of AMIs available at Amazon, most of which are not available to the public. You'll use the ownerId value to filter out the AMIs you don't have authorization to access. Your owner ID is your 12-digit Amazon Web Services account number. To find it, go to http://aws.amazon.com/, log into your account, then select the Your Account menu and the Security Credentials item.

Figure 1. Finding your security credentials
How to find your security credentials

Scroll down to the middle of the page, and you'll see your AWS account ID.

Figure 2. Your AWS account ID
Scroll down and your AWS account ID appears

When you put your account ID into the cloud.ini file, don't use the dashes. If this were your ID, you would add ownerId=999999999999 to the .ini file.

Notes:

  • Whenever you start an instance of an AMI, you're spending money. Even though EC2's charges are reasonable (some instances are as little as 10 cents an hour), you don't want to just start an AMI and leave it running indefinitely. Be sure to terminate your instances when you're through with them.
  • The examples in this article assume you have created at least one AMI. If you haven't, you can use the special value ownerId=amazon. This returns a list of more than 100 AMIs, but it will give you some images you can launch.
  • If you're using ownerId=amazon, be aware that some of those instances can be quite expensive. IBM® has a number of AMIs that feature enterprise-class software products. If you're using one of those AMIs, you're paying a licensing fee as long as the instance is running. If all you want to do is experiment with EC2, an AMI that's light on features is likely light on price, as well.

About the sample application

The sample application consists of six PHP pages:

  1. amis.php— Lists all the AMIs owned by your account. You can start an instance of any AMI just by clicking a button.
  2. manage_instance.php— Handles requests from amis.php to create, reboot, or terminate an instance of an AMI.
  3. instances.php— Lists all running instances controlled by your account. Reboot or terminate any running instance just by clicking a button.
  4. keypairs.php— Lists all public/private key pairs you've created. Examine existing key pairs, create new ones, and delete existing ones.
  5. securitygroups.php— Lists all security groups defined for your account. Click a button to see the details of a particular security group.
  6. securitygroup.php— Lists all permissions for a particular security group. Add new permissions or delete existing ones.

Working with AMIs

To get things started, create a Zend_Service_Amazon_Ec2_Image object.

Listing 2. Creating a Zend_Service_Amazon_Ec2_Image object
require_once 'Credentials.php';

$creds = new Credentials;
$aws_key = $creds->getCredential('amazon', 'accessKey');
$aws_secret = $creds->getCredential('amazon', 'secretKey');

$ec2_img = new Zend_Service_Amazon_Ec2_Image($aws_key, $aws_secret);

This object lets you work with the AMIs stored at Amazon. The Zend_Service_Amazon_Ec2_Image->describe() method returns the metadata about an AMI. With no argument, it returns the details of every AMI stored at Amazon. That creates a large amount of data, most of which isn't useful to you. Unfortunately, the current incarnation of the Zend Framework doesn't support the version of describe() that lets you specify an ownerId. That means the first time you want to list the AMIs relevant for the user, you have to sort through the entire list to find the ones you want. To get around this, you can cache the IDs of the AMIs you want to display. The processing logic is that if a file listing the relevant AMIs exists, you simply read that file and call describe('ami-id') for each ID. If the file doesn't exist, you have to create it; the PHP file also gives the user the option to refresh the list of AMIs if he wants. That will take a while, but it ensures that the list is consistent with the data at Amazon.

The code to create the list of AMIs looks like Listing 3.

Listing 3. Caching the IDs of AMIs
if (!file_exists('amis.text') || 
    array_key_exists('refresh', $_POST)) {
  $imageData = $ec2_img->describe();
  $amiFile = fopen('amis.text', 'w');
  $myOwnerId = $creds->getCredential('amazon', 'ownerId');
  foreach ($imageData as $nextImage) {
    if ($nextImage['imageOwnerId'] === $myOwnerId)
      fwrite($amiFile, $nextImage['imageId']."\n");
  }
  fclose($amiFile);
}

The code here checks to see if it needs to sort through all of the AMIs in Amazon's catalog. If the file doesn't exist or there is an explicit request to refresh it, the code gets the list of details for all the AMIs at Amazon. The goal here is to create a table of AMIs.

Figure 3. A listing of AMIs
Create a table of AMIs

Each row in the table features the ID of the AMI, its location, its status, and its platform. In addition, there are two drop-down lists that allow you to choose a key pair and a security group. The final column in the table is a push button that lets you start the AMI with the currently selected key pair and security group. To create the table, use the PHP file() method to read the list of AMIs into an array. For each item in the array, create a table row that contains all the relevant information. Following is the code that creates the rows of the table.

Listing 4. Generating the rows of the table of AMIs

Click to see code listing

Listing 4. Generating the rows of the table of AMIs

$amiList = file('amis.text', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($amiList as $nextAmi) {
  echo "<tr>";

  // Each table row is a form. Clicking the 'Start this image' button submits
  // the data from the current row, which tells instances.php which image to 
  // start and which key pair and security group to use. 
  echo "<form action='manage_instance.php' method='POST' ";  echo " onsubmit='return confirm(\"Start image '".$nextAmi."'?\")'>";  echo "<input type='hidden' name='imagetostart' value='".$nextAmi."'/>";

  echo "<td>".$nextAmi."</td>";

  // Get the description of this AMI
  $imageData = $ec2_img->describe(array('imageId' => $nextAmi));
  echo "<td>".$imageData[0]['imageLocation']."</td>";
  echo "<td>".$imageData[0]['imageState']."</td>";

  // If the platform is blank, assume it's some flavor of Linux
  if (strlen($imageData[0]['platform']))
    echo "<td>".$imageData[0]['platform']."</td>";
  else
    echo "<td>Linux</td>";

  // Insert the lists of key pairs and security groups
  echo "<td>".$keypairsForm."</td>";
  echo "<td>".$secGroupsForm."</td>";

  // If the state of the image is "available," insert a start button.
  if ($imageData[0]['imageState'] == 'available')
    echo "<td><input type='submit' value='Start this image'/></td>";
  else
    echo "<td>Not available</td>";

  echo "</form>";
  echo "</tr>";
}

Each table row must be in a separate <form> element so the browser knows which key pair and security group is selected when the Start button is clicked. As you iterate through the array of AMI IDs, calling describe() with a specific ID returns the description of that particular AMI. The rest of the table row consists of the location of the image (where the image is stored in your S3 account), the status of the image, a drop-down list of key pairs, a drop-down list of security groups, and a Start this image button. The Start button is displayed only if the status of the image is available. The form controls for each row of the table look like Figure 4.

Figure 4. Form controls for each row of the table
There are form controls for each row of the table

The lists of key pairs and security groups are created outside the loop.

Listing 4. Generating the lists of key pairs and security groups

Click to see code listing

Listing 4. Generating the lists of key pairs and security groups

$ec2_kp = new Zend_Service_Amazon_Ec2_Keypair($aws_key, $aws_secret);$keypairs = $ec2_kp->describe();
foreach ($keypairs as $key => $row) {
  $keyName[$key]  = $row['keyName'];
  $keyFingerprint[$key] = $row['keyFingerprint'];
}
array_multisort($keyName, $keypairs);

$keypairsList = "<select name='keypair'>";
foreach ($keypairs as $nextKeypair) {
  $keypairsList = $keypairsList."<option value='".$nextKeypair['keyName']."'>";
  $keypairsList = $keypairsList.$nextKeypair['keyName']."</option>";
}
$keypairsList = $keypairsList."</select>";

$secGroupsList = "<select name='secgroup'>";
$ec2_sg = new Zend_Service_Amazon_Ec2_Securitygroups($aws_key, $aws_secret);$securityGroups = $ec2_sg->describe();
foreach ($securityGroups as $key => $row) {
  $groupName[$key]  = $row['groupName'];
  $groupDescription[$key] = $row['groupDescription'];
}
array_multisort($groupName, $securityGroups);

foreach ($securityGroups as $nextGroup) {
  $secGroupsList = $secGroupsList."<option value='".$nextGroup['groupName']."'>";
  $secGroupsList = $secGroupsList.$nextGroup['groupName']."</option>";
}
$secGroupsList = $secGroupsList."</select>";

Each list is a string that contains the HTML <select> and <option> elements for the key pairs and security groups. (Notice that the code uses the PHP array_multisort to sort the names of key pairs and security groups.) Because starting an instance of an AMI means charges start to accrue in your AWS account, a confirmation dialog appears.

Figure 5. Starting an instance must be confirmed
Confirm that you started an instance

In this screen capture, the key pair id_rsa-gsg-keypair and the security group default are selected.


Working with instances

When you click the OK button to launch an instance, the information about the new instance is sent to the manage_instance.php page. The first step in working with instances is to create a Zend_Service_Amazon_Ec2_Instance object.

Listing 6. Creating a Zend_Service_Amazon_Ec2_Instance object
$creds = new Credentials;
$aws_key = $creds->getCredential('amazon', 'accessKey');
$aws_secret = $creds->getCredential('amazon', 'secretKey');

$ec2_instance = new Zend_Service_Amazon_Ec2_Instance($aws_key, $aws_secret);

With the object created, you can now start an instance of the requested image.

Listing 7. Creating a new instance

Click to see code listing

Listing 7. Creating a new instance

if (strlen($imageIdToStart) && strlen($keypairToStart) && strlen($secgroupToStart)) {
  $startResults = $ec2_instance->run(array('imageId' => $imageIdToStart,                                            'keyName' => $keypairToStart,                                           'securityGroup' => $secgroupToStart));

  echo "<p><b>Status of instance ".$startResults['instances'][0]['instanceId'].": ";
  echo $startResults['instances'][0]['instanceState']['name'].".</b></p><hr/>";
}

Note: You can specify other parameters when you start an instance, such as the number of instances, the size of the instance (how much memory and CPU power it has) and the geographic region where the instance should be hosted. To keep the examples simple, all requests to start an instance only start one instance, that instance has the smallest size (specified as m1.small in the actual request to Amazon) and is hosted in the default region for your account.

This code takes the ID of the AMI and the names of the key pair and security group selected in the form on the amis.php page. The page then displays the status of the new instance.

Figure 6. The status of a newly created instance
The page then displays the status of the new instance.

Clicking OK sends you to the instances.php page. That page displays the current list of instances.

Figure 7. The list of instances
That page displays the current list of instances

Note: The values for the image ID, key name, and security group are passed to the manage_instance.php page via HTTP POST. Using an intermediate page to create the new instance means you can refresh the instances.php page without inadvertently starting another instance. The manage_instance.php page also handles requests to reboot or terminate an image.

When you start an instance, its initial state is pending. That means the machine image is booting, but it isn't up and running just yet. Also notice that Amazon has assigned the instance ID i-373c995f to this instance; that ID is how you'll work with the instance later. Clicking the "Refresh list of instances" button updates the display.

Figure 8. Updated list of instances
Clicking the Refresh list of instances button updates the display

In the updated listing, the status of the instance is now running. Notice that Amazon has assigned the DNS name ec2-75-101-220-48.compute-1.amazonaws.com to this instance. That means that if you have enabled this instance for Web access, and the instance is running a Web server on port 80, you could visit http://ec2-75-101-220-48.compute-1.amazonaws.com to see the home page of the instance. (You can enable port 80 by creating a rule in a security group; more on security groups in a minute.)

To generate the rows of the instances table, you use the Zend_Service_Amazon_Ec2_Instance->describe() method.

Listing 8. Creating the table of instances
$instanceDetails = $ec2_instance->describe();
if (count($instanceDetails['instances'])) {
  echo "<table border='1' cellpadding='5'>";
  echo "<tr style='font-weight: bold;'>";
  echo "<td>Status</td><td>Instance ID</td><td>AMI ID</td><td>Public DNS name</td>";
  echo "<td>Security Group</td><td>Key Pair</td><td>&nbsp;</td><td>&nbsp;</td>";
  echo "</tr>";

  foreach ($instanceDetails['instances'] as $nextInstance) {
    echo "<tr>";
    $status = $nextInstance['instanceState']['name'];
    // Make the cell background green if the status is running.
    if (strripos('running', $status) === 0)
      echo "<td style='background-color: green; color: white; font-weight: bold;'>";

    // Make the cell background yellow if the status is shutting-down.
    else if (strripos('shutting-down', $status) === 0)
      echo "<td style='background-color: yellow; font-weight: bold;'>";

    // Make the cell background red if the status is terminated.
    else if (strripos('terminated', $status) === 0)
      echo "<td style='background-color: red; color: white; font-weight: bold;'>";

    // Make the cell background purple for any other status.
    else
      echo "<td style='background-color: purple; color: white; font-weight: bold;'>";
    echo $status."</td>";

    echo "<td>".$nextInstance['instanceId']."</td>";
    echo "<td>".$nextInstance['imageId']."</td>";
    echo "<td>".$nextInstance['dnsName']."</td>";
    echo "<td><a href='securitygroup.php?groupname=".$nextInstance['groupSet'][0];
    echo "&previous=instances'>".$nextInstance['groupSet'][0]."</a></td>";
    echo "<td>".$nextInstance['keyName']."</td>";
    . . .

The code first checks to see if there are any instances. Assuming there is at least one, each instance is described in a row of the table. The instance ID and AMI ID are displayed, along with the public DNS name, security group, and key pair. As with the amis.php page, each row of the table is a form.

Figure 9. Form controls to manage instances
Each row of the table is a form

This table lists three instances. The status of two of the items is running, so there are Reboot and Terminate buttons for those instances. For an instance in any other state (pending, shutting-down or terminated), the buttons don't appear.

Listing 9. Creating the form controls for the table of instances
if (strripos('running', $status) === false) {
      echo "<i>[Can't reboot]</i>"; 
    }
    else {
     echo "<form action='manage_instance.php' method='POST' ";
      echo "onsubmit='return confirm(\"Reboot instance &apos;";
      echo $nextInstance['instanceId']."&apos;?\")'>";
      echo "<input type='hidden' name='imagetoreboot' value='";
      echo $nextInstance['instanceId']."'/>";
      echo "<input type='submit' value='Reboot'/></form>";
    }
    echo "</td>";

    echo "<td style='text-align: center;'>";
    if (strripos('running', $status) === false) {
      echo "<i>[Can't terminate]</i>"; 
    }
    else {
     echo "<form action='manage_instance.php' method='POST' ";
      echo "onsubmit='return confirm(\"Terminate instance &apos;";
      echo $nextInstance['instanceId']."&apos;?\")'>";
      echo "<input type='hidden' name='imagetoterminate' value='";
      echo $nextInstance['instanceId']."'/>";
      echo "<input type='submit' value='Terminate'/></form>";
    }
    echo "</td>";

If the instance is running, the code creates buttons to reboot or terminate the instance. Clicking those buttons leads to a confirmation dialog.

Figure 10. Terminating or rebooting an instance must be confirmed
Code creates buttons to reboot or terminate the instance, and clicking those buttons leads to a confirmation dialog

Clicking OK takes you to the manage_instance.php page again.

Figure 11. The status of a newly terminated instance
Clicking OK takes you to the manage_instance.php page

Returning to the list of instances shows the updated status.

Figure 12. The updated list of instances
Returning to the list of instances shows the updated status

At some point, the status of the instance will change to terminated.

Figure 13. A terminated instance
The status of the instance will change to terminated

Note: At some point after an instance is terminated, EC2 deletes all the data associated with it. This usually happens within an hour. Once the data about the instance is deleted, it will no longer show up in the results.


Working with key pairs

A key pair is a pair of public and private keys used to authenticate a user. When you start an instance, you specify a key pair that can be used to access the instance when it's up and running. Amazon associates the public key with that instance. Depending on how the AMI is configured, you might not be able to access the instance without the private key. The file keypairs.php displays all of the key pairs defined for your account.

Figure 14. The list of key pairs
The file keypairs.php displays all the key pairs defined for your account

For each key pair, the table displays its name and fingerprint.

Listing 10. Creating the table of key pairs

Click to see code listing

Listing 10. Creating the table of key pairs

$creds = new Credentials;
$ec2_kp = new Zend_Service_Amazon_Ec2_Keypair>                ($creds->getCredential('amazon', 'accessKey'),                  $creds->getCredential('amazon', 'secretKey'));$keypairs = $ec2_kp->describe();
if (count($keypairs)) {
  echo "<p>Here are the key pairs defined for your account:</p>";

  echo "<table border='1' cellpadding='5'>";
  echo "<tr style='font-weight: bold;'>";
  echo "<td>Key Pair Name</td>";
  echo "<td>Fingerprint</td>";
  echo "<td>&nbsp;</td>";
  echo "</tr>";

  foreach ($keypairs as $key => $row) {
    $keyName[$key] = $row['keyName'];
    $keyFingerprint[$key] = $row['keyFingerprint'];
  }
  array_multisort($keyName, $keypairs);

  foreach ($keypairs as $nextKeypair) {
    echo "<tr>";
    echo "<td>&nbsp;".$nextKeypair['keyName']."</td>";
    echo "<td>&nbsp;".$nextKeypair['keyFingerprint']."</td>";
    echo "<td>";
    echo "<form action='".$_SERVER['PHP_SELF']."' method='post'";
    echo " onsubmit='return confirm(\"Do you really want to delete the key pair ";
    echo "&apos;".$nextKeypair['keyName']."&apos;?\");'>";
    echo "<input type='hidden' name='keypairtodelete' ";
    echo "value='".$nextKeypair['keyName']."'/>";
    echo "<input type='submit' value='Delete'/>";
    echo "</form>";
    echo "</td>";
    echo "</tr>";
  }
  echo "</table>";
}
else
  echo "<p>Sorry, your account doesn't have any key pairs defined.</p>";

The code creates a Zend_Service_Amazon_Ec2_Keypair object, then calls its describe() method to get the details of all the key pairs defined for your account. For each key pair, AWS returns the name of the key pair and its fingerprint. Each row of the table also contains an HTML form with a Delete button. The form requires that you confirm any delete request.

Figure 15. Deleting a key pair must be confirmed
The form requires that you confirm any delete request

The delete() method makes deleting the key pair straightforward.

Listing 11. Deleting a key pair
if (strlen($keypairToDelete)) {
  echo "<p><b>";
  try {
    $ec2_kp->delete($keypairToDelete);
    echo "The key pair named ".$keypairToDelete." was deleted successfully.";
  }
  catch (Zend_Service_Amazon_Ec2_Exception $ec2e) {
    echo "The key pair could not be deleted. The error message from Amazon is:";
    echo "<br/><br/><i>".$ec2e->getMessage()."</i>";
  }
  echo "</b></p><hr/><br/>";
}

The keypairs.php page also contains a form to create a new key pair. The only parameter is a name for the new key pair.

Figure 16. Form to create a new key pair
The keypairs.php page also contains a form to create a new key pair

Creating the key pair is as simple as calling the create() method.

Listing 12. Creating the table of rules in the security group
foreach ($list[0]['ipPermissions'] as $key => $row) {
  $ipProtocol[$key] = $row['ipProtocol'];
  $startingPort[$key] = $row['fromPort'];
  $toPort[$key] = $row['toPort'];
  $ipRanges[$key] = $row['ipRanges'];
}
array_multisort($startingPort, SORT_ASC, $list[0]['ipPermissions']);

foreach($list[0]['ipPermissions'] as $rule) {
. . .
  echo "<tr>\n";
  echo "<td>&nbsp;".strtoupper($rule['ipProtocol'])."</td>\n";
  echo "<td>&nbsp;".$rule['fromPort']."</td>\n";
  echo "<td>&nbsp;".$rule['toPort']."</td>\n";

  if ((is_array($rule['ipRanges']) && count($rule['ipRanges']) == 0) ||
      (strlen($rule['ipRanges']) == 0)) {
    echo "<td>&nbsp;---</td>\n";
    echo "<td>&nbsp;</td>\n";
  }
  else {
    echo "<td>&nbsp;".$rule['ipRanges']."</td>\n";
    echo "<td style='text-align: center;'>";
    echo "<form action='".$_SERVER['PHP_SELF']."' method='POST'";
    echo " onsubmit='return confirm(\"Do you really want to revoke access for IP ";
    echo " address range '".$rule['ipRanges']."' on this port?\");'>";
    echo "<input type='hidden' name='groupname' value='".$groupName."'/>";
    echo "<input type='hidden' name='fromport' value='".$rule['fromPort']."'/>";
    echo "<input type='hidden' name='toport' value='".$rule['toPort']."'/>";
    echo "<input type='hidden' name='iptorevoke' value='".$rule['ipRanges']."'/>";
    echo "<input type='submit' value='Revoke'/></form></td>";
  }

The response from Amazon contains the private key for the new key pair. The PHP page displays that information with a message to the user.

Figure 17. A newly created key pair
The PHP page displays that information with a message to the user

The user must copy and paste the private key information into a file. If this information is lost, it cannot be recreated. (Creating a more elegant solution that automatically writes the key pair data to a file is left as an exercise for the reader.)


Working with security groups

The form that lets you create new instances requires you to specify a machine image, a key pair, and a security group. A security group lets you define how other machines can access an instance. For example, if you wanted to use a running instance as a Web server, you would define a rule in your security group that connection requests on port 80 should be accepted from any machine. For each rule, you specify a protocol, a range of ports, and a range of IP addresses allowed to access those ports via that protocol. The display of a security group looks like Figure 18.

Figure 18. A security group
The display of a security group

The table here lists the security policies for the security group default. At the bottom of the screen capture, you can see that the policy allows hosts 67.93.2.12 and 76.182.90.9 to access port 80. Port 3389, typically used for Remote Desktop sessions with Windows VMs, is authorized for IP address 76.182.90.9, as well. You can specify a range of ports; in this example, the rules for ports 21, 22, and 23 could have been defined with a single rule. Each row of the table contains a form to delete an individual rule. The code to create the table is relatively straightforward.

Listing 13. Creating the table of rules in the security group
foreach ($list[0]['ipPermissions'] as $key => $row) {
  $ipProtocol[$key] = $row['ipProtocol'];
  $startingPort[$key] = $row['fromPort'];
  $toPort[$key] = $row['toPort'];
  $ipRanges[$key] = $row['ipRanges'];
}
array_multisort($startingPort, SORT_ASC, $list[0]['ipPermissions']);

foreach($list[0]['ipPermissions'] as $rule) {
. . .
  echo "<tr>\n";
  echo "<td>&nbsp;".strtoupper($rule['ipProtocol'])."</td>\n";
  echo "<td>&nbsp;".$rule['fromPort']."</td>\n";
  echo "<td>&nbsp;".$rule['toPort']."</td>\n";

  if ((is_array($rule['ipRanges']) && count($rule['ipRanges']) == 0) ||
      (strlen($rule['ipRanges']) == 0)) {
    echo "<td>&nbsp;---</td>\n";
    echo "<td>&nbsp;</td>\n";
  }
  else {
    echo "<td>&nbsp;".$rule['ipRanges']."</td>\n";
    if (strripos('0.0.0.0', $rule['ipRanges'][0]) === false) {
      echo "<td style='text-align: center;'>";
      echo "<form action='".$_SERVER['PHP_SELF']."' method='POST'";
      echo " onsubmit='return confirm(\"Do you really want to revoke access for IP ";
      echo " address range '".$rule['ipRanges']."' on this port?\");'>";
      echo "<input type='hidden' name='groupname' value='".$groupName."'/>";
      echo "<input type='hidden' name='fromport' value='".$rule['fromPort']."'/>";
      echo "<input type='hidden' name='toport' value='".$rule['toPort']."'/>";
      echo "<input type='hidden' name='iptorevoke' value='".$rule['ipRanges']."'/>";
      echo "<input type='submit' value='Revoke'/></form></td>";
    }
    else
    echo "<td>&nbsp;</td>";

Before building the rows of the table, the code uses the array_multisort function to sort the array of rules by the starting port number of each rule. Displaying the name of the protocol, the starting port, and the ending port is straightforward. The last two columns in the table are the IP address range and a button to revoke a permission. In some cases, the IP address range is returned as an empty array or an empty string. If that is the case, the table contains an empty cell instead of a Revoke button.

The last row of the table contains a form to create a new rule.

Figure 19. The form to create a new rule
The last row of the table contains a form to create a new rule

Amazon requires that the IP address be specified in Classless Inter-Domain Routing (CIDR) notation. CIDR notation is an IP address, followed by a slash and a number from 0 to 32. The number after the slash specifies how many bits of the IP address are in the prefix. For example, the CIDR notation 9.67.0.0/16 matches any address that starts with 9.67. The IP range 0.0.0.0/0 matches any IP address, while the range 76.182.90.9/32 matches only the IP address 76.182.90.9. If the user enters an IP address that is not in CIDR notation, the PHP page adds /32 to the address. (See Resources to learn more about CIDR.) The code to authorize an IP range is straightforward.

Listing 14. Authorizing a new IP address range
if (strlen($ipToAuthorize) && strripos($ipToAuthorize, '/') === false)
  $ipToAuthorize = $ipToAuthorize."/32";
  . . .
  try {
    $ec2_sg->authorizeIp($groupName, $protocol, $fromPort, $toPort, $ipToAuthorize);
    echo "IP address ".$ipToAuthorize." is now authorized.";
  }
  catch (Zend_Service_Amazon_Ec2_Exception $ec2e) {
     // We get an exception if this IP address is already authorized, that's okay. 
    if ($ec2e->getErrorCode() == 'InvalidPermission.Duplicate')
      echo "IP address range ".$ipToAuthorize." is already authorized.";

    // If the IP address range isn't formatted correctly, tell the user. 
    else if ($ec2e->getErrorCode() == 'InvalidPermission.Malformed')
      echo "The value ".$ipToAuthorize." is not a valid IP address range.";

    //  For all other exceptions, we just dump the exception to the screen.
    else {
      echo "<i>Something really bad happened:</i><br/><pre>";
      var_dump($ec2e);
      echo "</pre>";
    }
  }

Calling the authorizeIp method creates a new rule for the security group. The code here handles two common exceptions: The Zend Framework throws an exception if you authorize an IP address range that is already authorized, and a second exception occurs when the IP address range is malformed. For any other exceptions, the code just dumps the error message to the screen.

Note: A recently fixed bug in the Zend Framework returns an array of listings when more than one address range is authorized for a particular port. If your version of the Zend Framework doesn't have this fix, you will only see one address for each port. All of the authorizations are part of the security group stored in the Amazon cloud; they just don't show up in the results from Zend. The sample code has logic to handle this gracefully.

Finally, clicking the Revoke button next to a rule requires confirmation from the user.

Figure 20. Deleting a security rule must be confirmed
Clicking the Revoke button next to a rule requires confirmation from the user

Deleting a security rule is done through the revokeIp() method.


Summary

This article presented a set of pages to work with virtual machine images, running instances of those images, and the key pairs and security groups that allow you to manage them. Once again, the Zend Framework makes it easy to interact with the cloud. The next installment of this series will show you how to use the Zend Framework to work with more advanced EC2 features, such as Elastic IP addresses, Elastic Block Storage and CloudWatch.


Download

DescriptionNameSize
Sample codeos-php-cloud2-source.zip13KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Cloud computing, Web development
ArticleID=433538
ArticleTitle=Cloud computing with PHP, Part 2: Using Amazon EC2 with the Zend Framework
publish-date=10132009