diff --git a/cf-access-mcp.tf b/cf-access-mcp.tf index 155f59d..2038056 100644 --- a/cf-access-mcp.tf +++ b/cf-access-mcp.tf @@ -1,7 +1,8 @@ -# 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. +# MCP clients are headless HTTP agents, so every direct endpoint uses the +# existing Cloudflare Access service token (CF-Access-Client-* headers) rather +# than browser OIDC. The shared token is an owner-approved solo-developer trust +# boundary; individual ToolHive proxy Services and their backend credentials +# remain cluster-internal. resource "cloudflare_zero_trust_access_service_token" "mcp_gateway" { account_id = local.account_id name = "mcp-gateway" @@ -9,6 +10,10 @@ resource "cloudflare_zero_trust_access_service_token" "mcp_gateway" { duration = "forever" } +# Retain this Access application until Argo CD has removed the aggregate +# TunnelBinding route. Removing edge Access before that route reconciles away +# would briefly expose the aggregate without authentication. A later scoped +# cleanup PR removes this now-unrouted application after verification. resource "cloudflare_zero_trust_access_application" "mcp_gateway" { account_id = local.account_id name = "MCP Gateway" @@ -43,26 +48,16 @@ resource "cloudflare_zero_trust_access_application" "mcp_gateway" { ] } -# Per-backend MCP endpoints (mcp-.makeitwork.cloud) terminate on the -# same toolhive proxyrunners behind the same tunnel. One application per FQDN: -# Access domains cannot wildcard a name prefix, and first-level names are -# required anyway because Universal SSL only covers one subdomain level. -# -# This list controls Access applications only. Tunnel DNS is exclusively owned -# by TunnelBinding resources in kustomize-cluster. +# Individual ToolHive backend endpoints use one first-level hostname each so +# Universal SSL covers them. Cloudflare Access applications are explicit per +# hostname because Access does not wildcard a name prefix. Tunnel DNS and routes +# are exclusively owned by the corresponding TunnelBinding subjects in +# kustomize-cluster. locals { mcp_backends = [ - "makeitwork-apify", - "makeitwork-argocd", - "makeitwork-aws-docs", - "makeitwork-cloudflare", - "makeitwork-context7", - "makeitwork-github", - "makeitwork-github-xnoto", - "makeitwork-grafana", - "makeitwork-kubernetes", - "makeitwork-parallel-search", - "makeitwork-terraform-docs", + "apify", "argocd", "aws", "aws-docs", "cloudflare", "context7", "gcp", + "grafana", "kubernetes", "parallel-search", "playwright", "slidespeak", + "terraform-docs", "twilio-docs", ] } @@ -74,29 +69,19 @@ resource "cloudflare_zero_trust_access_application" "mcp_gateway_backend" { domain = "mcp-${each.key}.makeitwork.cloud" session_duration = "24h" - allowed_idps = [ - cloudflare_zero_trust_access_identity_provider.github.id, - ] + 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 - } - }] + 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 - } - }] + include = [{ group = { id = cloudflare_zero_trust_access_group.admins.id } }] } ] }