Files
project-gitops/docs/adr/0002-terraform-ownership.md
T

3.1 KiB

ADR 0002: Terraform state ownership

Status: accepted

Updated: 2026-07-26

Terraform의 현재 범위는 Vault API 객체입니다. Kubernetes 리소스는 Argo CD가 소유하며, machine/network provisioning은 provider와 운영 경계가 확정될 때 별도 root로 추가합니다.

dev-k3s는 정확히 세 state를 사용합니다.

State 소유 객체
vault-foundation KV/database/Transit mounts, Kubernetes auth backend/config, delegated automation policy와 선택적 분리 CI JWT auth role
vault-workloads workload ACL policy, Kubernetes auth role, project-auth-jwt Transit key
vault-database database/config/auth-system-postgres-dev connection과 auth-db-migration-dev dynamic role

Resource 또는 Vault API path 하나는 한 state에만 속합니다. State 사이는 이름 contract와 실행 순서만 공유하며 terraform_remote_state로 서로의 snapshot을 읽지 않습니다. Backend는 encryption, versioning, access control, locking을 제공해야 합니다.

Privilege delegation의 경계는 다음과 같습니다.

  • vault-foundation은 bootstrap 또는 보안 관리자 승인 때만 실행합니다. Routine CI identity를 두지 않습니다.
  • vault-foundation이 workload/database 전용 automation policy와, OIDC/JWT trust가 검증된 경우 서로 분리된 CI JWT login role을 생성합니다. 두 role의 exact claim map은 최소 한 공통 discriminator key에서 서로 다른 값을 가져야 하므로 동일 scalar-claim JWT가 둘 다 선택할 수 없습니다.
  • vault-workloadsvault-database는 각각의 short-lived identity를 소비할 뿐 자신에게 권한을 부여하는 객체를 소유하지 않습니다.
  • Delegated identity는 자신이 맡은 정확한 policy, auth role, database path만 CRUD할 수 있습니다.
  • Broad platform-admin 또는 상시 cluster-internal Vault administrator를 routine automation에 연결하지 않습니다.

실제 CI issuer가 repository, protected ref와 job discriminator claim을 어떤 형식으로 발행하는지 먼저 검증합니다. 그 계약을 확인할 수 없으면 JWT auth를 활성화하지 않고 bootstrap용 short-lived token만 사용합니다. Foundation의 future change는 routine identity가 아니라 encrypted unseal custody를 사용한 승인된 generated-root ceremony가 필요합니다.

Secret payload는 Terraform resource/data source로 관리하지 않습니다. Provider token과 PostgreSQL credential은 ephemeral variable과 write-only argument를 통해 실행 시점에만 전달합니다. Vault init material, token, password, plan과 state를 Git에 저장하지 않습니다.

기존 vault-core에서 세 state로 바꾸는 작업은 선언 이동과 state ownership 이관을 분리해 수행합니다. Source에서는 removed { destroy = false }, destination에서는 import를 사용하고, 양쪽 plan의 destroy가 0인지 확인하기 전에는 apply하지 않습니다. Broad platform-admin/vault-operator와 미사용 Keycloak/PostgreSQL operator policy/role은 새 state로 옮기지 않으며 consumer가 없음을 확인한 별도 decommission에서 제거합니다.