From ebbf2c297cb651aa1115f92b18423cec039be8ff Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 12 Jan 2026 14:40:50 -0700 Subject: [PATCH] docs: add Docker Compose setup for OAuth multi-user mode - Update Dockerfile to default to streamable-http transport - Add docker-compose.oauth-standalone.yml for existing OIDC providers - Add .env.oauth.example template with all required settings - Update README_DOCKER.md with OAuth deployment instructions OAuth mode requires streamable-http transport (not stdio) since authentication happens via browser redirect flow. --- .env.oauth.example | 82 +++++++++++++++++++++++++++++ Dockerfile | 14 ++--- README_DOCKER.md | 80 ++++++++++++++++++++++++++-- docker-compose.oauth-standalone.yml | 64 ++++++++++++++++++++++ 4 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 .env.oauth.example create mode 100644 docker-compose.oauth-standalone.yml diff --git a/.env.oauth.example b/.env.oauth.example new file mode 100644 index 0000000..830eff6 --- /dev/null +++ b/.env.oauth.example @@ -0,0 +1,82 @@ +# mcvsphere OAuth Configuration Template +# Copy to .env and configure for your environment +# +# Usage: +# cp .env.oauth.example .env +# # Edit .env with your values +# docker compose -f docker-compose.oauth-standalone.yml up -d + +# ───────────────────────────────────────────────────────────────────────────── +# Docker Compose +# ───────────────────────────────────────────────────────────────────────────── +COMPOSE_PROJECT_NAME=mcvsphere + +# ───────────────────────────────────────────────────────────────────────────── +# vCenter Connection (Required) +# ───────────────────────────────────────────────────────────────────────────── +VCENTER_HOST=vcenter.example.com +VCENTER_USER=mcpservice@vsphere.local +VCENTER_PASSWORD=your-secure-password + +# Optional: Skip SSL verification (dev only - use false in production) +VCENTER_INSECURE=false + +# Optional: Specify defaults (auto-detected if not set) +# VCENTER_DATACENTER=Datacenter +# VCENTER_CLUSTER=Cluster +# VCENTER_DATASTORE=datastore1 +# VCENTER_NETWORK=VM Network + +# ───────────────────────────────────────────────────────────────────────────── +# MCP Transport (Required for OAuth) +# ───────────────────────────────────────────────────────────────────────────── +MCP_TRANSPORT=streamable-http +MCP_HOST=0.0.0.0 +MCP_PORT=8080 + +# Your public domain (must match Caddy proxy) +MCP_DOMAIN=mcp.example.com + +# TLS mode: 'internal' for self-signed (dev), remove for auto HTTPS (prod) +MCP_TLS_MODE=internal + +# ───────────────────────────────────────────────────────────────────────────── +# OAuth / OIDC Provider (Required) +# ───────────────────────────────────────────────────────────────────────────── +OAUTH_ENABLED=true + +# OIDC Discovery URL (ends with /.well-known/openid-configuration) +# Examples: +# Authentik: https://auth.example.com/application/o/mcvsphere/ +# Keycloak: https://keycloak.example.com/realms/myrealm +# Auth0: https://myapp.auth0.com/ +# Okta: https://myorg.okta.com/oauth2/default +OAUTH_ISSUER_URL=https://auth.example.com/application/o/mcvsphere/ + +# OAuth Client Credentials (from your OIDC provider) +OAUTH_CLIENT_ID=your-client-id +OAUTH_CLIENT_SECRET=your-client-secret + +# Public callback URL (must be accessible from browser) +OAUTH_BASE_URL=https://mcp.example.com + +# ───────────────────────────────────────────────────────────────────────────── +# RBAC Permission Groups +# ───────────────────────────────────────────────────────────────────────────── +# Create these groups in your OIDC provider and assign users: +# +# | Group | Access Level | +# |------------------------|---------------------------------| +# | vsphere-super-admins | Full control (all 94 tools) | +# | vsphere-host-admins | Host operations + VM management | +# | vsphere-admins | VM lifecycle management | +# | vsphere-operators | Power ops + snapshots | +# | vsphere-readers | Read-only | +# +# Users without any vsphere-* group will be denied access (default-deny). + +# ───────────────────────────────────────────────────────────────────────────── +# Optional Settings +# ───────────────────────────────────────────────────────────────────────────── +# LOG_LEVEL=INFO +# MCVSPHERE_VERSION=0.2.2 diff --git a/Dockerfile b/Dockerfile index 27f8dbe..41c35a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,8 +48,11 @@ ENV PATH="/app/.venv/bin:$PATH" ENV PYTHONUNBUFFERED=1 ENV PYTHONDONTWRITEBYTECODE=1 -# Default to SSE transport for Docker -ENV MCP_TRANSPORT=sse +# Default transport - override with MCP_TRANSPORT env var +# stdio: Direct CLI usage (default for local) +# sse: Server-Sent Events (legacy HTTP) +# streamable-http: HTTP with OAuth support (required for multi-user) +ENV MCP_TRANSPORT=streamable-http ENV MCP_HOST=0.0.0.0 ENV MCP_PORT=8080 @@ -58,10 +61,9 @@ USER mcpuser EXPOSE 8080 -# Health check +# Health check - works with both SSE and streamable-http HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ - CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080')" || exit 1 + CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/.well-known/oauth-authorization-server')" || exit 1 -# Run the MCP server +# Run the MCP server - transport configured via environment ENTRYPOINT ["mcvsphere"] -CMD ["--transport", "sse"] diff --git a/README_DOCKER.md b/README_DOCKER.md index 8208a44..60fa3a8 100644 --- a/README_DOCKER.md +++ b/README_DOCKER.md @@ -235,15 +235,89 @@ MCP_LOG_LEVEL=DEBUG log_level: "DEBUG" ``` +## OAuth Multi-User Mode + +For shared infrastructure or production deployments, mcvsphere supports OAuth 2.1 with any OIDC provider. This enables: + +- **Browser-based authentication** via Authentik, Keycloak, Auth0, Okta, etc. +- **Group-based RBAC** with 5 permission levels +- **Audit logging** with user identity + +### Quick Start (Existing OIDC Provider) + +If you already have an OIDC provider: + +```bash +# 1. Copy and configure environment +cp .env.oauth.example .env +# Edit .env with your OIDC provider details + +# 2. Start mcvsphere with OAuth +docker compose -f docker-compose.oauth-standalone.yml up -d + +# 3. Add to Claude Code +claude mcp add -t http vsphere https://mcp.example.com/mcp +``` + +### Quick Start (New Authentik Deployment) + +To deploy mcvsphere with a complete Authentik identity provider: + +```bash +# 1. Generate secrets and configure +./scripts/setup-oauth.sh + +# 2. Start full stack (mcvsphere + Authentik + PostgreSQL + Redis) +docker compose -f docker-compose.oauth.yml up -d + +# 3. Configure Authentik at https://auth.yourdomain.com +# - Create OAuth2 provider +# - Create vsphere-* groups +# - Assign users to groups +``` + +### RBAC Permission Groups + +Create these groups in your OIDC provider: + +| Group | Access Level | +|-------|--------------| +| `vsphere-super-admins` | Full control (all 94 tools) | +| `vsphere-host-admins` | Host operations + VM management | +| `vsphere-admins` | VM lifecycle management | +| `vsphere-operators` | Power ops + snapshots | +| `vsphere-readers` | Read-only | + +Users without any `vsphere-*` group are denied access (default-deny security). + +### Required Environment Variables + +```bash +# Transport (must be streamable-http for OAuth) +MCP_TRANSPORT=streamable-http + +# OIDC Provider +OAUTH_ENABLED=true +OAUTH_ISSUER_URL=https://auth.example.com/application/o/mcvsphere/ +OAUTH_CLIENT_ID=your-client-id +OAUTH_CLIENT_SECRET=your-client-secret +OAUTH_BASE_URL=https://mcp.example.com +``` + +See [OAUTH-ARCHITECTURE.md](OAUTH-ARCHITECTURE.md) for detailed setup and troubleshooting. + +--- + ## Production Deployment ### Security Recommendations -1. Use a dedicated user account for vCenter access -2. Enable API key authentication -3. Use valid SSL certificates (set `insecure: false`) +1. Use a dedicated service account for vCenter access +2. Enable OAuth authentication (not API keys) +3. Use valid SSL certificates (set `VCENTER_INSECURE=false`) 4. Limit container resources 5. Use Docker secrets for sensitive data +6. Deploy behind a reverse proxy with HTTPS (Caddy recommended) ### High Availability diff --git a/docker-compose.oauth-standalone.yml b/docker-compose.oauth-standalone.yml new file mode 100644 index 0000000..13bead1 --- /dev/null +++ b/docker-compose.oauth-standalone.yml @@ -0,0 +1,64 @@ +# mcvsphere with OAuth - Standalone Mode +# For users who already have an OIDC provider (Authentik, Keycloak, Auth0, Okta, etc.) +# +# Usage: +# 1. Copy .env.oauth.example to .env +# 2. Configure your OIDC provider settings +# 3. docker compose -f docker-compose.oauth-standalone.yml up -d +# +# Requires: +# - External OIDC provider with OAuth 2.1 support +# - Caddy network (caddy-docker-proxy) for HTTPS termination +# +# For full Authentik deployment, use docker-compose.oauth.yml instead + +services: + mcvsphere: + build: + context: . + dockerfile: Dockerfile + image: mcvsphere:${MCVSPHERE_VERSION:-latest} + container_name: mcvsphere + restart: unless-stopped + env_file: + - .env + environment: + # Transport - streamable-http required for OAuth + MCP_TRANSPORT: streamable-http + MCP_HOST: 0.0.0.0 + MCP_PORT: 8080 + # OAuth - set in .env file + OAUTH_ENABLED: ${OAUTH_ENABLED:-true} + volumes: + - ./logs:/app/logs + networks: + - caddy + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/.well-known/oauth-authorization-server')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + labels: + # Caddy reverse proxy - configure domain in .env + caddy: ${MCP_DOMAIN:-mcp.localhost} + caddy.reverse_proxy: "{{upstreams 8080}}" + # TLS - use 'internal' for local dev, remove for production (auto HTTPS) + caddy.tls: ${MCP_TLS_MODE:-internal} + # WebSocket/streaming support + caddy.reverse_proxy.flush_interval: "-1" + caddy.reverse_proxy.transport: http + caddy.reverse_proxy.transport.read_timeout: "0" + caddy.reverse_proxy.transport.write_timeout: "0" + deploy: + resources: + limits: + memory: 512M + cpus: '1.0' + reservations: + memory: 256M + cpus: '0.25' + +networks: + caddy: + external: true