Skip to main content
Glama

Vault MCP Bridge

by geopolitis
README.md20.9 kB
Vault MCP Bridge Overview - FastAPI MCP-compatible server that manages agent-scoped secrets and crypto via HashiCorp Vault. - Auth: API Key, JWT (HS256 and RS256/JWKS), and mTLS via reverse-proxy headers. - Per-agent namespacing in KV v2; Transit support (encrypt/decrypt/sign/verify/rewrap/random). - Optional per-request child token issuance bound to per-agent policies; simple per-agent rate limiting. - Prometheus metrics at `/metrics` and optional OpenTelemetry tracing. Quickstart - Python env: - `python3 -m venv .venv && source .venv/bin/activate` - `python -m pip install -r requirements.txt` - Start local Vault (dev) for testing: - `cd local-vault && docker compose up -d && cd ..` - This provisions KV v2 at `secret/`, a Transit key, and example policies. See `local-vault/README.md`. - Run server: - Easiest: `python main.py` (env: `HOST=0.0.0.0 PORT=8089 RELOAD=true LOG_LEVEL=debug`) - Or: `python -m uvicorn main:app --reload` - Or factory: `python -m uvicorn vault_mcp.app:create_app --factory --reload` - Helper: `scripts/run_dev_jwt.sh` starts the app with Vault + JWT defaults (no manual env export). - Sanity checks: - `curl http://127.0.0.1:8089/healthz` - `bash scripts/smoke.sh` (expects local Vault and default dev auth) - Vault access (required before running the server): - Set credentials so the app can authenticate to Vault, e.g. for the bundled dev compose stack: ```bash export VAULT_ADDR=http://127.0.0.1:8200 export VAULT_TOKEN=root # replace with your own token or AppRole ``` - Without these env vars (or the AppRole equivalents) `/readyz` will return 503 and observability/secret routes will be unauthorized. - To avoid re-exporting each run you can `source config/dev-jwt.env` (new sample file) before launching, or use `scripts/run_dev_jwt.sh` which applies the same defaults automatically. - Optional admin UI: - `python3 -m venv .ui-venv && source .ui-venv/bin/activate` - `pip install -r ui/requirements.txt` - `streamlit run ui/streamlit_app.py` - Use the **Manage Subjects & Keys** page to manage `config/users.json`, rotate credentials, and sync profiles to the sidebar. - JWT helpers in the UI require `JWT_HS256_SECRET` to be set before launch (e.g., `source config/dev-jwt.env` or run `scripts/run_dev_jwt.sh`). - Use **AWS KMS Operations** to supply temporary AWS credentials (if needed) and call encrypt/decrypt, data-key, sign, and verify APIs. JWT quickstart (HS256): - Install helper deps: `pip install 'python-jose[cryptography]'` - Generate a token with the bundled script (adjust subject/scopes as needed): - ``` python scripts/gen_jwt.py \ --secret dev-secret \ --issuer mcp-auth \ --audience mcp-agents \ --sub agent_api \ --scopes read,write,delete,list \ --ttl 600 ``` - Use the printed value as the `Authorization: Bearer <token>` header when calling the API or configuring the Streamlit console. Features - KV v2 secret CRUD with per-agent prefixes and safe pathing. - Transit: encrypt/decrypt, sign/verify, rewrap, and random bytes (base64/hex). - AWS KMS (optional): encrypt/decrypt, generate data keys, and sign/verify with native AWS keys. - Database: dynamic credentials issuance and lease management. - SSH: OTP credential and SSH certificate signing. - Auth: API Key, JWT (HS256 or RS256 via JWKS), mTLS via headers. - Child tokens per request (optional); per-agent in-memory rate limiting. - MCP: JSON-RPC over HTTP at `POST /mcp/rpc` (with `GET /mcp/sse` keepalive channel) and stdio transport via `scripts/mcp_stdio.py`. - Streamlit operations hub: Vault Operations covers Secrets, Transit, Database, SSH, and MCP tools, while a dedicated AWS KMS page lets you enter temporary AWS credentials and invoke encrypt/decrypt, data-key, and signing APIs. - Streamlit agent admin: create multiple AI agent profiles, toggle LLM usage, assign credentials (linked user/API key/JWT), define tasks, capture a `secrets_backend` plan (Vault/KMS/Hybrid), and monitor progress/status. - MCP lifecycle: `initialize`, `tools/list`, `resources/list`, `prompts/list`, `tools/call`, `shutdown`. Protocol version: `2025-06-18`. - Tools exposed (with required scopes): - KV: `kv.read` (read, supports `version`), `kv.write` (write), `kv.list` (list), `kv.delete` (delete), `kv.undelete` (write), `kv.destroy` (write) - Transit: `transit.encrypt` (write), `transit.decrypt` (read), `transit.sign` (write), `transit.verify` (read), `transit.rewrap` (write), `transit.random` (read) - DB: `db.issue_creds` (write), `db.renew` (write), `db.revoke` (write) - SSH: `ssh.otp` (write), `ssh.sign` (write) - KMS: `kms.encrypt` (write), `kms.decrypt` (read), `kms.generate_data_key` (write), `kms.sign` (write), `kms.verify` (read) - Observability endpoints: `/observability/summary` (Vault/API status + in-flight requests) and `/observability/logs/{requests|responses|server}` (tail JSON logs, read scope required). - Metrics at `/metrics`; optional OpenTelemetry via OTLP HTTP exporter. Resources - Scheme `kv://{subject}/{path}` with optional `?version=N`. - `resources/list`: advertises `kv://{subject}/` (KV root) for the authenticated subject. - `resources/get`: - `kv://{subject}/foo/bar` returns `{ data, version }` (JSON) for that KV path. - `kv://{subject}/foo/` (trailing slash) returns `{ keys: [...] }` listing under that prefix. - Cross-subject access is forbidden. Prompts - `prompts/list`: returns prompt specs for `kv_read` and `kv_write` with input schemas. - `prompts/get`: - `kv_read`: returns example `messages` and a `suggested_tool` call for `kv.read`. - `kv_write`: returns example `messages` and a `suggested_tool` call for `kv.write`. Configuration (env) - Vault: - `VAULT_ADDR` (default: http://localhost:8200) - `VAULT_NAMESPACE` (Enterprise only) - `VAULT_TOKEN` or `VAULT_ROLE_ID` + `VAULT_SECRET_ID` - `KV_MOUNT` (default: secret) - `DEFAULT_PREFIX` (default: mcp) - Config file (optional, no .env needed): - Set `APP_CONFIG_FILE` to a JSON/TOML/YAML file path. Example defaults auto-detected from CWD: `config.toml`, `config.json`, `config.yaml`. - Environment variables always override file values. - Note: `.env` files are not auto-loaded anymore. - Precedence: runtime args (where applicable) → environment variables → `APP_CONFIG_FILE`/auto-detected config → built-in defaults. - Auth enable flags: - `AUTH_API_KEY_ENABLED` (default: true) - `AUTH_JWT_ENABLED` (default: true) - `AUTH_MTLS_ENABLED` (default: false) - CLI helper: `scripts/manage_user.py create <subject>` writes metadata to `config/users.json` and prints policy/API key export commands. - UI helper: in Streamlit, check “Generate JWT token” to issue a token during user creation. Generation failures show inline errors and the user entry is not written. - API Keys: - `API_KEYS_JSON` JSON map: `{ "<api-key>": "<agent-id>" }` - JWT: - Common: `JWT_ISSUER` (default: mcp-auth), `JWT_AUDIENCE` (default: mcp-agents) - HS256: `JWT_HS256_SECRET` - RS256/JWKS: `JWT_JWKS_URL` or `JWT_JWKS_FILE`, `JWT_JWKS_CACHE_SECONDS` (default: 300), `JWT_REQUIRE_KID` (default: false) - Validation toggles: `JWT_VALIDATE_ISSUER` (default: true), `JWT_VALIDATE_AUDIENCE` (default: true) - Helper: `python scripts/gen_jwt.py --secret <JWT_HS256_SECRET> --sub <agent>` for quick dev tokens (see Quickstart example above). - Metadata persisted per user: `jwt_created_at`, `jwt_expires_at`, `jwt_ttl_seconds`. The Streamlit **Current users** grid surfaces status, timestamps, TTL seconds, and offers CSV export. - mTLS via proxy headers: - `MTLS_IDENTITY_HEADER` (default: x-ssl-client-s-dn) - `MTLS_VERIFY_HEADER` (default: x-ssl-client-verify) - `MTLS_SUBJECT_CN_PREFIX` (default: CN=) - Child token issuance: - `CHILD_TOKEN_ENABLED` (default: false) - `CHILD_TOKEN_TTL` (default: 90s) - `CHILD_TOKEN_POLICY_PREFIX` (default: mcp-agent-) - Rate limiting: - `RATE_LIMIT_ENABLED` (default: true) - `RATE_LIMIT_REQUESTS` (default: 60) - `RATE_LIMIT_WINDOW_SECONDS` (default: 60) - Server behavior: - `HOST`, `PORT`, `RELOAD`, `LOG_LEVEL` for `python main.py` - Logs directory: `LOG_DIR` (default: `./logs`) - `EXPOSE_REST_ROUTES` (default: true) — when false, disables REST feature routers (`/secrets`, `/transit`, `/db`, `/ssh`, `/whoami`) for MCP‑only deployments. - AWS KMS (optional): - `AWS_KMS_ENABLED` (default: false) - `AWS_REGION` (required when enabling KMS unless provided by IAM role/default profile) - `AWS_KMS_DEFAULT_KEY_ID` (optional convenience default for encrypt/data-key/sign requests) - `AWS_KMS_ENDPOINT` (optional; point to LocalStack or custom endpoint) - `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` (optional; falls back to standard AWS credential resolution) - Observability: - Prometheus at `/metrics` (always enabled) - OpenTelemetry: `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_SERVICE_NAME` (optional) Auth Modes - API Key: send `X-API-Key: <key>`. Map keys to agents via `API_KEYS_JSON`. - JWT: send `Authorization: Bearer <token>` with `sub` and optional `scopes`. - mTLS: terminate TLS at proxy and pass DN via `X-SSL-Client-S-DN`; CN is used as subject. Agent Path Namespace - Secrets live under `{KV_MOUNT}/data/{DEFAULT_PREFIX}/{subject}/...` (KV v2). The server enforces safe relative paths within the agent prefix. Child Token Issuance - If `CHILD_TOKEN_ENABLED=true`, a child token is minted per request with policy `{CHILD_TOKEN_POLICY_PREFIX}{subject}` and TTL `CHILD_TOKEN_TTL`. - Ensure the policy exists and the parent token can create child tokens. Policy - Generate HCL for an agent: - `python scripts/gen_policy.py --agent alice --mount secret --prefix mcp > alice.hcl` - Suggested policy name: `mcp-agent-alice` - The policy grants CRUD/list on `data/{prefix}/{agent}/*`, list/read on `metadata/{prefix}/{agent}/*`, and versioned ops on delete/undelete/destroy. - Apply with the Vault CLI: `vault policy write mcp-agent-alice alice.hcl` Endpoints - KV v2 - PUT `/secrets/{path}` — write (scope: write) - GET `/secrets/{path}` — read (scope: read) [query `version` optional] - DELETE `/secrets/{path}` — delete latest version (scope: delete) - GET `/secrets?prefix=...` — list keys (scope: list) - POST `/secrets/{path}:undelete` — body `{ "versions": [1,2] }` (scope: write) - POST `/secrets/{path}:destroy` — body `{ "versions": [1,2] }` (scope: write) - Transit - POST `/transit/encrypt` — `{ "key": "k", "plaintext": "<b64>" }` (scope: write) - POST `/transit/decrypt` — `{ "key": "k", "ciphertext": "..." }` (scope: read) - POST `/transit/sign` — `{ key, input, hash_algorithm?, signature_algorithm? }` (scope: write) - POST `/transit/verify` — `{ key, input, signature, hash_algorithm? }` (scope: read) - POST `/transit/rewrap` — `{ key, ciphertext }` (scope: write) - GET `/transit/random?bytes=32&format=base64|hex` (scope: read) - KMS endpoints are always exposed for development but return 503 unless `AWS_KMS_ENABLED=true` - POST `/kms/encrypt` — `{ plaintext: <b64>, key_id?, encryption_context?, grant_tokens? }` (scope: write) - POST `/kms/decrypt` — `{ ciphertext: <b64>, encryption_context?, grant_tokens? }` (scope: read) - POST `/kms/data-key` — `{ key_id?, key_spec? | number_of_bytes?, encryption_context?, grant_tokens? }` (scope: write) - POST `/kms/sign` — `{ key_id?, message? | message_digest?, signing_algorithm, message_type?, grant_tokens? }` (scope: write) - POST `/kms/verify` — `{ key_id?, signature: <b64>, message? | message_digest?, signing_algorithm, message_type?, grant_tokens? }` (scope: read) - Database - POST `/db/creds/{role}` — issue dynamic DB creds (scope: write) - POST `/db/renew` — `{ lease_id, increment? }` (scope: write) - POST `/db/revoke` — `{ lease_id }` (scope: write) - SSH - POST `/ssh/otp` — `{ role, ip, username, port? }` (scope: write) - POST `/ssh/sign` — `{ role, public_key, cert_type?, valid_principals?, ttl? }` (scope: write) - Health/Debug/Metrics - GET `/healthz`, `/livez`, `/readyz`, `/whoami`, `/echo-headers`, `/metrics` - `/readyz` returns a JSON body detailing Vault (`ok`/`detail`) and, when enabled, AWS KMS readiness. Expect HTTP 503 with explanatory messages when Vault credentials are missing or KMS is misconfigured. - MCP - Mounted at `/mcp` when `fastapi-mcp` is available API Docs - Swagger UI: `http://127.0.0.1:8089/docs` - ReDoc: `http://127.0.0.1:8089/redoc` - OpenAPI: `http://127.0.0.1:8089/openapi.json` MCP Usage - HTTP JSON-RPC: `POST /mcp/rpc` with JSON-RPC 2.0 messages; authenticate same as REST (API key / JWT / mTLS). - SSE channel: `GET /mcp/sse` provides periodic keepalives for server→client messaging (placeholder; extend as needed). - stdio: `SUBJECT=agentA python scripts/mcp_stdio.py` and write newline-delimited JSON-RPC messages to stdin. - Initialize result includes `protocolVersion: 2025-06-18`, basic capabilities, and lists tools/resources/prompts. - SSE events: server emits `tool.completed` events with `{type, tool, subject, ts}`; keepalives every 15s. MCP Inspector - An official, interactive UI for MCP servers (Swagger-like, but for MCP) that discovers tools/resources/prompts and lets you call them live. - Connect via HTTP: - RPC URL: `http://127.0.0.1:8089/mcp/rpc` - SSE URL (optional): `http://127.0.0.1:8089/mcp/sse` - Auth headers: add `X-API-Key: dev-api-key` (or `Authorization: Bearer <JWT>`) in the Inspector’s connection settings. - If connecting from the hosted Inspector (HTTPS) to your local HTTP server, enable CORS: - `export CORS_ALLOW_ORIGINS=https://inspector.modelcontextprotocol.io` - Consider exposing your server via HTTPS (e.g., ngrok) to avoid mixed-content blocking. - Or connect via stdio: - Command: `SUBJECT=agent_api python scripts/mcp_stdio.py` - Inspector will spawn the process and speak JSON-RPC over stdio. - Once connected, Inspector should list the available tools: `kv.read`, `kv.write`, `kv.list`, `kv.delete`, `kv.undelete`, `kv.destroy`. - Current state: Resources/Prompts are empty; SSE sends keepalives only. Troubleshooting Inspector - `ModuleNotFoundError: vault_mcp` when running stdio: ensure you run from repo root, or use `SUBJECT=agent_api PYTHONPATH=$(pwd) python scripts/mcp_stdio.py`. The script now auto-adds repo root to `sys.path`. - CORS errors in the browser: set `CORS_ALLOW_ORIGINS=https://inspector.modelcontextprotocol.io` (comma separate multiple origins) and restart the server. - Mixed content blocked: use an HTTPS tunnel to your local server (e.g., `ngrok http 8089`) and switch Inspector URLs to `https`. Prometheus & OpenTelemetry - Prometheus endpoint: `GET /metrics` (text). Quick check: `curl -s http://127.0.0.1:8089/metrics | head`. - Metrics include `http_requests_total` and `http_request_duration_seconds` with labels `method`, `route`, `status`. - Additional telemetry: `http_requests_with_correlation_total` counts requests that include or receive a correlation ID. Every HTTP response returns `X-Correlation-Id`; if OTEL tracing is enabled, `X-Trace-Id` is also emitted. - OpenTelemetry tracing (optional): set `OTEL_EXPORTER_OTLP_ENDPOINT` (e.g., `http://localhost:4318/v1/traces`) and `OTEL_SERVICE_NAME` (default: `vault-mcp`). - Structured logs: JSON files under `./logs/` (`requests.log`, `responses.log`, `server.log`). Tail with `tail -f logs/requests.log`. Logging Details - Format: newline-delimited JSON. Core fields: `ts`, `lvl`, `msg`, `logger` plus context in `extra`. - Request logs (`vault_mcp.request`): include `request_id`, `client`, `method`, `path`, `status`, `duration_ms`. - Response/event logs (`vault_mcp.response`): per-endpoint keys, e.g., - `kv_put|kv_get|kv_delete`: `subject`, `path`, `keys`, `version`, `request_id` - `kv_list`: `subject`, `prefix`, `count`, `request_id` - `transit_*`: `subject`, `key`, size/validity hints, `request_id` - `db_*` and `ssh_*`: high-level descriptors (e.g., `role`, `lease_id_suffix`, `ip`, `user`), never secret values - Request ID: responses include `X-Request-Id`; it is echoed in logs for correlation. - Example request log line: - `{ "ts": "2024-01-01T10:00:00", "lvl": "info", "msg": "request", "logger": "vault_mcp.request", "request_id": "...", "client": "127.0.0.1", "method": "GET", "path": "/secrets", "status": 200, "duration_ms": 12 }` Examples (curl) - Write then read a secret (API key `dev-key` for agent `agent_api`): - `curl -X PUT -H 'X-API-Key: dev-key' -H 'Content-Type: application/json' \ -d '{"data": {"foo":"bar"}}' http://127.0.0.1:8089/secrets/configs/demo` - `curl -H 'X-API-Key: dev-key' http://127.0.0.1:8089/secrets/configs/demo` - Random bytes from Transit (hex): - `curl -H 'X-API-Key: dev-key' 'http://127.0.0.1:8089/transit/random?bytes=16&format=hex'` - RS256/JWKS quick test: - See `local-vault/jwks/README.md` for generating keys, running JWKS, and testing. Local Dev Helpers - Start server with sensible dev env: `bash scripts/run_dev.sh` - Enable all auth regardless of current env: `bash scripts/run_all_auth.sh` - Smoke test (server + local Vault): `bash scripts/smoke.sh` - Auth tests by agent: - API key (agent_api): `bash scripts/test_agent_api.sh` - JWT HS256 (agent_jwt): `bash scripts/test_agent_jwt.sh` - JWT RS256/JWKS (agent_jwt): `bash scripts/test_agent_jwt_rs256.sh` - mTLS headers (agent_mtls): `bash scripts/test_agent_mtls.sh` End-to-End and Example Agents - One-shot E2E (server + MCP HTTP + stdio + optional REST): - `LOG_LEVEL=DEBUG bash scripts/e2e_local.sh` - Example MCP agents (HTTP JSON-RPC): - API key: `bash scripts/run_example_agent.sh` - JWT: `bash scripts/run_example_agent.sh --jwt 'YOUR_JWT'` - mTLS headers: `bash scripts/run_example_agent.sh --mtls` - No‑LLM direct client (no model provider): `bash scripts/run_example_agent.sh --no-llm` Examples - LangChain agent that wraps these endpoints as tools: - See `examples/langchain_agent/README.md` and `examples/langchain_agent/agent.py` Testing - Run tests: `pytest` - Pytest overview: - `tests/test_health.py`: Verifies basic liveness endpoints — `GET /healthz` and `GET /livez` return `ok: true`. - `tests/test_auth_and_kv.py`: Exercises API‑key auth and KV v2 CRUD. - Uses a mocked hvac KV client to avoid real Vault. - Flow: `PUT /secrets/configs/demo` writes data, `GET` reads it back, `DELETE` removes it, subsequent `GET` returns 404. - Also checks `GET /whoami` returns the expected subject for `X-API-Key`. - `tests/test_transit_random.py`: Tests Transit random byte generation endpoint with deterministic mock. - Monkeypatches `client_for_principal` to return a stub where `generate_random_bytes` is predictable. - Validates both `format=hex` and default `base64` responses for `GET /transit/random`. - `tests/test_health_ready.py`: Covers `/readyz` for authenticated, unauthenticated, Vault error, and generic error cases. - `tests/test_kv_extras.py`: Covers `GET /secrets?prefix=...` list and version ops (`:undelete`, `:destroy`). - `tests/test_transit_endpoints.py`: Covers `/transit/encrypt|decrypt|sign|verify|rewrap` with a transit stub. - `tests/test_db_and_ssh_routes.py`: Covers `/db/creds|renew|revoke` and `/ssh/otp|sign` with stubs. - `tests/test_auth_modes.py`: HS256 JWT `/whoami` (valid and bad aud), mTLS header success/fail. - `tests/test_auth_jwt_rs256_local.py`: Local RS256: generates RSA + JWKS and validates `/whoami` via monkeypatched JWKS. - `tests/test_rate_limit_and_metrics.py`: Verifies `/metrics` and rate limiting on `/transit/random` (429 on third call). - `tests/test_security_path_and_scopes.py`: Path sanitization and 403 when scopes are insufficient. - `tests/test_app_exception_handlers.py`: Maps Vault `Forbidden` -> 403 and `VaultError` -> 502 JSON responses. - (If you add MCP client tests) exercise `POST /mcp/rpc` for `initialize`, `tools/list`, and `tools/call` with a JWT or API key. Run subsets - Keyword filter: `pytest -k transit` - Coverage detail: `pytest -q --cov=vault_mcp --cov-report=term-missing` Security Notes - Use TLS end-to-end; for mTLS, terminate at a trusted proxy and pass identity headers. - Avoid logging secret values; the app uses structured logging with response metadata only. - Prefer JWT or mTLS in production; reserve API keys for development. - Enable Vault audit devices and keep token TTLs minimal. Troubleshooting - Import errors (e.g., fastapi not found): ensure you use the same Python interpreter that installed deps. - `python -m uvicorn main:app --reload` - Uvicorn targets: use `<module>:<attribute>` — e.g., `main:app`. - Change port/host: `python -m uvicorn main:app --reload --port 8090 --host 0.0.0.0` - Increase logs: add `--log-level debug --access-log`

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/geopolitis/MCP-f-Secrets'

If you have feedback or need assistance with the MCP directory API, please join our Discord server