├── .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 | 
3 | ==============================
4 | 
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 |
--------------------------------------------------------------------------------