Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/terraform-apply.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ jobs:
run: |
terraform apply -auto-approve \
-var-file="../../config/secrets/prod.tfvars" \
-var-file="../../config/secrets/prod_db.tfvars" \
-var-file="../../config/secrets/app_stack.tfvars"
- name: Stop SSM Tunnel
if: always()
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/terraform-plan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ jobs:
run: |
terraform plan -no-color \
-var-file="../../config/secrets/prod.tfvars" \
-var-file="../../config/secrets/prod_db.tfvars" \
-var-file="../../config/secrets/app_stack.tfvars" \
2>&1 | tee plan_output.txt
echo "exitcode=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ modules/shared_resources/dist/*.zip
# --- IDE & OS ---
.idea/
.DS_Store

# --- AGENTS ---
AGENTS.local.md
2 changes: 1 addition & 1 deletion config/secrets
5 changes: 3 additions & 2 deletions environment/prod/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ module "prod_stack" {
db_data_volume_size = var.db_data_volume_size

# 보안 그룹 규칙
api_ingress_rules = var.api_ingress_rules
db_ingress_rules = var.db_ingress_rules
api_ingress_rules = var.api_ingress_rules
rds_ingress_rules = var.rds_ingress_rules
db_ec2_ingress_rules = var.db_ec2_ingress_rules

# RDS 식별자 설정
rds_identifier = var.rds_identifier
Expand Down
173 changes: 173 additions & 0 deletions environment/prod/mysql_backup.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
resource "aws_s3_bucket" "mysql_backup" {
bucket = var.mysql_backup_bucket_name
force_destroy = false
object_lock_enabled = true

tags = {
Name = var.mysql_backup_bucket_name
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

lifecycle {
prevent_destroy = true
}
}

resource "aws_s3_bucket_ownership_controls" "mysql_backup" {
bucket = aws_s3_bucket.mysql_backup.id

rule {
object_ownership = "BucketOwnerEnforced"
}
}

resource "aws_s3_bucket_public_access_block" "mysql_backup" {
bucket = aws_s3_bucket.mysql_backup.id

block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

resource "aws_s3_bucket_policy" "mysql_backup" {
bucket = aws_s3_bucket.mysql_backup.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DenyInsecureTransport"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.mysql_backup.arn,
"${aws_s3_bucket.mysql_backup.arn}/*",
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
},
]
})
}

resource "aws_s3_bucket_server_side_encryption_configuration" "mysql_backup" {
bucket = aws_s3_bucket.mysql_backup.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

resource "aws_s3_bucket_versioning" "mysql_backup" {
bucket = aws_s3_bucket.mysql_backup.id

versioning_configuration {
status = "Enabled"
}
}

resource "aws_s3_bucket_object_lock_configuration" "mysql_backup" {
bucket = aws_s3_bucket.mysql_backup.id

depends_on = [aws_s3_bucket_versioning.mysql_backup]

rule {
default_retention {
mode = "GOVERNANCE"
days = 14
}
}
}

resource "aws_s3_bucket_lifecycle_configuration" "mysql_backup" {
bucket = aws_s3_bucket.mysql_backup.id

depends_on = [
aws_s3_bucket_versioning.mysql_backup,
aws_s3_bucket_object_lock_configuration.mysql_backup,
]

rule {
id = "expire-mysql-backups-after-14-days"
status = "Enabled"

filter {}

expiration {
days = 14
}

noncurrent_version_expiration {
noncurrent_days = 1
}

abort_incomplete_multipart_upload {
days_after_initiation = 1
}
}

rule {
id = "remove-expired-delete-markers"
status = "Enabled"

filter {}

expiration {
expired_object_delete_marker = true
}
}
}

# S3 Gateway Endpoint에는 별도 시간당 또는 데이터 처리 비용이 없습니다.
# 접근 권한은 Terraform에 선언하지 않고 수동으로 관리하는 IAM 정책에서 제한합니다.
data "aws_route_table" "db_ec2" {
subnet_id = var.db_ec2_subnet_id
}

resource "aws_vpc_endpoint" "mysql_backup_s3" {
vpc_id = data.aws_vpc.default.id
service_name = "com.amazonaws.ap-northeast-2.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = [data.aws_route_table.db_ec2.id]

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowBackupBucketMetadataAccess"
Effect = "Allow"
Principal = "*"
Action = [
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:ListBucketVersions",
]
Resource = aws_s3_bucket.mysql_backup.arn
},
{
Sid = "AllowBackupObjectAccess"
Effect = "Allow"
Principal = "*"
Action = [
"s3:AbortMultipartUpload",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:ListMultipartUploadParts",
"s3:PutObject",
]
Resource = "${aws_s3_bucket.mysql_backup.arn}/*"
},
]
})

tags = {
Name = "solid-connection-prod-mysql-backup-s3"
}
}
9 changes: 9 additions & 0 deletions environment/prod/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "mysql_backup_bucket_name" {
description = "Prod MySQL 백업 S3 버킷 이름"
value = aws_s3_bucket.mysql_backup.bucket
}

output "mysql_backup_s3_vpc_endpoint_id" {
description = "Prod MySQL 백업용 S3 Gateway VPC Endpoint ID"
value = aws_vpc_endpoint.mysql_backup_s3.id
}
19 changes: 17 additions & 2 deletions environment/prod/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ variable "db_data_volume_size" {
type = number
}

variable "mysql_backup_bucket_name" {
description = "Prod MySQL dump와 binlog를 보관할 S3 버킷 이름"
type = string
}

variable "api_ingress_rules" {
description = "List of ingress rules for API Server"
type = list(object({
Expand All @@ -49,8 +54,18 @@ variable "api_ingress_rules" {
}))
}

variable "db_ingress_rules" {
description = "List of ingress rules for DB Server"
variable "rds_ingress_rules" {
description = "API Server Security Group에서 RDS로 허용할 ingress 규칙"
type = list(object({
from_port = number
to_port = number
protocol = string
description = string
}))
}

variable "db_ec2_ingress_rules" {
description = "API Server Security Group에서 DB EC2로 허용할 ingress 규칙"
type = list(object({
from_port = number
to_port = number
Expand Down
17 changes: 10 additions & 7 deletions modules/app_stack/security_groups.tf
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ resource "aws_security_group" "db_ec2_sg" {
description = "Security Group for DB EC2"
vpc_id = var.vpc_id

ingress {
description = "MySQL from API server"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.api_sg.id]
dynamic "ingress" {
for_each = var.db_ec2_ingress_rules
content {
description = ingress.value.description
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
security_groups = [aws_security_group.api_sg.id]
}
}

egress {
Expand All @@ -63,7 +66,7 @@ resource "aws_security_group" "db_sg" {
vpc_id = var.vpc_id

dynamic "ingress" {
for_each = var.db_ingress_rules
for_each = var.rds_ingress_rules
content {
description = ingress.value.description
from_port = ingress.value.from_port
Expand Down
15 changes: 13 additions & 2 deletions modules/app_stack/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,19 @@ variable "api_ingress_rules" {
}))
}

variable "db_ingress_rules" {
description = "List of ingress rules for DB Server"
variable "rds_ingress_rules" {
description = "API Server Security Group에서 RDS로 허용할 ingress 규칙"
type = list(object({
from_port = number
to_port = number
protocol = string
description = string
}))
default = []
}

variable "db_ec2_ingress_rules" {
description = "API Server Security Group에서 DB EC2로 허용할 ingress 규칙"
type = list(object({
from_port = number
to_port = number
Expand Down
Loading