Skip to content

ci: Terraform GitOps 파이프라인 (OIDC + 원격 state + 승인 게이트)#6

Merged
yeounhyeok merged 5 commits into
devfrom
chore/terraform-cicd
Jun 5, 2026
Merged

ci: Terraform GitOps 파이프라인 (OIDC + 원격 state + 승인 게이트)#6
yeounhyeok merged 5 commits into
devfrom
chore/terraform-cicd

Conversation

@yeounhyeok

Copy link
Copy Markdown
Contributor

개요

Terraform GitOps 파이프라인을 추가합니다. PR → terraform plan 코멘트, dev 머지 → terraform apply(수동 승인 게이트), 인증은 GitHub OIDC(장기 키 없음)로 동작합니다.

변경 사항 (코드)

  • terraform/environments/dev/backend.tfS3 원격 백엔드(use_lockfile로 잠금, DynamoDB 불필요)
  • .github/workflows/terraform.yml — plan(PR)/apply(dev push) 워크플로, OIDC role assume
  • providers.tf — CI에서 aws_profile="" 이면 null 처리(OIDC 임시자격증명 사용)
  • modules/ec2/main.tfami ignore_changes (most_recent AMI 갱신이 인스턴스를 교체하지 않도록)

이미 완료한 부트스트랩

  • S3 state 버킷 생성: sw-hub-dev-tfstate-850919911012 (버전관리·암호화·퍼블릭차단)
  • state 마이그레이션: 로컬 → S3 (dev/terraform.tfstate, 36 리소스)

⚠️ 머지 전 남은 수동 단계 (권한/시크릿 때문에 자동화 불가)

1. OIDC provider + CI role 생성

참고: 현재 swhub 유저에서 OIDC 권한이 빠져 있습니다(CI role 권한 정책으로 교체되며 누락). 아래를 admin 자격으로 실행하거나, swhub에 iam:CreateOpenIDConnectProvider를 잠시 추가 후 실행하세요.

# (a) GitHub OIDC provider
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1 1c58a3a8518e8759bf075b76b750d4f2df264fcd

# (b) CI role — trust(gha-trust.json) + perms(gha-perms.json)는 대화에서 준 JSON 사용
aws iam create-role --role-name sw-hub-dev-gha-terraform \
  --assume-role-policy-document file://gha-trust.json \
  --tags Key=Project,Value=sw-hub Key=Environment,Value=dev Key=ManagedBy,Value=bootstrap
aws iam put-role-policy --role-name sw-hub-dev-gha-terraform \
  --policy-name terraform-dev --policy-document file://gha-perms.json

→ 결과 ARN arn:aws:iam::850919911012:role/sw-hub-dev-gha-terraform 은 워크플로에 이미 박혀 있음.

2. GitHub Environment dev-apply 만들기 (승인 게이트)

Settings → Environments → New environment → dev-apply:

  • Required reviewers: 본인(yeounhyeok) 지정 ← 승인자는 직접 지정해야 함
  • (선택) Deployment branches: dev만 허용

3. Repository Secrets 추가 (Settings → Secrets → Actions)

  • CLOUDFLARE_API_TOKEN
  • TF_VAR_CLOUDFLARE_ACCOUNT_ID
  • TF_VAR_CLOUDFLARE_ZONE_NAME
  • TF_VAR_CLOUDFLARE_HOSTNAME

4. 첫 plan 리뷰 (중요)

현재 마이그레이션한 state는 cloudflare 튜닝(터널) 도입 이전 시점이라, 첫 terraform plancloudflare 리소스가 "생성 예정"으로 뜰 수 있습니다(이미 라이브엔 cloudflared 동작 중). 그대로 apply하지 말고, 기존 터널을 terraform import 하거나 상태를 맞춘 뒤 진행하세요.

비고

  • 위 1~3 완료 후, 이 PR을 다시 push하면 plan 워크플로가 OIDC로 동작합니다. (그 전엔 자격증명/시크릿 미설정으로 plan job이 실패하는 게 정상)
  • IMDSv2 강제(별도 PR), 8080 loopback 바인딩(BE PR)과는 독립적인 작업입니다.

- S3 원격 백엔드(use_lockfile) 도입 — CI 러너가 공유 state 사용 (DynamoDB 불필요)
- GitHub Actions 워크플로: PR→terraform plan 코멘트, dev 머지(push)→apply
- apply는 GitHub Environment 'dev-apply' 수동 승인 게이트 통과 후 실행
- AWS 인증은 OIDC로 role(sw-hub-dev-gha-terraform) assume — 장기 액세스 키 없음
- providers: CI에서 aws_profile="" 이면 null 처리(OIDC 임시자격증명 사용)
- EC2 ami ignore_changes — most_recent AMI 갱신이 인스턴스를 교체하지 않도록 (자동 apply 안전장치)
# Conflicts:
#	terraform/modules/ec2/main.tf
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

Terraform Plan success

plan 상세
module.rds.random_password.db: Refreshing state... [id=none]
data.cloudflare_zone.moa: Reading...
module.ec2.tls_private_key.this: Refreshing state... [id=35a5bb52f7a663dd07bd6a683ab633d918df27bb]
module.ec2.local_sensitive_file.private_key: Refreshing state... [id=dc2b1688c4ecf264ef497f0621b825cf769990d2]
local_sensitive_file.ansible_secrets: Refreshing state... [id=78caa92aa3b6856a8e2cdcd3badb6c5768f63b37]
data.cloudflare_zone.moa: Read complete after 0s [id=6fd90af1d5f578cc4ebcfda4c5a1dd25]
data.aws_caller_identity.current: Reading...
module.network.data.aws_availability_zones.available: Reading...
module.ec2.data.aws_ami.ubuntu: Reading...
data.aws_iam_policy_document.ec2_assume: Reading...
module.ec2.aws_key_pair.this: Refreshing state... [id=sw-hub-dev-key]
module.network.aws_vpc.this: Refreshing state... [id=vpc-0d0c0b3865566d128]
data.aws_iam_policy_document.ec2_assume: Read complete after 0s [id=2851119427]
aws_iam_role.app: Refreshing state... [id=sw-hub-dev-app-role]
data.aws_caller_identity.current: Read complete after 0s [id=850919911012]
module.s3["uploads"].aws_s3_bucket.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
aws_iam_instance_profile.app: Refreshing state... [id=sw-hub-dev-app-profile]
module.network.data.aws_availability_zones.available: Read complete after 1s [id=ap-northeast-2]
module.ec2.data.aws_ami.ubuntu: Read complete after 1s [id=ami-09a72717a566d88fa]
module.network.aws_subnet.private[0]: Refreshing state... [id=subnet-04075fc372936edb5]
module.network.aws_internet_gateway.this: Refreshing state... [id=igw-0c0046e95fcdf54a4]
module.network.aws_route_table.private: Refreshing state... [id=rtb-071916e258e0f8543]
module.network.aws_subnet.public[0]: Refreshing state... [id=subnet-0b9f213f0ec40c775]
module.network.aws_subnet.public[1]: Refreshing state... [id=subnet-00ac26cf16b904e54]
module.network.aws_subnet.private[1]: Refreshing state... [id=subnet-0dfff48c6fcb65f8e]
module.network.aws_route_table.public: Refreshing state... [id=rtb-063a31231e5c9c80b]
module.network.aws_route_table_association.private[0]: Refreshing state... [id=rtbassoc-055bb8f7431f193e0]
module.network.aws_route_table_association.private[1]: Refreshing state... [id=rtbassoc-09c19314be8fb3861]
module.rds.aws_db_subnet_group.this: Refreshing state... [id=sw-hub-dev-db-subnet-group]
module.ec2.aws_security_group.this: Refreshing state... [id=sg-0ad8add3d38300814]
module.network.aws_route_table_association.public[0]: Refreshing state... [id=rtbassoc-0e429d4723cd5a767]
module.network.aws_route_table_association.public[1]: Refreshing state... [id=rtbassoc-0e9d371d632d5b1ac]
module.rds.aws_security_group.this: Refreshing state... [id=sg-06d151aff52ec211a]
module.ec2.aws_instance.this[0]: Refreshing state... [id=i-0fa0e4a4342acb626]
module.s3["uploads"].aws_s3_bucket_public_access_block.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket_public_access_block.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.s3["backups"].aws_s3_bucket_versioning.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.s3["uploads"].aws_s3_bucket_versioning.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket_lifecycle_configuration.this[0]: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.s3["uploads"].aws_s3_bucket_server_side_encryption_configuration.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket_server_side_encryption_configuration.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.rds.aws_db_instance.this: Refreshing state... [id=db-KAC2ZLYDLRTDU65OGQ5E6DJEAM]
data.aws_iam_policy_document.s3_access: Reading...
data.aws_iam_policy_document.s3_access: Read complete after 0s [id=2628098098]
aws_iam_role_policy.s3_access: Refreshing state... [id=sw-hub-dev-app-role:s3-access]
local_file.ansible_hosts: Refreshing state... [id=db6661ffa28a84f2bddfa42780bea36daddb0300]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the
last "terraform apply" which may have affected this plan:

  # module.ec2.local_sensitive_file.private_key has been deleted
  - resource "local_sensitive_file" "private_key" {
      - filename             = "../../modules/ec2/../../../sw-hub-dev.pem" -> null
        id                   = "dc2b1688c4ecf264ef497f0621b825cf769990d2"
        # (9 unchanged attributes hidden)
    }


Unless you have made equivalent changes to your configuration, or ignored the
relevant attributes using ignore_changes, the following plan may include
actions to undo or respond to these changes.

─────────────────────────────────────────────────────────────────────────────

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  # cloudflare_record.moa_hostname will be created
  + resource "cloudflare_record" "moa_hostname" {
      + allow_overwrite = false
      + content         = (known after apply)
      + created_on      = (known after apply)
      + hostname        = (known after apply)
      + id              = (known after apply)
      + metadata        = (known after apply)
      + modified_on     = (known after apply)
      + name            = "moa"
      + proxiable       = (known after apply)
      + proxied         = true
      + ttl             = 1
      + type            = "CNAME"
      + value           = (known after apply)
      + zone_id         = "6fd90af1d5f578cc4ebcfda4c5a1dd25"
    }

  # cloudflare_zero_trust_tunnel_cloudflared.moa will be created
  + resource "cloudflare_zero_trust_tunnel_cloudflared" "moa" {
      + account_id   = "531aef118ed1df9d4d97166ad9ece7af"
      + cname        = (known after apply)
      + id           = (known after apply)
      + name         = "moa-dev"
      + secret       = (sensitive value)
      + tunnel_token = (sensitive value)
    }

  # cloudflare_zero_trust_tunnel_cloudflared_config.moa will be created
  + resource "cloudflare_zero_trust_tunnel_cloudflared_config" "moa" {
      + account_id = "531aef118ed1df9d4d97166ad9ece7af"
      + id         = (known after apply)
      + tunnel_id  = (known after apply)

      + config {
          + ingress_rule {
              + hostname = "moa.yeoun.org"
              + service  = "http://localhost:8080"
            }
          + ingress_rule {
              + service = "http_status:404"
            }
        }
    }

  # local_sensitive_file.ansible_secrets will be created
  + resource "local_sensitive_file" "ansible_secrets" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0700"
      + file_permission      = "0600"
      + filename             = "./../../../ansible/inventories/dev/group_vars/all/secrets.yml"
      + id                   = (known after apply)
    }

  # random_id.cloudflare_tunnel_secret will be created
  + resource "random_id" "cloudflare_tunnel_secret" {
      + b64_std     = (known after apply)
      + b64_url     = (known after apply)
      + byte_length = 32
      + dec         = (known after apply)
      + hex         = (known after apply)
      + id          = (known after apply)
    }

  # module.ec2.aws_security_group.this will be updated in-place
  ~ resource "aws_security_group" "this" {
        id                     = "sg-0ad8add3d38300814"
      ~ ingress                = [
          - {
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]
              - from_port        = 22
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 22
                # (1 unchanged attribute hidden)
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = "SSH access for operations"
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
        ]
        name                   = "sw-hub-dev-ec2-sg"
        tags                   = {
            "Name" = "sw-hub-dev-ec2-sg"
        }
        # (8 unchanged attributes hidden)
    }

  # module.ec2.local_sensitive_file.private_key will be created
  + resource "local_sensitive_file" "private_key" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0700"
      + file_permission      = "0600"
      + filename             = "../../modules/ec2/../../../sw-hub-dev.pem"
      + id                   = (known after apply)
    }

  # module.rds.aws_db_instance.this will be updated in-place
  ~ resource "aws_db_instance" "this" {
      ~ engine_version                        = "16.13" -> "16.11"
        id                                    = "db-KAC2ZLYDLRTDU65OGQ5E6DJEAM"
        tags                                  = {
            "Name" = "sw-hub-dev-db"
        }
        # (71 unchanged attributes hidden)
    }

Plan: 6 to add, 2 to change, 0 to destroy.

Changes to Outputs:
  + cloudflare_hostname     = "moa.yeoun.org"
  + cloudflare_tunnel_id    = (known after apply)
  + cloudflare_tunnel_token = (sensitive value)
  ~ ec2_summary             = {
      ~ ami_id           = "ami-0596f7562954deb8e" -> "ami-09a72717a566d88fa"
      ~ private_key_path = "/mnt/c/Users/yeope/yeounhyeok/projectWorkspace/SW-HUB/iac/sw-hub-dev.pem" -> "/home/runner/work/iac/iac/sw-hub-dev.pem"
        # (4 unchanged attributes hidden)
    }
  ~ rds_tunnel_command      = "ssh -i /mnt/c/Users/yeope/yeounhyeok/projectWorkspace/SW-HUB/iac/sw-hub-dev.pem -L 15432:sw-hub-dev-db.c12qekykev5c.ap-northeast-2.rds.amazonaws.com:5432 ubuntu@52.79.161.164" -> "ssh -i /home/runner/work/iac/iac/sw-hub-dev.pem -L 15432:sw-hub-dev-db.c12qekykev5c.ap-northeast-2.rds.amazonaws.com:5432 ubuntu@52.79.161.164"

─────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
Releasing state lock. This may take a few moments...

- cloudflare 터널: import 시 secret을 API로 다시 못 읽어 매 apply마다 재생성(라이브 터널 끊김)으로 잡히는 문제 → lifecycle ignore_changes=[secret]
- RDS: auto-minor-version-upgrade로 16.13으로 오른 마이너 버전을 terraform이 16.11로 되돌리려다 apply 실패 → ignore_changes=[engine_version]
- import 후 plan: 0 to destroy (터널/RDS 무중단)
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

Terraform Plan success

plan 상세
data.cloudflare_zone.moa: Reading...
module.ec2.tls_private_key.this: Refreshing state... [id=35a5bb52f7a663dd07bd6a683ab633d918df27bb]
module.rds.random_password.db: Refreshing state... [id=none]
module.ec2.local_sensitive_file.private_key: Refreshing state... [id=dc2b1688c4ecf264ef497f0621b825cf769990d2]
cloudflare_zero_trust_tunnel_cloudflared.moa: Refreshing state... [id=7e214e22-c418-4735-9311-2e340551dbf1]
local_sensitive_file.ansible_secrets: Refreshing state... [id=78caa92aa3b6856a8e2cdcd3badb6c5768f63b37]
data.cloudflare_zone.moa: Read complete after 1s [id=6fd90af1d5f578cc4ebcfda4c5a1dd25]
cloudflare_record.moa_hostname: Refreshing state... [id=8e371bb1117b727747287690283413ad]
cloudflare_zero_trust_tunnel_cloudflared_config.moa: Refreshing state... [id=7e214e22-c418-4735-9311-2e340551dbf1]
data.aws_iam_policy_document.ec2_assume: Reading...
module.network.data.aws_availability_zones.available: Reading...
module.ec2.aws_key_pair.this: Refreshing state... [id=sw-hub-dev-key]
module.ec2.data.aws_ami.ubuntu: Reading...
data.aws_caller_identity.current: Reading...
module.network.aws_vpc.this: Refreshing state... [id=vpc-0d0c0b3865566d128]
data.aws_iam_policy_document.ec2_assume: Read complete after 0s [id=2851119427]
aws_iam_role.app: Refreshing state... [id=sw-hub-dev-app-role]
data.aws_caller_identity.current: Read complete after 0s [id=850919911012]
module.s3["uploads"].aws_s3_bucket.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
aws_iam_instance_profile.app: Refreshing state... [id=sw-hub-dev-app-profile]
module.network.data.aws_availability_zones.available: Read complete after 1s [id=ap-northeast-2]
module.ec2.data.aws_ami.ubuntu: Read complete after 1s [id=ami-09a72717a566d88fa]
module.network.aws_internet_gateway.this: Refreshing state... [id=igw-0c0046e95fcdf54a4]
module.network.aws_subnet.public[1]: Refreshing state... [id=subnet-00ac26cf16b904e54]
module.network.aws_subnet.private[0]: Refreshing state... [id=subnet-04075fc372936edb5]
module.network.aws_subnet.private[1]: Refreshing state... [id=subnet-0dfff48c6fcb65f8e]
module.network.aws_subnet.public[0]: Refreshing state... [id=subnet-0b9f213f0ec40c775]
module.network.aws_route_table.private: Refreshing state... [id=rtb-071916e258e0f8543]
module.network.aws_route_table.public: Refreshing state... [id=rtb-063a31231e5c9c80b]
module.network.aws_route_table_association.private[0]: Refreshing state... [id=rtbassoc-055bb8f7431f193e0]
module.network.aws_route_table_association.private[1]: Refreshing state... [id=rtbassoc-09c19314be8fb3861]
module.rds.aws_db_subnet_group.this: Refreshing state... [id=sw-hub-dev-db-subnet-group]
module.ec2.aws_security_group.this: Refreshing state... [id=sg-0ad8add3d38300814]
module.network.aws_route_table_association.public[1]: Refreshing state... [id=rtbassoc-0e9d371d632d5b1ac]
module.network.aws_route_table_association.public[0]: Refreshing state... [id=rtbassoc-0e429d4723cd5a767]
module.rds.aws_security_group.this: Refreshing state... [id=sg-06d151aff52ec211a]
module.ec2.aws_instance.this[0]: Refreshing state... [id=i-0fa0e4a4342acb626]
module.s3["uploads"].aws_s3_bucket_public_access_block.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket_public_access_block.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.s3["uploads"].aws_s3_bucket_versioning.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["uploads"].aws_s3_bucket_server_side_encryption_configuration.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket_server_side_encryption_configuration.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.s3["backups"].aws_s3_bucket_versioning.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.s3["backups"].aws_s3_bucket_lifecycle_configuration.this[0]: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.rds.aws_db_instance.this: Refreshing state... [id=db-KAC2ZLYDLRTDU65OGQ5E6DJEAM]
data.aws_iam_policy_document.s3_access: Reading...
data.aws_iam_policy_document.s3_access: Read complete after 0s [id=2628098098]
aws_iam_role_policy.s3_access: Refreshing state... [id=sw-hub-dev-app-role:s3-access]
local_file.ansible_hosts: Refreshing state... [id=db6661ffa28a84f2bddfa42780bea36daddb0300]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the
last "terraform apply" which may have affected this plan:

  # module.ec2.local_sensitive_file.private_key has been deleted
  - resource "local_sensitive_file" "private_key" {
      - filename             = "../../modules/ec2/../../../sw-hub-dev.pem" -> null
        id                   = "dc2b1688c4ecf264ef497f0621b825cf769990d2"
        # (9 unchanged attributes hidden)
    }


Unless you have made equivalent changes to your configuration, or ignored the
relevant attributes using ignore_changes, the following plan may include
actions to undo or respond to these changes.

─────────────────────────────────────────────────────────────────────────────

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  # cloudflare_record.moa_hostname will be updated in-place
  ~ resource "cloudflare_record" "moa_hostname" {
      + allow_overwrite = false
        id              = "8e371bb1117b727747287690283413ad"
        name            = "moa"
        tags            = []
      + value           = "7e214e22-c418-4735-9311-2e340551dbf1.cfargotunnel.com"
        # (10 unchanged attributes hidden)
    }

  # local_sensitive_file.ansible_secrets will be created
  + resource "local_sensitive_file" "ansible_secrets" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0700"
      + file_permission      = "0600"
      + filename             = "./../../../ansible/inventories/dev/group_vars/all/secrets.yml"
      + id                   = (known after apply)
    }

  # random_id.cloudflare_tunnel_secret will be created
  + resource "random_id" "cloudflare_tunnel_secret" {
      + b64_std     = (known after apply)
      + b64_url     = (known after apply)
      + byte_length = 32
      + dec         = (known after apply)
      + hex         = (known after apply)
      + id          = (known after apply)
    }

  # module.ec2.aws_security_group.this will be updated in-place
  ~ resource "aws_security_group" "this" {
        id                     = "sg-0ad8add3d38300814"
      ~ ingress                = [
          - {
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]
              - from_port        = 22
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 22
                # (1 unchanged attribute hidden)
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = "SSH access for operations"
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
        ]
        name                   = "sw-hub-dev-ec2-sg"
        tags                   = {
            "Name" = "sw-hub-dev-ec2-sg"
        }
        # (8 unchanged attributes hidden)
    }

  # module.ec2.local_sensitive_file.private_key will be created
  + resource "local_sensitive_file" "private_key" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0700"
      + file_permission      = "0600"
      + filename             = "../../modules/ec2/../../../sw-hub-dev.pem"
      + id                   = (known after apply)
    }

Plan: 3 to add, 2 to change, 0 to destroy.

Changes to Outputs:
  ~ ec2_summary             = {
      ~ private_key_path = "/mnt/c/Users/yeope/yeounhyeok/projectWorkspace/SW-HUB/iac/sw-hub-dev.pem" -> "/home/runner/work/iac/iac/sw-hub-dev.pem"
        # (5 unchanged attributes hidden)
    }
  ~ rds_tunnel_command      = "ssh -i /mnt/c/Users/yeope/yeounhyeok/projectWorkspace/SW-HUB/iac/sw-hub-dev.pem -L 15432:sw-hub-dev-db.c12qekykev5c.ap-northeast-2.rds.amazonaws.com:5432 ubuntu@52.79.161.164" -> "ssh -i /home/runner/work/iac/iac/sw-hub-dev.pem -L 15432:sw-hub-dev-db.c12qekykev5c.ap-northeast-2.rds.amazonaws.com:5432 ubuntu@52.79.161.164"

Warning: Argument is deprecated

  with cloudflare_record.moa_hostname,
  on cloudflare.tf line 47, in resource "cloudflare_record" "moa_hostname":
  47:   value   = cloudflare_zero_trust_tunnel_cloudflared.moa.cname

`value` is deprecated in favour of `content` and will be removed in the next
major release.

─────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
Releasing state lock. This may take a few moments...

- OIDC 기반 인프라 배포 파이프라인 흐름
- 자격증명/시크릿 위치 정리, 런타임/앱 인증 흐름
- 부트스트랩 리소스, 드리프트 처리(ignore_changes) 메모, 일상 운영
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

Terraform Plan success

plan 상세
module.rds.random_password.db: Refreshing state... [id=none]
module.ec2.tls_private_key.this: Refreshing state... [id=35a5bb52f7a663dd07bd6a683ab633d918df27bb]
data.cloudflare_zone.moa: Reading...
module.ec2.local_sensitive_file.private_key: Refreshing state... [id=dc2b1688c4ecf264ef497f0621b825cf769990d2]
cloudflare_zero_trust_tunnel_cloudflared.moa: Refreshing state... [id=7e214e22-c418-4735-9311-2e340551dbf1]
local_sensitive_file.ansible_secrets: Refreshing state... [id=78caa92aa3b6856a8e2cdcd3badb6c5768f63b37]
data.cloudflare_zone.moa: Read complete after 1s [id=6fd90af1d5f578cc4ebcfda4c5a1dd25]
cloudflare_zero_trust_tunnel_cloudflared_config.moa: Refreshing state... [id=7e214e22-c418-4735-9311-2e340551dbf1]
cloudflare_record.moa_hostname: Refreshing state... [id=8e371bb1117b727747287690283413ad]
data.aws_iam_policy_document.ec2_assume: Reading...
module.network.aws_vpc.this: Refreshing state... [id=vpc-0d0c0b3865566d128]
data.aws_caller_identity.current: Reading...
module.ec2.data.aws_ami.ubuntu: Reading...
module.ec2.aws_key_pair.this: Refreshing state... [id=sw-hub-dev-key]
module.network.data.aws_availability_zones.available: Reading...
data.aws_iam_policy_document.ec2_assume: Read complete after 0s [id=2851119427]
aws_iam_role.app: Refreshing state... [id=sw-hub-dev-app-role]
data.aws_caller_identity.current: Read complete after 0s [id=850919911012]
module.s3["uploads"].aws_s3_bucket.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
aws_iam_instance_profile.app: Refreshing state... [id=sw-hub-dev-app-profile]
module.network.data.aws_availability_zones.available: Read complete after 1s [id=ap-northeast-2]
module.ec2.data.aws_ami.ubuntu: Read complete after 1s [id=ami-09a72717a566d88fa]
module.network.aws_internet_gateway.this: Refreshing state... [id=igw-0c0046e95fcdf54a4]
module.network.aws_route_table.private: Refreshing state... [id=rtb-071916e258e0f8543]
module.network.aws_subnet.public[0]: Refreshing state... [id=subnet-0b9f213f0ec40c775]
module.network.aws_subnet.public[1]: Refreshing state... [id=subnet-00ac26cf16b904e54]
module.network.aws_subnet.private[1]: Refreshing state... [id=subnet-0dfff48c6fcb65f8e]
module.network.aws_subnet.private[0]: Refreshing state... [id=subnet-04075fc372936edb5]
module.network.aws_route_table.public: Refreshing state... [id=rtb-063a31231e5c9c80b]
module.network.aws_route_table_association.private[1]: Refreshing state... [id=rtbassoc-09c19314be8fb3861]
module.network.aws_route_table_association.private[0]: Refreshing state... [id=rtbassoc-055bb8f7431f193e0]
module.rds.aws_db_subnet_group.this: Refreshing state... [id=sw-hub-dev-db-subnet-group]
module.ec2.aws_security_group.this: Refreshing state... [id=sg-0ad8add3d38300814]
module.network.aws_route_table_association.public[0]: Refreshing state... [id=rtbassoc-0e429d4723cd5a767]
module.network.aws_route_table_association.public[1]: Refreshing state... [id=rtbassoc-0e9d371d632d5b1ac]
module.rds.aws_security_group.this: Refreshing state... [id=sg-06d151aff52ec211a]
module.ec2.aws_instance.this[0]: Refreshing state... [id=i-0fa0e4a4342acb626]
module.s3["backups"].aws_s3_bucket_public_access_block.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.s3["uploads"].aws_s3_bucket_public_access_block.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["uploads"].aws_s3_bucket_server_side_encryption_configuration.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket_server_side_encryption_configuration.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.s3["backups"].aws_s3_bucket_versioning.this: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.s3["uploads"].aws_s3_bucket_versioning.this: Refreshing state... [id=sw-hub-dev-uploads-850919911012]
module.s3["backups"].aws_s3_bucket_lifecycle_configuration.this[0]: Refreshing state... [id=sw-hub-dev-backups-850919911012]
module.rds.aws_db_instance.this: Refreshing state... [id=db-KAC2ZLYDLRTDU65OGQ5E6DJEAM]
data.aws_iam_policy_document.s3_access: Reading...
data.aws_iam_policy_document.s3_access: Read complete after 0s [id=2628098098]
aws_iam_role_policy.s3_access: Refreshing state... [id=sw-hub-dev-app-role:s3-access]
local_file.ansible_hosts: Refreshing state... [id=db6661ffa28a84f2bddfa42780bea36daddb0300]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the
last "terraform apply" which may have affected this plan:

  # module.ec2.local_sensitive_file.private_key has been deleted
  - resource "local_sensitive_file" "private_key" {
      - filename             = "../../modules/ec2/../../../sw-hub-dev.pem" -> null
        id                   = "dc2b1688c4ecf264ef497f0621b825cf769990d2"
        # (9 unchanged attributes hidden)
    }


Unless you have made equivalent changes to your configuration, or ignored the
relevant attributes using ignore_changes, the following plan may include
actions to undo or respond to these changes.

─────────────────────────────────────────────────────────────────────────────

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  # cloudflare_record.moa_hostname will be updated in-place
  ~ resource "cloudflare_record" "moa_hostname" {
      + allow_overwrite = false
        id              = "8e371bb1117b727747287690283413ad"
        name            = "moa"
        tags            = []
      + value           = "7e214e22-c418-4735-9311-2e340551dbf1.cfargotunnel.com"
        # (10 unchanged attributes hidden)
    }

  # local_sensitive_file.ansible_secrets will be created
  + resource "local_sensitive_file" "ansible_secrets" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0700"
      + file_permission      = "0600"
      + filename             = "./../../../ansible/inventories/dev/group_vars/all/secrets.yml"
      + id                   = (known after apply)
    }

  # random_id.cloudflare_tunnel_secret will be created
  + resource "random_id" "cloudflare_tunnel_secret" {
      + b64_std     = (known after apply)
      + b64_url     = (known after apply)
      + byte_length = 32
      + dec         = (known after apply)
      + hex         = (known after apply)
      + id          = (known after apply)
    }

  # module.ec2.aws_security_group.this will be updated in-place
  ~ resource "aws_security_group" "this" {
        id                     = "sg-0ad8add3d38300814"
      ~ ingress                = [
          - {
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]
              - from_port        = 22
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 22
                # (1 unchanged attribute hidden)
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = "SSH access for operations"
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
        ]
        name                   = "sw-hub-dev-ec2-sg"
        tags                   = {
            "Name" = "sw-hub-dev-ec2-sg"
        }
        # (8 unchanged attributes hidden)
    }

  # module.ec2.local_sensitive_file.private_key will be created
  + resource "local_sensitive_file" "private_key" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0700"
      + file_permission      = "0600"
      + filename             = "../../modules/ec2/../../../sw-hub-dev.pem"
      + id                   = (known after apply)
    }

Plan: 3 to add, 2 to change, 0 to destroy.

Changes to Outputs:
  ~ ec2_summary             = {
      ~ private_key_path = "/mnt/c/Users/yeope/yeounhyeok/projectWorkspace/SW-HUB/iac/sw-hub-dev.pem" -> "/home/runner/work/iac/iac/sw-hub-dev.pem"
        # (5 unchanged attributes hidden)
    }
  ~ rds_tunnel_command      = "ssh -i /mnt/c/Users/yeope/yeounhyeok/projectWorkspace/SW-HUB/iac/sw-hub-dev.pem -L 15432:sw-hub-dev-db.c12qekykev5c.ap-northeast-2.rds.amazonaws.com:5432 ubuntu@52.79.161.164" -> "ssh -i /home/runner/work/iac/iac/sw-hub-dev.pem -L 15432:sw-hub-dev-db.c12qekykev5c.ap-northeast-2.rds.amazonaws.com:5432 ubuntu@52.79.161.164"

Warning: Argument is deprecated

  with cloudflare_record.moa_hostname,
  on cloudflare.tf line 47, in resource "cloudflare_record" "moa_hostname":
  47:   value   = cloudflare_zero_trust_tunnel_cloudflared.moa.cname

`value` is deprecated in favour of `content` and will be removed in the next
major release.

─────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
Releasing state lock. This may take a few moments...

@yeounhyeok yeounhyeok merged commit db76a40 into dev Jun 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant