diff --git a/README.md b/README.md index 0ca273c..2c2f1b1 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,56 @@ It creates a Virtual Private Cloud (VPC) with public and private subnets, Intern - Create environment-specific variable files (dev.tfvars or demo.tfvars) with the following structure: ```hcl - region = "us-east-1" - profile = "dev" - vpc_name = "dev" - vpc_cidr = "10.0.0.0/16" + region = "us-east-1" + profile = "" + vpc_name = "" + vpc_cidr = "10.0.0.0/16" + app_port = 8081 + public_key_path = "~/.ssh/aws_key.pub" + name_prefix = "app-sg-dev" + ami_id = "" + subnet_tier = "public" + target_az = "us-east-1a" + + tags = { + Project = "" + Owner = "" + Env = "" + } ``` --- +## EC2 Instance + +This Terraform setup also provisions an EC2 instance inside the created VPC. + +EC2 Configuration Overview + +- AMI: Ubuntu 20.04 LTS (or your custom AMI) +- Instance Type: t3.micro +- Root Volume: 25 GB GP2 (auto deleted on termination) +- SSH Key Pair: Generated from your local public key (aws_key_pair) +- Subnet Placement: Dynamically selected by tier (public/private) and Availability Zone +- Security Group: + - Ingress: TCP 22 (SSH), 80 (HTTP), 443 (HTTPS), and 8081 (your web app port) + - Egress: All outbound traffic allowed + +### How It Works + +- The public key defined in public_key_path will be uploaded to AWS as an EC2 Key Pair. +You can later connect using: + + ```shell + ssh -i ~/.ssh/aws_key.pem ubuntu@ + ``` + +- The EC2 instance will be launched in the public subnet of the selected Availability Zone. +If you set subnet_tier = "private", it will launch in the private subnet instead (without public IP). +- The app_port (e.g. 8081) defines the custom application port opened in the security group. + +--- + ## 🚀 How to Deploy (with Terraform Workspaces) Workspaces let you maintain multiple, isolated sets of infrastructure (states) @@ -68,7 +110,7 @@ terraform workspace list You’ll see something like: -``` +```txt default * dev demo @@ -124,6 +166,10 @@ After deployment, Terraform prints key identifiers: - private_subnets - igw_id - route_tables +- chosen_subnet_id +- chosen_az +- application_sg_id +- instance_id You can also view them via: `terraform output` diff --git a/ec2.tf b/ec2.tf new file mode 100644 index 0000000..1207d2a --- /dev/null +++ b/ec2.tf @@ -0,0 +1,47 @@ +locals { + az_to_public_subnet_id = { + for az, s in aws_subnet.public : + az => s.id + } + + az_to_private_subnet_id = { + for az, s in aws_subnet.private : + az => s.id + } + + default_az = sort(keys(local.az_to_public_subnet_id))[0] + chosen_az = coalesce(var.target_az, local.default_az) + + chosen_subnet_id = (var.subnet_tier == "public" + ? lookup(local.az_to_public_subnet_id, local.chosen_az, null) + : lookup(local.az_to_private_subnet_id, local.chosen_az, null) + ) +} + +resource "aws_instance" "app" { + ami = var.ami_id + instance_type = var.instance_type + subnet_id = local.chosen_subnet_id + vpc_security_group_ids = [aws_security_group.app_sg.id] + + # assign ssh key + key_name = var.key_name != "" ? var.key_name : null + + # Do NOT protect from accidental termination + disable_api_termination = false + + # Root volume requirements + root_block_device { + volume_type = "gp2" + volume_size = 25 + delete_on_termination = true + } + + # ensure a public IP if your subnet doesn't auto-assign + associate_public_ip_address = var.subnet_tier == "public" ? true : false + + tags = { + Name = "${var.name_prefix}-ec2" + Role = "webapp" + } +} diff --git a/keypair.tf b/keypair.tf new file mode 100644 index 0000000..ebc3ae2 --- /dev/null +++ b/keypair.tf @@ -0,0 +1,6 @@ +resource "aws_key_pair" "app" { + key_name = var.key_name + public_key = file(var.public_key_path) + + tags = { Name = "aws_key" } +} diff --git a/outputs.tf b/outputs.tf index 0fc3494..9f0ee5f 100644 --- a/outputs.tf +++ b/outputs.tf @@ -24,3 +24,19 @@ output "route_tables" { private = aws_route_table.private.id } } + +output "chosen_subnet_id" { + value = local.chosen_subnet_id +} + +output "chosen_az" { + value = local.chosen_az +} + +output "application_sg_id" { + value = aws_security_group.app_sg.id +} + +output "instance_id" { + value = aws_instance.app.public_ip +} diff --git a/security-group.tf b/security-group.tf new file mode 100644 index 0000000..1b52961 --- /dev/null +++ b/security-group.tf @@ -0,0 +1,44 @@ +resource "aws_security_group" "app_sg" { + name = "${var.name_prefix}-sg" + description = "Web App SG: 22,80,443,app open to world" + vpc_id = aws_vpc.csye6225.id + + tags = { Name = "${var.name_prefix}-sg" } +} + +locals { + app_ingress_ports = ["22", 80, 443, var.app_port] +} + +resource "aws_vpc_security_group_ingress_rule" "ipv4" { + for_each = toset([for p in local.app_ingress_ports : tostring(p)]) + security_group_id = aws_security_group.app_sg.id + cidr_ipv4 = "0.0.0.0/0" + from_port = tonumber(each.value) + to_port = tonumber(each.value) + ip_protocol = "tcp" + description = "Allow TCP ${each.value} from anywhere (IPv4)" +} + +resource "aws_vpc_security_group_ingress_rule" "ipv6" { + for_each = var.enable_ipv6 ? toset([for p in local.app_ingress_ports : tostring(p)]) : toset([]) + security_group_id = aws_security_group.app_sg.id + cidr_ipv6 = "::/0" + from_port = tonumber(each.value) + to_port = tonumber(each.value) + ip_protocol = "tcp" + description = "Allow TCP ${each.value} from anywhere (IPv6)" +} + +# Egress (allow all traffic) +resource "aws_vpc_security_group_egress_rule" "all_out_ipv4" { + security_group_id = aws_security_group.app_sg.id + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "-1" +} +resource "aws_vpc_security_group_egress_rule" "all_out_ipv6" { + count = var.enable_ipv6 ? 1 : 0 + security_group_id = aws_security_group.app_sg.id + cidr_ipv6 = "::/0" + ip_protocol = "-1" +} diff --git a/variables.tf b/variables.tf index cff273e..e820903 100644 --- a/variables.tf +++ b/variables.tf @@ -18,6 +18,62 @@ variable "vpc_cidr" { type = string } +variable "instance_type" { + description = "EC2 instance type" + type = string + default = "t3.micro" +} + +variable "ami_id" { + description = "Your custom AMI ID for the instance" + type = string +} + +variable "subnet_tier" { + description = "Which tier to place EC2 in: public or private" + type = string + default = "public" + validation { + condition = contains(["public", "private"], var.subnet_tier) + error_message = "subnet_tier must be 'public' or 'private'." + } +} + +variable "target_az" { + description = "AZ for the EC2 (e.g., us-east-1a). If null, pick the first AZ you created." + type = string + default = null +} + +variable "key_name" { + description = "Existing EC2 key pair name (for SSH); leave empty to skip" + type = string + default = "" +} + +variable "app_port" { + description = "Port your application listens on" + type = number + default = 8081 +} + +variable "enable_ipv6" { + type = bool + default = true +} + +variable "public_key_path" { + description = "Path to your SSH public key (.pub)" + type = string + default = "~/.ssh/aws_key.pub" +} + +variable "name_prefix" { + description = "Name prefix for resources" + type = string + default = "app" +} + variable "tags" { description = "Common tags applied to all resources" type = map(string)