June 19, 2018 · Exploration

Terraforming (Not The Sci-Fi Kind)

The Terraform Logo.

The Problem

Frequently developers and administrators have a need to spin up infrastructure to test with, or even use in production. With the world running on virtual machines, this presents an opportunity to further utilize the IaC (Infrastructure as Code) model closer to the "bare metal".

An IaC model helps IT organizations develop documented, repeatable processes for bringing new infrastructure online. Standardization, once a far-off goal in a sea of manual processes, suddenly seems achievable.

The Solution

This past week I began learning how to use Terraform.

Terraform itself is surprisingly easy to use, at least within the Amazon Web Services realm. It comes with many built-in resources for creating EC2 instances, configuring load balancers, DNS records, and more.

The particular use case I had in mind was a reproducible infrastructure-as-code template I could build to spin up an EC2 instance (read: a basic virtual machine) that had the correct configuration for Chocolately.Server, which is Chocolatey's simplest supported path for hosting your own private package feed for Windows.

This proved easier than I had imagined at first, involving only 77 lines of code in the main.tf file (the primary Terraform file, for what would become my module), including comments.

Including this module inside your own main.tf and running terraform apply will spin up an identical instance to mine in your own Amazon Web Services account. Cool!

You can read more about how to get started with Terraform here.

But Does It Work, Really?

You too can use this code! It's hosted on GitHub over here and is up on the Terraform Registry over there.

Below is the main.tf file, which does most of the legwork, for reference:

# Welcome to the chocoserver module! This module creates a basic Windows Server 2016 instance
# designed for use with Chocolately Server (a project of Chocolately.org).

# First create security group to access the instance via WinRM over HTTP and HTTPS.
resource "aws_security_group" "winrm_enabled" {
  name        = "Allow WinRM Traffic"
  description = "Allow access over WinRM for remote management."

  # WinRM access from anywhere.
  ingress {
    from_port   = 5985
    to_port     = 5986
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # Outbound internet access.
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# Lookup the current Windows Server 2016 AMI from Amazon.
data "aws_ami" "amazon_windows_2016" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["Windows_Server-2016-English-Full-Base-*"]
  }
}

# Create the EC2 instance for the Chocolately server.
# Uses recommended machine specs from https://chocolatey.org/docs/how-to-set-up-chocolatey-server#requirements.
resource "aws_instance" "chocoserver" {
  # Set instance size to 2 vCPUs and 8GiB RAM.

  instance_type = "t2.large"
  ami           = "${data.aws_ami.amazon_windows_2016.image_id}"

  # Increase default EBS volume size to 50GB SSD, minimum recommended for package store & OS.

  root_block_device = {
    volume_size = "50"
  }

  # The name of the SSH keypair you've created and downloaded from the AWS console.

  key_name = "${var.key_name}"

  # Add security group for WinRM access.

  security_groups = ["winrm_enabled"]

  # Bootstrap instance for WinRM over HTTP with basic authentication. 
  # Don't include this section if you wish to use HTTPS or will manage the instance with Chef or another tool.

  user_data = <<EOF
<script>
  winrm quickconfig -q & winrm set winrm/config @{MaxTimeoutms="1800000"} & winrm set winrm/config/service @{AllowUnencrypted="true"} & winrm set winrm/config/service/auth @{Basic="true"}
</script>
<powershell>
  netsh advfirewall firewall add rule name="WinRM in" protocol=TCP dir=in profile=any localport=5985 remoteip=any localip=any action=allow
  $user = [adsi]"WinNT://localhost/Administrator,user"
  $user.SetPassword("${var.admin_password}")
  $user.SetInfo()
  Write-Host "Restarting WinRM Service..."
  Stop-Service winrm
  Set-Service winrm -StartupType "Automatic"
  Start-Service winrm
</powershell>
EOF
}