feat: AWS infrastructure setup with Terraform
Infrastructure components: - VPC with single public subnet (10.0.0.0/16) - Security group (SSH/HTTP/HTTPS from anywhere) - EC2 instance (t3.medium, Ubuntu 24.04, 30GB encrypted gp3) - S3 bucket for backups (versioned, encrypted) - IAM role with S3FullAccess for EC2 - Route 53 DNS (gitea.poll-streams.com → EC2) - Ed25519 SSH key generation via Terraform
This commit is contained in:
parent
ee5e319dad
commit
e5069332e5
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# Terraform
|
||||
.terraform/
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
*.tfvars
|
||||
.terraform.tfstate.lock.info
|
||||
|
||||
# Ansible
|
||||
*.retry
|
||||
|
||||
# SSH keys
|
||||
*.pem
|
||||
*.key
|
||||
id_rsa*
|
||||
ssh-keys/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
55
ROADMAP.md
55
ROADMAP.md
@ -48,37 +48,38 @@ This phase will be achieved through discussion and research and will include the
|
||||
|
||||
## Phase 2: Infrastructure Setup
|
||||
|
||||
This phase focuses on preparing the hosting environment and basic infrastructure components.
|
||||
This phase provisions the AWS infrastructure using Terraform.
|
||||
|
||||
### 2.1 AWS Account Setup
|
||||
- Configure AWS CLI and credentials
|
||||
- Set up Terraform backend (S3 + DynamoDB for state locking)
|
||||
- Create Route 53 hosted zone for domain
|
||||
- Set up budget alerts and cost monitoring
|
||||
### 2.1 Terraform Backend Setup ✅
|
||||
- Configure AWS CLI and credentials locally
|
||||
- Set up Terraform backend (S3 bucket for state storage)
|
||||
- Initialize Terraform working directory
|
||||
|
||||
### 2.2 Terraform Infrastructure Foundation
|
||||
- Create VPC with public/private subnets
|
||||
- Set up Internet Gateway and NAT Gateway (if needed)
|
||||
- Configure Security Groups (EC2, RDS, ALB rules)
|
||||
- Provision EC2 instance with appropriate IAM role
|
||||
- Set up RDS PostgreSQL instance
|
||||
- Configure Route 53 DNS records
|
||||
- Install Docker and Docker Compose on EC2 (user data script)
|
||||
### 2.2 Core Infrastructure ✅
|
||||
- ✅ Create VPC with single public subnet
|
||||
- ✅ Set up Internet Gateway
|
||||
- ✅ Configure Security Group for EC2 (ports 22, 80, 443)
|
||||
- ✅ Provision EC2 instance (t3.medium, Ubuntu 24.04) with IAM role
|
||||
- ✅ Create S3 bucket for backups (with versioning & encryption)
|
||||
- ✅ Configure Route 53 DNS records (A record: gitea.poll-streams.com → EC2)
|
||||
- ✅ Use official Terraform AWS modules (VPC, Security Group)
|
||||
- ✅ Refactored into separate files: main.tf, vpc.tf, security.tf, compute.tf, storage.tf, iam.tf, dns.tf, outputs.tf
|
||||
|
||||
### 2.3 Security Hardening
|
||||
- Configure SSH key-based authentication only
|
||||
- Set up bastion host or Systems Manager Session Manager
|
||||
- Enable VPC Flow Logs
|
||||
- Configure CloudTrail for audit logging
|
||||
- Apply least-privilege IAM policies
|
||||
### 2.3 Security Configuration ✅
|
||||
- ✅ Configure SSH key-based authentication (Ed25519, generated via Terraform)
|
||||
- ✅ SSH access from anywhere (0.0.0.0/0) - security via key-based auth
|
||||
- ✅ Apply IAM policies (AmazonS3FullAccess for EC2 backups)
|
||||
- ✅ Security group follows least access (only 22, 80, 443 inbound; all outbound)
|
||||
- ✅ Encrypted EBS root volume (30GB gp3)
|
||||
|
||||
### Goals:
|
||||
- AWS infrastructure provisioned via Terraform
|
||||
- EC2 instance running and accessible
|
||||
- RDS PostgreSQL ready
|
||||
- Domain DNS configured
|
||||
- Security hardening complete
|
||||
- Infrastructure can be destroyed and recreated from code
|
||||
### Goals: ✅
|
||||
- ✅ AWS infrastructure fully defined in Terraform code
|
||||
- ✅ EC2 instance provisioned and accessible via SSH
|
||||
- ✅ S3 backup bucket created
|
||||
- ✅ Domain DNS configured and resolving
|
||||
- ✅ Infrastructure can be destroyed and recreated with `terraform apply`
|
||||
|
||||
**Phase 2 Complete!** Ready to begin Phase 3 (Automated Gitea Deployment).
|
||||
|
||||
---
|
||||
|
||||
|
||||
67
terraform/.terraform.lock.hcl
generated
Normal file
67
terraform/.terraform.lock.hcl
generated
Normal file
@ -0,0 +1,67 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/aws" {
|
||||
version = "6.49.0"
|
||||
constraints = "6.49.0"
|
||||
hashes = [
|
||||
"h1:gW/1w7xNATTgTXKN9Du926VKZ84YgV6BLJDTPimMYkk=",
|
||||
"zh:11a636bb415bf780f0ad300cab83d687aebdc51381112ae7b29862e0bee43017",
|
||||
"zh:2d6c4bb861c073d9900a2afc39cc1e38492c6996653e53c7a2083b526fb10ae9",
|
||||
"zh:49f7ee4a7488f3d31342c5e9dbb577c40e0847a0cab152a0082e9aeef45f5c0f",
|
||||
"zh:561283c9c9bd36b9d09832e50769b941eb45c43c6ab031f27c8bf78256af4af1",
|
||||
"zh:576bf944e66d097b29fc45b25a5bbc53e7d4e71a486e2cb126304cf77b51fe79",
|
||||
"zh:6c6bf8860773c121b9ca22743f733feea943f890fa3aba8740a59579dea16fc4",
|
||||
"zh:88b963a659e42daac7384a6abb99b3383f9f6c8abad5dedafcd443536b122b84",
|
||||
"zh:947e9404235ca094e39e7f3f464f99436320a168e1607c550e581fbc70553d48",
|
||||
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||
"zh:a9c5a37bd1e5e81e85ad3dc3141f1b453b96e9cb8e500bc15bfb9c488fc95dcc",
|
||||
"zh:b7b8a028fb2eafb98c676f9438808318f7ff0ea76eba03a6653d0940848a31c7",
|
||||
"zh:be29f31827d6d5567aaec26034e6cceb994460446778ba1d0a435fc7ccd8a9f5",
|
||||
"zh:c24c60ecffe3a7d44c762e62a29a802aec604096715ad3110b7ca2207124eb3a",
|
||||
"zh:e2a247c5c7815437969e87cdce1f27f323eea75b97ed53f7605fef05d6406071",
|
||||
"zh:e590ce9aca3f4fd964f7bf908a24dc3bc88e0b03a8554ccc81b9edcc84690670",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/local" {
|
||||
version = "2.9.0"
|
||||
constraints = "2.9.0"
|
||||
hashes = [
|
||||
"h1:9rBZCMNpxKwMlRbWH2QpwD3kqUCAejdOZQ/aiiDObXQ=",
|
||||
"zh:0baa4566cf77f1ff52f4293d1c8536202dd23edc197c3196413a28343c3ac3a0",
|
||||
"zh:16b5559c3c07088ddad11a9bb9e9c0799999363c2958e9a5be2bcbbf2cd9ca64",
|
||||
"zh:197c79015a10d1cce904a8ea722cbc750c42aeae2da53f44a6a0751d9fd1aa90",
|
||||
"zh:29d0b03e5343a80677ebfeb2e2c31cbe4b1f65e736e53417454a4277fec2544c",
|
||||
"zh:4896bfa6cf1d2fd562b47ef2e87f47862ae92a04f8ad5d764380f0c6653473b8",
|
||||
"zh:531f8529cbca49f681883e57761a05a8398afaef6d1ab0d205d26bf12f4428e8",
|
||||
"zh:6aaf5011d83161c86d2bfb80c0923ec934e578288758da2f37acb7aec129004b",
|
||||
"zh:7430275253d3d3c40aa6179e0ec0d63212874dbbc06c5a51b9d07ec590f9756c",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:be17dc611e95e26cdf6cad79dfccf1064f0e32032a2efeb939a9bbe7fb1cbfe9",
|
||||
"zh:f0e3b0aa644202e1d79d2000dca91f6019425da71e9800fa23f27e51c034f195",
|
||||
"zh:f62bae4519e4ead49182ddc8afe8cf61e2a4c3ba3973b0fbba967736a2696aa3",
|
||||
"zh:fcafa360a5b0b96244f26f4e3a6d642b716a376557142c2442ff2fb12d11da18",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/tls" {
|
||||
version = "4.3.0"
|
||||
constraints = "4.3.0"
|
||||
hashes = [
|
||||
"h1:j/BqLS2N2AScZyotd9nZpHdieJ7e5S8y+A+ZfIu8kL8=",
|
||||
"zh:0ab58d6f8991d436c7d2dbd89ed814709b949b07ac5a54ee53b0aec1fa772a8b",
|
||||
"zh:60b347abcb56f45d97c56f14d895069cd15a83993f199777f571b79fea3642ee",
|
||||
"zh:6889be32640349230de3f23856e6f04e0e9ced4a84a27d3f552fa54684448218",
|
||||
"zh:73f8e1ecf7135033165fb14b7e8bf4d656f3ce13065ec35762ea0481975328c7",
|
||||
"zh:94ce25ee253eca0b42cae9c856b36bca8103b6453012d1b279c3623c805f2d42",
|
||||
"zh:96bc6de9fd67bc446fd11257872e1ffb1029a996ed1d65a3f6b43f6d408ad9ab",
|
||||
"zh:97c609a310a51bfd504d704e036d72064a84bf0bdb36cc08cd4cc66098212b41",
|
||||
"zh:a12c16e94533c5bd123f75032576b9dc91dd5d5ccd5f7cf331d0f2e1adc55cf8",
|
||||
"zh:c4f014f876adf7af57188795050bda5b0029d8c7d7773031102b6c36dcf1fc21",
|
||||
"zh:d9b0a21583aaa3df3a95394fb949a3c515ff71c2ff5a1fc4a73d364aa90bfca5",
|
||||
"zh:da510d22f0c6d71ad19a76406f106b782448f512375787ecfabb338ed1e311a7",
|
||||
"zh:f0e9447a9ce3a24cdaa113089e65663c836d8b9bfdb915a1c0284e0112cab5c0",
|
||||
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
||||
]
|
||||
}
|
||||
8
terraform/backend.tf
Normal file
8
terraform/backend.tf
Normal file
@ -0,0 +1,8 @@
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "tf-state-qvest-task"
|
||||
key = "terraform.tfstate"
|
||||
region = "eu-central-1"
|
||||
use_lockfile = true
|
||||
}
|
||||
}
|
||||
58
terraform/compute.tf
Normal file
58
terraform/compute.tf
Normal file
@ -0,0 +1,58 @@
|
||||
# SSH Key Pair
|
||||
resource "tls_private_key" "ec2_key" {
|
||||
algorithm = "ED25519"
|
||||
}
|
||||
|
||||
resource "aws_key_pair" "ec2_key" {
|
||||
key_name = "${var.project_name}-key"
|
||||
public_key = tls_private_key.ec2_key.public_key_openssh
|
||||
|
||||
tags = {
|
||||
Name = "${var.project_name}-key"
|
||||
}
|
||||
}
|
||||
|
||||
resource "local_file" "private_key" {
|
||||
content = tls_private_key.ec2_key.private_key_openssh
|
||||
filename = "${path.module}/../ssh-keys/${var.project_name}-key.pem"
|
||||
file_permission = "0600"
|
||||
}
|
||||
|
||||
# EC2 Instance
|
||||
data "aws_ami" "ubuntu" {
|
||||
most_recent = true
|
||||
owners = ["099720109477"] # Canonical
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "virtualization-type"
|
||||
values = ["hvm"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_instance" "gitea" {
|
||||
ami = data.aws_ami.ubuntu.id
|
||||
instance_type = "t3.medium"
|
||||
subnet_id = module.vpc.public_subnets[0]
|
||||
key_name = aws_key_pair.ec2_key.key_name
|
||||
|
||||
vpc_security_group_ids = [module.security_group.id]
|
||||
iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
|
||||
|
||||
associate_public_ip_address = true
|
||||
|
||||
root_block_device {
|
||||
volume_size = 30
|
||||
volume_type = "gp3"
|
||||
delete_on_termination = true
|
||||
encrypted = true
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.project_name}-gitea"
|
||||
}
|
||||
}
|
||||
13
terraform/dns.tf
Normal file
13
terraform/dns.tf
Normal file
@ -0,0 +1,13 @@
|
||||
# Route 53 DNS Configuration
|
||||
data "aws_route53_zone" "main" {
|
||||
name = "poll-streams.com"
|
||||
private_zone = false
|
||||
}
|
||||
|
||||
resource "aws_route53_record" "gitea" {
|
||||
zone_id = data.aws_route53_zone.main.zone_id
|
||||
name = "gitea.poll-streams.com"
|
||||
type = "A"
|
||||
ttl = 300
|
||||
records = [aws_instance.gitea.public_ip]
|
||||
}
|
||||
35
terraform/iam.tf
Normal file
35
terraform/iam.tf
Normal file
@ -0,0 +1,35 @@
|
||||
# IAM Role for EC2 to access S3
|
||||
resource "aws_iam_role" "ec2_role" {
|
||||
name = "${var.project_name}-ec2-role"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Action = "sts:AssumeRole"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "ec2.amazonaws.com"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
tags = {
|
||||
Name = "${var.project_name}-ec2-role"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "s3_full_access" {
|
||||
role = aws_iam_role.ec2_role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
28
terraform/main.tf
Normal file
28
terraform/main.tf
Normal file
@ -0,0 +1,28 @@
|
||||
terraform {
|
||||
required_version = ">= 1.15.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "= 6.49.0"
|
||||
}
|
||||
tls = {
|
||||
source = "hashicorp/tls"
|
||||
version = "= 4.3.0"
|
||||
}
|
||||
local = {
|
||||
source = "hashicorp/local"
|
||||
version = "= 2.9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.aws_region
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Project = "qvest-task"
|
||||
}
|
||||
}
|
||||
}
|
||||
40
terraform/outputs.tf
Normal file
40
terraform/outputs.tf
Normal file
@ -0,0 +1,40 @@
|
||||
# Outputs
|
||||
output "ec2_public_ip" {
|
||||
description = "Public IP address of the EC2 instance"
|
||||
value = aws_instance.gitea.public_ip
|
||||
}
|
||||
|
||||
output "ec2_instance_id" {
|
||||
description = "EC2 instance ID"
|
||||
value = aws_instance.gitea.id
|
||||
}
|
||||
|
||||
output "s3_bucket_name" {
|
||||
description = "Name of the S3 backup bucket"
|
||||
value = aws_s3_bucket.backups.id
|
||||
}
|
||||
|
||||
output "ssh_private_key_path" {
|
||||
description = "Path to the SSH private key"
|
||||
value = local_file.private_key.filename
|
||||
}
|
||||
|
||||
output "ssh_connection_command" {
|
||||
description = "Command to SSH into the EC2 instance"
|
||||
value = "ssh -i ${local_file.private_key.filename} -o StrictHostKeyChecking=accept-new ubuntu@${aws_instance.gitea.public_ip}"
|
||||
}
|
||||
|
||||
output "ssh_connection_via_domain" {
|
||||
description = "SSH command using domain name (use after DNS propagates)"
|
||||
value = "ssh -i ${local_file.private_key.filename} -o StrictHostKeyChecking=accept-new ubuntu@gitea.poll-streams.com"
|
||||
}
|
||||
|
||||
output "gitea_domain" {
|
||||
description = "Gitea domain name"
|
||||
value = "gitea.poll-streams.com"
|
||||
}
|
||||
|
||||
output "gitea_url" {
|
||||
description = "Gitea URL (will be HTTPS after SSL setup)"
|
||||
value = "https://gitea.poll-streams.com"
|
||||
}
|
||||
47
terraform/security.tf
Normal file
47
terraform/security.tf
Normal file
@ -0,0 +1,47 @@
|
||||
# Security Group for EC2
|
||||
module "security_group" {
|
||||
source = "terraform-aws-modules/security-group/aws"
|
||||
version = "6.0.0"
|
||||
|
||||
name = "${var.project_name}-ec2-sg"
|
||||
description = "Security group for EC2 instance"
|
||||
vpc_id = module.vpc.vpc_id
|
||||
|
||||
ingress_rules = {
|
||||
ssh = {
|
||||
from_port = 22
|
||||
to_port = 22
|
||||
ip_protocol = "tcp"
|
||||
description = "SSH from anywhere"
|
||||
cidr_ipv4 = "0.0.0.0/0"
|
||||
}
|
||||
http = {
|
||||
from_port = 80
|
||||
to_port = 80
|
||||
ip_protocol = "tcp"
|
||||
description = "HTTP from anywhere"
|
||||
cidr_ipv4 = "0.0.0.0/0"
|
||||
}
|
||||
https = {
|
||||
from_port = 443
|
||||
to_port = 443
|
||||
ip_protocol = "tcp"
|
||||
description = "HTTPS from anywhere"
|
||||
cidr_ipv4 = "0.0.0.0/0"
|
||||
}
|
||||
}
|
||||
|
||||
egress_rules = {
|
||||
all = {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
ip_protocol = "-1"
|
||||
description = "Allow all outbound"
|
||||
cidr_ipv4 = "0.0.0.0/0"
|
||||
}
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.project_name}-ec2-sg"
|
||||
}
|
||||
}
|
||||
26
terraform/storage.tf
Normal file
26
terraform/storage.tf
Normal file
@ -0,0 +1,26 @@
|
||||
# S3 Bucket for Backups
|
||||
resource "aws_s3_bucket" "backups" {
|
||||
bucket = "${var.project_name}-backups"
|
||||
|
||||
tags = {
|
||||
Name = "${var.project_name}-backups"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_versioning" "backups" {
|
||||
bucket = aws_s3_bucket.backups.id
|
||||
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_server_side_encryption_configuration" "backups" {
|
||||
bucket = aws_s3_bucket.backups.id
|
||||
|
||||
rule {
|
||||
apply_server_side_encryption_by_default {
|
||||
sse_algorithm = "AES256"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
terraform/variables.tf
Normal file
11
terraform/variables.tf
Normal file
@ -0,0 +1,11 @@
|
||||
variable "aws_region" {
|
||||
description = "AWS region for all resources"
|
||||
type = string
|
||||
default = "eu-central-1"
|
||||
}
|
||||
|
||||
variable "project_name" {
|
||||
description = "Project name for resource naming"
|
||||
type = string
|
||||
default = "qvest-task"
|
||||
}
|
||||
18
terraform/vpc.tf
Normal file
18
terraform/vpc.tf
Normal file
@ -0,0 +1,18 @@
|
||||
# VPC and Networking
|
||||
module "vpc" {
|
||||
source = "terraform-aws-modules/vpc/aws"
|
||||
version = "6.6.1"
|
||||
|
||||
name = "${var.project_name}-vpc"
|
||||
cidr = "10.0.0.0/16"
|
||||
|
||||
azs = ["${var.aws_region}a"]
|
||||
public_subnets = ["10.0.1.0/24"]
|
||||
|
||||
enable_dns_hostnames = true
|
||||
enable_dns_support = true
|
||||
|
||||
tags = {
|
||||
Name = "${var.project_name}-vpc"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user