Cloud for .net – Windows Passwords, locks and keys

We created a box, and it’s with a nice security group that allows us to RDP in the machine.

By default, Amazon creates an administrator password for any machine you create, but you need an SSH key-pair to retrieve it.

Each lock has its key

AWS needs a key to open the lock that guards the Administrator password it created on its windows instance. Without a key, the door would be open and anyone with access to your AWS console could retrieve any password.

However, a single key would be difficult to provide. You’d have to create a key, which you should keep as private as the key to your house, and then give a copy to Amazon, and they don’t want you to open your house to you, or at least not when it comes to EC2 instances.

Thankfully, since the 1970s, we can thank British cryptographers for coming up with a better solution. Instead of having one key, you have two: one that you share with everyone, and one that you keep secret. If someone wants to have a lock only you can open, they only need your public key to build a door. With your private key, and only with your private key, will you be able to unlock the door. The public key is not enough, so it can be shared openly.

To create a key-pair, we use either ssh-keygen on all platforms that support Unix tooling, or with an extra package on windows. Make sure your terminal/cmd is open and in the folder we created so far.

For everyone:

$ mkdir .ssh
$ ssh-keygen -t rsa -C "dev" -P "" -f "./.ssh/dev"

For Windows:

c:\MyProject\> mkdir .ssh
c:\MyProject\> choco install win32-openssh
c:\MyProject\> ssh-keygen -t rsa -C "dev" -P "" -f "./.ssh/dev"

You should now have two files in the .ssh folder, dev-deploy, which is your private key and you should keep to yourself, and dev-deploy.pub which is your public key.

Adding keys

To add the key to terraform, we create a new file, let’s call it keys.tf, and we link to our public key, by creating an [aws_key_pair][aws-key-pair] resource.

resource "aws_key_pair" "dev" {
    key_name = "dev"
    public_key = "${file("./.ssh/dev.pub")}"
}

If you have a sharp eye, you’ll have noticed that we used another interpolation, one of the in-built functions of terraform, file, which allows us to load a file and use whatever content as a property.

We now add the key to our instance, to make sure that we can retrieve the password once it’s created.

resource "aws_instance" "my_cool_server" {
  ami = "ami-3d787d57"
  instance_type = "t2.micro"
  security_groups = ["${aws_security_group.rdp.name}"]
  key_name = "${aws_key_pair.dev.key_name}"
}

If you terraform apply, you’ll now have a Windows box with a password you can retrieve.

Retrieving the password

Now we have an RDP port, and we have a key to retrieve the Administrator password. You could do that from the AWS console, by following the manual guide from Amazon, but we’ve established by now that the command line helps us go faster.

We’ll use the aws cli, which we installed in the first part, to retrieve that password.

Add the following output in your project, to retrieve the instance id.

output "my_cool_server_instance_id" {
  value = "${aws_instance.my_cool_server.id}"
}

If you now run tf apply, you’ll have the instance id as part of the outputs. We can now use this to retrieve our password.

$ aws ec2 get-password-data --instance-id  i-xxxx --priv-launch-key ./.ssh/dev
c:\MyProject\> aws ec2 get-password-data --instance-id  i-xxxx --priv-launch-key ./.ssh/dev

If you’re on a mac, you can even launch the RDP session straight from the output, using a little script.

# Usage: ./rdp.sh my_cool_server_instance_id my_cool_server_public_ip
# make sure you have jq installed, using brew install jq

instance_id=$(terraform output | grep $1 | cut -d = -f 2 | sed -e 's/^[ ]//')

public_ip=$(aws ec2 terraform output | grep $2 | cut -d = -f 2 | sed -e 's/^[ ]//')

password=$(aws ec2 get-password-data --region us-east-1 --instance-id i-576e12cc --priv-launch-key ./.ssh/dev | jq -r '.PasswordData')

echo "$password" | pbcopy
open "rdp://full%20address=s:$public_ip&username=s:Administrator"

You’ll still have to type in the password, but it’s at least making it smoother.

Ads

Cloud for .net – RDP’ing into a Windows box

Now that we have a windows box, but we can’t do anything wish. If you tried to connect to RDP, you’d fail, because the ports are not open. We’re going to fix that by adding some security groups.

Security Groups

We need to allow RDP, which is port 3389, inbound to our instance. We do this with a security group.

resource "aws_security_group" "rdp" {
  ingress {
    from_port = 3389
    to_port = 3389
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

We open port 3389, on the TCP protocol, and we open it to anyone trying to connect (the cidr block). It goes without saying that we shouldnt’ do this in production, this is just to play with our VM.

We now need to associate this security group to our previous instance. To do that, we need to reference our security group in the aws_instance resource. The security-groups argument takes a name, and as the name is generated by terraform, we need to reference something that doesn’t exist yet. To do this, we use the interpolation syntax.

Interpolation

In terraform, each resource we declare has a bunch of inputs, the things you set, called arguments, and a bunch of outputs, called attributes. To access those attributes, the syntax is ${resource.name.attribute}.

Looking at the aws_security_group resource, it has an attribute name, so we use that.

resource "aws_instance" "my_cool_server" {
  ami = "ami-3d787d57"
  instance_type = "t2.micro"
  security_groups = ["${aws_security_group.rdp.name}"]
}

If you apply, you should now have a server with an RDP port open.

To connect to our machine, we need to know what its public IP address is. We could of course go to the web site, but why go away from the comfort of our terminal?

Outputs

Any resource attributes can be “exported”, aka made available for any containing script, or for yourself to RDP into a box! To do that, we declare an output, and set its value to the public IP address of our instance.

output "my_cool_server_public_ip" {
  value = "${aws_instance.my_cool_server.public_ip}"
}

If you go ahead and apply again, the public IP address will now be avaiable immediately.

$ tf apply
aws_security_group.rdp: Refreshing state... (ID: sg-2645665e)
aws_instance.my_cool_server: Refreshing state... (ID: i-23e9f6b9)

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

  my_cool_server_public_ip = 52.207.219.207

You can now connect to RDP to your box. But not login! Feel free to tf destory the machine, and next time we’ll see how to setup our credentials.

Ads

Cloud for .net – Infrastructure in source control

More and more clients are deploying their applications to the cloud. This presents many challenges for .net developers: Windows has had a poor story for remote management compared to the *nix world, AWS is less common than Azure, and the move to managing infrastructure in the cloud often comes with the extra challenge of deployment of heterogeneous environments.

Often, the phrase “chose the right tool for the job” is floated around as a practical approach to chosing technology. Yet, there are few tools that started by supporting Windows. This situation is changing rapidly, and tools like terraform and packer are starting to become practical approaches to deploying environments to the cloud.

Infrastructure as code

When starting with AWS, many developers, myself included, tend to start by doing what they already did before: do everything manually in the AWS console, spawn new VMs, go to remote desktop, install things using whatever documentation they may have written before, or simply, and more worryingly, from memory. Each new machine deployment is time-consuming, configuration misses steps, and there is no trail of what was changed and when.

The promise of [Terraform] is to help solve some of those infrastructure problems. Instead of accessing your AWS console, you create a bunch of files that allow you to keep in source control, in code, what you would have on your AWS console. It does the same with many other providers, but I’ll focus on AWS because it’s what I know.

Getting started

To get started, you need to install Terraform, and the AWS command-line utilities.

For mac, we use home brew.

brew update
brew install terraform
brew install awscli

For windows, we use chocolatey.

choco install terraform
choco install awscli 

Once brew has installed everything, you need to configure your aws secret information, which you can do the most easily using aws configure, it sets everything up for you to use the AWS command-line tools, which terraform and packer will also use.

Creating your first server

We’ll start with something simple that should get us a long way, creating just one server. Let’s create a new project.

$ git init restflix
$ cd restflix

Terraform is modeled around the concept of resources. Each “thing” that exists in the AWS console can be modelled as one of the resources supported by terraform.

To create an EC2 instance, we’ll need to create a new TF file, which uses a format called HCL: something in-between JSON and YAML, optimised for manual editing without crazy whitespace handling.

Let’s start by setting up the provider itself the AWS provider itself, so we can chose the region in which we run our code.

provider "aws" {
  region = "us-east-1"
}

On to spinning up a server. We start by adding an EC2 instance resource.

resource "aws_instance" "my_cool_server" {

}

First, we need to decide on the instance type, which is the kind of machine you want your OS to be ran on. There are many to chose from, but as you may be using a free account, we’ll use a t2.micro, which goes against your free credits.

Second, we need to know which OS image to use, which Amazon provides as AMIs. You have many to chose from, including Windows, which we’ll start with. Head to that page, chose your edition, and go to the “Manual Configuration” tab, in which you will find the ami-id.

Note that AMIs, or Amazon Machine Images, are per region. Each region Amazon supports has different AMI ids, so you want to make sure you chose the one for the default region on your account, which is us-east-1.

Putting it all together, we have our first instance ready.

resource "aws_instance" "my_cool_server" {
  ami = "ami-3d787d57"
  instance_type = "t2.micro"
}

That’s it. This will create a new instance for us.

Checking all is in order

In terraform, changes to the infrastructure are done in a two-step process. First we plan, to see what changes terraform will make to our infrastructure.

Bring back your command line, and issue the plan command.

$ tf plan
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_instance.my_cool_server
    ami:                      "" => "ami-3d787d57"
    availability_zone:        "" => "<computed>"
    ebs_block_device.#:       "" => "<computed>"
    ephemeral_block_device.#: "" => "<computed>"
    instance_state:           "" => "<computed>"
    instance_type:            "" => "t2.micro"
    key_name:                 "" => "<computed>"
    placement_group:          "" => "<computed>"
    private_dns:              "" => "<computed>"
    private_ip:               "" => "<computed>"
    public_dns:               "" => "<computed>"
    public_ip:                "" => "<computed>"
    root_block_device.#:      "" => "<computed>"
    security_groups.#:        "" => "<computed>"
    source_dest_check:        "" => "1"
    subnet_id:                "" => "<computed>"
    tenancy:                  "" => "<computed>"
    vpc_security_group_ids.#: "" => "<computed>"


Plan: 1 to add, 0 to change, 0 to destroy.

The result tells us what terraform will do, so we can check that only the right things get created.

Starting the instance

If you’re happy with the result, check-in your code, and terraform apply to make the changes and wait for your windows box to come alive.

$ git add -A
$ git commit -m "My first windows server"
$ terraform apply

aws_instance.my_cool_server: Refreshing state... (ID: i-6c1312f6)
aws_instance.my_cool_server: Creating...
  ami:                      "" => "ami-3d787d57"
  availability_zone:        "" => "<computed>"
  ebs_block_device.#:       "" => "<computed>"
  ephemeral_block_device.#: "" => "<computed>"
  instance_state:           "" => "<computed>"
  instance_type:            "" => "t2.micro"
  key_name:                 "" => "<computed>"
  placement_group:          "" => "<computed>"
  private_dns:              "" => "<computed>"
  private_ip:               "" => "<computed>"
  public_dns:               "" => "<computed>"
  public_ip:                "" => "<computed>"
  root_block_device.#:      "" => "<computed>"
  security_groups.#:        "" => "<computed>"
  source_dest_check:        "" => "1"
  subnet_id:                "" => "<computed>"
  tenancy:                  "" => "<computed>"
  vpc_security_group_ids.#: "" => "<computed>"
aws_instance.my_cool_server: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

And voila, you have your first windows server, all in source control.

Turtles, all the way down

As it stands, the machine can’t be remoted into, so you can’t really do anything with it. Let’s make sure you don’t pay for a running instance that you have no use for, we’re going to destroy it all.

$ terraform destroy

Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.my_cool_server: Refreshing state... (ID: i-bc171626)
aws_instance.my_cool_server: Destroying...
aws_instance.my_cool_server: Destruction complete

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.

Here we go, after confirming, to make sure sure sure you are not making a terrible mistake, the whole thing is going down. That was easy wasn’t it?

Ads