feat(week8): JWT auth, SSM Parameter Store, and UI overhaul

This commit is contained in:
ikechukwu Elizabeth
2026-05-30 07:06:21 +01:00
parent 0de2d1dd42
commit dcaef8ff13
49 changed files with 8868 additions and 723 deletions
+51 -12
View File
@@ -1,6 +1,6 @@
# ──────────────────────────────────────────────
# Data Source — Latest Amazon Linux 2023 AMI
# ──────────────────────────────────────────────
# --------------------------------------------------
# Data Source - Latest Amazon Linux 2023 AMI
# --------------------------------------------------
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
@@ -16,9 +16,9 @@ data "aws_ami" "amazon_linux_2023" {
}
}
# ──────────────────────────────────────────────
# --------------------------------------------------
# EC2 Instance
# ──────────────────────────────────────────────
# --------------------------------------------------
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
@@ -48,6 +48,45 @@ resource "aws_instance" "app" {
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
# --------------------------------------------------
# Fetch secrets from SSM Parameter Store at runtime.
# Credentials are never stored in user data or code.
# --------------------------------------------------
DB_HOST=$(aws ssm get-parameter \
--name "/${var.project_name}/db_host" \
--with-decryption \
--query "Parameter.Value" \
--output text \
--region ${var.aws_region})
DB_NAME=$(aws ssm get-parameter \
--name "/${var.project_name}/db_name" \
--with-decryption \
--query "Parameter.Value" \
--output text \
--region ${var.aws_region})
DB_USER=$(aws ssm get-parameter \
--name "/${var.project_name}/db_username" \
--with-decryption \
--query "Parameter.Value" \
--output text \
--region ${var.aws_region})
DB_PASSWORD=$(aws ssm get-parameter \
--name "/${var.project_name}/db_password" \
--with-decryption \
--query "Parameter.Value" \
--output text \
--region ${var.aws_region})
JWT_SECRET=$(aws ssm get-parameter \
--name "/${var.project_name}/jwt_secret" \
--with-decryption \
--query "Parameter.Value" \
--output text \
--region ${var.aws_region})
# Authenticate to ECR
aws ecr get-login-password --region ${var.aws_region} | \
docker login --username AWS --password-stdin \
@@ -57,17 +96,17 @@ resource "aws_instance" "app" {
docker pull ${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-backend:latest
docker pull ${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-frontend:latest
# Run backend — DB credentials passed as environment variables
# Values come from Terraform variables which are sourced from GitHub Secrets
# Run backend with secrets fetched from SSM
docker run -d \
--name task-backend \
--restart unless-stopped \
-p 3000:3000 \
-e DB_HOST=${var.db_host} \
-e DB_HOST=$DB_HOST \
-e DB_PORT=5432 \
-e DB_NAME=${var.db_name} \
-e DB_USER=${var.db_username} \
-e DB_PASSWORD=${var.db_password} \
-e DB_NAME=$DB_NAME \
-e DB_USER=$DB_USER \
-e DB_PASSWORD=$DB_PASSWORD \
-e JWT_SECRET=$JWT_SECRET \
${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-backend:latest
# Wait for backend to be ready
@@ -102,4 +141,4 @@ resource "aws_instance" "app" {
lifecycle {
ignore_changes = [ami, user_data]
}
}
}
+37 -11
View File
@@ -1,9 +1,8 @@
# ──────────────────────────────────────────────
# IAM Role — EC2 instance identity
# ──────────────────────────────────────────────
# --------------------------------------------------
# IAM Role - EC2 instance identity
# --------------------------------------------------
resource "aws_iam_role" "ec2_role" {
name = "${var.project_name}-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
@@ -16,7 +15,6 @@ resource "aws_iam_role" "ec2_role" {
}
]
})
tags = {
Name = "${var.project_name}-ec2-role"
Project = var.project_name
@@ -24,9 +22,9 @@ resource "aws_iam_role" "ec2_role" {
}
}
# ──────────────────────────────────────────────
# --------------------------------------------------
# Policy Attachments
# ──────────────────────────────────────────────
# --------------------------------------------------
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ec2_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
@@ -37,16 +35,44 @@ resource "aws_iam_role_policy_attachment" "ecr" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
}
# ──────────────────────────────────────────────
# --------------------------------------------------
# Inline policy - read SSM parameters for this project
# Scoped to only the parameters this app needs.
# --------------------------------------------------
resource "aws_iam_role_policy" "ssm_parameters" {
name = "${var.project_name}-ssm-read"
role = aws_iam_role.ec2_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath"
]
Resource = "arn:aws:ssm:${var.aws_region}:${var.aws_account_id}:parameter/${var.project_name}/*"
},
{
Effect = "Allow"
Action = ["kms:Decrypt"]
Resource = "*"
}
]
})
}
# --------------------------------------------------
# Instance Profile
# ──────────────────────────────────────────────
# --------------------------------------------------
resource "aws_iam_instance_profile" "ec2_profile" {
name = "${var.project_name}-ec2-profile"
role = aws_iam_role.ec2_role.name
tags = {
Name = "${var.project_name}-ec2-profile"
Project = var.project_name
Environment = var.environment
}
}
}
+65
View File
@@ -0,0 +1,65 @@
# --------------------------------------------------
# SSM Parameter Store
# Stores all runtime secrets for the EC2 instance.
# EC2 fetches these at startup - no secrets in user data.
# --------------------------------------------------
resource "aws_ssm_parameter" "db_host" {
name = "/${var.project_name}/db_host"
description = "RDS instance hostname"
type = "SecureString"
value = var.db_host
tags = {
Project = var.project_name
Environment = var.environment
}
}
resource "aws_ssm_parameter" "db_name" {
name = "/${var.project_name}/db_name"
description = "PostgreSQL database name"
type = "SecureString"
value = var.db_name
tags = {
Project = var.project_name
Environment = var.environment
}
}
resource "aws_ssm_parameter" "db_username" {
name = "/${var.project_name}/db_username"
description = "PostgreSQL master username"
type = "SecureString"
value = var.db_username
tags = {
Project = var.project_name
Environment = var.environment
}
}
resource "aws_ssm_parameter" "db_password" {
name = "/${var.project_name}/db_password"
description = "PostgreSQL master password"
type = "SecureString"
value = var.db_password
tags = {
Project = var.project_name
Environment = var.environment
}
}
resource "aws_ssm_parameter" "jwt_secret" {
name = "/${var.project_name}/jwt_secret"
description = "JWT signing secret for authentication"
type = "SecureString"
value = var.jwt_secret
tags = {
Project = var.project_name
Environment = var.environment
}
}
+8 -2
View File
@@ -4,7 +4,7 @@ variable "aws_region" {
}
variable "aws_account_id" {
description = "AWS account ID — set in terraform.tfvars, never hardcoded"
description = "AWS account ID - set in terraform.tfvars, never hardcoded"
type = string
}
@@ -57,7 +57,13 @@ variable "db_password" {
}
variable "db_host" {
description = "RDS instance hostname — output from aws_db_instance.tasks.address"
description = "RDS instance hostname - output from aws_db_instance.tasks.address"
type = string
sensitive = true
}
variable "jwt_secret" {
description = "JWT signing secret for authentication"
type = string
sensitive = true
}