Windows in Terraform

using AWS Cloud
Posted by Miguel Mora on May 22, 2023

INDEX

1. What is Terraform?
2. Prerequisites
3. What are we going to do?
4. Steps
     4.1. Create CLI User
          Access Key
     4.2. Software Installation
          Install AWS CLI
          Install Terraform
     4.3. Main Code
          Provider
          Creating the instance
          Connecting from outside
          RDP Connection
          RDP: Security Groups
          RDP: Key pair
          Modified Instance
          Output
               Output: Public IP
               Output: Credentials
5. Extras
     5.1. Dynamic AMI
     5.2. AWS Credentials on File

6. I want the code

 

1. What is Terraform?

Terraform is an Open Source tool  that allows you to generate an infrastructure through a declarative language. This is what is called Infrastructure as Code (IaC).

That is, it allows us to abstract from the environment in which this structure is going to be implemented, both for cloud environments (such as AWS, Azure, Google Cloud), and local environments (such as Virtualbox,  Mikrotik RouterOS or Unifi).

 

2. Prerequisites

For this lab we will need:

  • An AWS account that has been previously created
  • A PC or Virtual Machine with Admin Permissions

3. What are we going to do?

Tasks that we are going to do in this lab are:

  • Create a Programmatic User on AWS
  • Installing Terraform in a Linux environment
  • Know the use of different Terraform modules
  • Initialize and Apply Settings in an AWS Account

 

4. Steps

4.1. Create CLI User

In order to make Terrraform connect to our AWS account, it will be needed to create a user with CLI permissions. In this lab we will create a user with Administrator permissions, which we will only be used for programmatic use.

We will connect to our AWS account, and we will access the IAM -> Users section

A screenshot of a computer

Description automatically generated with low confidence

In this section we will click on Create user

We will fill the fields with a username (in our case we will use the user "usercli", and will leave “console access” option disabled, as this user will only be used for programmatic access.

 

 

In Permission groups, we'll need to set enough permissions to create resources. In our case we will create a new group with Administrator permissions.

To do this we will click on "Create group".

 

We will name this group (in our case we will use the name "admin"), and in permission policies we will associate the "AdministratorAccess" policy.  Then click  on "Create user group".

And in the Permissions window, select our new "admin" group

 

Finally we click on "Create User".

 

Access Keys

We have already created our new user. But it will be needed to generate an access code in order to be able to access this user.

To do this we select in the menu of IAM -> Users our user "usercli", and go to "Security credentials" tab

 

Now we will look for the "Access keys" section and click on "Create access key".

 

In the next menu, we will select the option "Command Line Interface (CLI)". This option will allow the user to access AWS account programmatically.

 

 

And we mark the security option

Finally, we will click on the "Create access key" button.

 

In the next section, we will see the key pairs that we will need later: "access key" and "secret key".

 

NOTE: Copy these values somewhere safe, since we will NOT be able to recover them when we leave this menu.

 

4.2. Software Installation

Install AWS CLI

Before you can use AWS on your computer, you will need to install the AWS CLI. In our case, as it’s an Ubuntu distribution, the following commands will be run:

apt-get install zip curl -y

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"

unzip awscliv2.zip

sudo ./aws/install

 

Install Terraform

We will need to update our system and also install the following packages for its installation (gnupg, curl)

sudo apt-get update && sudo apt-get install -y gnupg software-properties-common

Once installed, we proceed to install the GPG key

wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

 

Then we add the repository of Hashicorp and proceed to download the package information

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update

Finalmente instalamos terraform

sudo apt install terraform

 

4.3. Main Code

Provider

The first thing we're going to need is to inform Terraform how to connect to AWS.

In our case, as we have created a user to connect by CLI, it will be the one we use. Specifically, we will need the "access_key" and "secret_key" of that user.

In addition to that data, it will also be needed to set the region in which we are going to deploy the resources.

provider "aws" {
    region = "eu-west-1"
    access_key = "USER_ACCESS_KEY"
    secret_key = "USER_SECRET_KEY"
}

 

Creating the instance

Regarding the Windows instance, we will add the main lines; we will add more lines later.

resource "aws_instance" "windows-server" {
  ami = “ami-04f19c2d332c17a0c”
  instance_type = “t2.micro”
  associate_public_ip_address = true 
}

 

ami: Indicates the distribution image that we want to add to the instance. In our example, the ID belongs to a Windows Server 2012 R2.

instance_type: Indicates the hardware on which that instance will work. In this example we have chosen the instance "t2.micro" since it  is "free tier" (that is, free up to 750 hours of use for a year if you have a new account)

associate_public_ip_address: This will allow us to associate a public IP to the instance, to connect from outside.

 

Connecting from outside

With the previous configuration we will create a Windows 2012 R2 instance, but we will not be able to connect to. That’s why  we will add some more options.

RDP Connection

Apart from an IP address, we will need a protocol to connect to that instance. In this lab we will choose to connect by RDP (Remote Desktop Protocol).

RDP: Security Groups

Remember that Security Groups are permissions that we grant to the instances to control the incoming and outgoing traffic of the same.

 

ingress: In our case, we need to enable inbound access to port 3389, which is the usual port of the RDP protocol. We will also accept any IP, so in our cidr block  we use any range "0.0.0.0/0"

egress:  As outgoing access we will accept any protocol, port and IP.

# Security Groups
####################################################

# Define the security group for the Windows server

resource "aws_security_group" "sg-windows" {
  name        = "windows-sg"
  description = "Allow incoming connections" 
  ingress {
    from_port   = 3389
    to_port     = 3389
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow incoming RDP connections"
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

 

RDP: Key pair

En el caso de instancias Windows, es necesario una pareja de claves (key pair). Esta clave permitirá desencriptar la contraseña de administrador. Una vez desencriptada, podremos utilizarla para poder conectarnos a la instancia.

tls_private_key: Utilizaremos este recurso para generar una clave privada. Definiremos el uso del algoritmo RSA para esta clave

aws_key_pair: En esta sección crearemos el par de claves para controlar el acceso a la instancia. Utilizaremos la clave privada creada previamente para crearla.

# Private Key for Credentials
####################################################

resource "tls_private_key" "instance_key" {
  algorithm = "RSA"
}

resource "aws_key_pair" "instance_key_pair" {
  key_name   = "windows-instance-key"
  public_key = tls_private_key.instance_key.public_key_openssh
}

 

Once the instance is created, we can decrypt the Administrator password. To do this, we will decrypt it under code, and store it in an AWS SSM Parameter Store.

¿What is SSM Parameter Store?

It is a capability of AWS System Manager, which provides safe, hierarchical storage for managing configuration data and secrets.

You can store data such as passwords, database strings, Amazon Machine Image IDs (AMIs), and license codes as parameter values. You can also store values as plain text or as encrypted data.

 

The main command that allows to decrypt the password is:

rsadecrypt(aws_instance.windows-server.password_data, nonsensitive(tls_private_key.instance_key.private_key_pem))

 

tls_private_key.instance_key.private_key_pem: This is the private key previously created in PEM format.

nonsensitive(SENSITIVE_TEXT) function: The previous private cell is a sensitive element, so it will not allow us to use it.  This Terraform feature let us return a copy of that key by removing the "sensitive" mark.

aws_instance.windows-server.password_data: Once the instance is created , this argument points to the encrypted password of the instance.

rsadecrypt(CIPHERTEXT, PRIVATEKEY) function: Decrypts a ciphertext in RSA and returns it in plain text.

# Store Password
####################################################

resource "aws_ssm_parameter" "windows_ec2" {
  depends_on = [aws_instance.windows-server]
  name       = "/Instances/windows/windows-password"
  type       = "SecureString"
  value = rsadecrypt(aws_instance.windows-server.password_data, nonsensitive(tls_private_key.instance_key.private_key_pem))
}

Terraform Dependencies

Most of the time, Terraform infers dependencies between resources based on the configuration given, so that resources are created and destroyed in the correct order. This is automatically done, and it's named "implicit dependencies".
Occasionally, however, Terraform cannot infer dependencies between different parts of your infrastructure, and you will need to create a manual explicit dependency with the depends_on argument

 

Modified instance

We will  finally modify our instance creation resource to use these extra resources that we have added.

# Create EC2 Instance

resource "aws_instance" "windows-server" {
  # Instance info
  ami = “ami-04f19c2d332c17a0c”
  instance_type = "t2.micro"

  # Public IP
  associate_public_ip_address = true

  # Instance Credentials
  key_name                = aws_key_pair.instance_key_pair.key_name
  get_password_data = true

  # Security Group
  vpc_security_group_ids = [aws_security_group.sg-windows.id]
}

 

Output

Once we have all the resources created, there is certain data that we will require to connect to that instance. For this we will use in this example two different ways to display this information

Output: Public IP

We will need the public IP to be able to connect.  That is why we will show it to us using the "output" module. This information will be displayed on the screen after you finish creating the instance.

# Windows Public IP

output "windows_public_ip" {
  value = aws_instance.windows-server.public_ip
}

 

Output: Credentials

We had stored the credentials in a Parameter Store, so we are going to export that information in a local file, in the same directory where we launch the execution.

filename: Name of the destination file

content: What you are going to export; in our case, the credentials

# Export Credentials to File

resource "local_file" "RDP_key" {
  filename = "windows_key.txt"
  content  = aws_ssm_parameter.windows_ec2.value
}

 

5. Extras

 

5.1. Dynamic AMI

In AWS, you need to know the ID of the image you want to use (or AMI) in order to create an EC2 instance.

This ID changes depending on the version of the distribution, region and even language. That is why it would be necessary to previously know the exact identification of the image to apply it to our code.

But Terraform has a module that will help us in this task. The "Data" module allows you to connect to your AWS account and perform a query with the parameters that we specify.

In our case, we will indicate that we want the Spanish distribution of Windows 2012 R2, and we will specify that we want the latest version.

data "aws_ami" "windows-2012-r2" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["Windows_Server-2012-R2_RTM-Spanish-64Bit-Base-*"]
  }
}

 

And we will replace in the instance creation module the value of the AMI with the new value obtained from this new module "aws_ami"

# Create EC2 Instance

resource "aws_instance" "windows-server" {
  # Instance info
  ami = data.aws_ami.windows-2012-r2.id
  instance_type = "t2.micro"

  # Public IP
  associate_public_ip_address = true

  # Instance Credentials
  key_name                = aws_key_pair.instance_key_pair.key_name
  get_password_data = true

  # Security Group
  vpc_security_group_ids = [aws_security_group.sg-windows.id]
}

 

5.2. AWS Credentials on File

We had originally defined the provider module with the "access key" and "secret key" keys of the user "usercli" that we created in AWS.

So credentials can be seen in code, so in case of export it, we would generate a security problem.

The AWS CLI has a directory in which we can store credentials. By default it is usually located in "~/.aws/config" in Linux, and "C:\Users\USERNAME\. aws\config" on Windows.

That’s why we will edit the file (or create the file if it is not found), and assign the profile "default".

# nano /home/USER/.aws/config/credentials

[default]
aws_access_key_id = ACCESS_KEY
aws_secret_access_key = SECRET_ACCESS_KEY

 

And we will modify the Terraform provider module so that we will point to the location of those credentials

provider "aws" {
    region = "eu-west-1"
    shared_credentials_files = ["/home/USER/.aws/credentials"]
    profile = "default"
}

 

6. I want the code

You can find the whole code in the following repository:

Code

 

¿Te ha gustado?

Si te ha gustado y quieres aportar tu granito de arena para que esta comunidad crezca: