{"id":20925528,"url":"https://github.com/padok-team/dojo-terraform-aws-security","last_synced_at":"2025-07-08T03:39:17.948Z","repository":{"id":43127146,"uuid":"418999366","full_name":"padok-team/dojo-terraform-aws-security","owner":"padok-team","description":"Hands-on introduction to secure AWS resources with Terraform.","archived":false,"fork":false,"pushed_at":"2024-11-05T14:00:31.000Z","size":310,"stargazers_count":2,"open_issues_count":4,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-02T05:14:06.760Z","etag":null,"topics":["aws","dojo","security","terraform"],"latest_commit_sha":null,"homepage":"","language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/padok-team.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-19T16:11:13.000Z","updated_at":"2024-11-05T14:00:35.000Z","dependencies_parsed_at":"2023-11-10T23:24:19.346Z","dependency_job_id":"ef4438c9-a8c2-4d5e-813b-51d1c7f9792c","html_url":"https://github.com/padok-team/dojo-terraform-aws-security","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/padok-team%2Fdojo-terraform-aws-security","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/padok-team%2Fdojo-terraform-aws-security/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/padok-team%2Fdojo-terraform-aws-security/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/padok-team%2Fdojo-terraform-aws-security/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/padok-team","download_url":"https://codeload.github.com/padok-team/dojo-terraform-aws-security/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253993474,"owners_count":21996317,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","dojo","security","terraform"],"created_at":"2024-11-18T20:33:05.449Z","updated_at":"2025-05-13T17:33:01.160Z","avatar_url":"https://github.com/padok-team.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Exercise ~ Securing an infrastructure with Terraform\n\nWelcome to this Dojo !\nYour goal is to use terraform to create and secure an AWS environment, and learn the maximum during this session.\n\nThat's why it is essential that:\n\n- You experiment and try things that may break. Don't simply copy paste everything from StackOverflow until it passes the test !\n- 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.\n\n## 0. Setup your env\n\n### Connect to a distant VM\n\nTo work efficiently, you will work on a distant VM on which this repository is already cloned and all the following tools are already installed.\n\n- `git`: Version your code and collaborate\n- [`terraform`](https://developer.hashicorp.com/terraform/install): Build infrestructure as Code\n- [`aws`](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html): Interact with AWS\n\nTo connect to the VM:\n\n- Go to https://\\\u003chandleGithub\\\u003e.cs.dojo.padok.school\n- The password is `\u003chandleGithub\u003e12345` (Security by design)\n\n\u003cdetails\u003e\n\u003csummary\u003e💡 Tip N˚1\u003c/summary\u003e\n\nOnce in VSCode to see this document in a more human friendly way press `crtl+shift+v` or `cmd+shift+v` for mac os\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eIf you have your own VSCode configured and your github account is configured with a ssh public key, you can connect through ssh.\u003c/summary\u003e\n\n- Add the following [Remote SSH extension](https://code.visualstudio.com/docs/remote/ssh) to VSCode\n- Create a github account\n- Create a SSH key on your Github account: [Add a ssh key documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent)\n- Share your Github handle with Padok's team member\n\n- Launch a \"Remote SSH Session\" with VSCode extension via the command `ssh \u003chandleGithub\u003e@\u003chandleGithub\u003e.ssh.padok.school`\n\n\u003c/details\u003e\n\n## Explore the Terraform code\n\nYou can explore `iac/` folder to understand the different parts of the code\n\n![Schema](./insecure_architecture.png)\n\n### Bootstrap the environment\n\nTo create the insecure infrastructure, follow these steps:\n\n```bash\ncd iac/\n\n# initialize Terraform\nterraform init\n\n# Create a workspace named `prd`\nterraform workspace new prd\nterraform workspace select prd\n\n# View current workspace\nterraform workspace show\n\n# View the planned actions\nterraform plan -var-file $(terraform workspace show).tfvars\n\n# check for planned actions, and if everything seems ok, say 'yes' to apply them\nterraform apply -var-file $(terraform workspace show).tfvars\n```\n\nWait for the infrastructure to pop. You will get an output like:\n\n```\nprivate_key_pem = \u003csensitive\u003e\nproduction_webserver = \"http://35.180.124.183\"\nssh_production = \"ssh -o IdentitiesOnly=yes -i ~/.ssh/padok_supelec.id_rsa ubuntu@35.180.124.183\"\nssh_stagin = \"ssh -o IdentitiesOnly=yes -i ~/.ssh/padok_supelec.id_rsa ubuntu@35.180.124.183\"\nstaging_webserver = \"http://15.237.26.206\"\nunique_id = \"MLrHl77c\"\n```\n\nCheck that everything is good:\n\n```\nterraform output private_key_pem\n\n# Copy paste the key into a file\nsudo vim ~/.ssh/padok_supelec.id_rsa\nsudo chmod 400 ~/.ssh/padok_supelec.id_rsa\nsudo chown $(whoami) ~/.ssh/padok_supelec.id_rsa\n\n# Check SSH connections\nssh -o IdentitiesOnly=yes -i ~/.ssh/padok_supelec.id_rsa ubuntu@35.180.124.183\nssh -o IdentitiesOnly=yes -i ~/.ssh/padok_supelec.id_rsa ubuntu@35.180.124.183\n\n# Check Web server\ncurl http://15.237.26.206\n```\n\n## Let's secure it !\n\nReady? Set. Go!\n\n### Step 1 : Split the environments\n\nCreate a second Terraform workspace to be able to have two identical environments for staging and production:\n\n```\nterraform workspace new dev\n```\n\nThe `tfvars` files will be used to defined environment specific variable values.\n\n\nWe want only one webserver in each environment:\n\n1. The dev.tfvars has already been created with development values (vpc,subnet) . It will create resource with `dev` in their name for development environment.\n2. Modify `main.tf` to have only one resource to describe both environments (instead of the two existing resources `staging_webserver` and `production_webserver`).\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint 1\u003c/summary\u003e\n\n  Your single resource should not have an environment specific name.\n\n  To have only one resource, all parameters which are specific to one environment can be defined as variables.\n  Check accessible variables in `variables.tf` file.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint 2\u003c/summary\u003e\n\n  You can remove `staging_webserver` and rename `production_webserver` into `webserver`.\n  You can modify the resource variable `user_data` to use a variable from `variables.tf` file instead of the hard value `production`.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint 3\u003c/summary\u003e\n\n  You can use the variable `environment`.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint 4\u003c/summary\u003e\n\n  You can replace `\"production\"` or `\"staging\"` by `\"${var.environment}\"`\n\u003c/details\u003e\n\n3. Modify output.tf accordingly\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint 1\u003c/summary\u003e\n\n  Read the terraform error when doing `terraform plan -var-file $(terraform workspace show).tfvars` until there is none!\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint 2\u003c/summary\u003e\n\n  Remove or modify outputs that are specific to one environment.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint 3\u003c/summary\u003e\n\n  You can remove `\"staging_webserver\"` output\n  You can change `\"production_webserver\"` output to `\"webserver\"`\n  You can remove `\"ssh_staging\"` output\n  You can change `\"ssh_production\"` output to `\"ssh\"`\n  etc...\n\u003c/details\u003e\n\nWe will apply these changes in the production environment. It should delete the development webserver.\n\n```\n# Should output \"prd\"\nterraform workspace show\n\n# Check if Terraform code is valid\nterraform plan -var-file $(terraform workspace show).tfvars\n\n# Apply changes\nterraform apply -var-file $(terraform workspace show).tfvars\n\n```\n\nNow we will create the developement environment\n```\nterraform workspace select dev\n\nterraform apply -var-file $(terraform workspace show).tfvars\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eSolutions Step 1\u003c/summary\u003e\n\nYou can find solutions here:\n  * [solution/step_1/main.tf](./solution/step_2/main.tf)\n  * [solution/step_1/output.tf](./solution/step_2/output.tf)\n  * [solution/step_1/dev.tfvars](./solution/step_2/dev.tfvars)\n\u003c/details\u003e\n\n\n/!\\ You will need two different SSH keys for production and development environment now.\n\nOK ! Now we have separated network for environment. It is already much more secure !\n\n![secure_architecture_v1](./secure_architecture_v1.png)\n\n### Step 2 : Add Load Balancing\n\nWe 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.\n\n\n1. Modify the right variable of resource `webserver` in `main.tf` to use a private subnet.\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint\u003c/summary\u003e\n\n  Change the value of `subnet_id` variable.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eSolution\u003c/summary\u003e\n\n  Change `var.public_subnets[0]` by `var.private_subnets[0]`.\n\u003c/details\u003e\n\n2. Modify the right variable of resource `webserver` in `main.tf` to use the private security group.\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint\u003c/summary\u003e\n\n  Change the value of `vpc_security_group_ids` variable.\n\u003c/details\u003e\n\n3. Add a Load balancer in `main.tf` to receive HTTP requests\n    * Documentation : https://registry.terraform.io/providers/hashicorp/aws/3.38.0/docs/resources/lb\n    * The resource should contain the following variables :\n      * name: `\"${random_string.unique_id.id}-${var.environment}-webserver-lb\"`\n      * internal: `false`\n      * load_balancer_type: `\"application\"`\n      * security_groups: Public security group\n      * subnets: Public subnet\n      * enable_deletion_protection: `false`\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint 1\u003c/summary\u003e\n\n  Create a resource block for `aws_lb` resource as described in the documentation.\n  The resource should contain variables listed above.\n\n  `subnet_id` and `vpc_security_group_ids` parameters from the resource `aws_instance` from step1 can help you for `subnets` and `security_groups` parameters.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint 2\u003c/summary\u003e\n\n  Missing variables are:\n  * security_groups : `[aws_security_group.allow_pub.id]`\n  * subnets: `var.public_subnets`\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eSolution\u003c/summary\u003e\n\nAdd this to `main.tf` :\n\n```\nresource \"aws_lb\" \"webserver_lb\" {\n  name               = \"${random_string.unique_id.id}-${var.environment}-webserver-lb\"\n  internal           = false\n  load_balancer_type = \"application\"\n  security_groups    = [aws_security_group.allow_pub.id]\n  subnets            = var.public_subnets\n\n  enable_deletion_protection = false\n}\n```\n\u003c/details\u003e\n\n1. To configure the load balancer, you will have to add three more resources:\n\n* `aws_lb_listener` : Listeners are assigned a specific port to keep an ear out for incoming traffic\n  https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener\n* `aws_lb_target_group` : The listener forward traffic to target groups\n  https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group\n* `aws_lb_target_group_attachment` : The instance that the target group will point to.\n  https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group_attachment\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint\u003c/summary\u003e\n\n  Complete the following block with paramaters:\n  ```\n  resource \"aws_lb_target_group\" \"webserver_lb_tg\" {\n    name     = #TO COMPLETE\n    port     = #TO COMPLETE\n    protocol = #TO COMPLETE\n    vpc_id   = #TO COMPLETE\n  }\n\n  resource \"aws_lb_listener\" \"webserver_lb_listener\" {\n    load_balancer_arn = \"${aws_lb.webserver_lb.arn}\"\n    port              = #TO COMPLETE\n    protocol          = #TO COMPLETE\n\n    default_action {\n      target_group_arn = \"${aws_lb_target_group.webserver_lb_target.arn}\"\n      type             = #TO COMPLETE\n    }\n  }\n\n  resource \"aws_lb_target_group_attachment\" \"webserver_lb_tg_attachment\" {\n    target_group_arn = \"${aws_lb_target_group.webserver_lb_target.arn}\"\n    target_id        = #TO COMPLETE\n    port             = #TO COMPLETE\n  }\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint for `aws_lb_target_group`\u003c/summary\u003e\n\n  * name: take exemple from `aws_lb` name parameter\n  * port: lb_listener port\n  * protocol: lb_listener protocol\n  * vpc_id: take a look at available variables\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint for `aws_lb_listener`\u003c/summary\u003e\n\n  * port: lb_listener port\n  * protocol: lb_listener protocol\n  * default_action.type: The listener **forward** traffic to target groups\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint for `aws_lb_target_group_attachment`\u003c/summary\u003e\n\n  * target_id: id of the aws instance described in `aws_instance` resource\n  * port: lb_listener port\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eSolution\u003c/summary\u003e\n\nAdd this to `main.tf`:\n\n  ```\n  resource \"aws_lb_listener\" \"webserver_lb_listener\" {\n    load_balancer_arn = \"${aws_lb.webserver_lb.arn}\"\n    port              = \"80\"\n    protocol          = \"HTTP\"\n\n    default_action {\n      target_group_arn = \"${aws_lb_target_group.webserver_lb_target.arn}\"\n      type             = \"forward\"\n    }\n  }\n\n  resource \"aws_lb_target_group\" \"webserver_lb_target\" {\n    name     = \"${random_string.unique_id.id}-${var.environment}-webserver-lb-tg\"\n    port     = 80\n    protocol = \"HTTP\"\n    vpc_id   = var.vpc_id\n  }\n\n  resource \"aws_lb_target_group_attachment\" \"webserver_lb_tg_attachment\" {\n    target_group_arn = \"${aws_lb_target_group.webserver_lb_target.arn}\"\n    target_id        = \"${aws_instance.webserver.id}\"\n    port             = 80\n  }\n  ```\n\u003c/details\u003e\n\n5. Change `output.fr` to :\n   * Comment the `ssh` output and `private_key_pem`. (not useful for now)\n   * Modify the `webserver` output to get the Load balancer DNS name instead\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint\u003c/summary\u003e\n\n  value for `webserver` output: `\"http://${aws_lb.webserver_lb.dns_name}\"`\n\u003c/details\u003e\n\n\nApply changes !\n\nIf everything goes well, you now have a load balancer created.\n\nLook for the one with your random prefix in its name.\n\n```\ncurl http://RtIr69Zk-dev-webserver-lb-1866337979.eu-west-3.elb.amazonaws.com\n\u003ch2\u003eThis is the dev environment \u003ch2\u003e\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eSolutions Step 2\u003c/summary\u003e\n\nYou can find solutions here:\n  * [solution/step_2/main.tf](./solution/step_2/main.tf)\n  * [solution/step_2/output.tf](./solution/step_2/output.tf)\n\u003c/details\u003e\n\n## Step 3 : Network filtering\n\nIt 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!\n\n1. Add a variable `ip_whitelist` of type `list` in `variables.tf` and modify `prd.tfvars` and `dev.tfvars` accordingly\n\n\u003cdetails\u003e\n  \u003csummary\u003eHint\u003c/summary\u003e\n\n  Take example from other variables to define `ip_whitelist` in `variables.tf`.\n  Variable created should be define in both `prd.tfvars` and `dev.tfvars` but the list should contain an IP only for the dev environment.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eSolution\u003c/summary\u003e\n\n* `prd.tfvars` : `ip_whitelist = []`\n* `dev.tfvars` : `ip_whitelist = ['\u003cyour_ip\u003e/32']`\n\u003c/details\u003e\n\n1. 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.\n\n\u003cdetails\u003e\n  \u003csummary\u003eSolution\u003c/summary\u003e\n\nYou can find solution here:\n  [solution/step_3/network.tf](./solution/step_3/network.tf)\n\u003c/details\u003e\n\n# Finally : Apply to production environment\n\nThe beauty of terraform is that you can apply the changes to `prd` environment in a jiffy :\n\n```\nterraform workspace select prd\nterraform apply -var-file $(terraform workspace show).tfvars\n```\n\n![final](./secure_architecture.png)\n\n# Bonus\n\nWe could improve the infrastructure much more, for example:\n\n* Have ASG (Auto Scaling Group) instead of only one instance to ensure high availability and scalability.\n\n* 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.\n\n# LICENSE\n\n© 2022 [Padok](https://www.padok.fr/).\n\nLicensed under the [Apache License](https://www.apache.org/licenses/LICENSE-2.0), Version 2.0 ([LICENSE](./LICENSE))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpadok-team%2Fdojo-terraform-aws-security","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpadok-team%2Fdojo-terraform-aws-security","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpadok-team%2Fdojo-terraform-aws-security/lists"}