Mike Richardson in Packer 12 minutes

Packer, Windows, AWS and SSL Howto

Packer, Windows, and AWS. The default Windows configuration that AWS launches is not compatible with Packer out of the box. In this post, I’ll explain what’s going on and how you can adapt your Packer configuration to solve the problem.

Most of the following code comes from James Nugget’s excellent post Windows AMIs without the tears and Hippie Hacker’s as yet uncommitted push request to use the RDP SSL certificate. The smarts behind this solution is all theirs, any mistakes are mine.

Packer is a great way to pre-fabricate custom server images. You can use it to boot a server, configure it, and save that configuration as a template for future use.

Packer and AWS work together really well. Packer can fully integrate with AWS to launch instances and save Amazon Machine Images (AMIs) for future use. Generally speaking, once Packer launches a template server, it can then connect to it remotely and execute scripts to configure that server however you wish.

The Problem

By default, Windows AMI instances ship with a handy tool for remotely administering servers via a command line, called WinRM. WinRM, however, has a pretty un-handy starting config: it only allows Kerberos authentication and doesn’t use SSL to encrypt traffic.

The result? Packer can happily launch a Windows AMI for you, but it will never be able to connect to it remotely in order to run your configuration scripts.

Part One: Packer with WinRM

In this scenario, we use Packer to launch a source AMI (the official Amazon Windows images), run some scripts on it to configure it just how we want it, and then bake it into a new AMI.

Whenever Packer launches a new image, it can use either SSH or WinRM to configure that image. The problem is that Windows doesn’t include SSH, and WinRM won’t work out of the box without Kerberos.

The problem we have now is chicken and egg. As we’ve established, Packer can’t talk to the instance by default, so we can’t access the server once it boots in order to run scripts that could fix our problem.

EC2Config to the Rescue

Luckily, AWS have developed their own utility called EC2Config that allows us to inject scripts that will be run on the box at first boot.

These scripts run with Administrator privileges and without the need for remote access to kick them off. With EC2Config, we will use one script to configure WinRM as follows:

  • Enable SSL encryption of our WinRM session.
  • Turn off unencrypted (non-SSL) authentication
  • Allow us to authenticate using the AWS-defined ‘Administrator’ password.

Choosing the SSL Certificate

There are two ways we can configure the SSL component. Firstly, we could create and define our own certificate (either using a valid Certificate Authority, or self-signed. Or - with much less difficulty - we can re-purpose the Remote Desktop self-signed certificate that Windows creates for itself.

In this case, we’re going to keep it simple and use the RDP certificate. The powershell is a little bit too long to share here, but you can check out the whole thing here.

Part 2: Configuring Packer

Packer uses a JSON file to define the settings you want your image to be launched with. I’ve prepared a sample JSON file that you should be able to just copy-and-paste easily enough, but you’ll need to adapt this to your own environment by using the appropriate keys, VPC and Subnet and so on.

You can access the whole file here.

Briefly, let me break down what this file is telling Packer to do. If you’re already familiar with Packer, you can safely skip this part and just read the JSON directly.

Access Creds

Naturally, Packer will need security credentials to access your server. Here, we’re telling Packer to use environment variables for the EC2 keypair.

"access_key": "***your access key here***",
"secret_key": "***your access key here***",

Source Image Details

These settings tell Amazon how to configure our source image. That is, the box we’ll be configuring once, and saving for future use. These settings won’t affect the servers you will launch with your final template.

These settings won’t work for you by default. You need to assign your own VPC and Subnet ID at least. The source_ami you see here is the current Windows Server 2012 R2 image from Amazon at the time of writing (for the Sydney region).

Basically, you need to validate all of these settings and make sure they are appropriate to your environment.

"region": "ap-southeast-2",
"vpc_id": "vpc-ac5b32j3",
"subnet_id": "subnet-2057e605",
"source_ami": "ami-4c6d422f",
"instance_type": "t2.small",

WinRM Settings

These settings instruct Packer to use WinRM for communication, and to accept a self-signed SSL certificate. Finally, the user_data_file instructs Packer which script was want injected into the instance with EC2Config at boot.

"communicator": "winrm",
"winrm_username": "Administrator",
"associate_public_ip_address":true,
"user_data_file": "./win-scripts/win-configure-winrm-with-rdp-certificate.txt",
"winrm_insecure": true,
"winrm_use_ssl": true,
"associate_public_ip_address":true,

Don’t be alarmed by our use of winrm_insecure in the above settings. This tells Packer that it can accept unsigned SSL certificates, which are strictly regarded as ‘insecure’ because they aren’t issued by a trusted CA, but it’s not all that bad.

Disk Settings

Finally, these settings tell Packer what sort of disk configuration we want. You don’t really have to do this, but I find it’s a very good idea to give Windows instances in AWS a bit more space than the default 30GB.

Note that this is the only section where our settings will directly impact future instances. The launch_block_device_mappings defines the disk space for any instance launched from our template AMI, whereas ami_block_device_mappings defines the disk space for the instance that is temporarily launched to configure Windows and then save as a template.

"ami_block_device_mappings": [ {
	"device_name": "/dev/sda1",
	"volume_size": 60,
	"delete_on_termination": true,
	"volume_type": "gp2"
} ],
"launch_block_device_mappings": [ {
	"device_name": "/dev/sda1",
	"volume_size": 60,
	"delete_on_termination": true,
	"volume_type": "gp2"
} ]

Provisioners

The PowerShell provisioner instructs Packer to run PowerShell scripts after the instance boots and it does so using WinRM.

I feel that this point is worth laboring on a little more. Earlier, we used user_data_file to specify a PowerShell script to be executed in the instance using the EC2Config service. Now, we’re telling Packer to use WinRM to execute remote scripts.

Technically, you could use user_data_file to do all your customisation work in one giant PowerShell script. If your modifications are small, that might be a good idea, but generally spreading the work out into multiple scripts is a better and more managable option.

{
 "type": "powershell",
 "scripts": ["./win-scripts/enable-rdp.bat",
             "./win-scripts/set-region.ps1",
             "./win-scripts/ec2-reset-admin-password.ps1"]
}

When the source instance is booted, we then instruct Packer which scripts we want to run. These are executed after the EC2Config script, and use WinRM over SSL as we have configured.

Here, I have some pretty basic scripts. You can download these scripts from my GitHub repo. There is also a very, very handy repository of Windows configuration scripts over here.

Watch out!

The final script (ec2-reset-admin-password.ps1) we execute is a must have. This script tells AWS that on the next launch of this AMI, it should set a new random Administrator password. If you don’t have this script, the Administrator password is only known to Packer (unless you run in Debug mode) and the EC2 instances you launch of this image will be inaccessible.

Part 3: Making it go

Once you have set your custom variables building your template is as simple as running packer build windows.json from a folder containing the windows.json file, and the win-scripts folder.

$ packer build windows.json
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name...
==> amazon-ebs: Inspecting the source AMI...
==> amazon-ebs: Creating temporary keypair: packer 577cde9f-0d1e-b8a7-476f-56b6336cea87
==> amazon-ebs: Creating temporary security group for this instance...
==> amazon-ebs: Authorizing access to port 5986 the temporary security group...
==> amazon-ebs: Launching a source AWS instance...
    amazon-ebs: Instance ID: i-2b10416e
==> amazon-ebs: Waiting for instance (i-2b10416e) to become ready...
==> amazon-ebs: Waiting for auto-generated password for instance...
    amazon-ebs: It is normal for this process to take up to 15 minutes,
    amazon-ebs: but it usually takes around 5. Please wait.
    amazon-ebs:
    amazon-ebs: Password retrieved!
==> amazon-ebs: Waiting for WinRM to become available...
==> amazon-ebs: Connected to WinRM!
==> amazon-ebs: Provisioning with Powershell...
==> amazon-ebs: Provisioning with shell script: ./win-scripts/enable-rdp.bat
    amazon-ebs: Ok.
    amazon-ebs:
    amazon-ebs: The operation completed successfully.
    amazon-ebs: The operation completed successfully.
==> amazon-ebs: Provisioning with shell script: ./win-scripts/set-region.ps1
==> amazon-ebs: Provisioning with shell script: ./win-scripts/ec2-reset-admin-password.ps1
==> amazon-ebs: Stopping the source instance...
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: packer-ami-template-1467801247
    amazon-ebs: AMI: ami-f2f23991
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
==> amazon-ebs: Deleting temporary security group...
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:

ap-southeast-2: ami-f2f23991