I’ve decided to change the name of this project from EC2 Console to EC2 Bootstrapper. Why? Well, there are several “EC2 Console” projects out there. But we’re addressing a problem that no one else has – how to remotely (and securely) install software to a Windows machine on EC2 without having to logon to that machine and do it manually (for example, emailing the package to yourself, or posting it somewhere on the internet). That’s the bootstrapping issue, and right now, it’s the biggest hurdle for encouraging ASP.NET developer adoption of that platform, and making this kind of deployment easier than it is on Linux (where the most common approach would just be a question of bootstrapping SSH).
We’ll still have the usual management console basics, including listing, starting, and stopping machines. But “one-click” EC2 Windows application bootstrapping is the major contribution that this is making to the world.
To that end, the first code drop is available here (Codeplex). That drop includes the following things.
First, there’s a script that will run once (on first boot, then remove itself) to initialize the EC2 AMI (Amazon Machine Instance) with a self-signed server SSL certificate. The script uses SelfSSL.exe from the IIS 6.0 resource kit. That tool turns out the be a god-send: creating a self-signed certificate is easy enough with makecert.exe, but the interfaces for automatically configuring IIS with proper SSL defaults aren’t well documented.
The script is part of the solution to another problem as well. Namely, once the new AMI is running, and a new SSL cert is installed, how do we download that cert so the client can trust it? There are probably a number of ways to do that, but a convenient one is to export the cert (the public part; not the private key) on the server (using certutil.exe), copy the cert file to a known location in the default web site, and let the client grab it via HTTP.
Interesting aside about getting what was originally a .cer file to be served by IIS. In short, I couldn’t get it to work (i.e., IIS acted like the file didn’t exist, even though you could see it in the virtual dir listing in the MMC snap-in), even after I realized that there was no registered MIME handler for that extension. But I noticed that there is a handler for .crt, which is equivalent to .cer from a PKI plumbing perspective. Using the .crt extension fixed things. Don’t know why I couldn’t get a .cer MIME handler to work.
In addition to the script, the Codeplex drop includes some fun code. The most important piece is a web service which will be configured to listen on a high port, require Windows authentication, and allow (maybe require; not sure yet) SSL. Clients that can authenticate to the service will be able to upload an MSI file and have it installed on the AMI.
In a nutshell, creating an AMI that automatically starts up with the script and secure web service is our EC2 bootstrapping story.
A few notes about that web service. First, since we can’t count on the Network Service account being able to install an MSI, the service end point routine starts by impersonating the caller. Then it reads in the encoded MSI file data, decodes it, writes it to a temporary file, runs msiexec.exe in silent mode, waits for the result, and returns it (after un-impersonating). Even though we’re impersonating and requiring valid credentials, a good enhancement would probably be to explicitly check that the caller is a member of the local administrators group.
In addition to the service, there’s a test client. I ran into two challenges with that. The first was that, since this code is checked in publicly, I didn’t want to hardcode the admin password for my local test server. And, lamely, .NET doesn’t include a built-in credential prompt dialog. Instead, you have to either write your own or P/Invoke CredUI. I chose the latter approach, but found a P/Invoke signature for CredUIPromptForCredentials on the web that had at least a couple of bugs, not the least of which that it didn’t explicitly specify whether it was using the Ansi or Unicode version of that Win32 API. I had the quickest luck getting the “A” version to work – shouldn’t matter unless you’ve got a user name or password with characters that don’t round-trip correctly (which would be truly brave).
One more comment about CredUIPromptForCredentialsA (or W). The user name and password parameters are actually “In” as well as “Out”. In other words, the API lets the caller pre-populate those fields. Thus, if the marshaller finds garbage in the input string buffers, you’ll see weird characters in the edit fields when the dialog pops up. This is another issue not addressed by P/Invoke wrappers you may find on the web. The fix is to start with a String of the proper length initialized to NULL chars (presumably just at the beginning, although I didn’t try that potential optimization), then build the in/out StringBuilder from that.
The second test client challenge was in figuring out how to get Visual Studio to let me add a web service reference to a console application. In other words, I didn’t want to have to run wsdl.exe myself! The trick was to run the web service project without debugging, so I could leave it running while adjusting the solution/project settings for the console application. Then, under the console project in Solution Explorer, right-click on References | Add service reference | Advanced | Add web reference.