From 47418b8388d28007591f014109db3aa441431c04 Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Thu, 10 Jul 2025 00:04:36 +0900 Subject: [PATCH 01/19] =?UTF-8?q?OIDC=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20ci/cd=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 34 ++++++---- .github/workflows/ci.yml | 79 +++++++++++++--------- .gitignore | 19 ++++++ dev-team-account/OIDC/iam/backend.tf | 0 dev-team-account/OIDC/iam/main.tf | 41 +++++++++++ identity-team-account/OIDC/iam/backend.tf | 9 +++ identity-team-account/OIDC/iam/main.tf | 74 ++++++++++++++++++++ modules/github_oidc/main.tf | 47 +++++++++++++ modules/github_oidc/outputs.tf | 4 ++ modules/github_oidc/variables.tf | 20 ++++++ operation-team-account/OIDC/iam/backend.tf | 9 +++ operation-team-account/OIDC/iam/main.tf | 75 ++++++++++++++++++++ prod-team-account/OIDC/iam/backend.tf | 11 +++ prod-team-account/OIDC/iam/main.tf | 44 ++++++++++++ stage-team-account/OIDC/iam/backend.tf | 9 +++ stage-team-account/OIDC/iam/main.tf | 44 ++++++++++++ 16 files changed, 473 insertions(+), 46 deletions(-) create mode 100644 .gitignore create mode 100644 dev-team-account/OIDC/iam/backend.tf create mode 100644 dev-team-account/OIDC/iam/main.tf create mode 100644 identity-team-account/OIDC/iam/backend.tf create mode 100644 identity-team-account/OIDC/iam/main.tf create mode 100644 modules/github_oidc/main.tf create mode 100644 modules/github_oidc/outputs.tf create mode 100644 modules/github_oidc/variables.tf create mode 100644 operation-team-account/OIDC/iam/backend.tf create mode 100644 operation-team-account/OIDC/iam/main.tf create mode 100644 prod-team-account/OIDC/iam/backend.tf create mode 100644 prod-team-account/OIDC/iam/main.tf create mode 100644 stage-team-account/OIDC/iam/backend.tf create mode 100644 stage-team-account/OIDC/iam/main.tf diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e6020ef..0e06147 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -2,24 +2,24 @@ name: Terraform Apply on: push: - branches: [main] + branches: [main] # main 브랜치에 push될 때 실행 permissions: - contents: read - id-token: write + contents: read # 코드 리포지토리 읽기 권한 + id-token: write # OIDC 인증을 위한 ID 토큰 발급 권한 jobs: detect-changes: runs-on: ubuntu-latest outputs: - matrix: ${{ steps.set.outputs.matrix }} + matrix: ${{ steps.set.outputs.matrix }} # 다음 job에 전달할 matrix 출력 steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v3 # 현재 리포지토리 코드 체크아웃 - name: Filter Paths id: filter - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@v3 # 어떤 디렉토리에 변경이 있는지 필터링 with: filters: | operation: @@ -40,6 +40,7 @@ jobs: - name: Build Matrix from Filter (with subdirs) id: set env: + # 필터링된 결과를 환경변수로 받아옴 FILTER_OUTPUTS_operation: ${{ steps.filter.outputs.operation }} FILTER_OUTPUTS_identity: ${{ steps.filter.outputs.identity }} FILTER_OUTPUTS_prod: ${{ steps.filter.outputs.prod }} @@ -48,6 +49,7 @@ jobs: FILTER_OUTPUTS_stage: ${{ steps.filter.outputs.stage }} FILTER_OUTPUTS_management: ${{ steps.filter.outputs.management }} run: | + # 계정 별 IAM Role Key 매핑 declare -A ROLE_MAP=( ["operation"]="ROLE_ARN_OPERATION" ["identity"]="ROLE_ARN_IDENTITY" @@ -60,6 +62,7 @@ jobs: MATRIX_ITEMS=() + # 변경된 경로에 따라 matrix 구성 for KEY in "${!ROLE_MAP[@]}"; do VAR_NAME="FILTER_OUTPUTS_${KEY}" VALUE="${!VAR_NAME}" @@ -85,6 +88,7 @@ jobs: fi done + # 최종 matrix JSON 출력 if [ ${#MATRIX_ITEMS[@]} -eq 0 ]; then echo "matrix=[]" >> $GITHUB_OUTPUT else @@ -93,14 +97,14 @@ jobs: fi terraform-apply: - needs: detect-changes - if: ${{ needs.detect-changes.outputs.matrix != '[]' }} + needs: detect-changes # detect-changes job 이후 실행 + if: ${{ needs.detect-changes.outputs.matrix != '[]' }} # 변경사항이 있을 경우에만 실행 runs-on: ubuntu-latest strategy: - matrix: + matrix: # matrix 기반 반복 실행 include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} - fail-fast: false + fail-fast: false # 하나 실패해도 나머지 job은 계속 진행 steps: - name: Checkout repository @@ -110,17 +114,17 @@ jobs: uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ap-northeast-2 - role-to-assume: ${{ secrets[matrix.role_key] }} + role-to-assume: ${{ secrets[matrix.role_key] }} # OIDC 기반으로 계정별 IAM Role Assume - name: Setup Terraform uses: hashicorp/setup-terraform@v1 with: - terraform_version: 1.4.0 + terraform_version: 1.4.0 # Terraform 버전 명시 - name: Terraform Init - run: terraform init - working-directory: ${{ matrix.dir }} + run: terraform init # Terraform 초기화: 백엔드 설정 및 provider 다운로드 + working-directory: ${{ matrix.dir }} # matrix로 전달된 디렉토리에서 실행 - name: Terraform Apply - run: terraform apply -auto-approve + run: terraform apply -auto-approve # 사용자 승인 없이 자동 적용 working-directory: ${{ matrix.dir }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01c5818..7032419 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,51 +1,48 @@ name: Application-Deployment CI on: - pull_request: + pull_request: # PR이 main 브랜치로 올 때 실행 branches: [main] paths: - "operation-team-account/**" - "identity-team-account/**" - "prod-team-account/**" - "dev-team-account/**" - - "security-team-account/**" - "stage-team-account/**" - - "management-team-account/**" permissions: - contents: read - pull-requests: write - id-token: write + contents: read # 코드 읽을 수 있는 권한 + pull-requests: write # PR 코멘트 작성 등 가능 + id-token: write # OIDC기반 인증에 필요 jobs: detect-changes: runs-on: ubuntu-latest outputs: - matrix: ${{ steps.set.outputs.matrix }} + matrix: ${{ steps.set.outputs.matrix }} # 다음 job에서 사용할 matrix 값 steps: - name: Checkout Code uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 0 # git diff가 정상 동작하려면 전체 커밋 히스토리를 가져와야 함 - - name: Fetch origin/main + - name: Fetch origin/main # main 브랜치 히스토리 가져오기 run: git fetch origin main - name: Detect Changed Directories & Build Matrix id: set run: | - FILES=$(git diff --name-only origin/main...${{ github.sha }}) + FILES=$(git diff --name-only origin/main...${{ github.sha }}) # 변경된 파일 목록 echo "Changed files:" echo "$FILES" - declare -A ROLE_MAP=( + # 각 account별 디렉토리에 해당하는 IAM Role 매핑 + declare -A ROLE_MAP=( ["operation-team-account"]="ROLE_ARN_OPERATION" ["identity-team-account"]="ROLE_ARN_IDENTITY" ["prod-team-account"]="ROLE_ARN_PROD" ["dev-team-account"]="ROLE_ARN_DEV" - ["security-team-account"]="ROLE_ARN_SECURITY" ["stage-team-account"]="ROLE_ARN_STAGE" - ["management-team-account"]="ROLE_ARN_MANAGEMENT" ) TMP_FILE=$(mktemp) @@ -72,9 +69,7 @@ jobs: fi done - # 중복 제거 - UNIQUE_LINES=$(sort $TMP_FILE | uniq) - + # 중복 제거 및 JSON 포맷 생성 MATRIX_JSON="[" FIRST=1 @@ -100,13 +95,13 @@ jobs: terraform-ci: needs: detect-changes - if: ${{ needs.detect-changes.outputs.matrix != '[]' }} + if: ${{ needs.detect-changes.outputs.matrix != '[]' }} # 변경사항이 있을 때만 실행 runs-on: ubuntu-latest strategy: matrix: - include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} - fail-fast: false + include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} # 변경된 디렉토리 목록 기반 실행 + fail-fast: false # 하나 실패해도 나머지는 계속 실행 env: INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }} @@ -116,14 +111,16 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v3 + with: + fetch-depth: 0 - - name: Configure AWS Credentials + - name: Configure AWS Credentials # 각 matrix.role_key에 해당하는 AWS Role Assume uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ap-northeast-2 role-to-assume: ${{ secrets[matrix.role_key] }} - - name: Install tfsec + - name: Install tfsec # 보안 취약점 스캐너 설치 run: | curl -sSL https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash @@ -135,24 +132,34 @@ jobs: with: terraform_version: 1.4.0 - - name: Terraform Init + - name: Terraform Init # Terraform 초기화: backend 구성 및 provider 다운로드 run: terraform init working-directory: ${{ matrix.dir }} - name: Terraform Format Check - run: terraform fmt -check -recursive + run: terraform fmt -check -recursive # 코드 스타일 검사 working-directory: ${{ matrix.dir }} - name: Terraform Validate - run: terraform validate + run: terraform validate # Terraform 구성 파일이 유효한지 문법 및 구조 검사 working-directory: ${{ matrix.dir }} - name: Terraform Plan id: plan + continue-on-error: true #plan 실패해도 워크플로우 진행 run: | - terraform plan -no-color -out=tfplan.binary - terraform show -no-color tfplan.binary > plan.txt - terraform show -json tfplan.binary > plan.json + START_TIME=$(date -u +"%Y-%m-%d %H:%M:%S UTC") + echo "START_TIME=$START_TIME" >> $GITHUB_ENV + + PLAN_FILE=tfplan.binary + PLAN_TXT=plan.txt + + terraform plan -no-color -out=$PLAN_FILE || echo "PLAN_FAILED=true" >> $GITHUB_ENV + terraform show -no-color $PLAN_FILE > $PLAN_TXT || echo "Plan failed" > $PLAN_TXT + + # plan 출력값을 멀티라인 환경 변수에 저장 + PLAN_CONTENT=$(cat $PLAN_TXT | sed 's/`/\\`/g') + echo 'PLAN<> $GITHUB_OUTPUT cat plan.txt >> $GITHUB_OUTPUT echo 'EOF' >> $GITHUB_OUTPUT @@ -164,12 +171,22 @@ jobs: with: issue-number: ${{ github.event.pull_request.number }} body: | - ### Terraform Plan Output for `${{ matrix.dir }}` + ## [Terraform Plan Summary] + | 항목 | 값 | + |-----------------|-----| + | **Status** | `${{ steps.plan.outcome }}` | + | **Directory** | `${{ matrix.dir }}` | + | **Executed At** | `${{ env.START_TIME }}` | + + + --- + + ### Plan Output ```hcl - ${{ steps.plan.outputs.PLAN }} + ${{ steps.plan.outputs.PLAN_CONTENT }} ``` - - name: Setup Infracost + - name: Setup Infracost # 비용 예측 도구 세팅 uses: infracost/actions/setup@v2 - name: Infracost Breakdown @@ -184,4 +201,4 @@ jobs: uses: infracost/actions/comment@v1 with: path: ${{ matrix.dir }}/infracost.json - behavior: update + behavior: update # 기존 코멘트 업데이트 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1668f42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +.DS_Store +.idea + +# tfstate files +*.tfstate +*.tfstate.* +.terraform +.terraform/ +*.tfvars +*.tfvars.json +*.tfplan +*.tfplan.json +*.tfignore +*.tfbackup +*.tfstate.backup +*.tfstate.backup.* +*.tfstate.backup.json +*.tfstate.backup.json.* +.terraform.lock.hcl \ No newline at end of file diff --git a/dev-team-account/OIDC/iam/backend.tf b/dev-team-account/OIDC/iam/backend.tf new file mode 100644 index 0000000..e69de29 diff --git a/dev-team-account/OIDC/iam/main.tf b/dev-team-account/OIDC/iam/main.tf new file mode 100644 index 0000000..d1c174f --- /dev/null +++ b/dev-team-account/OIDC/iam/main.tf @@ -0,0 +1,41 @@ +# modules/github_oidc를 불러와 해당account별 OIDC역할을 자동으로 생성하는 구조 +# +module "github_oidc" { + source = "../../../modules/github_oidc" + + role_name = "application-deployment-role1" + + # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 + sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" + + + + # 이 role에 연결할 정책들(IAM 정책 ARN) + policy_arns = [ + "arn:aws:iam::aws:policy/AdministratorAccess" + ] +} + +#tfsec:ignore:aws-iam-no-policy-wildcards +resource "aws_iam_role_policy" "custom_inline_policy" { + name = "dev-role" + role = module.github_oidc.oidc_role_name # 모듈에서 출력된 role이름 참조 + + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "VisualEditor0", + "Effect" : "Allow", + "Action" : [ + "rds:*", + "s3:*", + "ec2:*", + "kms:*", + "dynamodb:*" + ], + "Resource" : "*" + } + ] + }) +} diff --git a/identity-team-account/OIDC/iam/backend.tf b/identity-team-account/OIDC/iam/backend.tf new file mode 100644 index 0000000..7f4b07a --- /dev/null +++ b/identity-team-account/OIDC/iam/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "cloudfence-identity-state" + key = "OIDC/iam.tfstate" + region = "ap-northeast-2" + encrypt = true + dynamodb_table = "s3-identity-lock" + } +} \ No newline at end of file diff --git a/identity-team-account/OIDC/iam/main.tf b/identity-team-account/OIDC/iam/main.tf new file mode 100644 index 0000000..97b6f44 --- /dev/null +++ b/identity-team-account/OIDC/iam/main.tf @@ -0,0 +1,74 @@ +# identity-team-account의 main.tf +# modules/github_oidc를 불러와 해당account별 OIDC역할을 자동으로 생성하는 구조 + +module "github_oidc" { + source = "../../../modules/github_oidc" + + role_name = "Organization-role" + + # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 + sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" + + + # 이 role에 연결할 정책들(IAM 정책 ARN) + policy_arns = [ + "arn:aws:iam::aws:policy/AdministratorAccess" + ] +} + +#tfsec:ignore:aws-iam-no-policy-wildcards +resource "aws_iam_role_policy" "custom_inline_policy" { + name = "org-role" + role = module.github_oidc.oidc_role_name # 모듈에서 출력된 role이름 참조 + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "sso:CreatePermissionSet", + "sso:DescribePermissionSet", + "sso:UpdatePermissionSet", + "sso:DeletePermissionSet", + "sso:AttachManagedPolicyToPermissionSet", + "sso:ListPermissionSets", + "sso:ListInstances", + "sso:ProvisionPermissionSet", + "sso:PutInlinePolicyToPermissionSet", + "sso:DeleteInlinePolicyFromPermissionSet", + "sso:ListPermissionSetProvisioningStatus", + "organizations:*", + "identitystore:*" + ], + Resource = "*" + }, + { + "Sid" : "S3Access", + "Effect" : "Allow", + "Action" : [ + "s3:*" + ], + "Resource" : [ + "*" + ] + }, + { + "Sid" : "KMSAccess", + "Effect" : "Allow", + "Action" : [ + "kms:*" + ], + "Resource" : "*" + }, + { + "Sid" : "DynamoDBAccess", + "Effect" : "Allow", + "Action" : [ + "dynamodb:*" + ], + "Resource" : "*" + } + ] + }) +} diff --git a/modules/github_oidc/main.tf b/modules/github_oidc/main.tf new file mode 100644 index 0000000..d2cdcf6 --- /dev/null +++ b/modules/github_oidc/main.tf @@ -0,0 +1,47 @@ +#현재 계정정보를 가져옴 +data "aws_caller_identity" "current" {} + + +provider "aws" { + region = "ap-northeast-2" + +} + + +# GitHub Actions용 OIDC provider 설정 +resource "aws_iam_openid_connect_provider" "github" { + url = "https://token.actions.githubusercontent.com" + + client_id_list = [ + "sts.amazonaws.com" # OIDC에서 사용할 클라이언트 ID + ] + + thumbprint_list = [ + "6938fd4d98bab03faadb97b34396831e3780aea1" # GitHub 공식 인증서 지문 thumbprint(공식값) + ] +} + +# oidc_role 이라는 이름의 IAM Role +resource "aws_iam_role" "oidc_role" { + name = var.role_name # 생성할 Role 이름 + + # GitHub에서 이 역할을 assume할 수 있게 설정 + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Principal = { + Federated = aws_iam_openid_connect_provider.github.arn # 위에서 만든 OIDC Provider의 ARN + }, + Action = "sts:AssumeRoleWithWebIdentity", + Condition = { + StringLike = { + # 어떤 GitHub repo에서만 이 Role을 사용할 수 있는지 제어 + "token.actions.githubusercontent.com:sub" : var.sub_condition + } + } + } + ] + }) +} \ No newline at end of file diff --git a/modules/github_oidc/outputs.tf b/modules/github_oidc/outputs.tf new file mode 100644 index 0000000..56e363e --- /dev/null +++ b/modules/github_oidc/outputs.tf @@ -0,0 +1,4 @@ +output "oidc_role_name" { + description = "OIDC로 생성된 IAM Role 이름" + value = aws_iam_role.oidc_role.name +} \ No newline at end of file diff --git a/modules/github_oidc/variables.tf b/modules/github_oidc/variables.tf new file mode 100644 index 0000000..d5dd127 --- /dev/null +++ b/modules/github_oidc/variables.tf @@ -0,0 +1,20 @@ +variable "role_name" { + type = string + description = "OIDC 역할 이름" +} + +variable "sub_condition" { + type = string + description = "OIDC Subject (sub) 조건" +} + +variable "policy_arns" { + type = list(string) + description = "Attach할 IAM 정책 목록" +} + +resource "aws_iam_role_policy_attachment" "attach_policy" { + for_each = toset(var.policy_arns) + role = aws_iam_role.oidc_role.name + policy_arn = each.value +} diff --git a/operation-team-account/OIDC/iam/backend.tf b/operation-team-account/OIDC/iam/backend.tf new file mode 100644 index 0000000..2b14d75 --- /dev/null +++ b/operation-team-account/OIDC/iam/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "cloudfence-operation-state" + key = "OIDC/iam.tfstate" + region = "ap-northeast-2" + encrypt = true + dynamodb_table = "s3-operation-lock" + } +} \ No newline at end of file diff --git a/operation-team-account/OIDC/iam/main.tf b/operation-team-account/OIDC/iam/main.tf new file mode 100644 index 0000000..081b464 --- /dev/null +++ b/operation-team-account/OIDC/iam/main.tf @@ -0,0 +1,75 @@ +# modules/github_oidc를 불러와 해당account별 OIDC역할을 자동으로 생성하는 구조 + + + + +module "github_oidc" { + source = "../../../modules/github_oidc" + + role_name = "operation-cicd" + + # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 + sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" + + + # 이 role에 연결할 정책들(IAM 정책 ARN) + policy_arns = [ + "arn:aws:iam::aws:policy/AmazonEC2FullAccess", + "arn:aws:iam::aws:policy/AmazonECS_FullAccess", + "arn:aws:iam::aws:policy/AmazonElasticContainerRegistryPublicFullAccess", + "arn:aws:iam::aws:policy/AmazonVPCFullAccess", + "arn:aws:iam::aws:policy/AWSCodeDeployFullAccess", + "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS", + "arn:aws:iam::aws:policy/AWSWAFFullAccess", + "arn:aws:iam::aws:policy/IAMFullAccess" + ] +} + +#tfsec:ignore:aws-iam-no-policy-wildcards +resource "aws_iam_role_policy" "custom_inline_policy" { + name = "operation-cicd" + role = module.github_oidc.oidc_role_name # 모듈에서 출력된 role이름 참조 + + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Effect" : "Allow", + "Action" : [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:*", + "sts:AssumeRole" + ], + "Resource" : [ + "*" + ] + }, + { + "Effect" : "Allow", + "Action" : [ + "cloudwatch:*", + "cloudtrail:*" + ], + "Resource" : "*" + }, + { + "Sid" : "KMSAccess", + "Effect" : "Allow", + "Action" : [ + "kms:*" + ], + "Resource" : "*" + }, + { + "Sid" : "DynamoDBAccess", + "Effect" : "Allow", + "Action" : [ + "dynamodb:*" + ], + "Resource" : "*" + } + ] + }) +} diff --git a/prod-team-account/OIDC/iam/backend.tf b/prod-team-account/OIDC/iam/backend.tf new file mode 100644 index 0000000..ec45978 --- /dev/null +++ b/prod-team-account/OIDC/iam/backend.tf @@ -0,0 +1,11 @@ + +terraform { + backend "s3" { + bucket = "cloudfence-prod-state" + key = "OIDC/iam.tfstate" + region = "ap-northeast-2" + encrypt = true + dynamodb_table = "s3-prod-lock" + + } +} \ No newline at end of file diff --git a/prod-team-account/OIDC/iam/main.tf b/prod-team-account/OIDC/iam/main.tf new file mode 100644 index 0000000..e7e8bd4 --- /dev/null +++ b/prod-team-account/OIDC/iam/main.tf @@ -0,0 +1,44 @@ +# prod-team-account의 main.tf +# modules/github_oidc를 불러와 해당account별 OIDC역할을 자동으로 생성하는 구조 + +module "github_oidc" { + source = "../../../modules/github_oidc" + + role_name = "Application-Deployment-role2" + + # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 + sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" + + + # 이 role에 연결할 정책들(IAM 정책 ARN) + policy_arns = [ + "arn:aws:iam::aws:policy/AdministratorAccess" + ] +} + +data "aws_caller_identity" "current" {} + + +#tfsec:ignore:aws-iam-no-policy-wildcards +resource "aws_iam_role_policy" "custom_inline_policy" { + name = "prod-role" + role = module.github_oidc.oidc_role_name # 모듈에서 출력된 role이름 참조 + + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "VisualEditor0", + "Effect" : "Allow", + "Action" : [ + "rds:*", + "s3:*", + "ec2:*", + "dynamodb:*", + "kms:*" + ], + "Resource" : "*" + } + ] + }) +} \ No newline at end of file diff --git a/stage-team-account/OIDC/iam/backend.tf b/stage-team-account/OIDC/iam/backend.tf new file mode 100644 index 0000000..1b3bf8a --- /dev/null +++ b/stage-team-account/OIDC/iam/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "cloudfence-stage-state" + key = "OIDC/iam.tfstate" + region = "ap-northeast-2" + encrypt = true + dynamodb_table = "s3-stage-lock" + } +} \ No newline at end of file diff --git a/stage-team-account/OIDC/iam/main.tf b/stage-team-account/OIDC/iam/main.tf new file mode 100644 index 0000000..7a5954a --- /dev/null +++ b/stage-team-account/OIDC/iam/main.tf @@ -0,0 +1,44 @@ +# stage-team-account의 main.tf +# modules/github_oidc를 불러와 해당account별 OIDC역할을 자동으로 생성하는 구조 + +module "github_oidc" { + source = "../../../modules/github_oidc" + + role_name = "Application-deployment-role3" + + # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 + sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" + + + # 이 role에 연결할 정책들(IAM 정책 ARN) + policy_arns = [ + "arn:aws:iam::aws:policy/AdministratorAccess" + ] +} + + + + +#tfsec:ignore:aws-iam-no-policy-wildcards +resource "aws_iam_role_policy" "custom_inline_policy" { + name = "stage-role" + role = module.github_oidc.oidc_role_name # 모듈에서 출력된 role이름 참조 + + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "VisualEditor0", + "Effect" : "Allow", + "Action" : [ + "s3:*", + "ec2:*", + "rds:*", + "dynamodb:*", + "kms:*" + ], + "Resource" : "*" + } + ] + }) +} \ No newline at end of file From 79be86498bf3b04afe777bc8cb572c1ea87b6a0a Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Thu, 10 Jul 2025 00:19:53 +0900 Subject: [PATCH 02/19] =?UTF-8?q?ci=20=EC=A4=91=EB=B3=B5=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 1 + dev-team-account/OIDC/iam/backend.tf | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7032419..eeb6248 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,7 @@ jobs: done # 중복 제거 및 JSON 포맷 생성 + UNIQUE_LINES=$(sort $TMP_FILE | uniq) MATRIX_JSON="[" FIRST=1 diff --git a/dev-team-account/OIDC/iam/backend.tf b/dev-team-account/OIDC/iam/backend.tf index e69de29..d0d6476 100644 --- a/dev-team-account/OIDC/iam/backend.tf +++ b/dev-team-account/OIDC/iam/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "cloudfence-dev-state" + key = "OIDC/iam.tfstate" + region = "ap-northeast-2" + encrypt = true + dynamodb_table = "s3-dev-lock" + } +} \ No newline at end of file From 9a22c159ab5168e26088ba89d40a3625e46d1096 Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Thu, 10 Jul 2025 00:24:37 +0900 Subject: [PATCH 03/19] =?UTF-8?q?=ED=8F=AC=EB=A7=B7=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.tf | 8 ++------ modules/github_oidc/main.tf | 4 ++-- prod-team-account/OIDC/iam/backend.tf | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/main.tf b/main.tf index e0031a5..a1b16b7 100644 --- a/main.tf +++ b/main.tf @@ -12,7 +12,7 @@ provider "aws" { # S3 버킷 생성 resource "aws_s3_bucket" "tfstate_app" { - bucket = "cloudfence-tfstate-app" + bucket = "cloudfence-tfstate-app" lifecycle { prevent_destroy = true @@ -64,7 +64,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "encryption" { # DynamoDB 테이블 생성 resource "aws_dynamodb_table" "lock_app" { - name = "tfstate-lock-app" + name = "tfstate-lock-app" billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" @@ -81,8 +81,4 @@ resource "aws_dynamodb_table" "lock_app" { Name = "Terraform Lock Table" Environment = "application-deployment" } -<<<<<<< HEAD } -======= -} ->>>>>>> origin/main diff --git a/modules/github_oidc/main.tf b/modules/github_oidc/main.tf index d2cdcf6..dfe8a3f 100644 --- a/modules/github_oidc/main.tf +++ b/modules/github_oidc/main.tf @@ -3,8 +3,8 @@ data "aws_caller_identity" "current" {} provider "aws" { - region = "ap-northeast-2" - + region = "ap-northeast-2" + } diff --git a/prod-team-account/OIDC/iam/backend.tf b/prod-team-account/OIDC/iam/backend.tf index ec45978..006fcd1 100644 --- a/prod-team-account/OIDC/iam/backend.tf +++ b/prod-team-account/OIDC/iam/backend.tf @@ -6,6 +6,6 @@ terraform { region = "ap-northeast-2" encrypt = true dynamodb_table = "s3-prod-lock" - + } } \ No newline at end of file From 60fe9825e9b8b0263f0f4a4bb298686454bf345a Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Thu, 10 Jul 2025 01:21:06 +0900 Subject: [PATCH 04/19] =?UTF-8?q?iam=20=EC=A0=95=EC=B1=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev-team-account/OIDC/iam/main.tf | 3 ++- identity-team-account/OIDC/iam/main.tf | 12 +++++++++++- stage-team-account/OIDC/iam/main.tf | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/dev-team-account/OIDC/iam/main.tf b/dev-team-account/OIDC/iam/main.tf index d1c174f..fa681a5 100644 --- a/dev-team-account/OIDC/iam/main.tf +++ b/dev-team-account/OIDC/iam/main.tf @@ -32,7 +32,8 @@ resource "aws_iam_role_policy" "custom_inline_policy" { "s3:*", "ec2:*", "kms:*", - "dynamodb:*" + "dynamodb:*", + "iam:*" ], "Resource" : "*" } diff --git a/identity-team-account/OIDC/iam/main.tf b/identity-team-account/OIDC/iam/main.tf index 97b6f44..5aafec3 100644 --- a/identity-team-account/OIDC/iam/main.tf +++ b/identity-team-account/OIDC/iam/main.tf @@ -68,7 +68,17 @@ resource "aws_iam_role_policy" "custom_inline_policy" { "dynamodb:*" ], "Resource" : "*" - } + }, + { + "Sid": "Statement3", + "Effect": "Allow", + "Action": [ + "iam:*" + ], + "Resource": [ + "*" + ] + } ] }) } diff --git a/stage-team-account/OIDC/iam/main.tf b/stage-team-account/OIDC/iam/main.tf index 7a5954a..97141c0 100644 --- a/stage-team-account/OIDC/iam/main.tf +++ b/stage-team-account/OIDC/iam/main.tf @@ -35,7 +35,8 @@ resource "aws_iam_role_policy" "custom_inline_policy" { "ec2:*", "rds:*", "dynamodb:*", - "kms:*" + "kms:*", + "iam:*" ], "Resource" : "*" } From 85a88bc3da4f0421c12ebce95820fb583d198f20 Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Thu, 10 Jul 2025 01:23:09 +0900 Subject: [PATCH 05/19] =?UTF-8?q?=ED=8F=AC=EB=A7=B7=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- identity-team-account/OIDC/iam/main.tf | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/identity-team-account/OIDC/iam/main.tf b/identity-team-account/OIDC/iam/main.tf index 5aafec3..ea798fb 100644 --- a/identity-team-account/OIDC/iam/main.tf +++ b/identity-team-account/OIDC/iam/main.tf @@ -70,15 +70,15 @@ resource "aws_iam_role_policy" "custom_inline_policy" { "Resource" : "*" }, { - "Sid": "Statement3", - "Effect": "Allow", - "Action": [ - "iam:*" - ], - "Resource": [ - "*" - ] - } + "Sid" : "Statement3", + "Effect" : "Allow", + "Action" : [ + "iam:*" + ], + "Resource" : [ + "*" + ] + } ] }) } From 1e3e4e1448ea4a786140a3b115a0deb960bfa13b Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Sun, 13 Jul 2025 16:28:06 +0900 Subject: [PATCH 06/19] =?UTF-8?q?OIDC=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20PR=20output=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 10 +-- .github/workflows/ci.yml | 45 ++++++++-- dev-team-account/OIDC/iam/main.tf | 11 +-- identity-team-account/OIDC/iam/main.tf | 51 +++++++----- modules/github_oidc/main.tf | 57 +++++++------ modules/github_oidc/variables.tf | 20 ++++- operation-team-account/OIDC/iam/main.tf | 75 +++++++++++++---- prod-team-account/OIDC/iam/main.tf | 104 ++++++++++++++++++++++-- stage-team-account/OIDC/iam/main.tf | 10 +-- 9 files changed, 285 insertions(+), 98 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 0e06147..18c0033 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -30,12 +30,9 @@ jobs: - 'prod-team-account/**' dev: - 'dev-team-account/**' - security: - - 'security-team-account/**' stage: - 'stage-team-account/**' - management: - - 'management-team-account/**' + - name: Build Matrix from Filter (with subdirs) id: set @@ -45,9 +42,7 @@ jobs: FILTER_OUTPUTS_identity: ${{ steps.filter.outputs.identity }} FILTER_OUTPUTS_prod: ${{ steps.filter.outputs.prod }} FILTER_OUTPUTS_dev: ${{ steps.filter.outputs.dev }} - FILTER_OUTPUTS_security: ${{ steps.filter.outputs.security }} FILTER_OUTPUTS_stage: ${{ steps.filter.outputs.stage }} - FILTER_OUTPUTS_management: ${{ steps.filter.outputs.management }} run: | # 계정 별 IAM Role Key 매핑 declare -A ROLE_MAP=( @@ -55,9 +50,8 @@ jobs: ["identity"]="ROLE_ARN_IDENTITY" ["prod"]="ROLE_ARN_PROD" ["dev"]="ROLE_ARN_DEV" - ["security"]="ROLE_ARN_SECURITY" ["stage"]="ROLE_ARN_STAGE" - ["management"]="ROLE_ARN_MANAGEMENT" + ) MATRIX_ITEMS=() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eeb6248..80316e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: - "dev-team-account/**" - "stage-team-account/**" + permissions: contents: read # 코드 읽을 수 있는 권한 pull-requests: write # PR 코멘트 작성 등 가능 @@ -43,6 +44,7 @@ jobs: ["prod-team-account"]="ROLE_ARN_PROD" ["dev-team-account"]="ROLE_ARN_DEV" ["stage-team-account"]="ROLE_ARN_STAGE" + ) TMP_FILE=$(mktemp) @@ -147,7 +149,7 @@ jobs: - name: Terraform Plan id: plan - continue-on-error: true #plan 실패해도 워크플로우 진행 + continue-on-error: true run: | START_TIME=$(date -u +"%Y-%m-%d %H:%M:%S UTC") echo "START_TIME=$START_TIME" >> $GITHUB_ENV @@ -155,15 +157,40 @@ jobs: PLAN_FILE=tfplan.binary PLAN_TXT=plan.txt - terraform plan -no-color -out=$PLAN_FILE || echo "PLAN_FAILED=true" >> $GITHUB_ENV - terraform show -no-color $PLAN_FILE > $PLAN_TXT || echo "Plan failed" > $PLAN_TXT + # 기본 실패 메시지 + PLAN_CONTENT="Terraform plan failed." - # plan 출력값을 멀티라인 환경 변수에 저장 - PLAN_CONTENT=$(cat $PLAN_TXT | sed 's/`/\\`/g') + # Terraform Plan 실행 + if terraform plan -no-color -out=$PLAN_FILE; then + echo "[INFO] terraform plan succeeded." + + # show 및 no change 여부 판단 + terraform show -no-color $PLAN_FILE > $PLAN_TXT - echo 'PLAN<> $GITHUB_OUTPUT - cat plan.txt >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT + IS_EMPTY=$(terraform show -json $PLAN_FILE 2>/dev/null | jq -e '.resource_changes == []' 2>/dev/null || echo "false") + + if [ "$IS_EMPTY" = "true" ]; then + PLAN_CONTENT="(plan success) No changes to apply. Everything looks good!" + else + # 길이 제한 없이 전체 내용 출력 (출력 너무 클 경우 GitHub가 거부할 수 있음 — 감수한 상태) + PLAN_CONTENT=$( + cat $PLAN_TXT | \ + sed 's/`/\\`/g' | \ + sed 's/::/%::%/g' | \ + tr -d '\r' | \ + sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" + ) + fi + else + echo "[ERROR] terraform plan failed. Skipping show." + echo "Terraform plan failed." > $PLAN_TXT + fi + + { + echo "PLAN_CONTENT<> "$GITHUB_OUTPUT" working-directory: ${{ matrix.dir }} - name: Comment Terraform Plan on PR @@ -202,4 +229,4 @@ jobs: uses: infracost/actions/comment@v1 with: path: ${{ matrix.dir }}/infracost.json - behavior: update # 기존 코멘트 업데이트 + behavior: update # 기존 코멘트 업데이트 \ No newline at end of file diff --git a/dev-team-account/OIDC/iam/main.tf b/dev-team-account/OIDC/iam/main.tf index fa681a5..591f5cb 100644 --- a/dev-team-account/OIDC/iam/main.tf +++ b/dev-team-account/OIDC/iam/main.tf @@ -6,14 +6,15 @@ module "github_oidc" { role_name = "application-deployment-role1" # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 - sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" - + sub_condition = ["repo:WHS-DevSecOps-infra/Organization:*", + "repo:WHS-DevSecOps-infra/Application-Deployment:*"] + thumbprint_list = [ + "d89e3bd43d5d909b47a18977aa9d5ce36cee184c" + ] # 이 role에 연결할 정책들(IAM 정책 ARN) - policy_arns = [ - "arn:aws:iam::aws:policy/AdministratorAccess" - ] + policy_arns = [] } #tfsec:ignore:aws-iam-no-policy-wildcards diff --git a/identity-team-account/OIDC/iam/main.tf b/identity-team-account/OIDC/iam/main.tf index ea798fb..55cd384 100644 --- a/identity-team-account/OIDC/iam/main.tf +++ b/identity-team-account/OIDC/iam/main.tf @@ -7,12 +7,17 @@ module "github_oidc" { role_name = "Organization-role" # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 - sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" + sub_condition = ["repo:WHS-DevSecOps-infra/Organization:*", + "repo:WHS-DevSecOps-infra/Application-Deployment:*", + "repo:WHS-DevSecOps-infra/Monitoring:*"] # 이 role에 연결할 정책들(IAM 정책 ARN) policy_arns = [ - "arn:aws:iam::aws:policy/AdministratorAccess" + + ] + thumbprint_list = [ + "d89e3bd43d5d909b47a18977aa9d5ce36cee184c" ] } @@ -22,26 +27,16 @@ resource "aws_iam_role_policy" "custom_inline_policy" { role = module.github_oidc.oidc_role_name # 모듈에서 출력된 role이름 참조 policy = jsonencode({ - Version = "2012-10-17", - Statement = [ + "Version" : "2012-10-17", + "Statement" : [ { - Effect = "Allow", - Action = [ - "sso:CreatePermissionSet", - "sso:DescribePermissionSet", - "sso:UpdatePermissionSet", - "sso:DeletePermissionSet", - "sso:AttachManagedPolicyToPermissionSet", - "sso:ListPermissionSets", - "sso:ListInstances", - "sso:ProvisionPermissionSet", - "sso:PutInlinePolicyToPermissionSet", - "sso:DeleteInlinePolicyFromPermissionSet", - "sso:ListPermissionSetProvisioningStatus", - "organizations:*", - "identitystore:*" + "Sid" : "SSOAccess", + "Effect" : "Allow", + "Action" : [ + "sso:*", + "sso:CreateAccountAssignment" ], - Resource = "*" + "Resource" : "*" }, { "Sid" : "S3Access", @@ -69,6 +64,22 @@ resource "aws_iam_role_policy" "custom_inline_policy" { ], "Resource" : "*" }, + { + "Sid" : "Statement1", + "Effect" : "Allow", + "Action" : [ + "organizations:*" + ], + "Resource" : "*" + }, + { + "Sid" : "Statement2", + "Effect" : "Allow", + "Action" : [ + "identitystore:*" + ], + "Resource" : "*" + }, { "Sid" : "Statement3", "Effect" : "Allow", diff --git a/modules/github_oidc/main.tf b/modules/github_oidc/main.tf index dfe8a3f..1e5482f 100644 --- a/modules/github_oidc/main.tf +++ b/modules/github_oidc/main.tf @@ -1,14 +1,8 @@ -#현재 계정정보를 가져옴 -data "aws_caller_identity" "current" {} - provider "aws" { region = "ap-northeast-2" } - - -# GitHub Actions용 OIDC provider 설정 resource "aws_iam_openid_connect_provider" "github" { url = "https://token.actions.githubusercontent.com" @@ -16,32 +10,43 @@ resource "aws_iam_openid_connect_provider" "github" { "sts.amazonaws.com" # OIDC에서 사용할 클라이언트 ID ] - thumbprint_list = [ - "6938fd4d98bab03faadb97b34396831e3780aea1" # GitHub 공식 인증서 지문 thumbprint(공식값) - ] + thumbprint_list = var.thumbprint_list } -# oidc_role 이라는 이름의 IAM Role resource "aws_iam_role" "oidc_role" { - name = var.role_name # 생성할 Role 이름 + name = var.role_name + description = "cicd" - # GitHub에서 이 역할을 assume할 수 있게 설정 assume_role_policy = jsonencode({ Version = "2012-10-17", - Statement = [ - { - Effect = "Allow", - Principal = { - Federated = aws_iam_openid_connect_provider.github.arn # 위에서 만든 OIDC Provider의 ARN - }, - Action = "sts:AssumeRoleWithWebIdentity", - Condition = { - StringLike = { - # 어떤 GitHub repo에서만 이 Role을 사용할 수 있는지 제어 - "token.actions.githubusercontent.com:sub" : var.sub_condition + Statement = concat( + [ + { + Effect = "Allow", + Principal = { + Federated = aws_iam_openid_connect_provider.github.arn + }, + Action = "sts:AssumeRoleWithWebIdentity", + Condition = { + StringEquals = { + "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" + }, + StringLike = { + "token.actions.githubusercontent.com:sub" = var.sub_condition + } } } - } - ] + ], + var.add_root_trust ? [ + { + Effect = "Allow", + Principal = { + AWS = "arn:aws:iam::${var.account_id}:root" + }, + Action = "sts:AssumeRole" + } + ] : [] + ) + }) -} \ No newline at end of file +} diff --git a/modules/github_oidc/variables.tf b/modules/github_oidc/variables.tf index d5dd127..d2f17a5 100644 --- a/modules/github_oidc/variables.tf +++ b/modules/github_oidc/variables.tf @@ -4,7 +4,7 @@ variable "role_name" { } variable "sub_condition" { - type = string + type = list(string) description = "OIDC Subject (sub) 조건" } @@ -18,3 +18,21 @@ resource "aws_iam_role_policy_attachment" "attach_policy" { role = aws_iam_role.oidc_role.name policy_arn = each.value } + +variable "thumbprint_list" { + description = "OIDC provider thumbprint list" + type = list(string) + default = ["6938fd4d98bab03faadb97b34396831e3780aea1"] +} + +variable "add_root_trust" { + description = "Whether to add root account trust" + type = bool + default = false +} + +variable "account_id" { + description = "AWS account ID for root trust" + type = string + default = "" +} diff --git a/operation-team-account/OIDC/iam/main.tf b/operation-team-account/OIDC/iam/main.tf index 081b464..fa5ea6b 100644 --- a/operation-team-account/OIDC/iam/main.tf +++ b/operation-team-account/OIDC/iam/main.tf @@ -6,23 +6,23 @@ module "github_oidc" { source = "../../../modules/github_oidc" - role_name = "operation-cicd" + role_name = "operation-cicd" + account_id = "502676416967" + add_root_trust = true - # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 - sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" + # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 + sub_condition = ["repo:WHS-DevSecOps-infra/Organization:*", + "repo:WHS-DevSecOps-infra/Monitoring:*", + "repo:WHS-DevSecOps-infra/Application-Deployment:*", + "repo:yunhoch0i/Application-Deployment:*"] + thumbprint_list = ["d89e3bd43d5d909b47a18977aa9d5ce36cee184c"] # 이 role에 연결할 정책들(IAM 정책 ARN) - policy_arns = [ - "arn:aws:iam::aws:policy/AmazonEC2FullAccess", - "arn:aws:iam::aws:policy/AmazonECS_FullAccess", - "arn:aws:iam::aws:policy/AmazonElasticContainerRegistryPublicFullAccess", - "arn:aws:iam::aws:policy/AmazonVPCFullAccess", - "arn:aws:iam::aws:policy/AWSCodeDeployFullAccess", - "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS", - "arn:aws:iam::aws:policy/AWSWAFFullAccess", - "arn:aws:iam::aws:policy/IAMFullAccess" - ] + policy_arns = [] + + + } #tfsec:ignore:aws-iam-no-policy-wildcards @@ -42,9 +42,7 @@ resource "aws_iam_role_policy" "custom_inline_policy" { "s3:*", "sts:AssumeRole" ], - "Resource" : [ - "*" - ] + "Resource" : ["*"] }, { "Effect" : "Allow", @@ -69,7 +67,50 @@ resource "aws_iam_role_policy" "custom_inline_policy" { "dynamodb:*" ], "Resource" : "*" + }, + { + "Sid" : "TerraformBackendOperationState", + "Effect" : "Allow", + "Action" : [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket" + ], + "Resource" : [ + "arn:aws:s3:::cloudfence-operation-state", + "arn:aws:s3:::cloudfence-operation-state/*" + ] + }, + { + "Sid" : "TerraformDynamoDBLock", + "Effect" : "Allow", + "Action" : [ + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:DeleteItem" + ], + "Resource" : "arn:aws:dynamodb:*:*:table/s3-operation-lock" + }, + { + "Sid" : "KMSAccessForState", + "Effect" : "Allow", + "Action" : [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey" + ], + "Resource" : "arn:aws:kms:ap-northeast-2:502676416967:key/9901c9d1-8b00-47a9-bd7a-53cfc1f70d25" + }, + { + "Sid" : "ECRAndIAMManagement", + "Effect" : "Allow", + "Action" : [ + "ecr:*", + "iam:CreateServiceLinkedRole" + ], + "Resource" : "*" } ] - }) + } + ) } diff --git a/prod-team-account/OIDC/iam/main.tf b/prod-team-account/OIDC/iam/main.tf index e7e8bd4..c4163a5 100644 --- a/prod-team-account/OIDC/iam/main.tf +++ b/prod-team-account/OIDC/iam/main.tf @@ -4,16 +4,15 @@ module "github_oidc" { source = "../../../modules/github_oidc" - role_name = "Application-Deployment-role2" - + role_name = "Application-Deployment-role2" + add_root_trust = false # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 - sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" - + sub_condition = ["repo:WHS-DevSecOps-infra/Organization:*", + "repo:WHS-DevSecOps-infra/Application-Deployment:*"] + thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"] # 이 role에 연결할 정책들(IAM 정책 ARN) - policy_arns = [ - "arn:aws:iam::aws:policy/AdministratorAccess" - ] + policy_arns = [] } data "aws_caller_identity" "current" {} @@ -38,6 +37,97 @@ resource "aws_iam_role_policy" "custom_inline_policy" { "kms:*" ], "Resource" : "*" + }, + { + "Sid" : "TerraformBackendProdState", + "Effect" : "Allow", + "Action" : [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket" + ], + "Resource" : [ + "arn:aws:s3:::cloudfence-prod-state", + "arn:aws:s3:::cloudfence-prod-state/*" + ] + }, + { + "Sid" : "TerraformBackendOperationState", + "Effect" : "Allow", + "Action" : [ + "s3:GetObject", + "s3:ListBucket" + ], + "Resource" : [ + "arn:aws:s3:::cloudfence-operation-state", + "arn:aws:s3:::cloudfence-operation-state/*" + ] + }, + { + "Sid" : "TerraformDynamoDBLock", + "Effect" : "Allow", + "Action" : [ + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:DeleteItem" + ], + "Resource" : "arn:aws:dynamodb:*:*:table/s3-operation-lock" + }, + { + "Sid" : "KMSDecryptForStateFiles", + "Effect" : "Allow", + "Action" : [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey" + ], + "Resource" : "arn:aws:kms:ap-northeast-2:243359234795:key/c2c5da76-b55b-4bcc-a240-10cc6d6e9940" + }, + { + "Sid" : "AllProdResourceManagement", + "Effect" : "Allow", + "Action" : [ + "ec2:*", + "ecs:*", + "iam:*", + "elasticloadbalancing:*", + "codedeploy:*", + "autoscaling:*", + "cloudwatch:*", + "wafv2:*" + ], + "Resource" : "*" + }, + { + "Sid" : "AllowACMCertificateManagement", + "Effect" : "Allow", + "Action" : [ + "acm:RequestCertificate", + "acm:DescribeCertificate", + "acm:DeleteCertificate", + "acm:ListTagsForCertificate", + "acm:AddTagsToCertificate", + "acm:RemoveTagsFromCertificate" + ], + "Resource" : "*" + }, + { + "Sid" : "AllowRoute53DNSValidation", + "Effect" : "Allow", + "Action" : [ + "route53:GetChange", + "route53:ChangeResourceRecordSets", + "route53:ListHostedZonesByName", + "route53:GetHostedZone", + "route53:ListResourceRecordSets" + ], + "Resource" : "arn:aws:route53:::hostedzone/*" + }, + { + "Sid" : "AllowRoute53GetChange", + "Effect" : "Allow", + "Action" : "route53:GetChange", + "Resource" : "arn:aws:route53:::change/*" } ] }) diff --git a/stage-team-account/OIDC/iam/main.tf b/stage-team-account/OIDC/iam/main.tf index 97141c0..338f4b7 100644 --- a/stage-team-account/OIDC/iam/main.tf +++ b/stage-team-account/OIDC/iam/main.tf @@ -6,16 +6,16 @@ module "github_oidc" { role_name = "Application-deployment-role3" + thumbprint_list = ["d89e3bd43d5d909b47a18977aa9d5ce36cee184c"] # GitHub Actions에서 이 role을 사용할 수 있도록 허용하는 sub조건 - sub_condition = "repo:WHS-DevSecOps-infra/Application-Deployment:*" + sub_condition = ["repo:WHS-DevSecOps-infra/Organization:*", + "repo:WHS-DevSecOps-infra/Application-Deployment:*"] # 이 role에 연결할 정책들(IAM 정책 ARN) - policy_arns = [ - "arn:aws:iam::aws:policy/AdministratorAccess" - ] -} + policy_arns = [] +} From 4af40c467ead6fb14ea564a0efcd36bac92a200e Mon Sep 17 00:00:00 2001 From: imyourhopeee <144752063+imyourhopeee@users.noreply.github.com> Date: Mon, 14 Jul 2025 22:47:34 +0900 Subject: [PATCH 07/19] change pr output line --- .github/workflows/ci.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80316e2..935596f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,6 @@ on: - "dev-team-account/**" - "stage-team-account/**" - permissions: contents: read # 코드 읽을 수 있는 권한 pull-requests: write # PR 코멘트 작성 등 가능 @@ -167,20 +166,16 @@ jobs: # show 및 no change 여부 판단 terraform show -no-color $PLAN_FILE > $PLAN_TXT + terraform show -json $PLAN_FILE > plan.json + IS_EMPTY=$(terraform show -json $PLAN_FILE 2>/dev/null | jq -e '.resource_changes == []' 2>/dev/null || echo "false") if [ "$IS_EMPTY" = "true" ]; then PLAN_CONTENT="(plan success) No changes to apply. Everything looks good!" else # 길이 제한 없이 전체 내용 출력 (출력 너무 클 경우 GitHub가 거부할 수 있음 — 감수한 상태) - PLAN_CONTENT=$( - cat $PLAN_TXT | \ - sed 's/`/\\`/g' | \ - sed 's/::/%::%/g' | \ - tr -d '\r' | \ - sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" - ) - fi + PLAN_CONTENT=$(cat $PLAN_TXT) + fi else echo "[ERROR] terraform plan failed. Skipping show." echo "Terraform plan failed." > $PLAN_TXT @@ -229,4 +224,4 @@ jobs: uses: infracost/actions/comment@v1 with: path: ${{ matrix.dir }}/infracost.json - behavior: update # 기존 코멘트 업데이트 \ No newline at end of file + behavior: update # 기존 코멘트 업데이트 From 2a408ab493c530a0310dadd7896ce6a1676dbb89 Mon Sep 17 00:00:00 2001 From: imyourhopeee <144752063+imyourhopeee@users.noreply.github.com> Date: Mon, 14 Jul 2025 23:01:01 +0900 Subject: [PATCH 08/19] change pr output line_2 --- .github/workflows/ci.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 935596f..a0b9280 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,16 +156,12 @@ jobs: PLAN_FILE=tfplan.binary PLAN_TXT=plan.txt - # 기본 실패 메시지 PLAN_CONTENT="Terraform plan failed." - # Terraform Plan 실행 if terraform plan -no-color -out=$PLAN_FILE; then echo "[INFO] terraform plan succeeded." - - # show 및 no change 여부 판단 - terraform show -no-color $PLAN_FILE > $PLAN_TXT + terraform show -no-color $PLAN_FILE > $PLAN_TXT terraform show -json $PLAN_FILE > plan.json IS_EMPTY=$(terraform show -json $PLAN_FILE 2>/dev/null | jq -e '.resource_changes == []' 2>/dev/null || echo "false") @@ -173,8 +169,14 @@ jobs: if [ "$IS_EMPTY" = "true" ]; then PLAN_CONTENT="(plan success) No changes to apply. Everything looks good!" else - # 길이 제한 없이 전체 내용 출력 (출력 너무 클 경우 GitHub가 거부할 수 있음 — 감수한 상태) - PLAN_CONTENT=$(cat $PLAN_TXT) + # Escape 처리 추가됨 + PLAN_CONTENT=$( + cat $PLAN_TXT | \ + sed 's/`/\\`/g' | \ + sed 's/::/%::%/g' | \ + tr -d '\r' | \ + sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" + ) fi else echo "[ERROR] terraform plan failed. Skipping show." From ffedc06582a1af877e7b8a61a3ff562ca1d06282 Mon Sep 17 00:00:00 2001 From: imyourhopeee <144752063+imyourhopeee@users.noreply.github.com> Date: Mon, 14 Jul 2025 23:16:30 +0900 Subject: [PATCH 09/19] change pr output line_3 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0b9280..8a1eaf7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -184,9 +184,9 @@ jobs: fi { - echo "PLAN_CONTENT<> "$GITHUB_OUTPUT" working-directory: ${{ matrix.dir }} From 4b696f21dc42029e9eaacf5f3ee2ef5311ae5825 Mon Sep 17 00:00:00 2001 From: imyourhopeee <144752063+imyourhopeee@users.noreply.github.com> Date: Mon, 14 Jul 2025 23:33:17 +0900 Subject: [PATCH 10/19] change pr output line_3 --- .github/workflows/ci.yml | 53 ++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a1eaf7..a8d3fb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,61 +155,50 @@ jobs: PLAN_FILE=tfplan.binary PLAN_TXT=plan.txt - - PLAN_CONTENT="Terraform plan failed." + PLAN_TXT_CLEAN=plan_content.txt if terraform plan -no-color -out=$PLAN_FILE; then echo "[INFO] terraform plan succeeded." - terraform show -no-color $PLAN_FILE > $PLAN_TXT terraform show -json $PLAN_FILE > plan.json IS_EMPTY=$(terraform show -json $PLAN_FILE 2>/dev/null | jq -e '.resource_changes == []' 2>/dev/null || echo "false") if [ "$IS_EMPTY" = "true" ]; then - PLAN_CONTENT="(plan success) No changes to apply. Everything looks good!" + echo "(plan success) No changes to apply. Everything looks good!" > $PLAN_TXT_CLEAN else - # Escape 처리 추가됨 - PLAN_CONTENT=$( - cat $PLAN_TXT | \ + cat $PLAN_TXT | \ sed 's/`/\\`/g' | \ sed 's/::/%::%/g' | \ tr -d '\r' | \ - sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" - ) + sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" > $PLAN_TXT_CLEAN fi else - echo "[ERROR] terraform plan failed. Skipping show." - echo "Terraform plan failed." > $PLAN_TXT + echo "[ERROR] terraform plan failed. Skipping show." > $PLAN_TXT_CLEAN fi - { - printf "PLAN_CONTENT<> "$GITHUB_OUTPUT" + # PR comment용 텍스트 조립 + echo "## [Terraform Plan Summary]" > comment.txt + echo "| 항목 | 값 |" >> comment.txt + echo "|-----------------|-----|" >> comment.txt + echo "| **Status** | ${{ steps.plan.outcome }} |" >> comment.txt + echo "| **Directory** | ${{ matrix.dir }} |" >> comment.txt + echo "| **Executed At** | $START_TIME |" >> comment.txt + echo "" >> comment.txt + echo "---" >> comment.txt + echo "" >> comment.txt + echo "### Plan Output" >> comment.txt + echo '```hcl' >> comment.txt + cat $PLAN_TXT_CLEAN >> comment.txt + echo '```' >> comment.txt working-directory: ${{ matrix.dir }} - - name: Comment Terraform Plan on PR + - name: Post Terraform Plan Comment if: github.event.pull_request.number != '' uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} - body: | - ## [Terraform Plan Summary] - | 항목 | 값 | - |-----------------|-----| - | **Status** | `${{ steps.plan.outcome }}` | - | **Directory** | `${{ matrix.dir }}` | - | **Executed At** | `${{ env.START_TIME }}` | - - - --- - - ### Plan Output - ```hcl - ${{ steps.plan.outputs.PLAN_CONTENT }} - ``` + body-path: ${{ matrix.dir }}/comment.txt - name: Setup Infracost # 비용 예측 도구 세팅 uses: infracost/actions/setup@v2 From b3227e1554da4b50f3cc81097f3c16bf6c94b377 Mon Sep 17 00:00:00 2001 From: imyourhopeee <144752063+imyourhopeee@users.noreply.github.com> Date: Mon, 14 Jul 2025 23:41:48 +0900 Subject: [PATCH 11/19] change pr output line_4 --- .github/workflows/ci.yml | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8d3fb4..7e2b6f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -157,15 +157,18 @@ jobs: PLAN_TXT=plan.txt PLAN_TXT_CLEAN=plan_content.txt + PLAN_CONTENT="Terraform plan failed." + if terraform plan -no-color -out=$PLAN_FILE; then echo "[INFO] terraform plan succeeded." + terraform show -no-color $PLAN_FILE > $PLAN_TXT terraform show -json $PLAN_FILE > plan.json IS_EMPTY=$(terraform show -json $PLAN_FILE 2>/dev/null | jq -e '.resource_changes == []' 2>/dev/null || echo "false") if [ "$IS_EMPTY" = "true" ]; then - echo "(plan success) No changes to apply. Everything looks good!" > $PLAN_TXT_CLEAN + PLAN_CONTENT="(plan success) No changes to apply. Everything looks good!" else cat $PLAN_TXT | \ sed 's/`/\\`/g' | \ @@ -174,32 +177,31 @@ jobs: sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" > $PLAN_TXT_CLEAN fi else - echo "[ERROR] terraform plan failed. Skipping show." > $PLAN_TXT_CLEAN + echo "[ERROR] terraform plan failed. Skipping show." + echo "Terraform plan failed." > $PLAN_TXT fi - - # PR comment용 텍스트 조립 - echo "## [Terraform Plan Summary]" > comment.txt - echo "| 항목 | 값 |" >> comment.txt - echo "|-----------------|-----|" >> comment.txt - echo "| **Status** | ${{ steps.plan.outcome }} |" >> comment.txt - echo "| **Directory** | ${{ matrix.dir }} |" >> comment.txt - echo "| **Executed At** | $START_TIME |" >> comment.txt - echo "" >> comment.txt - echo "---" >> comment.txt - echo "" >> comment.txt - echo "### Plan Output" >> comment.txt - echo '```hcl' >> comment.txt - cat $PLAN_TXT_CLEAN >> comment.txt - echo '```' >> comment.txt working-directory: ${{ matrix.dir }} - - name: Post Terraform Plan Comment + - name: Comment Terraform Plan on PR if: github.event.pull_request.number != '' uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} - body-path: ${{ matrix.dir }}/comment.txt + body: | + ## [Terraform Plan Summary] + | 항목 | 값 | + |-----------------|-----| + | **Status** | `${{ steps.plan.outcome }}` | + | **Directory** | `${{ matrix.dir }}` | + | **Executed At** | `${{ env.START_TIME }}` | + + + --- + ### Plan Output + ```hcl + ${{ steps.plan.outputs.PLAN_CONTENT }} + ``` - name: Setup Infracost # 비용 예측 도구 세팅 uses: infracost/actions/setup@v2 From 38a955c0380584116ff7db5341e8e2e33d5cdfc6 Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Mon, 14 Jul 2025 23:35:45 +0900 Subject: [PATCH 12/19] =?UTF-8?q?=EB=AA=A8=EB=93=88=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev-team-account/OIDC/iam/main.tf | 2 +- identity-team-account/OIDC/iam/main.tf | 2 +- modules/{github_oidc => iam_OIDC}/main.tf | 0 modules/{github_oidc => iam_OIDC}/outputs.tf | 0 modules/{github_oidc => iam_OIDC}/variables.tf | 0 operation-team-account/OIDC/iam/main.tf | 2 +- prod-team-account/OIDC/iam/main.tf | 2 +- stage-team-account/OIDC/iam/main.tf | 2 +- 8 files changed, 5 insertions(+), 5 deletions(-) rename modules/{github_oidc => iam_OIDC}/main.tf (100%) rename modules/{github_oidc => iam_OIDC}/outputs.tf (100%) rename modules/{github_oidc => iam_OIDC}/variables.tf (100%) diff --git a/dev-team-account/OIDC/iam/main.tf b/dev-team-account/OIDC/iam/main.tf index 591f5cb..818f749 100644 --- a/dev-team-account/OIDC/iam/main.tf +++ b/dev-team-account/OIDC/iam/main.tf @@ -1,7 +1,7 @@ # modules/github_oidc를 불러와 해당account별 OIDC역할을 자동으로 생성하는 구조 # module "github_oidc" { - source = "../../../modules/github_oidc" + source = "../../../modules/iam_OIDC" role_name = "application-deployment-role1" diff --git a/identity-team-account/OIDC/iam/main.tf b/identity-team-account/OIDC/iam/main.tf index 55cd384..ab27c6e 100644 --- a/identity-team-account/OIDC/iam/main.tf +++ b/identity-team-account/OIDC/iam/main.tf @@ -2,7 +2,7 @@ # modules/github_oidc를 불러와 해당account별 OIDC역할을 자동으로 생성하는 구조 module "github_oidc" { - source = "../../../modules/github_oidc" + source = "../../../modules/iam_OIDC" role_name = "Organization-role" diff --git a/modules/github_oidc/main.tf b/modules/iam_OIDC/main.tf similarity index 100% rename from modules/github_oidc/main.tf rename to modules/iam_OIDC/main.tf diff --git a/modules/github_oidc/outputs.tf b/modules/iam_OIDC/outputs.tf similarity index 100% rename from modules/github_oidc/outputs.tf rename to modules/iam_OIDC/outputs.tf diff --git a/modules/github_oidc/variables.tf b/modules/iam_OIDC/variables.tf similarity index 100% rename from modules/github_oidc/variables.tf rename to modules/iam_OIDC/variables.tf diff --git a/operation-team-account/OIDC/iam/main.tf b/operation-team-account/OIDC/iam/main.tf index fa5ea6b..c8d9493 100644 --- a/operation-team-account/OIDC/iam/main.tf +++ b/operation-team-account/OIDC/iam/main.tf @@ -4,7 +4,7 @@ module "github_oidc" { - source = "../../../modules/github_oidc" + source = "../../../modules/iam_OIDC" role_name = "operation-cicd" account_id = "502676416967" diff --git a/prod-team-account/OIDC/iam/main.tf b/prod-team-account/OIDC/iam/main.tf index c4163a5..efcc1ff 100644 --- a/prod-team-account/OIDC/iam/main.tf +++ b/prod-team-account/OIDC/iam/main.tf @@ -2,7 +2,7 @@ # modules/github_oidc를 불러와 해당account별 OIDC역할을 자동으로 생성하는 구조 module "github_oidc" { - source = "../../../modules/github_oidc" + source = "../../../modules/iam_OIDC" role_name = "Application-Deployment-role2" add_root_trust = false diff --git a/stage-team-account/OIDC/iam/main.tf b/stage-team-account/OIDC/iam/main.tf index 338f4b7..d9c0edd 100644 --- a/stage-team-account/OIDC/iam/main.tf +++ b/stage-team-account/OIDC/iam/main.tf @@ -2,7 +2,7 @@ # modules/github_oidc를 불러와 해당account별 OIDC역할을 자동으로 생성하는 구조 module "github_oidc" { - source = "../../../modules/github_oidc" + source = "../../../modules/iam_OIDC" role_name = "Application-deployment-role3" From ecac1894d3abbe79d666ed4e7975143c1f651090 Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Tue, 15 Jul 2025 00:00:38 +0900 Subject: [PATCH 13/19] =?UTF-8?q?ci=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e2b6f2..d003642 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,8 +43,7 @@ jobs: ["prod-team-account"]="ROLE_ARN_PROD" ["dev-team-account"]="ROLE_ARN_DEV" ["stage-team-account"]="ROLE_ARN_STAGE" - - ) + ) TMP_FILE=$(mktemp) @@ -155,31 +154,41 @@ jobs: PLAN_FILE=tfplan.binary PLAN_TXT=plan.txt - PLAN_TXT_CLEAN=plan_content.txt + # 기본 실패 메시지 PLAN_CONTENT="Terraform plan failed." + # Terraform Plan 실행 if terraform plan -no-color -out=$PLAN_FILE; then echo "[INFO] terraform plan succeeded." - + + # show 및 no change 여부 판단 terraform show -no-color $PLAN_FILE > $PLAN_TXT - terraform show -json $PLAN_FILE > plan.json IS_EMPTY=$(terraform show -json $PLAN_FILE 2>/dev/null | jq -e '.resource_changes == []' 2>/dev/null || echo "false") if [ "$IS_EMPTY" = "true" ]; then PLAN_CONTENT="(plan success) No changes to apply. Everything looks good!" else + # 길이 제한 없이 전체 내용 출력 (출력 너무 클 경우 GitHub가 거부할 수 있음 — 감수한 상태) + PLAN_CONTENT=$( cat $PLAN_TXT | \ - sed 's/`/\\`/g' | \ - sed 's/::/%::%/g' | \ - tr -d '\r' | \ - sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" > $PLAN_TXT_CLEAN - fi + sed 's/`/\\`/g' | \ + sed 's/::/%::%/g' | \ + tr -d '\r' | \ + sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" + ) + fi else echo "[ERROR] terraform plan failed. Skipping show." echo "Terraform plan failed." > $PLAN_TXT fi + + { + echo "PLAN_CONTENT<> "$GITHUB_OUTPUT" working-directory: ${{ matrix.dir }} - name: Comment Terraform Plan on PR @@ -202,6 +211,7 @@ jobs: ```hcl ${{ steps.plan.outputs.PLAN_CONTENT }} ``` + - name: Setup Infracost # 비용 예측 도구 세팅 uses: infracost/actions/setup@v2 From c7091c182c510dc4b7f628d48b33b201e979dac5 Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Tue, 15 Jul 2025 00:12:19 +0900 Subject: [PATCH 14/19] =?UTF-8?q?ci=20=EA=B8=80=EC=9E=90=20=EA=B9=A8?= =?UTF-8?q?=EC=A7=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 45 ++++++++++------------------------------ 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d003642..65d91a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: - "identity-team-account/**" - "prod-team-account/**" - "dev-team-account/**" + - "security-team-account/**" - "stage-team-account/**" permissions: @@ -42,8 +43,10 @@ jobs: ["identity-team-account"]="ROLE_ARN_IDENTITY" ["prod-team-account"]="ROLE_ARN_PROD" ["dev-team-account"]="ROLE_ARN_DEV" + ["security-team-account"]="ROLE_ARN_SECURITY" ["stage-team-account"]="ROLE_ARN_STAGE" - ) + ["management-team-account"]="ROLE_ARN_MANAGEMENT" + ) TMP_FILE=$(mktemp) @@ -154,41 +157,15 @@ jobs: PLAN_FILE=tfplan.binary PLAN_TXT=plan.txt + PLAN_JSON=plan.json - # 기본 실패 메시지 - PLAN_CONTENT="Terraform plan failed." + terraform plan -no-color -out=$PLAN_FILE || echo "PLAN_FAILED=true" >> $GITHUB_ENV + terraform show -no-color $PLAN_FILE > $PLAN_TXT || echo "Plan failed" > $PLAN_TXT + terraform show -json $PLAN_FILE > $PLAN_JSON || true - # Terraform Plan 실행 - if terraform plan -no-color -out=$PLAN_FILE; then - echo "[INFO] terraform plan succeeded." - - # show 및 no change 여부 판단 - terraform show -no-color $PLAN_FILE > $PLAN_TXT - - IS_EMPTY=$(terraform show -json $PLAN_FILE 2>/dev/null | jq -e '.resource_changes == []' 2>/dev/null || echo "false") - - if [ "$IS_EMPTY" = "true" ]; then - PLAN_CONTENT="(plan success) No changes to apply. Everything looks good!" - else - # 길이 제한 없이 전체 내용 출력 (출력 너무 클 경우 GitHub가 거부할 수 있음 — 감수한 상태) - PLAN_CONTENT=$( - cat $PLAN_TXT | \ - sed 's/`/\\`/g' | \ - sed 's/::/%::%/g' | \ - tr -d '\r' | \ - sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" - ) - fi - else - echo "[ERROR] terraform plan failed. Skipping show." - echo "Terraform plan failed." > $PLAN_TXT - fi - - { - echo "PLAN_CONTENT<> "$GITHUB_OUTPUT" + echo "PLAN_CONTENT<> $GITHUB_OUTPUT + cat $PLAN_TXT >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT working-directory: ${{ matrix.dir }} - name: Comment Terraform Plan on PR From 1d24067eae52032a8753a5b46fb5aaeb4d7b9148 Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Tue, 15 Jul 2025 00:15:39 +0900 Subject: [PATCH 15/19] =?UTF-8?q?ci=20=EA=B8=80=EC=9E=90=20=EA=B9=A8?= =?UTF-8?q?=EC=A7=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 79 +++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65d91a0..15a3b66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,51 +1,47 @@ name: Application-Deployment CI on: - pull_request: # PR이 main 브랜치로 올 때 실행 + pull_request: branches: [main] paths: - "operation-team-account/**" - "identity-team-account/**" - "prod-team-account/**" - "dev-team-account/**" - - "security-team-account/**" - "stage-team-account/**" permissions: - contents: read # 코드 읽을 수 있는 권한 - pull-requests: write # PR 코멘트 작성 등 가능 - id-token: write # OIDC기반 인증에 필요 + contents: read + pull-requests: write + id-token: write jobs: detect-changes: runs-on: ubuntu-latest outputs: - matrix: ${{ steps.set.outputs.matrix }} # 다음 job에서 사용할 matrix 값 + matrix: ${{ steps.set.outputs.matrix }} steps: - name: Checkout Code uses: actions/checkout@v3 with: - fetch-depth: 0 # git diff가 정상 동작하려면 전체 커밋 히스토리를 가져와야 함 + fetch-depth: 0 - - name: Fetch origin/main # main 브랜치 히스토리 가져오기 + - name: Fetch origin/main run: git fetch origin main - name: Detect Changed Directories & Build Matrix id: set run: | - FILES=$(git diff --name-only origin/main...${{ github.sha }}) # 변경된 파일 목록 + FILES=$(git diff --name-only origin/main...${{ github.sha }}) echo "Changed files:" echo "$FILES" - # 각 account별 디렉토리에 해당하는 IAM Role 매핑 declare -A ROLE_MAP=( ["operation-team-account"]="ROLE_ARN_OPERATION" ["identity-team-account"]="ROLE_ARN_IDENTITY" ["prod-team-account"]="ROLE_ARN_PROD" ["dev-team-account"]="ROLE_ARN_DEV" - ["security-team-account"]="ROLE_ARN_SECURITY" ["stage-team-account"]="ROLE_ARN_STAGE" - ["management-team-account"]="ROLE_ARN_MANAGEMENT" ) TMP_FILE=$(mktemp) @@ -56,14 +52,12 @@ jobs: ROLE_KEY="${ROLE_MAP[$TOP_DIR]}" if [ -n "$ROLE_KEY" ]; then - # 루트 디렉터리 if [ "$DIR" == "$TOP_DIR" ]; then TF_COUNT=$(find "$DIR" -maxdepth 1 -name '*.tf' | wc -l) if [ "$TF_COUNT" -gt 0 ]; then echo "$DIR|$ROLE_KEY" >> $TMP_FILE fi else - # 하위 디렉터리 TF_COUNT=$(find "$DIR" -maxdepth 1 -name '*.tf' | wc -l) if [ "$TF_COUNT" -gt 0 ]; then echo "$DIR|$ROLE_KEY" >> $TMP_FILE @@ -72,7 +66,6 @@ jobs: fi done - # 중복 제거 및 JSON 포맷 생성 UNIQUE_LINES=$(sort $TMP_FILE | uniq) MATRIX_JSON="[" FIRST=1 @@ -99,13 +92,13 @@ jobs: terraform-ci: needs: detect-changes - if: ${{ needs.detect-changes.outputs.matrix != '[]' }} # 변경사항이 있을 때만 실행 + if: ${{ needs.detect-changes.outputs.matrix != '[]' }} runs-on: ubuntu-latest strategy: matrix: - include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} # 변경된 디렉토리 목록 기반 실행 - fail-fast: false # 하나 실패해도 나머지는 계속 실행 + include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} + fail-fast: false env: INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }} @@ -118,13 +111,13 @@ jobs: with: fetch-depth: 0 - - name: Configure AWS Credentials # 각 matrix.role_key에 해당하는 AWS Role Assume + - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ap-northeast-2 role-to-assume: ${{ secrets[matrix.role_key] }} - - name: Install tfsec # 보안 취약점 스캐너 설치 + - name: Install tfsec run: | curl -sSL https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash @@ -136,16 +129,16 @@ jobs: with: terraform_version: 1.4.0 - - name: Terraform Init # Terraform 초기화: backend 구성 및 provider 다운로드 + - name: Terraform Init run: terraform init working-directory: ${{ matrix.dir }} - name: Terraform Format Check - run: terraform fmt -check -recursive # 코드 스타일 검사 + run: terraform fmt -check -recursive working-directory: ${{ matrix.dir }} - name: Terraform Validate - run: terraform validate # Terraform 구성 파일이 유효한지 문법 및 구조 검사 + run: terraform validate working-directory: ${{ matrix.dir }} - name: Terraform Plan @@ -157,15 +150,36 @@ jobs: PLAN_FILE=tfplan.binary PLAN_TXT=plan.txt - PLAN_JSON=plan.json - terraform plan -no-color -out=$PLAN_FILE || echo "PLAN_FAILED=true" >> $GITHUB_ENV - terraform show -no-color $PLAN_FILE > $PLAN_TXT || echo "Plan failed" > $PLAN_TXT - terraform show -json $PLAN_FILE > $PLAN_JSON || true + PLAN_CONTENT="Terraform plan failed." + + if terraform plan -no-color -out=$PLAN_FILE; then + echo "[INFO] terraform plan succeeded." + + terraform show -no-color $PLAN_FILE > $PLAN_TXT + + IS_EMPTY=$(terraform show -json $PLAN_FILE 2>/dev/null | jq -e '.resource_changes == []' 2>/dev/null || echo "false") - echo "PLAN_CONTENT<> $GITHUB_OUTPUT - cat $PLAN_TXT >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + if [ "$IS_EMPTY" = "true" ]; then + PLAN_CONTENT="(plan success) No changes to apply. Everything looks good!" + else + PLAN_CONTENT=$( + cat $PLAN_TXT | \ + sed 's/`/\\`/g' | \ + tr -d '\r' | \ + sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" + ) + fi + else + echo "[ERROR] terraform plan failed. Skipping show." + echo "Terraform plan failed." > $PLAN_TXT + fi + + { + echo "PLAN_CONTENT<> "$GITHUB_OUTPUT" working-directory: ${{ matrix.dir }} - name: Comment Terraform Plan on PR @@ -181,7 +195,6 @@ jobs: | **Directory** | `${{ matrix.dir }}` | | **Executed At** | `${{ env.START_TIME }}` | - --- ### Plan Output @@ -189,7 +202,7 @@ jobs: ${{ steps.plan.outputs.PLAN_CONTENT }} ``` - - name: Setup Infracost # 비용 예측 도구 세팅 + - name: Setup Infracost uses: infracost/actions/setup@v2 - name: Infracost Breakdown @@ -204,4 +217,4 @@ jobs: uses: infracost/actions/comment@v1 with: path: ${{ matrix.dir }}/infracost.json - behavior: update # 기존 코멘트 업데이트 + behavior: update From 4cd81b3fc5fae3c38fb1e987562d19c072523c26 Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Tue, 15 Jul 2025 00:25:07 +0900 Subject: [PATCH 16/19] =?UTF-8?q?ci=20=EA=B8=80=EC=9E=90=20=EA=B9=A8?= =?UTF-8?q?=EC=A7=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15a3b66..ffd13b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,10 @@ on: - "dev-team-account/**" - "stage-team-account/**" +env: + ACTIONS_STEP_DEBUG: false + + permissions: contents: read pull-requests: write From 4a3b962dbb103a06da85ac02be4073f71b78d5ee Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Tue, 15 Jul 2025 00:27:35 +0900 Subject: [PATCH 17/19] =?UTF-8?q?ci=20=EA=B8=80=EC=9E=90=20=EA=B9=A8?= =?UTF-8?q?=EC=A7=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 83 ++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffd13b4..cf4ffc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,51 +1,51 @@ name: Application-Deployment CI on: - pull_request: + pull_request: # PR이 main 브랜치로 올 때 실행 branches: [main] paths: - "operation-team-account/**" - "identity-team-account/**" - "prod-team-account/**" - "dev-team-account/**" + - "security-team-account/**" - "stage-team-account/**" -env: - ACTIONS_STEP_DEBUG: false - - permissions: - contents: read - pull-requests: write - id-token: write + contents: read # 코드 읽을 수 있는 권한 + pull-requests: write # PR 코멘트 작성 등 가능 + id-token: write # OIDC기반 인증에 필요 jobs: detect-changes: runs-on: ubuntu-latest outputs: - matrix: ${{ steps.set.outputs.matrix }} + matrix: ${{ steps.set.outputs.matrix }} # 다음 job에서 사용할 matrix 값 steps: - name: Checkout Code uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 0 # git diff가 정상 동작하려면 전체 커밋 히스토리를 가져와야 함 - - name: Fetch origin/main + - name: Fetch origin/main # main 브랜치 히스토리 가져오기 run: git fetch origin main - name: Detect Changed Directories & Build Matrix id: set run: | - FILES=$(git diff --name-only origin/main...${{ github.sha }}) + FILES=$(git diff --name-only origin/main...${{ github.sha }}) # 변경된 파일 목록 echo "Changed files:" echo "$FILES" + # 각 account별 디렉토리에 해당하는 IAM Role 매핑 declare -A ROLE_MAP=( ["operation-team-account"]="ROLE_ARN_OPERATION" ["identity-team-account"]="ROLE_ARN_IDENTITY" ["prod-team-account"]="ROLE_ARN_PROD" ["dev-team-account"]="ROLE_ARN_DEV" + ["security-team-account"]="ROLE_ARN_SECURITY" ["stage-team-account"]="ROLE_ARN_STAGE" + ["management-team-account"]="ROLE_ARN_MANAGEMENT" ) TMP_FILE=$(mktemp) @@ -56,12 +56,14 @@ jobs: ROLE_KEY="${ROLE_MAP[$TOP_DIR]}" if [ -n "$ROLE_KEY" ]; then + # 루트 디렉터리 if [ "$DIR" == "$TOP_DIR" ]; then TF_COUNT=$(find "$DIR" -maxdepth 1 -name '*.tf' | wc -l) if [ "$TF_COUNT" -gt 0 ]; then echo "$DIR|$ROLE_KEY" >> $TMP_FILE fi else + # 하위 디렉터리 TF_COUNT=$(find "$DIR" -maxdepth 1 -name '*.tf' | wc -l) if [ "$TF_COUNT" -gt 0 ]; then echo "$DIR|$ROLE_KEY" >> $TMP_FILE @@ -70,6 +72,7 @@ jobs: fi done + # 중복 제거 및 JSON 포맷 생성 UNIQUE_LINES=$(sort $TMP_FILE | uniq) MATRIX_JSON="[" FIRST=1 @@ -96,13 +99,13 @@ jobs: terraform-ci: needs: detect-changes - if: ${{ needs.detect-changes.outputs.matrix != '[]' }} + if: ${{ needs.detect-changes.outputs.matrix != '[]' }} # 변경사항이 있을 때만 실행 runs-on: ubuntu-latest strategy: matrix: - include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} - fail-fast: false + include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} # 변경된 디렉토리 목록 기반 실행 + fail-fast: false # 하나 실패해도 나머지는 계속 실행 env: INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }} @@ -115,13 +118,13 @@ jobs: with: fetch-depth: 0 - - name: Configure AWS Credentials + - name: Configure AWS Credentials # 각 matrix.role_key에 해당하는 AWS Role Assume uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ap-northeast-2 role-to-assume: ${{ secrets[matrix.role_key] }} - - name: Install tfsec + - name: Install tfsec # 보안 취약점 스캐너 설치 run: | curl -sSL https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash @@ -133,16 +136,16 @@ jobs: with: terraform_version: 1.4.0 - - name: Terraform Init + - name: Terraform Init # Terraform 초기화: backend 구성 및 provider 다운로드 run: terraform init working-directory: ${{ matrix.dir }} - name: Terraform Format Check - run: terraform fmt -check -recursive + run: terraform fmt -check -recursive # 코드 스타일 검사 working-directory: ${{ matrix.dir }} - name: Terraform Validate - run: terraform validate + run: terraform validate # Terraform 구성 파일이 유효한지 문법 및 구조 검사 working-directory: ${{ matrix.dir }} - name: Terraform Plan @@ -154,36 +157,15 @@ jobs: PLAN_FILE=tfplan.binary PLAN_TXT=plan.txt + PLAN_JSON=plan.json - PLAN_CONTENT="Terraform plan failed." - - if terraform plan -no-color -out=$PLAN_FILE; then - echo "[INFO] terraform plan succeeded." - - terraform show -no-color $PLAN_FILE > $PLAN_TXT + terraform plan -no-color -out=$PLAN_FILE > /dev/null 2>&1 || echo "PLAN_FAILED=true" >> $GITHUB_ENV + terraform show -no-color $PLAN_FILE > $PLAN_TXT 2>/dev/null || echo "Plan failed" > $PLAN_TXT + terraform show -json $PLAN_FILE > $PLAN_JSON || true - IS_EMPTY=$(terraform show -json $PLAN_FILE 2>/dev/null | jq -e '.resource_changes == []' 2>/dev/null || echo "false") - - if [ "$IS_EMPTY" = "true" ]; then - PLAN_CONTENT="(plan success) No changes to apply. Everything looks good!" - else - PLAN_CONTENT=$( - cat $PLAN_TXT | \ - sed 's/`/\\`/g' | \ - tr -d '\r' | \ - sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" - ) - fi - else - echo "[ERROR] terraform plan failed. Skipping show." - echo "Terraform plan failed." > $PLAN_TXT - fi - - { - echo "PLAN_CONTENT<> "$GITHUB_OUTPUT" + echo "PLAN_CONTENT<> $GITHUB_OUTPUT + cat $PLAN_TXT >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT working-directory: ${{ matrix.dir }} - name: Comment Terraform Plan on PR @@ -199,6 +181,7 @@ jobs: | **Directory** | `${{ matrix.dir }}` | | **Executed At** | `${{ env.START_TIME }}` | + --- ### Plan Output @@ -206,7 +189,7 @@ jobs: ${{ steps.plan.outputs.PLAN_CONTENT }} ``` - - name: Setup Infracost + - name: Setup Infracost # 비용 예측 도구 세팅 uses: infracost/actions/setup@v2 - name: Infracost Breakdown @@ -221,4 +204,4 @@ jobs: uses: infracost/actions/comment@v1 with: path: ${{ matrix.dir }}/infracost.json - behavior: update + behavior: update # 기존 코멘트 업데이트 From f2a7cee3d407425d88c7d0d6ca1be9c555aa0bea Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Tue, 15 Jul 2025 00:29:25 +0900 Subject: [PATCH 18/19] =?UTF-8?q?ci=20=EA=B8=80=EC=9E=90=20=EA=B9=A8?= =?UTF-8?q?=EC=A7=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf4ffc2..29e158f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,7 +150,7 @@ jobs: - name: Terraform Plan id: plan - continue-on-error: true + continue-on-error: false run: | START_TIME=$(date -u +"%Y-%m-%d %H:%M:%S UTC") echo "START_TIME=$START_TIME" >> $GITHUB_ENV From 6580b0f6d10bf69066199347fbe60f97bb5a1bd9 Mon Sep 17 00:00:00 2001 From: rnjsdbwlsqwer Date: Tue, 15 Jul 2025 00:33:38 +0900 Subject: [PATCH 19/19] =?UTF-8?q?ci=20=EA=B8=80=EC=9E=90=20=EA=B9=A8?= =?UTF-8?q?=EC=A7=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 78 +++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29e158f..1b53d82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: Application-Deployment CI on: - pull_request: # PR이 main 브랜치로 올 때 실행 + pull_request: branches: [main] paths: - "operation-team-account/**" @@ -12,32 +12,31 @@ on: - "stage-team-account/**" permissions: - contents: read # 코드 읽을 수 있는 권한 - pull-requests: write # PR 코멘트 작성 등 가능 - id-token: write # OIDC기반 인증에 필요 + contents: read + pull-requests: write + id-token: write jobs: detect-changes: runs-on: ubuntu-latest outputs: - matrix: ${{ steps.set.outputs.matrix }} # 다음 job에서 사용할 matrix 값 + matrix: ${{ steps.set.outputs.matrix }} steps: - name: Checkout Code uses: actions/checkout@v3 with: - fetch-depth: 0 # git diff가 정상 동작하려면 전체 커밋 히스토리를 가져와야 함 + fetch-depth: 0 - - name: Fetch origin/main # main 브랜치 히스토리 가져오기 + - name: Fetch origin/main run: git fetch origin main - name: Detect Changed Directories & Build Matrix id: set run: | - FILES=$(git diff --name-only origin/main...${{ github.sha }}) # 변경된 파일 목록 + FILES=$(git diff --name-only origin/main...${{ github.sha }}) echo "Changed files:" echo "$FILES" - # 각 account별 디렉토리에 해당하는 IAM Role 매핑 declare -A ROLE_MAP=( ["operation-team-account"]="ROLE_ARN_OPERATION" ["identity-team-account"]="ROLE_ARN_IDENTITY" @@ -56,14 +55,12 @@ jobs: ROLE_KEY="${ROLE_MAP[$TOP_DIR]}" if [ -n "$ROLE_KEY" ]; then - # 루트 디렉터리 if [ "$DIR" == "$TOP_DIR" ]; then TF_COUNT=$(find "$DIR" -maxdepth 1 -name '*.tf' | wc -l) if [ "$TF_COUNT" -gt 0 ]; then echo "$DIR|$ROLE_KEY" >> $TMP_FILE fi else - # 하위 디렉터리 TF_COUNT=$(find "$DIR" -maxdepth 1 -name '*.tf' | wc -l) if [ "$TF_COUNT" -gt 0 ]; then echo "$DIR|$ROLE_KEY" >> $TMP_FILE @@ -72,7 +69,6 @@ jobs: fi done - # 중복 제거 및 JSON 포맷 생성 UNIQUE_LINES=$(sort $TMP_FILE | uniq) MATRIX_JSON="[" FIRST=1 @@ -99,13 +95,13 @@ jobs: terraform-ci: needs: detect-changes - if: ${{ needs.detect-changes.outputs.matrix != '[]' }} # 변경사항이 있을 때만 실행 + if: ${{ needs.detect-changes.outputs.matrix != '[]' }} runs-on: ubuntu-latest strategy: matrix: - include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} # 변경된 디렉토리 목록 기반 실행 - fail-fast: false # 하나 실패해도 나머지는 계속 실행 + include: ${{ fromJson(needs.detect-changes.outputs.matrix) }} + fail-fast: false env: INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }} @@ -118,39 +114,39 @@ jobs: with: fetch-depth: 0 - - name: Configure AWS Credentials # 각 matrix.role_key에 해당하는 AWS Role Assume + - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ap-northeast-2 role-to-assume: ${{ secrets[matrix.role_key] }} - - name: Install tfsec # 보안 취약점 스캐너 설치 + - name: Install Terraform + run: | + curl -LO https://releases.hashicorp.com/terraform/1.4.0/terraform_1.4.0_linux_amd64.zip + unzip terraform_1.4.0_linux_amd64.zip + sudo mv terraform /usr/local/bin/ + + - name: Install tfsec run: | curl -sSL https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash - name: Run tfsec (fail on HIGH+) run: tfsec --minimum-severity HIGH --no-color ${{ matrix.dir }} - - name: Setup Terraform - uses: hashicorp/setup-terraform@v1 - with: - terraform_version: 1.4.0 - - - name: Terraform Init # Terraform 초기화: backend 구성 및 provider 다운로드 + - name: Terraform Init run: terraform init working-directory: ${{ matrix.dir }} - name: Terraform Format Check - run: terraform fmt -check -recursive # 코드 스타일 검사 + run: terraform fmt -check -recursive working-directory: ${{ matrix.dir }} - name: Terraform Validate - run: terraform validate # Terraform 구성 파일이 유효한지 문법 및 구조 검사 + run: terraform validate working-directory: ${{ matrix.dir }} - name: Terraform Plan id: plan - continue-on-error: false run: | START_TIME=$(date -u +"%Y-%m-%d %H:%M:%S UTC") echo "START_TIME=$START_TIME" >> $GITHUB_ENV @@ -159,13 +155,30 @@ jobs: PLAN_TXT=plan.txt PLAN_JSON=plan.json - terraform plan -no-color -out=$PLAN_FILE > /dev/null 2>&1 || echo "PLAN_FAILED=true" >> $GITHUB_ENV + # Run terraform plan + terraform plan -no-color -out=$PLAN_FILE > /dev/null 2> plan_error.txt || echo "PLAN_FAILED=true" >> $GITHUB_ENV + + # Show plan text output terraform show -no-color $PLAN_FILE > $PLAN_TXT 2>/dev/null || echo "Plan failed" > $PLAN_TXT + + # Remove ANSI color codes + cat $PLAN_TXT | \ + sed 's/`/\\`/g' | \ + tr -d '\r' | \ + sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" \ + > cleaned_plan.txt + + PLAN_CONTENT=$(cat cleaned_plan.txt) + + # Save JSON plan for infracost terraform show -json $PLAN_FILE > $PLAN_JSON || true - echo "PLAN_CONTENT<> $GITHUB_OUTPUT - cat $PLAN_TXT >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + # Output plan content for PR comment + { + echo "PLAN_CONTENT<> $GITHUB_OUTPUT working-directory: ${{ matrix.dir }} - name: Comment Terraform Plan on PR @@ -181,7 +194,6 @@ jobs: | **Directory** | `${{ matrix.dir }}` | | **Executed At** | `${{ env.START_TIME }}` | - --- ### Plan Output @@ -189,7 +201,7 @@ jobs: ${{ steps.plan.outputs.PLAN_CONTENT }} ``` - - name: Setup Infracost # 비용 예측 도구 세팅 + - name: Setup Infracost uses: infracost/actions/setup@v2 - name: Infracost Breakdown @@ -204,4 +216,4 @@ jobs: uses: infracost/actions/comment@v1 with: path: ${{ matrix.dir }}/infracost.json - behavior: update # 기존 코멘트 업데이트 + behavior: update