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
25 changes: 24 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ ENV_DIR := infra/environments/aws/prod
CHECK_SCRIPT := infra/check.sh
MANAGEMENT_PORT := 8081
INSTANCE_TAG := chaeso-zip-vm
ASSET_DOMAIN := assets.chaeso-zip.com

.DEFAULT_GOAL := help
.PHONY: help bootstrap up down status redeploy health check ps logs ssh _profile
.PHONY: help bootstrap up down status redeploy health check ps logs ssh upload-asset upload-assets _profile

# 접속 헬퍼
ssm_target = $$(aws ec2 describe-instances \
Expand All @@ -39,6 +40,9 @@ help:
@echo " make check 컨테이너/DB/볼륨 종합 점검"
@echo " make ps 컨테이너 상태"
@echo " make logs 실시간 로그"
@echo " make upload-asset FILE=./logo.png [KEY=channels/xxx-logo.png] 공개 자산 1개(로고, 프로필 등)"
@echo " make upload-asset FILE=\"./a.png ./b.png ./c.png\" [PREFIX=channels/] 공개 자산 여러 개"
@echo " make upload-assets DIR=./logos [PREFIX=channels/] 공개 자산 폴더 전체 업로드"
@echo ""
@echo "운영 (Terraform):"
@echo " make bootstrap state 버킷 생성(최초 1회)"
Expand Down Expand Up @@ -110,3 +114,22 @@ logs: _profile

ssh: _profile
$(ssh_to)

upload-asset: _profile
@test -n "$(FILE)" || { echo "사용법: make upload-asset FILE=\"./a.png ./b.png\" [PREFIX=channels/] [KEY=1개일 때만]"; exit 1; }
@if [ $(words $(FILE)) -gt 1 ] && [ -n "$(KEY)" ]; then \
echo "파일이 여러 개면 KEY 대신 PREFIX를 쓰세요"; exit 1; \
fi
@bucket=$$(cd $(ENV_DIR) && terraform output -raw public_bucket_name) && \
for f in $(FILE); do \
key=$${KEY:-$(PREFIX)$$(basename "$$f")}; \
aws s3 cp "$$f" "s3://$$bucket/$$key" --cache-control "public, max-age=300, must-revalidate" && \
echo "✅ 업로드 완료 → https://$(ASSET_DOMAIN)/$$key (raw: https://$$bucket.s3.$(REGION).amazonaws.com/$$key)"; \
done

upload-assets: _profile
@test -n "$(DIR)" || { echo "사용법: make upload-assets DIR=./logos [PREFIX=channels/]"; exit 1; }
@bucket=$$(cd $(ENV_DIR) && terraform output -raw public_bucket_name) && \
prefix="$(PREFIX)" && \
aws s3 sync "$(DIR)" "s3://$$bucket/$$prefix" --cache-control "public, max-age=300, must-revalidate" && \
echo "✅ 전체 업로드 완료 → https://$(ASSET_DOMAIN)/$$prefix (raw: https://$$bucket.s3.$(REGION).amazonaws.com/$$prefix)"
16 changes: 15 additions & 1 deletion docs/infra.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ infra/
├── network/ # VPC/subnet/IGW/SG
└── app/ # EC2/EIP/EBS/instance profile + startup-script
├── schedule.tf # EventBridge Scheduler 야간 자동 정지/기동
└── s3-ad-history.tf # 온보딩 광고 이력 업로드용 S3 (§5)
├── s3-ad-history.tf # 온보딩 광고 이력 업로드용 S3 (private, presigned)
└── s3-public.tf # 공개 정적 자산용 S3 (§5)
```

---
Expand Down Expand Up @@ -85,6 +86,8 @@ make ps # 컨테이너 상태
| `make check` | 컨테이너/DB/볼륨 종합 점검 |
| `make ps` | 컨테이너 상태 |
| `make logs` | 실시간 로그 |
| `make upload-asset FILE=./logo.png [KEY=channels/xxx.png]` | 공개 자산 업로드(1개/여러 개, §5) |
| `make upload-assets DIR=./logos [PREFIX=channels/]` | 공개 자산 폴더 전체 업로드 (§5) |

---

Expand All @@ -100,4 +103,15 @@ make ps # 컨테이너 상태
| `chaeso-zip-stop-night` | `0 0 * * ? *` (매일 00:00) | `StopInstances` (Force=false, graceful) |
| `chaeso-zip-start-morning` | `0 9 * * ? *` (매일 09:00) | `StartInstances` |

---

## 5. 공개 static S3 버킷

- 채널 로고, 프로필 이미지 등 공개 이미지 저장용 버킷입니다.
- 버킷명 입력 없이 — `make upload-asset`/`upload-assets`로 파일을 업로드 가능합니다.
- 버킷은 하나만 두고 `channels/<channel-id>.png`, `users/<user-id>.png`처럼 키 prefix로 용도를 구분합니다.
- 서빙 도메인은 `https://assets.chaeso-zip.com`입니다.

상세 코드는 `../infra/modules/aws/app/s3-public.tf`를 참고해주세요.

---
4 changes: 4 additions & 0 deletions infra/environments/aws/prod/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,7 @@ output "public_ip" {
output "ad_history_bucket_name" {
value = module.app.ad_history_bucket_name
}

output "public_bucket_name" {
value = module.app.public_bucket_name
}
21 changes: 21 additions & 0 deletions infra/environments/aws/prod/team-access.tf
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,25 @@ resource "aws_iam_user_policy_attachment" "teammate_ssm" {
count = var.teammate_username != "" ? 1 : 0
user = aws_iam_user.teammate[0].name
policy_arn = aws_iam_policy.ssm_shell.arn
}

resource "aws_iam_user_policy" "teammate_public_s3" {
count = var.teammate_username != "" ? 1 : 0
name = "chaeso-zip-teammate-public-s3"
user = aws_iam_user.teammate[0].name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = "s3:ListBucket"
Resource = "arn:aws:s3:::${module.app.public_bucket_name}"
},
{
Effect = "Allow"
Action = ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"]
Resource = "arn:aws:s3:::${module.app.public_bucket_name}/*"
},
]
})
}
4 changes: 3 additions & 1 deletion infra/modules/aws/app/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ resource "aws_instance" "this" {
tags = { Name = "${var.name_prefix}-vm" }

root_block_device {
encrypted = true
encrypted = true
volume_size = 30
volume_type = "gp3"
}
Comment thread
SinnoLn marked this conversation as resolved.

lifecycle {
Expand Down
4 changes: 4 additions & 0 deletions infra/modules/aws/app/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ output "public_ip" {
output "ad_history_bucket_name" {
value = aws_s3_bucket.ad_history.bucket
}

output "public_bucket_name" {
value = aws_s3_bucket.public.bucket
}
2 changes: 1 addition & 1 deletion infra/modules/aws/app/s3-ad-history.tf
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ resource "aws_s3_bucket_policy" "s3_access_logs" {
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.s3_access_logs.arn}/*"
Condition = {
ArnLike = { "aws:SourceArn" = aws_s3_bucket.ad_history.arn }
ArnLike = { "aws:SourceArn" = [aws_s3_bucket.ad_history.arn, aws_s3_bucket.public.arn] }
StringEquals = { "aws:SourceAccount" = data.aws_caller_identity.current.account_id }
}
},
Expand Down
50 changes: 50 additions & 0 deletions infra/modules/aws/app/s3-public.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
resource "aws_s3_bucket" "public" {
bucket = "${var.name_prefix}-public-${data.aws_caller_identity.current.account_id}"
}

resource "aws_s3_bucket_logging" "public" {
bucket = aws_s3_bucket.public.id
target_bucket = aws_s3_bucket.s3_access_logs.id
target_prefix = "public/"
}
Comment thread
SinnoLn marked this conversation as resolved.

resource "aws_s3_bucket_public_access_block" "public" { # NOSONAR
bucket = aws_s3_bucket.public.id

# public read-only bucket for static assets (logos, profile images); write access is IAM-gated
block_public_acls = true
block_public_policy = false
ignore_public_acls = true
restrict_public_buckets = false
}

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

depends_on = [aws_s3_bucket_public_access_block.public]
}
1 change: 0 additions & 1 deletion src/test/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,4 @@ app:
onboarding:
s3:
bucket: test-onboarding-bucket
region: ap-northeast-2
presign-ttl: PT5M
Loading