# 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-workloads`와 `vault-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에서 제거합니다.