refactor: 구조 변경

This commit is contained in:
donghyeon-ka
2026-08-28 17:24:26 +09:00
parent a6f6c663e0
commit b8626946b1
192 changed files with 2251 additions and 206 deletions
@@ -0,0 +1,22 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/vault" {
version = "5.7.0"
constraints = "~> 5.7.0"
hashes = [
"h1:Pm0AcUSYmBPZgRahQX/ahiYcjtZODSAEc2rK8r8MQ18=",
"zh:1dd9ab6d23f61a5e522efcb462f1fd6f4a210c77b9038c8e12fa5fa663b45d01",
"zh:3c98d37ead857c980f7b9285f8c3e1eb7a8fd6d6799275c311c6997973389cc9",
"zh:3df895fbaed383e3748ba1b50f5f1046f75503483bc3d783992059f85c85ba31",
"zh:3e9faaa0a85c6f03c7fd7f8b7008bb3fbb8777f26c001875947cafa47f91c657",
"zh:52a057d0c6cde7cbfd9ceb78a3781dcfc81cf108c533f454530ea6bb87a9bea8",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:8521c3825254a5f7fbff8f42ca57cabf052366f0420f5f239ebebf8292c03d0e",
"zh:953563d429e40087eb34faf22f28e781e50eee27cfc9ac1ad04308ba592a647f",
"zh:a52dd76bb7f5b86cb8de7380d2e68b47ec4445782c16ee205e6a013be35a57b6",
"zh:bdad38c95a14c8cce1eeadcc539cf9bf74902ce7c662b79105ad993bb48ec073",
"zh:d3c676d7d12c15b58518fa3ee7fc398a13893b4057fe9bf4bc1fe635f3fb995a",
"zh:f8673b6c06da80e912c9e32dd4853f07bfca386968d5b33c9fceb6f68b519959",
]
}
@@ -0,0 +1,19 @@
# dev-k3s Vault foundation
## 대상과 State
- Environment/cluster: `dev-k3s`
- Provider: HashiCorp Vault
- State: `vault-foundation`
- Backend example: `backend.s3.hcl.example`
- Owner: bootstrap/security administrator
Vault mount, Kubernetes auth config, delegated automation policy와 선택적 CI JWT
role을 소유합니다. Routine automation 대상이 아니며 downstream state가 자기
실행 권한을 직접 만들지 않도록 합니다.
`policies/`는 이 state가 소유하는 정확한 delegated automation ACL입니다.
실행과 복구 절차는
[`docs/runbooks/dev-bootstrap.md`](../../../../docs/runbooks/dev-bootstrap.md)와
[`docs/runbooks/terraform-state-migration.md`](../../../../docs/runbooks/terraform-state-migration.md)를
따릅니다.
@@ -0,0 +1,5 @@
bucket = "project-gitops-terraform-state"
key = "dev-k3s/vault-foundation.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true
@@ -0,0 +1,189 @@
terraform {
required_version = ">= 1.11.0"
required_providers {
vault = {
source = "hashicorp/vault"
version = "~> 5.7.0"
}
}
backend "s3" {}
}
provider "vault" {
address = var.vault_addr
token = var.vault_token
}
removed {
from = module.workload_policies
lifecycle {
destroy = false
}
}
removed {
from = module.workload_roles
lifecycle {
destroy = false
}
}
removed {
from = vault_transit_secret_backend_key.project_auth_jwt
lifecycle {
destroy = false
}
}
removed {
from = vault_policy.platform_admin
lifecycle {
destroy = false
}
}
removed {
from = vault_kubernetes_auth_backend_role.operator
lifecycle {
destroy = false
}
}
removed {
from = vault_jwt_auth_backend_role.ci
lifecycle {
destroy = false
}
}
locals {
platform_policy_dir = "${path.module}/policies"
database_automation_policy_name = "vault-database-automation-dev"
database_mount_path = "database"
ci_database_role_name = "project-gitops-vault-database-dev"
ci_jwt_auth_path = "jwt-ci"
ci_workloads_role_name = "project-gitops-vault-workloads-dev"
kubernetes_auth_path = "kubernetes"
kv_mount_path = "kv"
transit_mount_path = "transit"
workloads_automation_policy_name = "vault-workloads-automation-dev"
automation_roles = var.ci_jwt_oidc_discovery_url == null ? {} : {
workloads = {
bound_claims = var.ci_workloads_bound_claims
name = local.ci_workloads_role_name
policy = vault_policy.workloads_automation.name
}
database = {
bound_claims = var.ci_database_bound_claims
name = local.ci_database_role_name
policy = vault_policy.database_automation.name
}
}
}
resource "vault_mount" "kv" {
path = local.kv_mount_path
type = "kv"
options = {
version = "2"
}
lifecycle {
prevent_destroy = true
}
}
resource "vault_mount" "database" {
path = local.database_mount_path
type = "database"
lifecycle {
prevent_destroy = true
}
}
resource "vault_mount" "transit" {
path = local.transit_mount_path
type = "transit"
lifecycle {
prevent_destroy = true
}
}
resource "vault_auth_backend" "kubernetes" {
path = local.kubernetes_auth_path
type = "kubernetes"
lifecycle {
prevent_destroy = true
}
}
resource "vault_kubernetes_auth_backend_config" "cluster" {
backend = vault_auth_backend.kubernetes.path
disable_iss_validation = true
disable_local_ca_jwt = false
kubernetes_host = var.kubernetes_host
}
resource "vault_policy" "workloads_automation" {
name = local.workloads_automation_policy_name
policy = file("${local.platform_policy_dir}/vault-workloads-automation-dev.hcl")
}
resource "vault_policy" "database_automation" {
name = local.database_automation_policy_name
policy = file("${local.platform_policy_dir}/vault-database-automation-dev.hcl")
}
resource "vault_jwt_auth_backend" "ci" {
count = var.ci_jwt_oidc_discovery_url == null ? 0 : 1
bound_issuer = var.ci_jwt_bound_issuer
oidc_discovery_url = var.ci_jwt_oidc_discovery_url
path = local.ci_jwt_auth_path
lifecycle {
prevent_destroy = true
precondition {
condition = (
var.ci_jwt_bound_issuer != null &&
length(var.ci_jwt_bound_audiences) > 0 &&
length(var.ci_workloads_bound_claims) > 0 &&
length(var.ci_database_bound_claims) > 0 &&
length([
for claim, value in var.ci_workloads_bound_claims : claim
if lookup(var.ci_database_bound_claims, claim, value) != value
]) > 0
)
error_message = "Enabled CI JWT auth requires issuer/audience constraints and workload/database claim maps with at least one shared discriminator key carrying different values."
}
}
}
resource "vault_jwt_auth_backend_role" "automation" {
for_each = local.automation_roles
backend = vault_jwt_auth_backend.ci[0].path
bound_audiences = var.ci_jwt_bound_audiences
bound_claims = each.value.bound_claims
bound_claims_type = "string"
role_name = each.value.name
role_type = "jwt"
token_explicit_max_ttl = var.ci_token_ttl_seconds
token_no_default_policy = true
token_policies = [each.value.policy]
user_claim = var.ci_jwt_user_claim
}
@@ -0,0 +1,24 @@
# Managed by vault-foundation. The database runner may reconcile only the named
# PostgreSQL connection, dynamic role, and minimal self-service token endpoints.
path "database/config/auth-system-postgres-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "database/roles/auth-db-migration-dev" {
capabilities = ["create", "read", "update", "delete"]
}
# no-default-policy runner tokens retain only the self-service operations used
# for capability checks, identity verification, and explicit revocation.
path "sys/capabilities-self" {
capabilities = ["update"]
}
path "auth/token/lookup-self" {
capabilities = ["read"]
}
path "auth/token/revoke-self" {
capabilities = ["update"]
}
@@ -0,0 +1,65 @@
# Managed by vault-foundation. This trusted security runner may reconcile only
# the named workload policies, Kubernetes auth roles, application Transit key,
# and the minimal self-service token endpoints declared below.
path "sys/policies/acl/auth-server-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "sys/policies/acl/auth-db-migration-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "sys/policies/acl/postgres-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "sys/policies/acl/keycloak-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "sys/policies/acl/keycloak-client-sync-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "auth/kubernetes/role/auth-server-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "auth/kubernetes/role/auth-db-migration-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "auth/kubernetes/role/postgres-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "auth/kubernetes/role/keycloak-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "auth/kubernetes/role/keycloak-client-sync-dev" {
capabilities = ["create", "read", "update", "delete"]
}
path "transit/keys/project-auth-jwt" {
capabilities = ["create", "read", "update"]
}
path "transit/keys/project-auth-jwt/config" {
capabilities = ["update"]
}
# no-default-policy runner tokens retain only the self-service operations used
# for capability checks, identity verification, and explicit revocation.
path "sys/capabilities-self" {
capabilities = ["update"]
}
path "auth/token/lookup-self" {
capabilities = ["read"]
}
path "auth/token/revoke-self" {
capabilities = ["update"]
}
@@ -0,0 +1,71 @@
variable "ci_database_bound_claims" {
description = "Exact repository, protected-ref, and database-job claims for the database role."
type = map(string)
default = {}
}
variable "ci_jwt_bound_audiences" {
description = "Exact CI JWT audiences."
type = set(string)
default = []
}
variable "ci_jwt_bound_issuer" {
description = "Expected CI JWT issuer."
type = string
default = null
nullable = true
}
variable "ci_jwt_oidc_discovery_url" {
description = "CI OIDC discovery URL. Null leaves external CI authentication disabled."
type = string
default = null
nullable = true
}
variable "ci_jwt_user_claim" {
description = "JWT claim used as the Vault identity alias."
type = string
default = "sub"
}
variable "ci_token_ttl_seconds" {
description = "Maximum lifetime for delegated CI tokens."
type = number
default = 1800
validation {
condition = (
var.ci_token_ttl_seconds >= 60 &&
var.ci_token_ttl_seconds <= 3600 &&
floor(var.ci_token_ttl_seconds) == var.ci_token_ttl_seconds
)
error_message = "ci_token_ttl_seconds must be a whole number between 60 and 3600."
}
}
variable "ci_workloads_bound_claims" {
description = "Exact repository, protected-ref, and workloads-job claims for the workloads role."
type = map(string)
default = {}
}
variable "kubernetes_host" {
description = "Kubernetes TokenReview API address."
type = string
default = "https://kubernetes.default.svc.cluster.local:443"
}
variable "vault_addr" {
description = "Vault API address reachable by the foundation operator."
type = string
default = "http://127.0.0.1:8200"
}
variable "vault_token" {
description = "Short-lived bootstrap or security-administrator token."
type = string
sensitive = true
ephemeral = true
}