From 730c4c337fb7cfb1aec36fa98c1e9c62439ae88a Mon Sep 17 00:00:00 2001 From: xnoto Date: Tue, 25 Aug 2026 20:53:28 -0600 Subject: [PATCH] feat(mcp): gate mcp.makeitwork.cloud behind Cloudflare Access Add the mcp.makeitwork.cloud CNAME for the cluster-apps tunnel and a dedicated Access service token plus self_hosted application for the ToolHive MCP gateway (non_identity machine access for headless agent clients, admins group for interactive debug). Service-token client credentials are exposed as outputs (secret marked sensitive). opencode.makeitwork.cloud stays on server-password auth per #25: an Access gate breaks opencode attach. Revisit with an attach-compatible design before re-gating it. --- README.md | 4 ++++ cf-access-mcp.tf | 44 ++++++++++++++++++++++++++++++++++++++++++++ cf-tunnels.tf | 1 + outputs.tf | 12 ++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 cf-access-mcp.tf diff --git a/README.md b/README.md index 3959aba..7ecfcff 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,11 @@ No modules. | [cloudflare_zero_trust_access_application.alertmanager](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_application) | resource | | [cloudflare_zero_trust_access_application.grafana_alerts](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_application) | resource | | [cloudflare_zero_trust_access_application.k3s](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_application) | resource | +| [cloudflare_zero_trust_access_application.mcp_gateway](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_application) | resource | | [cloudflare_zero_trust_access_application.warp](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_application) | resource | | [cloudflare_zero_trust_access_group.admins](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_group) | resource | | [cloudflare_zero_trust_access_identity_provider.github](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_identity_provider) | resource | +| [cloudflare_zero_trust_access_service_token.mcp_gateway](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_access_service_token) | resource | | [cloudflare_zero_trust_organization.main](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_organization) | resource | | [cloudflare_zero_trust_tunnel_cloudflared.warp](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_tunnel_cloudflared) | resource | | [cloudflare_zero_trust_tunnel_cloudflared_route.private_network](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zero_trust_tunnel_cloudflared_route) | resource | @@ -61,6 +63,8 @@ No inputs. | Name | Description | | ---- | ----------- | +| [mcp\_gateway\_service\_token\_client\_id](#output\_mcp\_gateway\_service\_token\_client\_id) | CF-Access-Client-Id for MCP gateway clients | +| [mcp\_gateway\_service\_token\_client\_secret](#output\_mcp\_gateway\_service\_token\_client\_secret) | CF-Access-Client-Secret for MCP gateway clients | | [tunnel\_ids](#output\_tunnel\_ids) | Cloudflare Tunnel IDs for reference in kustomize-cluster ConfigMaps | diff --git a/cf-access-mcp.tf b/cf-access-mcp.tf new file mode 100644 index 0000000..ab2b5ca --- /dev/null +++ b/cf-access-mcp.tf @@ -0,0 +1,44 @@ +# Gates mcp.makeitwork.cloud (ToolHive VirtualMCPServer in kustomize-cluster +# workloads/mcp-gateway). MCP clients are headless HTTP agents, so machine +# access uses a dedicated service token (CF-Access-Client-* headers) rather +# than the browser OIDC flow; the admins policy stays for interactive debug. +resource "cloudflare_zero_trust_access_service_token" "mcp_gateway" { + account_id = local.account_id + name = "mcp-gateway" + # Non-expiring; rotate deliberately by bumping client_secret_version. + duration = "forever" +} + +resource "cloudflare_zero_trust_access_application" "mcp_gateway" { + account_id = local.account_id + name = "MCP Gateway" + type = "self_hosted" + domain = "mcp.makeitwork.cloud" + session_duration = "24h" + + allowed_idps = [ + cloudflare_zero_trust_access_identity_provider.github.id, + ] + + policies = [ + { + name = "mcp-gateway-clients" + decision = "non_identity" + include = [{ + service_token = { + token_id = cloudflare_zero_trust_access_service_token.mcp_gateway.id + } + }] + }, + { + name = "makeitworkcloud-admins" + decision = "allow" + session_duration = "24h" + include = [{ + group = { + id = cloudflare_zero_trust_access_group.admins.id + } + }] + } + ] +} diff --git a/cf-tunnels.tf b/cf-tunnels.tf index 239ce85..2692666 100644 --- a/cf-tunnels.tf +++ b/cf-tunnels.tf @@ -22,6 +22,7 @@ locals { "forgejo", "grafana", "k3s", + "mcp", "opencode", "status", "alertmanager", diff --git a/outputs.tf b/outputs.tf index c36825b..d5d16ac 100644 --- a/outputs.tf +++ b/outputs.tf @@ -8,3 +8,15 @@ output "tunnel_ids" { warp = cloudflare_zero_trust_tunnel_cloudflared.warp.id } } + +# Clients send these as CF-Access-Client-Id / CF-Access-Client-Secret headers. +output "mcp_gateway_service_token_client_id" { + description = "CF-Access-Client-Id for MCP gateway clients" + value = cloudflare_zero_trust_access_service_token.mcp_gateway.client_id +} + +output "mcp_gateway_service_token_client_secret" { + description = "CF-Access-Client-Secret for MCP gateway clients" + value = cloudflare_zero_trust_access_service_token.mcp_gateway.client_secret + sensitive = true +}