Windows en Terraform

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

ÍNDICE

1. ¿Qué es Terraform?
2. Prerequisitos
3. ¿Qué vamos a hacer?
4. Pasos
     4.1. Crear usuario CLI
          Claves de Acceso
     4.2. Instalación Software
          Instalar AWS CLI
          Instalar Terraform
     4.3. Código Principal
          Proveedor
          Creación de la instancia
          Conexión desde fuera
          Conexión RDP
          RDP: Security Groups
          RDP: Key pair
          Instancia Modificada
          Output
               Output: IP Pública
               Output: Credenciales
5. Extras
     5.1. AMI Dinámica
     5.2. Credenciales de AWS en archivo

6. Quiero el código

 

1. ¿Qué es Terraform?

Terraform es una herramienta Open Source que permite generar una infraestructura a través de un lenguaje declarativo. Esto es lo que se llama Infraestructura como código (IaC)

Es decir, permite abstraernos del entorno en que se vaya a implementar dicha estructura, tanto para entornos en la nube (como AWS, Azure, Google Cloud), como entornos locales (como Virtualbox, RouterOS de Mikrotik o Unifi), para centrarnos en la creación de esta.

 

2. Prerequisitos

Para este lab necesitaremos:

  • Una cuenta en AWS que haya sido creada previamente
  • Una máquina virtual o PC que con Permisos de Administrador

3. ¿Qué vamos a hacer?

En resumen, las tareas que vamos a hacer en este lab son:

  • Crear un usuario programático en AWS
  • Instalación de Terraform en un entorno Linux
  • Conocer el uso de distintos módulos de Terraform
  • Inicializar y aplicar la configuración en una cuenta AWS

 

4. Pasos

4.1. Crear usuario CLI

Para que Terraform se conecte a nuestra cuenta de AWS, será necesario previamente crear dicho usuario. En este laboratorio crearemos un usuario con permisos de Administrador, que únicamente utilizaremos para uso programático.

 

Nos conectaremos a nuestra cuenta AWS, y accederemos a la sección IAM -> Users

A screenshot of a computer

Description automatically generated with low confidence

En dicha sección cliquearemos en Crear usuario

Rellenaremos con el nombre de usuario que nos interese (en nuestro caso utilizaremos el usuario “usercli”, y dejaremos desactivada la opción de acceso a consola, ya que utilizaremos este usuario únicamente para acceso programático.

 

 

En Grupos de permisos, deberemos proporcionarle permisos suficientes para crear recursos. En nuestro caso crearemos un grupo nuevo con permisos de Administrador.

Para ello haremos click en “Crear grupo”.

 

Le pondremos un nombre a dicho grupo (en nuestro caso utilizaremos el nombre “admin”), y en políticas de permisos asociaremos la política “AdministratorAccess”.  Haremos click en “Crear grupo de usuario”.

Y en la ventana de Permisos, seleccionamos nuestro nuevo grupo “admin”

 

Finalmente hacemos click en “Crear Usuario”.

 

Claves de Acceso

Ya tenemos creado nuestro nuevo usuario. Pero será necesario generar una clave de acceso para poder acceder a este usuario.

Para ello seleccionamos en el menú de IAM -> Users nuestro usuario “usercli”, y accedemos al apartado “Credenciales de seguridad”

 

Ahora buscaremos la sección “Access keys” y haremos click en “Create access key”.

 

En el menú que nos aparece, seleccionaremos la opción “Command Line Interface (CLI)”. Esta opción nos permitirá acceder de forma programática.

 

 

Y marcamos la opción de seguridad

Finalmente, haremos click en el botón “Create access key”.

 

En la siguiente sección, nos aparecerá la pareja de keys que necesitaremos posteriormente: “access key” y “secret key”.

 

NOTA: Copia estos valores en algún sitio seguro, ya que NO podremos recuperarlos cuando salgamos de este menú.

 

4.2. Instalación Software

Instalar AWS CLI

Para poder utilizar AWS en nuestro equipo, será necesario instalar AWS CLI. En nuestro caso, para una distribución Ubuntu, será necesario ejecutar los siguientes comandos:

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

 

Instalar Terraform

Primero será necesario actualizar nuestro sistema e instalar los paquetes necesarios para su instalación (gnupg, curl)

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

Una vez instalado, procedemos a instalar la key GPG

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

 

Agregamos el repositorio de Hashicorp y procedemos a descargar la información de paquetes

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. Código Principal

Proveedor

Lo primero que vamos a necesitar va a ser indicarle a Terraform la manera de poder conectarse a AWS.

En nuestro caso, dado que hemos creado un usuario para conectarnos por CLI, será el que utilicemos. Concretamente necesitaremos el “access_key” y el “secret_key” de dicho usuario.

Además de estos datos, será necesario indicar la región en la que vamos a trabajar.

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

 

Creación de la instancia

Respecto a la instancia de Windows, agregaremos las líneas que consideramos primordiales, y luego iremos agregando las necesarias.

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

 

ami: Indica la imagen de la distribución que queremos agregar a la instancia. En nuestro ejemplo, el identificador pertenece a un Windows Server 2012 R2.

instance_type: Indica el hardware en la que trabajará dicha instancia. En este ejemplo hemos escogido la instancia “t2.micro” ya que es “free tier” (es decir, gratuito hasta 750 horas de uso durante un año si posees una cuenta nueva)

associate_public_ip_address: Esto permitirá asociar una IP pública a la instancia, para conectarnos desde fuera.

 

Conexión desde fuera

Con la configuración anterior habremos creado una instancia de Windows 2012 R2, pero no podremos conectarnos. Es por ello que agregaremos algunas opciones más.

Conexión RDP

Aparte de una dirección IP, necesitaremos un protocolo para conectarnos a dicha instancia. En este lab optaremos por conectarnos por RDP (Remote Desktop Protocol).

RDP: Security Groups

Recordamos que los Security Groups son permisos que otorgamos a las instancias para controlar el tráfico entrante y saliente del mismo.

 

ingress: En nuestro caso, es necesario habilitar el acceso entrante al puerto 3389, que es el puerto habitual del protocolo RDP. En nuestro ejemplo aceptaremos cualquier IP, por lo que en nuestro bloque de cidr usamos cualquier rango “0.0.0.0/0”

egress:  Como acceso saliente aceptaremos cualquier protocolo, puerto e 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
}

 

Una vez la instancia se cree, podremos desencriptar la contraseña de Administrador. Para ello, vamos a desencriptarlo bajo código, y almacenarlo en un SSM Parameter Store de AWS.

¿Qué es SSM Parameter Store?

Es una capacidad de AWS System Manager, que proporciona un almacenamiento seguro y jerárquico para la administración de los datos de configuración y de los secretos.

 

Puede almacenar datos como contraseñas, cadenas de base de datos, ID de Amazon Machine Image (AMI) y códigos de licencia como valores de parámetros. Puede almacenar valores como texto sin formato o como datos cifrados.

 

El comando principal que permite desencriptar la contraseña es:

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

 

tls_private_key.instance_key.private_key_pem: Esta es la clave privada creada previamente en formado PEM.

Función nonsensitive(SENSITIVE_TEXT): La clave privada anterior es un elemento sensible, por lo que no nos permitirá utilizarlo. Esta función de Terraform nos permite devolver una copia de dicha clave eliminando la marca de “sensible”.

aws_instance.windows-server.password_data: Una vez se crea la instancia, este argumento apunta a la contraseña encriptada de la misma.

Función rsadecrypt(CIPHERTEXT, PRIVATEKEY): Permite desencriptar un texto cifrado en RSA y lo devuelve en texto plano.

# 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))
}
Dependencias en Terraform

La mayoría de las veces, Terraform infiere dependencias entre recursos en función de la configuración proporcionada, de modo que los recursos se crean y destruyen en el orden correcto. Esto se realiza de forma automática y recibe el nombre de "dependencias implícitas".
Sin embargo, en ocasiones, Terraform no puede inferir dependencias entre diferentes partes de su infraestructura, y deberá crear una dependencia explícita de forma manual con el argumento depend_on

 

Instancia Modificada

Finalmente, modificaremos nuestro recurso de creación de la instancia para que utilice estos recursos extras que hemos agregado.

# 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

Una vez tenemos todos los recursos creados, hay ciertos datos que requeriremos para conectarnos a dicha instancia. Para ello utilizaremos en este ejemplo dos formas distintas para mostrar dicha información

Output: IP Pública

Necesitaremos la IP pública para poder conectarnos. Es por ello que haremos que nos la muestre utilizando el módulo “output”. Esta información la mostrará por pantalla una vez termine de crear la instancia.

# Windows Public IP

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

 

Output: Credenciales

Dado que hemos almacenado las credenciales en un Parameter Store, vamos a exportar dicha información en un fichero local, en el mismo directorio donde lanzamos la ejecución.

filename: Nombre del fichero destino

content: Lo que va a exportar; en nuestro caso, las credenciales

# Export Credentials to File

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

 

5. Extras

 

5.1. AMI Dinámica

En AWS, para poder crear una instancia EC2, es necesario conocer el ID de la imagen que queremos utilizar (o AMI).

Dicha ID varía según la versión de la distribución, región e incluso idioma. Es por ello que sería necesario conocer previamente la identificación exacta de la imagen para poder aplicarla a nuestro código.

Pero Terraform posee un módulo que nos ayudará en esta tarea. El módulo “Data” permite conectarse a la cuenta de AWS, y realizar una búsqueda con los parámetros que concretemos.

En nuestro caso, vamos a indicar que queremos la distribución en español de Windows 2012 R2, y concretaremos que queremos la última versión.

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

 

Y sustituiremos en el módulo de creación de la instancia el valor de la AMI por el nuevo valor obtenido de este nuevo módulo “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. Credenciales de AWS en archivo

Originalmente habíamos definido el módulo del proveedor con las claves “access key” y “secret key” del usuario “usercli” que creamos en AWS.

De esta forma, las credenciales se encuentran a la vista de código, por lo que en el caso de querer exportarlo, generaríamos un problema de seguridad.

AWS CLI tiene un directorio en el cual poder almacenar credenciales. Por defecto suele estar localizado en “~/.aws/config” en Linux, y “C:\Users\USERNAME\.aws\config” en Windows.

Es por ello que editaremos el archivo (o crearemos el archivo si no se encuentra) , y le asignaremos el perfil “default”.

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

[default]
aws_access_key_id = ACCESS_KEY
aws_secret_access_key = SECRET_ACCESS_KEY

 

Y modificaremos el módulo de proveedor de Terraform de forma que apuntaremos a la ubicación de dichas credenciales

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

 

6. Quiero el código

El código completo lo podrás encontrar en el repositorio:

Código

 

¿Te ha gustado?

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