diff --git a/README.md b/README.md
index b1ec3f3..d72b481 100644
--- a/README.md
+++ b/README.md
@@ -68,14 +68,19 @@ No modules.
| [google_apikeys_key.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/apikeys_key) | resource |
| [google_billing_budget.project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/billing_budget) | resource |
| [google_iam_workload_identity_pool.github](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool) | resource |
+| [google_iam_workload_identity_pool.kubernetes](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool) | resource |
| [google_iam_workload_identity_pool_provider.github](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider) | resource |
+| [google_iam_workload_identity_pool_provider.opencode_kubernetes](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider) | resource |
| [google_kms_crypto_key.sops](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/kms_crypto_key) | resource |
| [google_kms_key_ring.sops](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/kms_key_ring) | resource |
| [google_project.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project) | resource |
+| [google_project_iam_member.opencode_mcp](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_project_iam_member.terraformer](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_project_service.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_service) | resource |
+| [google_service_account.opencode_mcp](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource |
| [google_service_account.terraformer](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource |
| [google_service_account_iam_member.github_workload_identity_user](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource |
+| [google_service_account_iam_member.opencode_mcp_workload_identity_user](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource |
| [google_storage_bucket.state](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket) | resource |
## Inputs
@@ -99,6 +104,8 @@ No modules.
| Name | Description |
| ---- | ----------- |
| [github\_workload\_identity\_provider](#output\_github\_workload\_identity\_provider) | GitHub Actions Workload Identity Provider resource name. |
+| [opencode\_mcp\_service\_account\_email](#output\_opencode\_mcp\_service\_account\_email) | Keyless service account for the managed Google Cloud MCP server. |
+| [opencode\_mcp\_workload\_identity\_provider](#output\_opencode\_mcp\_workload\_identity\_provider) | WIF provider for the OpenCode Kubernetes ServiceAccount. |
| [project\_id](#output\_project\_id) | Created GCP project ID. |
| [sops\_kms\_resource](#output\_sops\_kms\_resource) | Cloud KMS resource for SOPS gcp\_kms recipients. |
| [state\_bucket\_name](#output\_state\_bucket\_name) | GCS bucket to configure as the OpenTofu backend after bootstrap. |
diff --git a/identity.tf b/identity.tf
index 0092f02..27d0912 100644
--- a/identity.tf
+++ b/identity.tf
@@ -14,6 +14,31 @@ resource "google_project_iam_member" "terraformer" {
member = "serviceAccount:${google_service_account.terraformer.email}"
}
+# This service account is reserved for OpenCode's direct connection to the
+# Google-managed Cloud CLI MCP server. It has no key and can only be
+# impersonated by the named Kubernetes ServiceAccount through WIF.
+resource "google_service_account" "opencode_mcp" {
+ project = google_project.this.project_id
+ account_id = "opencode-mcp"
+ display_name = "OpenCode managed MCP"
+
+ depends_on = [google_project_service.this]
+}
+
+resource "google_project_iam_member" "opencode_mcp" {
+ for_each = toset([
+ "roles/browser",
+ "roles/cloudkms.viewer",
+ "roles/iam.securityReviewer",
+ "roles/mcp.toolUser",
+ "roles/serviceusage.serviceUsageViewer",
+ ])
+
+ project = google_project.this.project_id
+ role = each.value
+ member = "serviceAccount:${google_service_account.opencode_mcp.email}"
+}
+
resource "google_iam_workload_identity_pool" "github" {
project = google_project.this.project_id
workload_identity_pool_id = "github"
@@ -47,3 +72,38 @@ resource "google_service_account_iam_member" "github_workload_identity_user" {
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/${local.github_repository}"
}
+
+# Kubernetes publishes the public ServiceAccount signing keys at this issuer.
+# The provider accepts only the OpenCode workload identity, preventing other
+# cluster workloads from impersonating the Google MCP service account.
+resource "google_iam_workload_identity_pool" "kubernetes" {
+ project = google_project.this.project_id
+ workload_identity_pool_id = "kubernetes"
+ display_name = "k3s workloads"
+ description = "k3s ServiceAccount identities for Make IT Work Cloud workloads."
+
+ depends_on = [google_project_service.this]
+}
+
+resource "google_iam_workload_identity_pool_provider" "opencode_kubernetes" {
+ project = google_project.this.project_id
+ workload_identity_pool_id = google_iam_workload_identity_pool.kubernetes.workload_identity_pool_id
+ workload_identity_pool_provider_id = "opencode"
+ display_name = "OpenCode k3s workload"
+
+ attribute_mapping = {
+ "google.subject" = "assertion.sub"
+ }
+
+ attribute_condition = "assertion.sub == \"system:serviceaccount:opencode:opencode-mcp\""
+
+ oidc {
+ issuer_uri = "https://api.makeitwork.cloud"
+ }
+}
+
+resource "google_service_account_iam_member" "opencode_mcp_workload_identity_user" {
+ service_account_id = google_service_account.opencode_mcp.name
+ role = "roles/iam.workloadIdentityUser"
+ member = "principal://iam.googleapis.com/${google_iam_workload_identity_pool.kubernetes.name}/subject/system:serviceaccount:opencode:opencode-mcp"
+}
diff --git a/main.tf b/main.tf
index 8ce80cd..c23e1fd 100644
--- a/main.tf
+++ b/main.tf
@@ -4,6 +4,7 @@ locals {
project_services = toset([
"apikeys.googleapis.com",
"cloudbilling.googleapis.com",
+ "cloudcli.googleapis.com",
"cloudkms.googleapis.com",
"cloudresourcemanager.googleapis.com",
"generativelanguage.googleapis.com",
diff --git a/outputs.tf b/outputs.tf
index 9e95736..56b6c3f 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -22,3 +22,13 @@ output "github_workload_identity_provider" {
description = "GitHub Actions Workload Identity Provider resource name."
value = google_iam_workload_identity_pool_provider.github.name
}
+
+output "opencode_mcp_service_account_email" {
+ description = "Keyless service account for the managed Google Cloud MCP server."
+ value = google_service_account.opencode_mcp.email
+}
+
+output "opencode_mcp_workload_identity_provider" {
+ description = "WIF provider for the OpenCode Kubernetes ServiceAccount."
+ value = google_iam_workload_identity_pool_provider.opencode_kubernetes.name
+}