← Identity Fabric
Grayskull Technology · Lab Identity Governance Fabric · as-built

MCP Gateway Authorization — As-Built Topology

how one AD-rooted group model, rendered from policy-as-code, governs three gateways with one job each — agentgateway (MCP tools · live), Envoy AI Gateway (LLM providers), Cisco Secure Access (private apps) — over the same identity truth
The load-bearing idea

Groups are an identity-plane object, not a gateway object. 17 AD security groups in OU=Groups,OU=Lab — tiered T0–T4 — reach Duo via the AD Duo Directory Sync (the Auth-Proxy path) — a distinct sync from Entra Connect's AD → Entra — and serve every gateway. What is per-gateway is the policy binding, authored once in mcp-authz-intent.yaml and rendered to each PDP. The same 17 groups govern all three planes — MCP tools, LLM providers, and private apps; a new gateway or plane renders the same intent to its PDP, it never mints a new group set.

The plane, end to end

Source of truth → sync → decision (PDP) → enforcement (PEP) → resources. Policy-as-code drives the PDPs; the leaver signal pulls tokens back out.

flowchart TB
  HRIS["HRIS-lite → IGA → ITSM gate
onboarding drives AD"] subgraph SOT["AD on-prem · lab.local (ADUC) — source of truth"] ADG["Users + 17 SG-* groups
OU=Groups,OU=Lab · tiered T0–T4"] end subgraph PROXY["Auth-proxy host (SYNC01) — two sync engines, side by side"] ECN["Entra Connect
AD users+groups → Entra"] DDS["AD Duo Auth Proxy
Duo Directory Sync · [ADDS]"] end ENT["Entra ID · testenvir010"] DUO["Duo directory
31 AD-synced (incl. 17 SG-*) · 5 Entra · 1 Google"] subgraph PAC["Policy-as-code · SSOT"] INT["mcp-authz-intent.yaml"]-->RND["render_duo.py
(render_secureaccess.py →)"] end subgraph PDP["Decision · Duo cloud Authorization API (true PDP)"] DAC["ext_authz connectors
agentgw + envoy · LIVE"] CSA["Cisco Secure Access
managed SSE · PLANNED"] end subgraph PEP["Enforcement · gateways (PEP) — one job each"] AGW["agentgateway 0.12.0 · LIVE
aigw-agentgw · MCP tools"] ENV["Envoy AI Gateway
LLM providers · repurposing"] SAG["Cisco Secure Access
private apps (ZTNA) · PLANNED"] end subgraph RES["Resources"] MCP["MCP servers
github · ms-learn · splunk"] LLM["LLM providers
Anthropic · OpenAI · Ollama"] APPS["Private apps
-lite consoles + fabric API"] end HRIS-->ADG ADG-->|Entra path|ECN-->ENT ADG-->|AD→Duo path · SG-* groups|DDS-->DUO ENT-.->|Entra→Duo sync|DUO DUO-->DAC ENT-.->CSA RND-->DAC RND-->CSA DAC-->AGW DAC-->ENV CSA-->SAG AGW-->MCP ENV-->LLM SAG-->APPS LEAVER["HRIS leaver → IGA → Duo session-revoked (SSF/CAEP)"]-.->|pulls token|AGW LEAVER-.->ENV LEAVER-.->SAG
Two directory-sync paths (validated via Duo Admin API)

HRIS onboarding drives AD on-prem. The auth-proxy host (SYNC01) runs two sync engines side by side: Entra Connect pushes AD users + groups into Entra ID, and the AD Duo Auth Proxy (Duo Directory Sync "Grayskull Technology [ADDS]") pushes them into Duo. The 17 SG-* groups reach Duo on the AD path — not through Entra. Duo's directory today holds 31 groups from the AD sync (incl. all 17 SG-*), 5 from a Microsoft Entra sync, and 1 from Google — three inbound directory syncs converging on one Duo, which the gateways' authorization then reads.

Per-tool-call decision

Two checks on every call: authenticate the Duo JWT, then authorize the tool against the sponsor's groups. Default-deny.

sequenceDiagram
  participant C as Agent / MCP client
  participant G as Gateway (PEP)
  participant D as Duo Authorization Connector (PDP)
  C->>G: tools/call (Bearer Duo JWT)
  G->>G: mcpAuthentication — validate iss/aud/JWKS
  G->>D: ext_authz Check (token + target_tool)
  D->>D: sponsor groups ∩ allowedTools(group, server)
  alt authorized
    D-->>G: ALLOW (allowedTools)
    G-->>C: tool result
  else not in any group's bundle
    D-->>G: DENY
    G-->>C: 403 (tool hidden / refused)
  end
  

The 17 groups (net-new, deployed to ADUC)

■ T0 reference■ T1 read ■ T2 write■ T3 privileged (.adm/JIT) ■ T4 sensitive (.adm/JIT+alert)
AD groupPlaneTierProvisioningScope
SG-MCP-MSLearnMCPT0birthrightms-learn ×3
SG-MCP-GitHub-ReadMCPT1birthrightgithub read ×26
SG-MCP-GitHub-WriteMCPT2requestablegithub write ×14
SG-MCP-GitHub-MergeMCPT3JIT · .admmerge_pull_request · SoD ≠ Write
SG-MCP-GitHub-AdminMCPT3JIT · .admcreate_repository, delete_file
SG-MCP-AppSecMCPT4JIT+alert · .admrun_secret_scanning
SG-MCP-Splunk-ReadMCPT1birthrightsplunk metadata ×6
SG-MCP-Splunk-QueryMCPT3JIT · .admrun_query, run_saved_search
SG-MCP-Splunk-IAMDataMCPT4JIT+alert · .admget_user_info, get_user_list
SG-LLM-LocalLLMT0birthrightOllama (no egress)
SG-LLM-ExternalLLMT2requestableAnthropic/OpenAI · DLP-gated
SG-LLM-FrontierLLMT3JITfrontier models · budget-bound
SG-AGW-Fabric-ReadAPIT1requestableGET · hris/iga/itsm/cmdb/pacs
SG-AGW-Fabric-OperatorAPIT3JIT · .admcontrol endpoints
SG-MCP-AgentGW-AdminadminT3JIT · .admagentgateway console/config
SG-MCP-EnvoyGW-AdminadminT3JIT · .admEnvoy admin (planned)
SG-MCP-SecureAccess-AdminadminT3JIT · .admSecure Access admin (planned)
Why this scales

Groups = f(roles × tiers) — independent of gateway count and tool count. A new tool is auto-tiered by the onboarding rubric into an existing group; a new gateway renders the same intent to its PDP. T3/T4 bind to an elevated .adm identity, so neither a human on their daily account nor an agent acting on-behalf-of them can reach destructive or data-exfil tools. Compliance maps clean: NIST 800-53 AC-2/3/5/6, 800-171/CMMC 3.1.x, ISO 27001 A.5/A.8, SOC 2 CC6, HIPAA §164.312.

← conceptual: Agentic AI Gateways & Access (page 5)