Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Proxy Builder 在 GCP Compute Engine 上运行 sing-box,为每个用户提供

系统定义 `development` 与 `production` 两个环境。每个环境使用独立的 GCP Project、OpenTofu state、WIF Provider、Service Accounts、运行资源、代理密钥和用户集合。当前只有 `development` 在线。

GitHub Repository Variables 保存 pull-request plan 身份;同名 GitHub Environment 保存 apply/deploy 身份与五项应用 Secrets。本地 `.secrets/<environment>/` 是权限受限的灾难恢复副本,不进入 Git。
GitHub Repository Variables 保存 PR 与 main 手动 plan 身份;同名 GitHub Environment 保存 apply/deploy 身份与五项应用 Secrets。本地 `.secrets/<environment>/` 是权限受限的灾难恢复副本,不进入 Git。

## 组件与数据流

Expand Down
2 changes: 1 addition & 1 deletion docs/design/environments-and-delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Git 分支只标识版本,不隐式选择环境。所有可写工作流必须

## GitHub 配置

Repository Variables 为 PR plan 提供非秘密只读接入标识:
Repository Variables 为 PR 与 main 手动 plan 提供非秘密只读接入标识:

```text
DEV_GCP_WIF_PROVIDER
Expand Down
2 changes: 1 addition & 1 deletion docs/design/infrastructure-and-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ startup script 不接收 OpenTofu secret 输入,也不得把应用秘密写入
| `proxy-runtime` | 运行 VM;无 Project 级管理角色,无 Secret Manager 读取权限 |
| `subscription-runtime` | 仅读取指定环境的 `proxy-users` 与 `obfs-password` secret |

WIF attribute mapping 必须包含 `repository_id`、`repository_owner_id`、`event_name`、`ref` 和 `sub`。每个环境只有一个 GitHub provider,其 admission condition 使用不可变 repository ID。`github-plan` 只绑定精确的 `repo:<owner>/<repo>:pull_request` subject;`github-apply` 与 `github-deploy` 只绑定精确的 `repo:<owner>/<repo>:environment:<environment>` subject。fork PR 不获得 WIF,GitHub Environment 审批仍是可写身份的必要条件。
WIF attribute mapping 必须包含 `repository_id`、`repository_owner_id`、`event_name`、`ref` 和 `sub`。每个环境只有一个 GitHub provider,其 admission condition 使用不可变 repository ID。`github-plan` 绑定精确的 `repo:<owner>/<repo>:pull_request` 与 `repo:<owner>/<repo>:ref:refs/heads/main` subject,分别用于 PR 和 main 的手动只读 plan;`github-apply` 与 `github-deploy` 只绑定精确的 `repo:<owner>/<repo>:environment:<environment>` subject。fork PR 不获得 WIF,GitHub Environment 审批仍是可写身份的必要条件。

subscription token 为客户端兼容性保留在 query parameter。platform 必须按当前环境的 subscription service 名创建 `google_logging_project_exclusion`,仅排除 `run.googleapis.com/requests` 的 Cloud Run 自动 request log,避免其 `requestUrl` 持久化 token;应用自身的脱敏结构化日志继续保留。bootstrap 因此启用 Cloud Logging API,并仅向 `github-apply` 授予 `roles/logging.configWriter`。

Expand Down
7 changes: 7 additions & 0 deletions infra/modules/github_identity/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ locals {
name_prefix = var.resource_prefix
environment_sub = "repo:${var.github_repository}:environment:${var.environment}"
pull_request_sub = "repo:${var.github_repository}:pull_request"
main_ref_sub = "repo:${var.github_repository}:ref:refs/heads/main"

required_services = toset([
"artifactregistry.googleapis.com",
Expand Down Expand Up @@ -165,6 +166,12 @@ resource "google_service_account_iam_member" "plan_wif" {
member = "principal://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/subject/${local.pull_request_sub}"
}

resource "google_service_account_iam_member" "plan_main_wif" {
service_account_id = google_service_account.github["plan"].name
role = "roles/iam.workloadIdentityUser"
member = "principal://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/subject/${local.main_ref_sub}"
}

resource "google_service_account_iam_member" "environment_wif" {
for_each = toset(["apply", "deploy"])

Expand Down
4 changes: 4 additions & 0 deletions infra/modules/github_identity/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ output "plan_wif_member" {
value = google_service_account_iam_member.plan_wif.member
}

output "plan_main_wif_member" {
value = google_service_account_iam_member.plan_main_wif.member
}

output "required_services" {
value = local.required_services
}
Expand Down
7 changes: 6 additions & 1 deletion infra/stacks/bootstrap/tests/contracts.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ run "identity_contract" {

assert {
condition = module.github_identity.plan_wif_member == "principal://iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/mock-pool/subject/repo:${var.github_repository}:pull_request"
error_message = "plan 身份必须只允许当前仓库的 pull_request subject。"
error_message = "plan 身份必须允许当前仓库的 pull_request subject。"
}

assert {
condition = module.github_identity.plan_main_wif_member == "principal://iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/mock-pool/subject/repo:${var.github_repository}:ref:refs/heads/main"
error_message = "plan 身份必须允许当前仓库 main 的手动 plan subject。"
}

assert {
Expand Down
Loading