agentic-community / agentic-community/mcp-gateway-registry
MCPGW OAuth/OIDC and M2M token integration: security gaps and multi-IdP support
- 主要语言
- Python
- 星标
- 912
- 派生
- 234
- 平均合并
- 1 天 11 小时
- 30 天内合并 PR
- 62
描述
## Background
PR #849 shipped optional OAuth/OIDC support in the `mcpgw` MCP server (`servers/mcpgw/server.py`). The goal is to let MCP clients (Cursor, VS Code, Claude Code) authenticate via standard OAuth instead of requiring users to manually obtain and paste bearer tokens.
The entire feature is gated behind `OIDC_ENABLED` (env var, default: **disabled**). When disabled, all behavior is identical to pre-PR-849. No deployment currently enables this.
**This issue tracks the work needed to make the feature production-ready before it can be enabled in any environment.**
## What Exists Today (dormant, in main)
1. **OAuthProxy** (User -> mcpgw auth): FastMCP `OAuthProxy` that redirects MCP clients to the IdP login page, exchanges auth codes for tokens, and verifies JWTs via JWKS. Uses `OIDC_CLIENT_ID`/`OIDC_CLIENT_SECRET`.
2. **M2M Token Manager** (mcpgw -> registry auth): `_M2MTokenManager` class that fetches a Keycloak `client_credentials` token using `M2M_CLIENT_ID`/`M2M_CLIENT_SECRET`, caches it, and refreshes before expiry.
3. **`_get_registry_headers()`**: 3-tier auth priority for mcpgw -> registry calls:
- Tier 1: Static `REGISTRY_API_TOKEN`
- Tier 2: M2M service token
- Tier 3: Caller's bearer token passthrough (current default, old behavior)
## Security Gap: User Identity Lost at Registry Boundary
When M2M is enabled (tier 2), every user's request to the registry arrives as the M2M service account. The registry cannot distinguish between users:
- **Per-user authorization is bypassed**: Group/role-based access controls apply to the M2M service account, not the actual user
- **Audit trail is lost**: All actions logged at the registry appear as the M2M client
- **Privilege escalation risk**: A user with limited IdP permissions gets whatever permissions the M2M service account has
### Sequence showing the problem
```
User (Cursor) -> mcpgw: OAuth login, gets user JWT
mcpgw verifies user JWT (good, user is authenticated)
mcpgw -> registry: X-Authorization: Bearer <-- user identity gone
Registry sees: "mcp-gateway-m2m", not "alice@example.com"
```
### Recommended fix: identity propagation
**Option A: OAuth Token Exchange (RFC 8693)**
mcpgw exchanges the user's token for a new token carrying both user identity and mcpgw's service identity.
**Option B: Identity Header Propagation**
mcpgw uses M2M token for auth but includes user identity in headers (`X-User-Sub`, `X-User-Groups`). Registry trusts mcpgw as a proxy.
**Option C: Pass through user token (current tier 3)**
Just forward the user's JWT. Simple, but only works if the user's IdP-issued token is valid at the registry.
## IdP Compatibility Gap
Current implementation is Keycloak-specific with hardcoded `/realms/{realm}/protocol/openid-connect/*` URL patterns and `KEYCLOAK_*` env vars.
### What each IdP needs
| IdP | Endpoint Pattern | Token Exchange | Status |
|-----|-----------------|----------------|--------|
| Keycloak | `/realms/{realm}/protocol/openid-connect/*` | RFC 8693 supported | Hardcoded today |
| Entra ID | `/oauth2/v2.0/*` under tenant | On-behalf-of flow | Not supported |
| Okta | `/oauth2/{authServerId}/v1/*` | Token exchange supported | Not supported |
| Auth0 | `/{domain}/oauth/*` | Not natively supported | Not supported |
**Fix**: Use OIDC Discovery (`/.well-known/openid-configuration`) with a single `OIDC_ISSUER_URL` env var instead of hardcoded URL patterns. All compliant IdPs expose this endpoint.
## Other Gaps
1. **Hardcoded redirect URIs**: `allowed_client_redirect_uris` only includes localhost and Cursor/VS Code URI schemes. Production deployments need configurable patterns via env var.
2. **Import-time side effects**: `OAuthProxy` and `_M2MTokenManager` are created at module import time, causing startup failures if IdP is unreachable.
3. **M2M token resilience**: If the IdP is temporarily unreachable during token refresh, all tool calls fail. Should use the cached token until actual expiry instead of failing immediately.
4. **Audit logging**: No logging at the mcpgw level recording which user made which tool call.
## Files Involved
- `servers/mcpgw/server.py` (lines 36-131): All OAuth/M2M code
- `servers/mcpgw/server.py` (lines 199-210): `_get_registry_headers()` auth priority
- Future: registry API changes to accept and enforce propagated identity headers
## Acceptance Criteria
- [ ] User identity is preserved end-to-end when OIDC is enabled
- [ ] Works with at least Keycloak and Entra ID (via OIDC Discovery)
- [ ] Redirect URIs are configurable via env var
- [ ] M2M token manager handles IdP outages gracefully
- [ ] No behavioral change when `OIDC_ENABLED` is disabled (default)
- [ ] Integration tests covering OAuth flow with mock IdP
- [ ] Documentation with sequence diagrams showing the full auth flow
---
## Additional Acceptance Criterion: Infrastructure Wiring
The MCPGW OIDC env vars (`OIDC_ENABLED`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `M2M_CLIENT_ID`, `M2M_CLIENT_SECRET`, `MCPGW_BASE_URL`) are currently only in the Python code and `.env.example`. They are NOT wired into:
- `terraform/aws-ecs/modules/mcp-gateway/ecs-services.tf` (mcpgw container env)
- `terraform/aws-ecs/terraform.tfvars.example`
- Helm chart values (if applicable)
The PR that resolves this issue MUST also add the Terraform and Helm wiring so the feature can actually be enabled in ECS/EKS deployments. Do not add the wiring separately before the security gaps are fixed. See issue #896.
- [ ] Add MCPGW OIDC env vars to ECS task definition (ecs-services.tf)
- [ ] Add corresponding Terraform variables and tfvars.example entries
- [ ] Add Helm chart values (if Helm charts exist for mcpgw)
贡献指南
评估
这个 Issue 还没有评估数据。