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.
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 collaborateterraform
: Build infrestructure as Codeaws
: 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
You can explore iac/
folder to understand the different parts of the code
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
Ready? Set. Go!
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:
- The dev.tfvars has already been created with development values (vpc,subnet) . It will create resource with
dev
in their name for development environment. - Modify
main.tf
to have only one resource to describe both environments (instead of the two existing resourcesstaging_webserver
andproduction_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}"
- 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 !
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.
- Modify the right variable of resource
webserver
inmain.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]
.
- Modify the right variable of resource
webserver
inmain.tf
to use the private security group.
Hint
Change the value of vpc_security_group_ids
variable.
- Add a Load balancer in
main.tf
to receive HTTP requests- Documentation : https://registry.terraform.io/providers/hashicorp/aws/3.38.0/docs/resources/lb
- The resource should contain the following variables :
- name:
"${random_string.unique_id.id}-${var.environment}-webserver-lb"
- internal:
false
- load_balancer_type:
"application"
- security_groups: Public security group
- subnets: Public subnet
- enable_deletion_protection:
false
- name:
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
}
- To configure the load balancer, you will have to add three more resources:
aws_lb_listener
: Listeners are assigned a specific port to keep an ear out for incoming traffic https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listeneraws_lb_target_group
: The listener forward traffic to target groups https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_groupaws_lb_target_group_attachment
: The instance that the target group will point to. https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group_attachment
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
}
- Change
output.fr
to :- Comment the
ssh
output andprivate_key_pem
. (not useful for now) - Modify the
webserver
output to get the Load balancer DNS name instead
- Comment the
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>
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!
- Add a variable
ip_whitelist
of typelist
invariables.tf
and modifyprd.tfvars
anddev.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']
- Modify
allow_pub
resource innetwork.tf
to allow ingress traffic only from your public IP address fordev
environment. Forprd
environment, allow ingress traffic from everyone.
Solution
You can find solution here: solution/step_3/network.tf
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
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.
© 2022 Padok.
Licensed under the Apache License, Version 2.0 (LICENSE)