├── .github └── workflows │ └── terraform.yml ├── .gitignore ├── .terraform.lock.hcl ├── README.md ├── autoscaling.tf ├── backend.tf ├── docs ├── architecture.jpg └── waf.jpg ├── efs.tf ├── local.tf ├── output.tf ├── provider.tf ├── rds.tf ├── route_53.tf ├── staging.terraform.tfvars ├── templates └── user_data.tpl ├── variables.tf ├── vpc.tf └── waf.tf /.github/workflows/terraform.yml: -------------------------------------------------------------------------------- 1 | name: 'Terraform' 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - master 8 | push: 9 | branches: 10 | - master 11 | paths: 12 | - '*/**' 13 | permissions: 14 | id-token: write 15 | contents: read 16 | env: 17 | AWS_REGION: ap-south-1 18 | TF_VERSION: 1.2.6 19 | 20 | jobs: 21 | terraform: 22 | name: infrastructure-staging 23 | runs-on: ubuntu-latest 24 | 25 | defaults: 26 | run: 27 | shell: bash 28 | 29 | steps: 30 | - name: Configure AWS Credentials 31 | uses: aws-actions/configure-aws-credentials@v1 32 | with: 33 | aws-region: ${{ env.AWS_REGION }} 34 | role-to-assume: arn:aws:iam::434605749312:role/aws-deployment-tf 35 | role-session-name: staging-session 36 | - name: Checkout 37 | uses: actions/checkout@v3 38 | 39 | - name: Setup terraform 40 | uses: hashicorp/setup-terraform@v1 41 | with: 42 | terraform_version: ${{ env.TF_VERSION }} 43 | 44 | - name: Terraform Init 45 | run: terraform init 46 | 47 | - name: Terraform Format 48 | run: terraform fmt -check 49 | 50 | - name: Terraform Validate 51 | run: terraform validate -no-color 52 | 53 | - name: Terraform Plan 54 | run: terraform plan --var-file=staging.terraform.tfvars 55 | 56 | - name: Terraform Apply 57 | run: terraform apply --auto-approve --var-file=staging.terraform.tfvars 58 | 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | *.tfvars 17 | *.tfvars.json 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | -------------------------------------------------------------------------------- /.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "5.45.0" 6 | constraints = ">= 4.29.0" 7 | hashes = [ 8 | "h1:xFKE0MsBjV86pMpbrLbAHCzv5kREDYO0xt5LRZMeZn8=", 9 | "zh:1379bcf45aef3d486ee18b4f767bfecd40a0056510d26107f388be3d7994c368", 10 | "zh:1615a6f5495acfb3a0cb72324587261dd4d72711a3cc51aff13167b14531501e", 11 | "zh:18b69a0f33f8b1862fbd3f200756b7e83e087b73687085f2cf9c7da4c318e3e6", 12 | "zh:2c5e7aecd197bc3d3b19290bad8cf4c390c2c6a77bb165da4e11f53f2dfe2e54", 13 | "zh:3794da9bef97596e3bc60e12cdd915bda5ec2ed62cd1cd93723d58b4981905fe", 14 | "zh:40a5e45ed91801f83db76dffd467dcf425ea2ca8642327cf01119601cb86021c", 15 | "zh:4abfc3f53d0256a7d5d1fa5e931e4601b02db3d1da28f452341d3823d0518f1a", 16 | "zh:4eb0e98078f79aeb06b5ff6115286dc2135d12a80287885698d04036425494a2", 17 | "zh:75470efbadea4a8d783642497acaeec5077fc4a7f3df3340defeaa1c7de29bf7", 18 | "zh:8861a0b4891d5fa2fa7142f236ae613cea966c45b5472e3915a4ac3abcbaf487", 19 | "zh:8bf6f21cd9390b742ca0b4393fde92616ca9e6553fb75003a0999006ad233d35", 20 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 21 | "zh:ad73008a044e75d337acda910fb54d8b81a366873c8a413fec1291034899a814", 22 | "zh:bf261713b0b8bebfe8c199291365b87d9043849f28a2dc764bafdde73ae43693", 23 | "zh:da3bafa1fd830be418dfcc730e85085fe67c0d415c066716f2ac350a2306f40a", 24 | ] 25 | } 26 | 27 | provider "registry.terraform.io/hashicorp/random" { 28 | version = "3.6.0" 29 | constraints = ">= 3.6.0" 30 | hashes = [ 31 | "h1:p6WG1IPHnqx1fnJVKNjv733FBaArIugqy58HRZnpPCk=", 32 | "zh:03360ed3ecd31e8c5dac9c95fe0858be50f3e9a0d0c654b5e504109c2159287d", 33 | "zh:1c67ac51254ba2a2bb53a25e8ae7e4d076103483f55f39b426ec55e47d1fe211", 34 | "zh:24a17bba7f6d679538ff51b3a2f378cedadede97af8a1db7dad4fd8d6d50f829", 35 | "zh:30ffb297ffd1633175d6545d37c2217e2cef9545a6e03946e514c59c0859b77d", 36 | "zh:454ce4b3dbc73e6775f2f6605d45cee6e16c3872a2e66a2c97993d6e5cbd7055", 37 | "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", 38 | "zh:91df0a9fab329aff2ff4cf26797592eb7a3a90b4a0c04d64ce186654e0cc6e17", 39 | "zh:aa57384b85622a9f7bfb5d4512ca88e61f22a9cea9f30febaa4c98c68ff0dc21", 40 | "zh:c4a3e329ba786ffb6f2b694e1fd41d413a7010f3a53c20b432325a94fa71e839", 41 | "zh:e2699bc9116447f96c53d55f2a00570f982e6f9935038c3810603572693712d0", 42 | "zh:e747c0fd5d7684e5bfad8aa0ca441903f15ae7a98a737ff6aca24ba223207e2c", 43 | "zh:f1ca75f417ce490368f047b63ec09fd003711ae48487fba90b4aba2ccf71920e", 44 | ] 45 | } 46 | 47 | provider "registry.terraform.io/hashicorp/template" { 48 | version = "2.2.0" 49 | constraints = ">= 2.2.0" 50 | hashes = [ 51 | "h1:0wlehNaxBX7GJQnPfQwTNvvAf38Jm0Nv7ssKGMaG6Og=", 52 | "zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386", 53 | "zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53", 54 | "zh:09ba83c0625b6fe0a954da6fbd0c355ac0b7f07f86c91a2a97849140fea49603", 55 | "zh:0e3a6c8e16f17f19010accd0844187d524580d9fdb0731f675ffcf4afba03d16", 56 | "zh:45f2c594b6f2f34ea663704cc72048b212fe7d16fb4cfd959365fa997228a776", 57 | "zh:77ea3e5a0446784d77114b5e851c970a3dde1e08fa6de38210b8385d7605d451", 58 | "zh:8a154388f3708e3df5a69122a23bdfaf760a523788a5081976b3d5616f7d30ae", 59 | "zh:992843002f2db5a11e626b3fc23dc0c87ad3729b3b3cff08e32ffb3df97edbde", 60 | "zh:ad906f4cebd3ec5e43d5cd6dc8f4c5c9cc3b33d2243c89c5fc18f97f7277b51d", 61 | "zh:c979425ddb256511137ecd093e23283234da0154b7fa8b21c2687182d9aea8b2", 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zero-to-Hero-Deploying-a-Full-Stack-AWS-Architecture-Using-Terraform 2 | ![ARCH](docs/architecture.jpg) 3 | ============================== 4 | ![WAF](docs/waf.jpg) 5 | 6 | ## Requirements 7 | 8 | | Name | Version | 9 | |------|---------| 10 | | [terraform](#requirement\_terraform) | >= 0.15.0 | 11 | | [aws](#requirement\_aws) | >= 4.29.0 | 12 | | [random](#requirement\_random) | >= 3.6.0 | 13 | | [template](#requirement\_template) | >= 2.2.0 | 14 | 15 | ## Providers 16 | 17 | | Name | Version | 18 | |------|---------| 19 | | [aws](#provider\_aws) | >= 4.29.0 | 20 | | [random](#provider\_random) | >= 3.6.0 | 21 | | [template](#provider\_template) | >= 2.2.0 | 22 | 23 | ## Modules 24 | 25 | No modules. 26 | 27 | ## Resources 28 | 29 | | Name | Type | 30 | |------|------| 31 | | [aws_acm_certificate.example_cert](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate) | resource | 32 | | [aws_acm_certificate_validation.example_cert](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation) | resource | 33 | | [aws_autoscaling_group.app_asg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | resource | 34 | | [aws_autoscaling_policy.web_policy_down](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_policy) | resource | 35 | | [aws_autoscaling_policy.web_policy_up](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_policy) | resource | 36 | | [aws_cloudwatch_metric_alarm.web_cpu_alarm_down](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | 37 | | [aws_cloudwatch_metric_alarm.web_cpu_alarm_up](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource | 38 | | [aws_db_instance.db](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance) | resource | 39 | | [aws_db_subnet_group.rds_subnet_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_subnet_group) | resource | 40 | | [aws_efs_file_system.app_efs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system) | resource | 41 | | [aws_efs_mount_target.efs_mt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_mount_target) | resource | 42 | | [aws_eip.nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource | 43 | | [aws_iam_instance_profile.ssm_instance_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | 44 | | [aws_iam_role.ssm_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 45 | | [aws_iam_role_policy_attachment.ssm_policies](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 46 | | [aws_internet_gateway.gw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) | resource | 47 | | [aws_launch_template.app](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | 48 | | [aws_lb.app_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb) | resource | 49 | | [aws_lb_listener.front_end](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource | 50 | | [aws_lb_listener.https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource | 51 | | [aws_lb_target_group.app_tg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource | 52 | | [aws_nat_gateway.nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway) | resource | 53 | | [aws_route53_record.cert_validation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | 54 | | [aws_route53_record.www](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | 55 | | [aws_route53_zone.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | 56 | | [aws_route_table.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | 57 | | [aws_route_table.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | 58 | | [aws_route_table_association.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | 59 | | [aws_route_table_association.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | 60 | | [aws_security_group.alb_sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | 61 | | [aws_security_group.ec2_sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | 62 | | [aws_security_group.efs_sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | 63 | | [aws_security_group.rds_sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | 64 | | [aws_ssm_parameter.db_password](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | 65 | | [aws_subnet.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | 66 | | [aws_subnet.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | 67 | | [aws_vpc.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) | resource | 68 | | [aws_wafv2_ip_set.block_ip_set](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_ip_set) | resource | 69 | | [aws_wafv2_web_acl.main_acl](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl) | resource | 70 | | [aws_wafv2_web_acl_association.alb_assoc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_association) | resource | 71 | | [random_password.root_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | 72 | | [aws_kms_key.db_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_key) | data source | 73 | | [aws_route53_zone.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | 74 | | [template_file.user_data](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source | 75 | 76 | ## Inputs 77 | 78 | | Name | Description | Type | Default | Required | 79 | |------|-------------|------|---------|:--------:| 80 | | [alb\_sg\_egress\_rules](#input\_alb\_sg\_egress\_rules) | n/a | `any` | `{}` | no | 81 | | [alb\_sg\_ingress\_rules](#input\_alb\_sg\_ingress\_rules) | alb | `any` | `{}` | no | 82 | | [ami\_id](#input\_ami\_id) | The AMI ID to be used for the EC2 instances. | `string` | n/a | yes | 83 | | [ec2\_sg\_egress\_rules](#input\_ec2\_sg\_egress\_rules) | n/a | `any` | `{}` | no | 84 | | [ec2\_sg\_ingress\_rules](#input\_ec2\_sg\_ingress\_rules) | ec2 | `any` | `{}` | no | 85 | | [env](#input\_env) | n/a | `string` | n/a | yes | 86 | | [instance\_type](#input\_instance\_type) | The EC2 instance type for the Auto Scaling Group. | `string` | n/a | yes | 87 | | [private\_subnets](#input\_private\_subnets) | A map of private subnet CIDR blocks keyed by their respective availability zones. | `map(string)` | n/a | yes | 88 | | [public\_subnets](#input\_public\_subnets) | A map of public subnet CIDR blocks keyed by their respective availability zones. | `map(string)` | n/a | yes | 89 | | [rds\_conf](#input\_rds\_conf) | n/a | `any` | `{}` | no | 90 | | [rds\_sg\_egress\_rules](#input\_rds\_sg\_egress\_rules) | n/a | `any` | n/a | yes | 91 | | [rds\_sg\_ingress\_rules](#input\_rds\_sg\_ingress\_rules) | rds | `any` | n/a | yes | 92 | | [region](#input\_region) | The AWS region where resources will be created. | `string` | n/a | yes | 93 | | [vpc\_conf](#input\_vpc\_conf) | #VPC | `map(any)` | `{}` | no | 94 | 95 | ## Outputs 96 | 97 | | Name | Description | 98 | |------|-------------| 99 | | [alb\_arn](#output\_alb\_arn) | The ARN of the Application Load Balancer | 100 | | [alb\_dns\_name](#output\_alb\_dns\_name) | The DNS name of the Application Load Balancer | 101 | | [db\_endpoint](#output\_db\_endpoint) | n/a | 102 | | [db\_id](#output\_db\_id) | n/a | 103 | | [db\_name](#output\_db\_name) | n/a | 104 | | [db\_password\_ssm](#output\_db\_password\_ssm) | n/a | 105 | | [db\_username](#output\_db\_username) | n/a | 106 | | [efs\_file\_system\_id](#output\_efs\_file\_system\_id) | n/a | 107 | | [security\_group\_id](#output\_security\_group\_id) | The ID of the security group attached to the ALB | 108 | | [target\_group\_arn](#output\_target\_group\_arn) | The ARN of the target group used with the ALB | 109 | | [waf\_web\_acl\_arn](#output\_waf\_web\_acl\_arn) | The ARN of the Web ACL associated with the ALB | 110 | 111 | 112 | 113 | You can find the video at [https://youtu.be/4C1Kle5MIo8](https://youtu.be/4C1Kle5MIo8) . 114 | -------------------------------------------------------------------------------- /autoscaling.tf: -------------------------------------------------------------------------------- 1 | 2 | #Auto Scaling Group and Launch Template with IAM Role 3 | resource "aws_iam_role" "ssm_role" { 4 | name = "${var.env}-ec2-ssm-role" 5 | 6 | assume_role_policy = jsonencode({ 7 | Version = "2012-10-17" 8 | Statement = [ 9 | { 10 | Action = "sts:AssumeRole" 11 | Principal = { 12 | Service = "ec2.amazonaws.com" 13 | } 14 | Effect = "Allow" 15 | Sid = "" 16 | }, 17 | ] 18 | }) 19 | 20 | tags = { 21 | Name = "${var.env}-SSMRole" 22 | } 23 | } 24 | resource "aws_iam_instance_profile" "ssm_instance_profile" { 25 | name = "${var.env}-ec2-ssm-instance-profile" 26 | role = aws_iam_role.ssm_role.name 27 | } 28 | 29 | resource "aws_iam_role_policy_attachment" "ssm_policies" { 30 | for_each = local.ssm_policies 31 | role = aws_iam_role.ssm_role.name 32 | policy_arn = each.value 33 | 34 | # Each key is used to create a unique name context for the resource 35 | # This helps in identifying the policy attached clearly in the Terraform state file. 36 | } 37 | 38 | resource "aws_launch_template" "app" { 39 | name_prefix = "${var.env}-lt-" 40 | image_id = var.ami_id 41 | instance_type = var.instance_type 42 | 43 | iam_instance_profile { 44 | arn = aws_iam_instance_profile.ssm_instance_profile.arn 45 | } 46 | monitoring { 47 | enabled = true 48 | } 49 | network_interfaces { 50 | associate_public_ip_address = false 51 | subnet_id = tolist(values(aws_subnet.private))[0].id 52 | security_groups = [aws_security_group.ec2_sg.id] 53 | } 54 | 55 | user_data = base64encode(data.template_file.user_data.rendered) 56 | 57 | tag_specifications { 58 | resource_type = "instance" 59 | tags = { 60 | Name = "${var.env}-instance" 61 | } 62 | } 63 | lifecycle { 64 | create_before_destroy = true 65 | 66 | } 67 | update_default_version = true 68 | 69 | } 70 | 71 | data "template_file" "user_data" { 72 | template = file("templates/user_data.tpl") 73 | vars = { 74 | efs_file_system_id = aws_efs_file_system.app_efs.id 75 | } 76 | } 77 | resource "aws_lb" "app_alb" { 78 | name = "${var.env}-alb" 79 | internal = false 80 | load_balancer_type = "application" 81 | security_groups = [aws_security_group.alb_sg.id] 82 | subnets = values(aws_subnet.public).*.id 83 | 84 | tags = { 85 | Name = "${var.env}-ApplicationLB" 86 | } 87 | } 88 | 89 | resource "aws_lb_target_group" "app_tg" { 90 | name = "${var.env}-tg" 91 | port = 80 92 | protocol = "HTTP" 93 | vpc_id = aws_vpc.main.id 94 | 95 | health_check { 96 | enabled = true 97 | interval = 30 98 | path = "/" 99 | protocol = "HTTP" 100 | timeout = 5 101 | healthy_threshold = 2 102 | unhealthy_threshold = 2 103 | matcher = "200-299" 104 | } 105 | 106 | tags = { 107 | Name = "${var.env}-TargetGroup" 108 | } 109 | } 110 | 111 | resource "aws_lb_listener" "front_end" { 112 | load_balancer_arn = aws_lb.app_alb.arn 113 | port = 80 114 | protocol = "HTTP" 115 | 116 | default_action { 117 | type = "redirect" 118 | 119 | redirect { 120 | port = "443" 121 | protocol = "HTTPS" 122 | status_code = "HTTP_301" 123 | } 124 | } 125 | } 126 | resource "aws_lb_listener" "https" { 127 | load_balancer_arn = aws_lb.app_alb.arn 128 | port = 443 129 | protocol = "HTTPS" 130 | ssl_policy = "ELBSecurityPolicy-2016-08" # Update as needed 131 | certificate_arn = aws_acm_certificate.example_cert.arn 132 | 133 | default_action { 134 | type = "forward" 135 | target_group_arn = aws_lb_target_group.app_tg.arn 136 | } 137 | } 138 | 139 | 140 | resource "aws_security_group" "alb_sg" { 141 | name = "${var.env}-alb-sg" 142 | description = "Security group for the application load balancer" 143 | vpc_id = aws_vpc.main.id 144 | dynamic "ingress" { 145 | for_each = var.alb_sg_ingress_rules 146 | content { 147 | description = format("Allow access for %s", ingress.key) 148 | from_port = ingress.value.from_port 149 | to_port = ingress.value.to_port 150 | protocol = lookup(ingress.value, "protocol", "tcp") 151 | cidr_blocks = lookup(ingress.value, "cidr_blocks", []) 152 | security_groups = lookup(ingress.value, "security_groups", []) 153 | } 154 | } 155 | dynamic "egress" { 156 | for_each = var.alb_sg_egress_rules 157 | content { 158 | description = format("Allow access for %s", egress.key) 159 | from_port = egress.value.from_port 160 | to_port = egress.value.to_port 161 | protocol = lookup(egress.value, "protocol", "tcp") 162 | cidr_blocks = lookup(egress.value, "cidr_blocks", []) 163 | security_groups = lookup(egress.value, "security_groups", []) 164 | } 165 | } 166 | tags = { 167 | Name = "${var.env}-ALBSecurityGroup" 168 | } 169 | } 170 | 171 | resource "aws_autoscaling_group" "app_asg" { 172 | launch_template { 173 | id = aws_launch_template.app.id 174 | version = aws_launch_template.app.latest_version 175 | } 176 | 177 | min_size = 2 178 | max_size = 3 179 | desired_capacity = 3 180 | vpc_zone_identifier = values(aws_subnet.private).*.id 181 | health_check_type = "ELB" 182 | target_group_arns = [aws_lb_target_group.app_tg.arn] 183 | lifecycle { 184 | create_before_destroy = true 185 | } 186 | enabled_metrics = [ 187 | "GroupMinSize", 188 | "GroupMaxSize", 189 | "GroupDesiredCapacity", 190 | "GroupInServiceInstances", 191 | "GroupTotalInstances" 192 | ] 193 | metrics_granularity = "1Minute" 194 | 195 | 196 | tag { 197 | key = "Name" 198 | value = "${var.env}-asg-instance" 199 | propagate_at_launch = true 200 | } 201 | } 202 | 203 | resource "aws_security_group" "ec2_sg" { 204 | name = "${var.env}-ec2-sg" 205 | description = "Security group for EC2 instances in the ASG" 206 | vpc_id = aws_vpc.main.id 207 | 208 | dynamic "ingress" { 209 | for_each = var.ec2_sg_ingress_rules 210 | content { 211 | description = format("Allow access for %s", ingress.key) 212 | from_port = ingress.value.from_port 213 | to_port = ingress.value.to_port 214 | protocol = lookup(ingress.value, "protocol", "tcp") 215 | cidr_blocks = lookup(ingress.value, "cidr_blocks", []) 216 | security_groups = lookup(ingress.value, "security_groups", []) 217 | } 218 | } 219 | dynamic "egress" { 220 | for_each = var.ec2_sg_egress_rules 221 | content { 222 | description = format("Allow access for %s", egress.key) 223 | from_port = egress.value.from_port 224 | to_port = egress.value.to_port 225 | protocol = lookup(egress.value, "protocol", "tcp") 226 | cidr_blocks = lookup(egress.value, "cidr_blocks", []) 227 | security_groups = lookup(egress.value, "security_groups", []) 228 | } 229 | } 230 | 231 | tags = { 232 | Name = "${var.env}-EC2SecurityGroup" 233 | } 234 | } 235 | 236 | resource "aws_autoscaling_policy" "web_policy_up" { 237 | 238 | name = "web_policy_up" 239 | scaling_adjustment = 1 240 | adjustment_type = "ChangeInCapacity" 241 | cooldown = 30 242 | autoscaling_group_name = aws_autoscaling_group.app_asg.name 243 | } 244 | 245 | resource "aws_cloudwatch_metric_alarm" "web_cpu_alarm_up" { 246 | alarm_name = "web_cpu_alarm_up" 247 | comparison_operator = "GreaterThanOrEqualToThreshold" 248 | evaluation_periods = "2" 249 | metric_name = "CPUUtilization" 250 | namespace = "AWS/EC2" 251 | period = "120" 252 | statistic = "Average" 253 | threshold = "20" 254 | dimensions = { 255 | AutoScalingGroupName = "${aws_autoscaling_group.app_asg.name}" 256 | } 257 | alarm_description = "This metric monitor EC2 instance CPU utilization" 258 | alarm_actions = ["${aws_autoscaling_policy.web_policy_up.arn}"] 259 | } 260 | 261 | resource "aws_autoscaling_policy" "web_policy_down" { 262 | name = "web_policy_down" 263 | scaling_adjustment = -1 264 | adjustment_type = "ChangeInCapacity" 265 | cooldown = 30 266 | autoscaling_group_name = aws_autoscaling_group.app_asg.name 267 | } 268 | resource "aws_cloudwatch_metric_alarm" "web_cpu_alarm_down" { 269 | alarm_name = "web_cpu_alarm_down" 270 | comparison_operator = "LessThanOrEqualToThreshold" 271 | evaluation_periods = "2" 272 | metric_name = "CPUUtilization" 273 | namespace = "AWS/EC2" 274 | period = "120" 275 | statistic = "Average" 276 | threshold = "10" 277 | dimensions = { 278 | AutoScalingGroupName = "${aws_autoscaling_group.app_asg.name}" 279 | } 280 | alarm_description = "This metric monitor EC2 instance CPU utilization" 281 | alarm_actions = ["${aws_autoscaling_policy.web_policy_down.arn}"] 282 | } 283 | -------------------------------------------------------------------------------- /backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "s3" { 3 | bucket = "tf-aws-architecture" 4 | key = "terraform.tfstate" 5 | region = "ap-south-1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravindrasinghh/Zero-to-Hero-Deploying-a-Full-Stack-AWS-Architecture-Using-Terraform/c34363009c4eae5f8e70b15d394208fdeeb8fbbb/docs/architecture.jpg -------------------------------------------------------------------------------- /docs/waf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravindrasinghh/Zero-to-Hero-Deploying-a-Full-Stack-AWS-Architecture-Using-Terraform/c34363009c4eae5f8e70b15d394208fdeeb8fbbb/docs/waf.jpg -------------------------------------------------------------------------------- /efs.tf: -------------------------------------------------------------------------------- 1 | resource "aws_efs_file_system" "app_efs" { 2 | creation_token = "appEFS" 3 | 4 | tags = { 5 | Name = "${var.env}-efs" 6 | } 7 | } 8 | 9 | resource "aws_efs_mount_target" "efs_mt" { 10 | for_each = { for idx, subnet in aws_subnet.private : idx => subnet } 11 | file_system_id = aws_efs_file_system.app_efs.id 12 | subnet_id = each.value.id 13 | security_groups = [aws_security_group.efs_sg.id] 14 | } 15 | 16 | resource "aws_security_group" "efs_sg" { 17 | name = "${var.env}-efs-sg" 18 | description = "Security group for EFS" 19 | vpc_id = aws_vpc.main.id 20 | 21 | ingress { 22 | from_port = 2049 23 | to_port = 2049 24 | protocol = "tcp" 25 | security_groups = [aws_security_group.ec2_sg.id] # Allowing NFS traffic from instance SG 26 | } 27 | 28 | egress { 29 | from_port = 0 30 | to_port = 0 31 | protocol = "-1" 32 | cidr_blocks = ["0.0.0.0/0"] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /local.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | ssm_policies = { 3 | "ssm_managed_instance_core" = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore", 4 | "ssm_full_access" = "arn:aws:iam::aws:policy/AmazonSSMFullAccess", 5 | "ec2_role_for_ssm" = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM" 6 | "session_manager" = "arn:aws:iam::aws:policy/AmazonSSMManagedEC2InstanceDefaultPolicy" 7 | } 8 | target_subnet_id = { for k, s in aws_subnet.public : k => s.id if s.cidr_block == "10.0.1.0/24" } 9 | } 10 | -------------------------------------------------------------------------------- /output.tf: -------------------------------------------------------------------------------- 1 | output "alb_arn" { 2 | value = aws_lb.app_alb.arn 3 | description = "The ARN of the Application Load Balancer" 4 | } 5 | 6 | output "alb_dns_name" { 7 | value = aws_lb.app_alb.dns_name 8 | description = "The DNS name of the Application Load Balancer" 9 | } 10 | 11 | output "waf_web_acl_arn" { 12 | value = aws_wafv2_web_acl.main_acl.arn 13 | description = "The ARN of the Web ACL associated with the ALB" 14 | } 15 | 16 | output "security_group_id" { 17 | value = aws_security_group.alb_sg.id 18 | description = "The ID of the security group attached to the ALB" 19 | } 20 | 21 | output "target_group_arn" { 22 | value = aws_lb_target_group.app_tg.arn 23 | description = "The ARN of the target group used with the ALB" 24 | } 25 | 26 | output "db_endpoint" { 27 | value = aws_db_instance.db.endpoint 28 | } 29 | 30 | output "db_username" { 31 | value = aws_db_instance.db.username 32 | } 33 | 34 | output "db_password_ssm" { 35 | value = aws_ssm_parameter.db_password.name 36 | } 37 | 38 | output "db_name" { 39 | value = aws_db_instance.db.identifier 40 | } 41 | 42 | output "db_id" { 43 | value = aws_db_instance.db.id 44 | } 45 | 46 | output "efs_file_system_id" { 47 | value = aws_efs_file_system.app_efs.id 48 | } 49 | -------------------------------------------------------------------------------- /provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "ap-south-1" 3 | allowed_account_ids = [434605749312] 4 | default_tags { 5 | tags = { 6 | environment = var.env 7 | managedby = "terraform" 8 | } 9 | } 10 | } 11 | terraform { 12 | required_version = ">= 0.15.0" 13 | required_providers { 14 | aws = { 15 | source = "hashicorp/aws" 16 | version = ">= 4.29.0" 17 | } 18 | random = { 19 | source = "hashicorp/random" 20 | version = ">= 3.6.0" 21 | } 22 | template = { 23 | source = "hashicorp/template" 24 | version = ">= 2.2.0" 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /rds.tf: -------------------------------------------------------------------------------- 1 | data "aws_kms_key" "db_kms_key" { 2 | key_id = "alias/aws/rds" 3 | } 4 | resource "aws_db_subnet_group" "rds_subnet_group" { 5 | name = "${var.env}-rds-ssubnet-group" 6 | subnet_ids = [for s in aws_subnet.private : s.id] 7 | 8 | tags = { 9 | Name = "My DB Subnet Group in ${var.env}" 10 | } 11 | } 12 | 13 | resource "random_password" "root_password" { 14 | length = 16 15 | special = false 16 | min_numeric = 5 17 | } 18 | 19 | resource "aws_db_instance" "db" { 20 | depends_on = [aws_db_subnet_group.rds_subnet_group] 21 | identifier = "${var.env}-rds" 22 | allocated_storage = var.rds_conf.allocated_storage 23 | storage_type = var.rds_conf.storage_type 24 | engine = var.rds_conf.engine 25 | engine_version = var.rds_conf.engine_version 26 | instance_class = var.rds_conf.instance_class 27 | multi_az = var.rds_conf.multi_az 28 | username = var.rds_conf.username 29 | password = aws_ssm_parameter.db_password.value 30 | storage_encrypted = var.rds_conf.storage_encrypted 31 | kms_key_id = var.rds_conf.storage_encrypted == true ? data.aws_kms_key.db_kms_key.arn : null 32 | vpc_security_group_ids = ["${aws_security_group.rds_sg.id}"] 33 | db_subnet_group_name = aws_db_subnet_group.rds_subnet_group.name 34 | publicly_accessible = var.rds_conf.publicly_accessible 35 | backup_retention_period = var.rds_conf.backup_retention_period 36 | skip_final_snapshot = true 37 | } 38 | 39 | resource "aws_ssm_parameter" "db_password" { 40 | name = "/rds/${var.env}-rds/password" 41 | value = var.rds_conf.multi_az == true ? random_password.root_password.result : "test" 42 | type = "SecureString" 43 | key_id = "alias/aws/ssm" 44 | } 45 | resource "aws_security_group" "rds_sg" { 46 | name = "${var.env}-rds-sg" 47 | description = "Security group for rds instances in the ASG" 48 | vpc_id = aws_vpc.main.id 49 | 50 | dynamic "ingress" { 51 | for_each = var.rds_sg_ingress_rules 52 | content { 53 | description = format("Allow access for %s", ingress.key) 54 | from_port = ingress.value.from_port 55 | to_port = ingress.value.to_port 56 | protocol = lookup(ingress.value, "protocol", "tcp") 57 | cidr_blocks = lookup(ingress.value, "cidr_blocks", []) 58 | security_groups = lookup(ingress.value, "security_groups", []) 59 | } 60 | } 61 | dynamic "egress" { 62 | for_each = var.rds_sg_egress_rules 63 | content { 64 | description = format("Allow access for %s", egress.key) 65 | from_port = egress.value.from_port 66 | to_port = egress.value.to_port 67 | protocol = lookup(egress.value, "protocol", "tcp") 68 | cidr_blocks = lookup(egress.value, "cidr_blocks", []) 69 | security_groups = lookup(egress.value, "security_groups", []) 70 | } 71 | } 72 | 73 | tags = { 74 | Name = "${var.env}-EC2SecurityGroup" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /route_53.tf: -------------------------------------------------------------------------------- 1 | data "aws_route53_zone" "main" { 2 | name = "codedevops.cloud." # Ensure the domain name ends with a dot 3 | depends_on = [aws_route53_zone.main] 4 | } 5 | resource "aws_route53_zone" "main" { 6 | name = "codedevops.cloud" 7 | } 8 | resource "aws_route53_record" "www" { 9 | zone_id = aws_route53_zone.main.zone_id 10 | name = "www.codedevops.cloud" 11 | type = "A" 12 | 13 | alias { 14 | name = aws_lb.app_alb.dns_name 15 | zone_id = aws_lb.app_alb.zone_id 16 | evaluate_target_health = true 17 | } 18 | } 19 | resource "aws_acm_certificate" "example_cert" { 20 | domain_name = "www.codedevops.cloud" 21 | validation_method = "DNS" 22 | 23 | subject_alternative_names = ["*.codedevops.cloud"] 24 | 25 | tags = { 26 | Environment = "production" 27 | } 28 | 29 | lifecycle { 30 | create_before_destroy = true 31 | } 32 | } 33 | resource "aws_route53_record" "cert_validation" { 34 | for_each = { 35 | for dvo in aws_acm_certificate.example_cert.domain_validation_options : dvo.domain_name => { 36 | name = dvo.resource_record_name 37 | record = dvo.resource_record_value 38 | type = dvo.resource_record_type 39 | } 40 | } 41 | 42 | zone_id = data.aws_route53_zone.main.id 43 | name = each.value.name 44 | type = each.value.type 45 | records = [each.value.record] 46 | ttl = 60 47 | } 48 | 49 | resource "aws_acm_certificate_validation" "example_cert" { 50 | certificate_arn = aws_acm_certificate.example_cert.arn 51 | validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn] 52 | depends_on = [aws_route53_record.cert_validation] 53 | 54 | } 55 | -------------------------------------------------------------------------------- /staging.terraform.tfvars: -------------------------------------------------------------------------------- 1 | # Basic Environment Settings 2 | env = "staging" 3 | vpc_conf = { 4 | cidr = "10.0.0.0/16" 5 | instance_tenancy = "default" 6 | enable_dns_hostnames = true 7 | enable_dns_support = true 8 | } 9 | region = "ap-south-1" 10 | 11 | # Subnet Configurations - Specify CIDR blocks and map them to availability zones 12 | public_subnets = { 13 | "ap-south-1a" = "10.0.1.0/24" 14 | "ap-south-1b" = "10.0.2.0/24" 15 | } 16 | 17 | private_subnets = { 18 | "ap-south-1a" = "10.0.3.0/24" 19 | "ap-south-1b" = "10.0.4.0/24" 20 | } 21 | 22 | # EC2 and AMI Configuration 23 | instance_type = "t3a.medium" 24 | ami_id = "ami-09298640a92b2d12c" # Replace with a valid AMI ID for your region 25 | 26 | 27 | #ALB 28 | 29 | alb_sg_ingress_rules = { 30 | https = { 31 | from_port = 443 32 | to_port = 443 33 | cidr_blocks = ["0.0.0.0/0"] 34 | }, 35 | http = { 36 | from_port = 80 37 | to_port = 80 38 | cidr_blocks = ["0.0.0.0/0"] 39 | } 40 | } 41 | alb_sg_egress_rules = { 42 | all = { 43 | from_port = 0 44 | to_port = 0 45 | protocol = "-1" 46 | cidr_blocks = ["0.0.0.0/0"] 47 | } 48 | } 49 | ec2_sg_ingress_rules = { 50 | https = { 51 | from_port = 443 52 | to_port = 443 53 | cidr_blocks = ["0.0.0.0/0"] 54 | }, 55 | http = { 56 | from_port = 80 57 | to_port = 80 58 | cidr_blocks = ["0.0.0.0/0"] 59 | } 60 | } 61 | ec2_sg_egress_rules = { 62 | all = { 63 | from_port = 0 64 | to_port = 0 65 | protocol = "-1" 66 | cidr_blocks = ["0.0.0.0/0"] 67 | } 68 | } 69 | rds_conf = { 70 | instance_class = "db.t3.xlarge" 71 | engine = "mysql" 72 | engine_version = "8.0.35" 73 | allocated_storage = 20 74 | storage_type = "gp2" 75 | multi_az = true 76 | username = "admin" 77 | db_name = "mydb" 78 | storage_encrypted = true 79 | publicly_accessible = false 80 | backup_retention_period = 7 81 | } 82 | rds_sg_ingress_rules = { 83 | https = { 84 | from_port = 3306 85 | to_port = 3306 86 | cidr_blocks = ["0.0.0.0/0"] 87 | } 88 | } 89 | rds_sg_egress_rules = { 90 | https = { 91 | from_port = 0 92 | to_port = 0 93 | protocol = "-1" 94 | cidr_blocks = ["0.0.0.0/0"] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /templates/user_data.tpl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Install Apache HTTP Server 3 | yum install httpd stress amazon-efs-utils nfs-utils -y 4 | 5 | 6 | # Start the httpd service 7 | systemctl start httpd 8 | systemctl enable httpd 9 | 10 | # Ensure the directory exists and set permissions 11 | mkdir -p /var/www/html/ 12 | chmod 755 /var/www/html 13 | 14 | 15 | # Create a mount point 16 | mkdir /mnt/efs 17 | mount -t efs ${efs_file_system_id}:/ /mnt/efs 18 | echo '${efs_file_system_id}:/ /mnt/efs efs defaults,_netdev 0 0' >> /etc/fstab 19 | 20 | # Fetch the hostname of the instance 21 | hostname=$(hostname) 22 | 23 | # Create an index.html file and write the hostname to it 24 | echo "Welcome to the world from $hostname" > /var/www/html/index.html 25 | 26 | # Ensure the httpd service is enabled on boot 27 | chkconfig httpd on 28 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "env" { 2 | type = string 3 | } 4 | ##VPC 5 | variable "vpc_conf" { 6 | type = map(any) 7 | default = {} 8 | } 9 | 10 | variable "region" { 11 | description = "The AWS region where resources will be created." 12 | type = string 13 | } 14 | variable "public_subnets" { 15 | description = "A map of public subnet CIDR blocks keyed by their respective availability zones." 16 | type = map(string) 17 | } 18 | 19 | variable "private_subnets" { 20 | description = "A map of private subnet CIDR blocks keyed by their respective availability zones." 21 | type = map(string) 22 | } 23 | 24 | variable "instance_type" { 25 | description = "The EC2 instance type for the Auto Scaling Group." 26 | type = string 27 | } 28 | 29 | variable "ami_id" { 30 | description = "The AMI ID to be used for the EC2 instances." 31 | type = string 32 | } 33 | 34 | 35 | 36 | #alb 37 | variable "alb_sg_ingress_rules" { 38 | type = any 39 | default = {} 40 | } 41 | variable "alb_sg_egress_rules" { 42 | type = any 43 | default = {} 44 | } 45 | #ec2 46 | variable "ec2_sg_ingress_rules" { 47 | type = any 48 | default = {} 49 | } 50 | variable "ec2_sg_egress_rules" { 51 | type = any 52 | default = {} 53 | } 54 | 55 | variable "rds_conf" { 56 | type = any 57 | default = {} 58 | } 59 | 60 | #rds 61 | variable "rds_sg_ingress_rules" { 62 | type = any 63 | } 64 | variable "rds_sg_egress_rules" { 65 | type = any 66 | } 67 | -------------------------------------------------------------------------------- /vpc.tf: -------------------------------------------------------------------------------- 1 | #VPC and Subnet Configuration 2 | 3 | resource "aws_vpc" "main" { 4 | cidr_block = var.vpc_conf.cidr 5 | instance_tenancy = var.vpc_conf.instance_tenancy 6 | enable_dns_support = var.vpc_conf.enable_dns_support 7 | enable_dns_hostnames = var.vpc_conf.enable_dns_hostnames 8 | tags = { 9 | Name = "${var.env}-vpc" 10 | } 11 | } 12 | resource "aws_eip" "nat" { 13 | for_each = var.public_subnets 14 | 15 | vpc = tobool(true) 16 | } 17 | 18 | resource "aws_nat_gateway" "nat" { 19 | for_each = aws_eip.nat 20 | 21 | allocation_id = each.value.id 22 | subnet_id = values(local.target_subnet_id)[0] 23 | 24 | tags = { 25 | Name = "${var.env}-nat-gateway-${each.key}" 26 | } 27 | } 28 | resource "aws_route_table" "private" { 29 | for_each = var.private_subnets 30 | 31 | vpc_id = aws_vpc.main.id 32 | 33 | route { 34 | cidr_block = "0.0.0.0/0" 35 | gateway_id = aws_nat_gateway.nat[each.key].id 36 | } 37 | 38 | tags = { 39 | Name = "${var.env}-private-route-table-${each.key}" 40 | } 41 | } 42 | 43 | resource "aws_route_table_association" "private" { 44 | for_each = aws_subnet.private 45 | 46 | subnet_id = each.value.id 47 | route_table_id = aws_route_table.private[each.key].id 48 | } 49 | 50 | 51 | 52 | resource "aws_subnet" "public" { 53 | for_each = var.public_subnets 54 | 55 | vpc_id = aws_vpc.main.id 56 | cidr_block = each.value 57 | map_public_ip_on_launch = true 58 | availability_zone = each.key 59 | 60 | tags = { 61 | Name = "${var.env}-public-${each.key}" 62 | } 63 | } 64 | 65 | resource "aws_subnet" "private" { 66 | for_each = var.private_subnets 67 | 68 | vpc_id = aws_vpc.main.id 69 | cidr_block = each.value 70 | map_public_ip_on_launch = false 71 | availability_zone = each.key 72 | 73 | tags = { 74 | Name = "${var.env}-private-${each.key}" 75 | } 76 | } 77 | #Internet Gateway and Routing 78 | 79 | 80 | resource "aws_internet_gateway" "gw" { 81 | vpc_id = aws_vpc.main.id 82 | 83 | 84 | tags = { 85 | Name = "${var.env}-igw" 86 | } 87 | } 88 | 89 | resource "aws_route_table" "public" { 90 | vpc_id = aws_vpc.main.id 91 | 92 | route { 93 | cidr_block = "0.0.0.0/0" 94 | gateway_id = aws_internet_gateway.gw.id 95 | } 96 | 97 | tags = { 98 | Name = "${var.env}-public-rtb" 99 | } 100 | } 101 | 102 | resource "aws_route_table_association" "public" { 103 | for_each = aws_subnet.public 104 | 105 | subnet_id = each.value.id 106 | route_table_id = aws_route_table.public.id 107 | } 108 | -------------------------------------------------------------------------------- /waf.tf: -------------------------------------------------------------------------------- 1 | 2 | resource "aws_wafv2_ip_set" "block_ip_set" { 3 | name = "${var.env}-block-ip-set" 4 | scope = "REGIONAL" # Use REGIONAL for ALBs 5 | ip_address_version = "IPV4" 6 | addresses = [ 7 | "106.214.95.140/32" 8 | ] 9 | 10 | description = "IP Set for blocking specific IP addresses" 11 | } 12 | resource "aws_wafv2_web_acl" "main_acl" { 13 | name = "${var.env}-web-acl" 14 | scope = "REGIONAL" 15 | description = "Web ACL with IP blocking rules for the ALB" 16 | 17 | default_action { 18 | allow {} # Default action is to allow requests 19 | } 20 | 21 | rule { 22 | name = "BlockSpecificIPs" 23 | priority = 1 24 | action { 25 | block {} 26 | } 27 | statement { 28 | ip_set_reference_statement { 29 | arn = aws_wafv2_ip_set.block_ip_set.arn 30 | } 31 | } 32 | visibility_config { 33 | cloudwatch_metrics_enabled = true 34 | metric_name = "BlockSpecificIPs" 35 | sampled_requests_enabled = true 36 | } 37 | } 38 | 39 | visibility_config { 40 | cloudwatch_metrics_enabled = true 41 | metric_name = "${var.env}-web-acl" 42 | sampled_requests_enabled = true 43 | } 44 | 45 | tags = { 46 | Name = "${var.env}-WebACL" 47 | } 48 | } 49 | resource "aws_wafv2_web_acl_association" "alb_assoc" { 50 | resource_arn = aws_lb.app_alb.arn 51 | web_acl_arn = aws_wafv2_web_acl.main_acl.arn 52 | depends_on = [aws_lb.app_alb] 53 | } 54 | --------------------------------------------------------------------------------