Wire Forge-native release infrastructure
Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 4m14s
Build Site / Next.js Build (push) Failing after 6s
Infra: OpenTofu / OpenTofu (grafana) (push) Successful in 5s
Lint Governance / BEP Metadata (push) Successful in 0s
Build: Android / Android Rust Core Stub (push) Failing after 23s

This commit is contained in:
Conrad Kramer 2026-06-07 05:51:12 -07:00
parent 97c569fb35
commit 002bd382e9
199 changed files with 14268 additions and 185 deletions

5
infra/identity/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.terraform/
backend.hcl
terraform.tfstate
terraform.tfstate.backup
*.tfplan

20
infra/identity/.terraform.lock.hcl generated Normal file
View file

@ -0,0 +1,20 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/hashicorp/google" {
version = "6.50.0"
constraints = ">= 5.40.0, < 7.0.0"
hashes = [
"h1:MAAe4zFFdqS9M5rpmJK/vKgdb6ZMD/s/0Xd97yTDipA=",
"zh:1d4695f807d998f11fcdcfa174766287b82a8093513af857bcdad2d81c642480",
"zh:3173ac5df0294624d113812e49e2a55714aff7db617488168cecdf4168df9e29",
"zh:34d2b3d44c23bd6354fc4ab5917b302872ea1ab8de107034567f955b1717fa5b",
"zh:3a77f3cc2f3664cd5aaeeef4d044e6ec1695a079588fffec3ca03953664e5f04",
"zh:6b444e4b629ea8dc8cb112a39dde098dc5584d26d6de4177558f556a9a226696",
"zh:96545c8cd4d3a57069c5d1799eab5aedd887e16d98b5559a195f6d2c2d9bc674",
"zh:ba464caafde95ee16671d6b5ec90f053ed77a9d06c567456db6efd9160fa3165",
"zh:d876938e5b0d3f57a984d9be72467995f87fef6569968623415dc51d9f54d30b",
"zh:dfd908d873e314ab807d0abc9cfd42d2611cd06dc1b9ec719ebdbb738e8e68d6",
"zh:f9f16819a7738d564afd45fd169ba61004ec4e4e7089d2a4950cb8895be1fe1f",
]
}

145
infra/identity/README.md Normal file
View file

@ -0,0 +1,145 @@
# Burrow Identity KMS and WIF
This OpenTofu stack owns Google Cloud KMS and Workload Identity Federation
resources for Burrow identity and release automation.
It may manage:
- the `burrow-identity` Google KMS key ring
- non-exportable KMS signing keys for Authentik, the SAML CA, and release
signing
- dedicated non-exportable KMS signing keys for APT, RPM, pacman, Flatpak, and
AUR source release surfaces
- a non-exportable KMS signing key for the Apple Developer ID Application CSR
path (`apple-developer-id-application`)
- a non-exportable KMS signing key for the Apple iOS Distribution CSR path
(`apple-ios-distribution`)
- a non-exportable software KMS signing key for Sparkle Ed25519 appcast
signatures (`sparkle-ed25519`)
- an Authentik-backed WIF pool/provider for Forgejo runners
- the runner service account and its narrow IAM bindings for the
`burrow-automation` group and the Authentik WIF client ID
`google-wif.burrow.net`
It must not manage:
- Authentik private signing key material
- OpenBao bootstrap tokens
- store credentials or service-account JSON keys
Forgejo CI should authenticate with `Scripts/ci/google-wif-auth.sh`. That helper
uses Authentik to obtain an OIDC token and lets Google WIF impersonate
`burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com`
in project `project-88c23ce9-918a-470a-b33`.
## Apple Developer ID CSR
`Scripts/apple/google-kms-csr.py` builds a standard PKCS#10 CSR using the public
key from `projects/project-88c23ce9-918a-470a-b33/locations/global/keyRings/burrow-identity/cryptoKeys/apple-developer-id-application`
and asks Google KMS to sign the CSR payload. The private key remains
non-exportable in KMS.
Forgejo can generate the Apple-uploadable CSR with:
```sh
Scripts/forgejo-dispatch.sh apple-developer-id-kms-csr.yml --ref main
```
Apple currently documents Developer ID certificate creation through the
Developer website or Xcode. After downloading the returned `.cer`, keep it with
release artifacts; do not convert this key path into an exportable `.p12`.
The active certificate created from this path is Apple certificate
`9JKN6HXBHC`, stored at
`Apple/Certificates/developer-id-application-9JKN6HXBHC.cer`. Its public key
matches KMS key version `apple-developer-id-application/cryptoKeyVersions/1`.
## Apple iOS Distribution CSR
The iOS App Store distribution certificate uses the same CSR builder with the
`apple-ios-distribution` KMS key:
```sh
Scripts/apple/google-kms-csr.py \
--kms-key apple-ios-distribution \
--common-name "Burrow iOS Distribution" \
--output ios-distribution.certSigningRequest
```
The App Store Connect API can create the certificate from that CSR:
```sh
Scripts/apple/create-asc-certificate.mjs \
--certificate-type IOS_DISTRIBUTION \
--csr-file ios-distribution.certSigningRequest \
--out-dir Apple/Certificates
```
The active certificate created from this path is Apple certificate
`3G42677598`, stored at `Apple/Certificates/ios-distribution-3G42677598.cer`.
Its public key matches KMS key version
`apple-ios-distribution/cryptoKeyVersions/1`.
## Sparkle Appcast Signing
Sparkle appcasts use `sparkle-ed25519`, a non-exportable Google KMS key with
algorithm `EC_SIGN_ED25519` and software protection level. Google KMS rejects
Ed25519 at HSM protection level, so this key is intentionally separate from the
HSM RSA keys used for Apple certificate CSRs and repository signing.
The public EdDSA key embedded in macOS release builds is:
```text
Myv9ZNZT6YGKMtMezh52ra4WqaeEKc4VlvVU0evhJeI=
```
The appcast signer is:
```sh
Scripts/sparkle/sign-appcast-kms.py \
--appcast dist/builds/<build>/sparkle/<channel>/appcast.xml \
--artifact-dir dist/builds/<build>/sparkle/<channel>
```
## Run
```sh
Scripts/identity-tofu.sh init -backend-config=backend.hcl
Scripts/identity-tofu.sh plan -var-file=terraform.tfvars
```
For local syntax work without remote state:
```sh
Scripts/identity-tofu.sh init -backend=false
Scripts/identity-tofu.sh validate
```
Start with `manage_google = false`. Import existing resources before enabling
management, then turn on one resource group at a time.
## Bootstrap Imports
The first live Apple Developer ID pass may need these resources imported before
an `identity` apply can own them:
```sh
Scripts/identity-tofu.sh import 'google_kms_key_ring.identity[0]' \
projects/project-88c23ce9-918a-470a-b33/locations/global/keyRings/burrow-identity
Scripts/identity-tofu.sh import 'google_kms_crypto_key.signing["apple_developer_id_application"]' \
projects/project-88c23ce9-918a-470a-b33/locations/global/keyRings/burrow-identity/cryptoKeys/apple-developer-id-application
Scripts/identity-tofu.sh import 'google_kms_crypto_key.signing["apple_ios_distribution"]' \
projects/project-88c23ce9-918a-470a-b33/locations/global/keyRings/burrow-identity/cryptoKeys/apple-ios-distribution
Scripts/identity-tofu.sh import 'google_kms_crypto_key.signing["sparkle_ed25519"]' \
projects/project-88c23ce9-918a-470a-b33/locations/global/keyRings/burrow-identity/cryptoKeys/sparkle-ed25519
Scripts/identity-tofu.sh import 'google_service_account.runner[0]' \
projects/project-88c23ce9-918a-470a-b33/serviceAccounts/burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com
```
Run imports with the same `manage_google`, key-ring, and service-account
variables that will be used for apply. Key-level IAM bindings should be imported
after the signing keys and service account exist.

View file

@ -0,0 +1,4 @@
bucket = "burrow-opentofu-state"
key = "identity/identity.tfstate"
region = "us-west-2"
encrypt = true

173
infra/identity/google.tf Normal file
View file

@ -0,0 +1,173 @@
locals {
google_required_services = toset([
"cloudkms.googleapis.com",
"cloudresourcemanager.googleapis.com",
"iam.googleapis.com",
"iamcredentials.googleapis.com",
"sts.googleapis.com",
])
google_existing_kms_key_ring_id = var.google_kms_key_ring_id != "" ? var.google_kms_key_ring_id : "projects/${var.google_project_id}/locations/${var.google_kms_key_ring_location}/keyRings/${var.google_kms_key_ring_name}"
google_kms_key_ring_id = var.manage_google_kms_key_ring ? (
google_kms_key_ring.identity[0].id
) : local.google_existing_kms_key_ring_id
google_runner_service_account_email = var.manage_google_runner_service_account ? (
google_service_account.runner[0].email
) : var.google_runner_service_account_email
google_runner_wif_group_members = var.google_wif_runner_group != "" ? (
toset([
"principalSet://iam.googleapis.com/projects/${var.google_project_number}/locations/global/workloadIdentityPools/${var.google_wif_pool_id}/attribute.burrow_groups/${var.google_wif_runner_group}"
])
) : (
toset([])
)
google_runner_wif_client_members = var.google_wif_runner_client_id != "" ? (
toset([
"principalSet://iam.googleapis.com/projects/${var.google_project_number}/locations/global/workloadIdentityPools/${var.google_wif_pool_id}/attribute.burrow_client_id/${var.google_wif_runner_client_id}"
])
) : (
toset([])
)
google_runner_wif_members = setunion(
local.google_runner_wif_group_members,
local.google_runner_wif_client_members,
var.google_wif_runner_group == "" && var.google_wif_runner_client_id == "" ? toset([
"principalSet://iam.googleapis.com/projects/${var.google_project_number}/locations/global/workloadIdentityPools/${var.google_wif_pool_id}/*"
]) : toset([])
)
google_signer_members = setunion(
var.google_release_signer_members,
toset(["serviceAccount:${local.google_runner_service_account_email}"]),
)
google_signer_grants = var.manage_google ? {
for grant in flatten([
for key_name in keys(google_kms_crypto_key.signing) : [
for member in local.google_signer_members : {
id = "${key_name}|${member}"
key_name = key_name
member = member
}
]
]) : grant.id => grant
} : {}
}
resource "google_project_service" "identity" {
for_each = var.manage_google ? local.google_required_services : toset([])
project = var.google_project_id
service = each.value
disable_on_destroy = false
}
resource "google_kms_key_ring" "identity" {
count = var.manage_google && var.manage_google_kms_key_ring ? 1 : 0
project = var.google_project_id
location = var.google_kms_key_ring_location
name = var.google_kms_key_ring_name
depends_on = [google_project_service.identity]
}
resource "google_kms_crypto_key" "signing" {
for_each = var.manage_google && local.google_kms_key_ring_id != "" ? var.google_signing_keys : {}
name = each.value.name
key_ring = local.google_kms_key_ring_id
purpose = "ASYMMETRIC_SIGN"
labels = merge(
{
project = "burrow"
component = "identity"
managed_by = "opentofu"
},
var.tags,
try(each.value.labels, {}),
)
version_template {
algorithm = try(each.value.algorithm, "RSA_SIGN_PKCS1_2048_SHA256")
protection_level = try(each.value.protection_level, "HSM")
}
lifecycle {
prevent_destroy = true
}
}
resource "google_service_account" "runner" {
count = var.manage_google && var.manage_google_runner_service_account ? 1 : 0
project = var.google_project_id
account_id = var.google_runner_service_account_id
display_name = "Burrow Forgejo Runner"
description = "Authentik-backed WIF service account for Burrow Forgejo runners."
depends_on = [google_project_service.identity]
}
resource "google_iam_workload_identity_pool" "burrow" {
count = var.manage_google ? 1 : 0
project = var.google_project_id
workload_identity_pool_id = var.google_wif_pool_id
display_name = "Burrow Authentik"
description = "Authentik-issued identities for Burrow automation."
disabled = false
depends_on = [google_project_service.identity]
}
resource "google_iam_workload_identity_pool_provider" "forgejo_runners" {
count = var.manage_google ? 1 : 0
project = var.google_project_id
workload_identity_pool_id = google_iam_workload_identity_pool.burrow[0].workload_identity_pool_id
workload_identity_pool_provider_id = var.google_wif_provider_id
display_name = "Burrow Forgejo Runners"
description = "Authentik OIDC provider for Burrow Forgejo runner cloud access."
disabled = false
attribute_mapping = {
"google.subject" = "assertion.sub"
"google.groups" = "assertion.groups"
"attribute.burrow_subject" = "assertion.sub"
"attribute.burrow_username" = "assertion.preferred_username"
"attribute.burrow_email" = "assertion.email"
"attribute.burrow_groups" = "assertion.groups"
"attribute.burrow_client_id" = "assertion.azp"
}
attribute_condition = var.google_wif_attribute_condition
oidc {
issuer_uri = var.google_wif_issuer_uri
allowed_audiences = var.google_wif_allowed_audiences
}
}
resource "google_service_account_iam_member" "runner_wif" {
for_each = var.manage_google ? local.google_runner_wif_members : toset([])
service_account_id = var.manage_google_runner_service_account ? (
google_service_account.runner[0].name
) : "projects/${var.google_project_id}/serviceAccounts/${local.google_runner_service_account_email}"
role = "roles/iam.workloadIdentityUser"
member = each.value
}
resource "google_kms_crypto_key_iam_member" "signer" {
for_each = local.google_signer_grants
crypto_key_id = google_kms_crypto_key.signing[each.value.key_name].id
role = "roles/cloudkms.signerVerifier"
member = each.value.member
}

19
infra/identity/outputs.tf Normal file
View file

@ -0,0 +1,19 @@
output "google_kms_key_ring_id" {
description = "Google KMS key ring id used by the identity stack."
value = local.google_kms_key_ring_id
}
output "google_runner_service_account_email" {
description = "Forgejo runner service account email."
value = local.google_runner_service_account_email
}
output "google_wif_pool_name" {
description = "Google WIF pool resource name when managed."
value = try(google_iam_workload_identity_pool.burrow[0].name, null)
}
output "google_signing_key_ids" {
description = "Managed Google KMS signing key ids."
value = { for name, key in google_kms_crypto_key.signing : name => key.id }
}

View file

@ -0,0 +1,3 @@
provider "google" {
project = var.google_project_id
}

View file

@ -0,0 +1,15 @@
manage_google = false
google_project_id = "project-88c23ce9-918a-470a-b33"
google_project_number = "416198671487"
# Import the key ring before setting this true if it already exists.
manage_google_kms_key_ring = false
# Create this only after the Authentik WIF provider design is final.
manage_google_runner_service_account = false
google_wif_issuer_uri = "https://auth.burrow.net/application/o/google-cloud/"
google_wif_allowed_audiences = ["google-wif.burrow.net"]
google_wif_runner_group = "burrow-automation"
google_wif_runner_client_id = "google-wif.burrow.net"

201
infra/identity/variables.tf Normal file
View file

@ -0,0 +1,201 @@
variable "manage_google" {
description = "Manage Google Cloud identity and WIF resources."
type = bool
default = false
}
variable "google_project_id" {
description = "Google Cloud project id for Burrow identity resources."
type = string
default = "project-88c23ce9-918a-470a-b33"
}
variable "google_project_number" {
description = "Google Cloud project number for IAM principal URIs."
type = string
default = "416198671487"
}
variable "google_kms_key_ring_name" {
description = "Google KMS key ring for identity and release signing keys."
type = string
default = "burrow-identity"
}
variable "google_kms_key_ring_location" {
description = "Google KMS key ring location."
type = string
default = "global"
}
variable "google_kms_key_ring_id" {
description = "Existing Google KMS key ring id. Leave blank to derive it from project, location, and name."
type = string
default = ""
}
variable "manage_google_kms_key_ring" {
description = "Create/manage the Google KMS key ring. Import existing rings before enabling this."
type = bool
default = false
}
variable "google_signing_keys" {
description = "Non-exportable Burrow signing keys to create in the identity key ring."
type = map(object({
name = string
algorithm = optional(string, "RSA_SIGN_PKCS1_2048_SHA256")
protection_level = optional(string, "HSM")
labels = optional(map(string), {})
}))
default = {
authentik = {
name = "authentik-signing"
labels = {
key_target = "authentik"
}
}
saml_ca = {
name = "saml-ca"
labels = {
key_target = "saml-ca"
}
}
release = {
name = "release-signing"
labels = {
key_target = "release-signing"
}
}
package_apt_repository = {
name = "package-apt-repository"
labels = {
key_target = "package-apt-repository"
repo_format = "apt"
}
}
package_rpm_repository = {
name = "package-rpm-repository"
labels = {
key_target = "package-rpm-repository"
repo_format = "rpm"
}
}
package_pacman_repository = {
name = "package-pacman-repository"
labels = {
key_target = "package-pacman-repository"
repo_format = "pacman"
}
}
package_flatpak_repository = {
name = "package-flatpak-repository"
labels = {
key_target = "package-flatpak-repository"
repo_format = "flatpak"
}
}
package_aur_source = {
name = "package-aur-source"
labels = {
key_target = "package-aur-source"
repo_format = "aur"
}
}
apple_developer_id_application = {
name = "apple-developer-id-application"
labels = {
key_target = "apple-developer-id-application"
apple_cert_type = "developer-id-application"
}
}
apple_ios_distribution = {
name = "apple-ios-distribution"
labels = {
key_target = "apple-ios-distribution"
apple_cert_type = "ios-distribution"
}
}
sparkle_ed25519 = {
name = "sparkle-ed25519"
algorithm = "EC_SIGN_ED25519"
protection_level = "SOFTWARE"
labels = {
key_target = "sparkle-ed25519"
release_surface = "sparkle"
}
}
}
}
variable "google_release_signer_members" {
description = "Additional IAM members allowed to sign/verify with Burrow release and package repository keys."
type = set(string)
default = []
}
variable "manage_google_runner_service_account" {
description = "Create/manage the Forgejo runner service account for WIF."
type = bool
default = false
}
variable "google_runner_service_account_id" {
description = "Google service account id for Authentik-backed Forgejo runners."
type = string
default = "burrow-forgejo-runner"
}
variable "google_runner_service_account_email" {
description = "Existing runner service account email when not managed here."
type = string
default = "burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com"
}
variable "google_wif_pool_id" {
description = "Workload Identity Federation pool id for Burrow automation."
type = string
default = "burrow-authentik"
}
variable "google_wif_provider_id" {
description = "Workload Identity Federation provider id for Authentik-issued OIDC tokens."
type = string
default = "forgejo-runners"
}
variable "google_wif_issuer_uri" {
description = "Authentik OIDC issuer used by Google WIF."
type = string
default = "https://auth.burrow.net/application/o/google-cloud/"
}
variable "google_wif_allowed_audiences" {
description = "Allowed audiences for Authentik-issued WIF tokens."
type = list(string)
default = ["google-wif.burrow.net"]
}
variable "google_wif_runner_group" {
description = "Mapped Authentik group allowed to assume the runner service account. Empty allows the whole pool."
type = string
default = "burrow-automation"
}
variable "google_wif_runner_client_id" {
description = "Mapped Authentik OIDC client ID allowed to assume the runner service account for machine-to-machine CI. Empty disables this grant."
type = string
default = "google-wif.burrow.net"
}
variable "google_wif_attribute_condition" {
description = "CEL condition applied to Authentik-issued WIF tokens."
type = string
default = "assertion.iss == 'https://auth.burrow.net/application/o/google-cloud/'"
}
variable "tags" {
description = "Additional labels applied to supported Google resources."
type = map(string)
default = {}
}

View file

@ -0,0 +1,12 @@
terraform {
required_version = ">= 1.8.0"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 5.40.0, < 7.0"
}
}
backend "s3" {}
}