Skip to content

padok-team/dojo-terraform-aws-security

Repository files navigation

Exercise ~ Securing an infrastructure with Terraform

Welcome to this Dojo ! Your goal is to use terraform to create and secure an AWS environment, and learn the maximum during this session.

That's why it is essential that:

  • You experiment and try things that may break. Don't simply copy paste everything from StackOverflow until it passes the test !
  • To keep moving or learn more, please andon! It means then whenever you have a question, a doubt, a feedback, call someone from the staff, we'll love to help you.

0. Setup your env

Connect to a distant VM

To work efficiently, you will work on a distant VM on which this repository is already cloned and all the following tools are already installed.

  • git: Version your code and collaborate
  • terraform: Build infrestructure as Code
  • aws: Interact with AWS

To connect to the VM:

  • Go to https://<handleGithub>.cs.dojo.padok.school
  • The password is <handleGithub>12345 (Security by design)
💡 Tip N˚1

Once in VSCode to see this document in a more human friendly way press crtl+shift+v or cmd+shift+v for mac os

If you have your own VSCode configured and your github account is configured with a ssh public key, you can connect through ssh.
  • Add the following Remote SSH extension to VSCode

  • Create a github account

  • Create a SSH key on your Github account: Add a ssh key documentation

  • Share your Github handle with Padok's team member

  • Launch a "Remote SSH Session" with VSCode extension via the command ssh <handleGithub>@<handleGithub>.ssh.padok.school

Explore the Terraform code

You can explore iac/ folder to understand the different parts of the code

Schema

Bootstrap the environment

To create the insecure infrastructure, follow these steps:

cd iac/

# initialize Terraform
terraform init

# Create a workspace named `prd`
terraform workspace new prd
terraform workspace select prd

# View current workspace
terraform workspace show

# View the planned actions
terraform plan -var-file $(terraform workspace show).tfvars

# check for planned actions, and if everything seems ok, say 'yes' to apply them
terraform apply -var-file $(terraform workspace show).tfvars

Wait for the infrastructure to pop. You will get an output like:

private_key_pem = <sensitive>
production_webserver = "http://35.180.124.183"
ssh_production = "ssh -o IdentitiesOnly=yes -i ~/.ssh/padok_supelec.id_rsa [email protected]"
ssh_stagin = "ssh -o IdentitiesOnly=yes -i ~/.ssh/padok_supelec.id_rsa [email protected]"
staging_webserver = "http://15.237.26.206"
unique_id = "MLrHl77c"

Check that everything is good:

terraform output private_key_pem

# Copy paste the key into a file
sudo vim ~/.ssh/padok_supelec.id_rsa
sudo chmod 400 ~/.ssh/padok_supelec.id_rsa
sudo chown $(whoami) ~/.ssh/padok_supelec.id_rsa

# Check SSH connections
ssh -o IdentitiesOnly=yes -i ~/.ssh/padok_supelec.id_rsa [email protected]
ssh -o IdentitiesOnly=yes -i ~/.ssh/padok_supelec.id_rsa [email protected]

# Check Web server
curl http://15.237.26.206

Let's secure it !

Ready? Set. Go!

Step 1 : Split the environments

Create a second Terraform workspace to be able to have two identical environments for staging and production:

terraform workspace new dev

The tfvars files will be used to defined environment specific variable values.

We want only one webserver in each environment:

  1. The dev.tfvars has already been created with development values (vpc,subnet) . It will create resource with dev in their name for development environment.
  2. Modify main.tf to have only one resource to describe both environments (instead of the two existing resources staging_webserver and production_webserver).
Hint 1

Your single resource should not have an environment specific name.

To have only one resource, all parameters which are specific to one environment can be defined as variables. Check accessible variables in variables.tf file.

Hint 2

You can remove staging_webserver and rename production_webserver into webserver. You can modify the resource variable user_data to use a variable from variables.tf file instead of the hard value production.

Hint 3

You can use the variable environment.

Hint 4

You can replace "production" or "staging" by "${var.environment}"

  1. Modify output.tf accordingly
Hint 1

Read the terraform error when doing terraform plan -var-file $(terraform workspace show).tfvars until there is none!

Hint 2

Remove or modify outputs that are specific to one environment.

Hint 3

You can remove "staging_webserver" output You can change "production_webserver" output to "webserver" You can remove "ssh_staging" output You can change "ssh_production" output to "ssh" etc...

We will apply these changes in the production environment. It should delete the development webserver.

# Should output "prd"
terraform workspace show

# Check if Terraform code is valid
terraform plan -var-file $(terraform workspace show).tfvars

# Apply changes
terraform apply -var-file $(terraform workspace show).tfvars

Now we will create the developement environment

terraform workspace select dev

terraform apply -var-file $(terraform workspace show).tfvars
Solutions Step 1

You can find solutions here:

/!\ You will need two different SSH keys for production and development environment now.

OK ! Now we have separated network for environment. It is already much more secure !

secure_architecture_v1

Step 2 : Add Load Balancing

We would like the webserver for development and production to be in a private subnet, with a frontal load balancer. It would avoid any port except from 80 to be publically accessible on the webserver. It also means that it won't be possible anymore to SSH into the machines (for now). Finally, it is a good practice to use a load balancer. It would allow us for exemple to have autoscaling in the future, or add AWS WAF to protect the website against vulnerabilities.

  1. Modify the right variable of resource webserver in main.tf to use a private subnet.
Hint

Change the value of subnet_id variable.

Solution

Change var.public_subnets[0] by var.private_subnets[0].

  1. Modify the right variable of resource webserver in main.tf to use the private security group.
Hint

Change the value of vpc_security_group_ids variable.

  1. Add a Load balancer in main.tf to receive HTTP requests
Hint 1

Create a resource block for aws_lb resource as described in the documentation. The resource should contain variables listed above.

subnet_id and vpc_security_group_ids parameters from the resource aws_instance from step1 can help you for subnets and security_groups parameters.

Hint 2

Missing variables are:

  • security_groups : [aws_security_group.allow_pub.id]
  • subnets: var.public_subnets
Solution

Add this to main.tf :

resource "aws_lb" "webserver_lb" {
  name               = "${random_string.unique_id.id}-${var.environment}-webserver-lb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.allow_pub.id]
  subnets            = var.public_subnets

  enable_deletion_protection = false
}
  1. To configure the load balancer, you will have to add three more resources:
Hint

Complete the following block with paramaters:

resource "aws_lb_target_group" "webserver_lb_tg" {
  name     = #TO COMPLETE
  port     = #TO COMPLETE
  protocol = #TO COMPLETE
  vpc_id   = #TO COMPLETE
}

resource "aws_lb_listener" "webserver_lb_listener" {
  load_balancer_arn = "${aws_lb.webserver_lb.arn}"
  port              = #TO COMPLETE
  protocol          = #TO COMPLETE

  default_action {
    target_group_arn = "${aws_lb_target_group.webserver_lb_target.arn}"
    type             = #TO COMPLETE
  }
}

resource "aws_lb_target_group_attachment" "webserver_lb_tg_attachment" {
  target_group_arn = "${aws_lb_target_group.webserver_lb_target.arn}"
  target_id        = #TO COMPLETE
  port             = #TO COMPLETE
}
Hint for `aws_lb_target_group`
  • name: take exemple from aws_lb name parameter
  • port: lb_listener port
  • protocol: lb_listener protocol
  • vpc_id: take a look at available variables
Hint for `aws_lb_listener`
  • port: lb_listener port
  • protocol: lb_listener protocol
  • default_action.type: The listener forward traffic to target groups
Hint for `aws_lb_target_group_attachment`
  • target_id: id of the aws instance described in aws_instance resource
  • port: lb_listener port
Solution

Add this to main.tf:

resource "aws_lb_listener" "webserver_lb_listener" {
  load_balancer_arn = "${aws_lb.webserver_lb.arn}"
  port              = "80"
  protocol          = "HTTP"

  default_action {
    target_group_arn = "${aws_lb_target_group.webserver_lb_target.arn}"
    type             = "forward"
  }
}

resource "aws_lb_target_group" "webserver_lb_target" {
  name     = "${random_string.unique_id.id}-${var.environment}-webserver-lb-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id
}

resource "aws_lb_target_group_attachment" "webserver_lb_tg_attachment" {
  target_group_arn = "${aws_lb_target_group.webserver_lb_target.arn}"
  target_id        = "${aws_instance.webserver.id}"
  port             = 80
}
  1. Change output.fr to :
    • Comment the ssh output and private_key_pem. (not useful for now)
    • Modify the webserver output to get the Load balancer DNS name instead
Hint

value for webserver output: "http://${aws_lb.webserver_lb.dns_name}"

Apply changes !

If everything goes well, you now have a load balancer created.

Look for the one with your random prefix in its name.

curl http://RtIr69Zk-dev-webserver-lb-1866337979.eu-west-3.elb.amazonaws.com
<h2>This is the dev environment <h2>
Solutions Step 2

You can find solutions here:

Step 3 : Network filtering

It is time to restrict access to the public subnet for dev environment: we want the prd environment to be accessible by everyone, but we want the dev environement to be accessible only from your IP address!

  1. Add a variable ip_whitelist of type list in variables.tf and modify prd.tfvars and dev.tfvars accordingly
Hint

Take example from other variables to define ip_whitelist in variables.tf. Variable created should be define in both prd.tfvars and dev.tfvars but the list should contain an IP only for the dev environment.

Solution
  • prd.tfvars : ip_whitelist = []
  • dev.tfvars : ip_whitelist = ['<your_ip>/32']
  1. Modify allow_pub resource in network.tf to allow ingress traffic only from your public IP address for dev environment. For prd environment, allow ingress traffic from everyone.
Solution

You can find solution here: solution/step_3/network.tf

Finally : Apply to production environment

The beauty of terraform is that you can apply the changes to prd environment in a jiffy :

terraform workspace select prd
terraform apply -var-file $(terraform workspace show).tfvars

final

Bonus

We could improve the infrastructure much more, for example:

  • Have ASG (Auto Scaling Group) instead of only one instance to ensure high availability and scalability.

  • Create and admin bastion to access our server in SSH : Developers could SSH to an EC2 instance in a public subnet, and from it get access to the webserver in SSH with SSH tunnelling.

LICENSE

© 2022 Padok.

Licensed under the Apache License, Version 2.0 (LICENSE)

About

Hands-on introduction to secure AWS resources with Terraform.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published