Skip to content

Commit 875710f

Browse files
committed
add HashiCorp Vault integration tutorial
Add tutorial for integrating ToolHive Kubernetes Operator with HashiCorp Vault Agent Injector for secure MCP server secret management. Covers: - Vault installation and configuration with Kubernetes auth - Pod template metadata overrides for Vault annotations - Automatic secret injection using Agent Injector - End-to-end GitHub MCP server example - Production security recommendations and troubleshooting Also adds the tutorial to the sidebar navigation under Tutorials section.
1 parent 5d8f8f1 commit 875710f

File tree

2 files changed

+278
-0
lines changed

2 files changed

+278
-0
lines changed
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
---
2+
title: HashiCorp Vault integration
3+
description:
4+
Learn how to securely manage MCP server secrets using HashiCorp Vault with
5+
ToolHive Kubernetes Operator.
6+
---
7+
8+
This tutorial shows how to integrate HashiCorp Vault with the ToolHive
9+
Kubernetes Operator to securely manage secrets for your MCP servers. Using
10+
Vault's Agent Injector, you can automatically provision secrets into MCP server
11+
pods without exposing sensitive data in your Kubernetes manifests.
12+
13+
As an example, we'll be deploying a GitHub MCP server.
14+
15+
:::info[Prerequisites]
16+
17+
Before starting this tutorial, ensure you have:
18+
19+
- A Kubernetes cluster with the ToolHive Operator installed
20+
- kubectl configured to access your cluster
21+
- Helm 3.x installed
22+
- Basic familiarity with HashiCorp Vault concepts
23+
- A GitHub Personal Access Token (PAT)
24+
25+
If you need help installing the ToolHive Operator, see the
26+
[Kubernetes quickstart guide](./quickstart-k8s.mdx).
27+
28+
:::
29+
30+
## Overview
31+
32+
The integration works by using HashiCorp Vault's Agent Injector to automatically
33+
inject secrets into MCP server pods. When you add specific annotations to your
34+
MCPServer resource, the Vault Agent Injector:
35+
36+
1. Detects the annotations and injects a Vault Agent sidecar
37+
2. Authenticates with Vault using Kubernetes service account tokens of the
38+
`proxyrunner` pod
39+
3. Retrieves secrets from Vault and writes them to a shared volume
40+
4. Makes the secrets available as environment variables to your MCP server pod
41+
42+
## Step 1: Install and configure Vault
43+
44+
First, install Vault with the Agent Injector enabled in your Kubernetes cluster.
45+
46+
### Install Vault using Helm
47+
48+
Add the HashiCorp Helm repository and install Vault:
49+
50+
```bash
51+
# Add HashiCorp Helm repository
52+
helm repo add hashicorp https://helm.releases.hashicorp.com
53+
helm repo update
54+
55+
# Create vault namespace
56+
kubectl create namespace vault
57+
58+
# Install Vault with Agent Injector
59+
helm install vault hashicorp/vault \
60+
--namespace vault \
61+
--set "server.dev.enabled=true" \
62+
--set "server.dev.devRootToken=dev-only-token" \
63+
--set "injector.enabled=true"
64+
```
65+
66+
:::warning[Development setup only]
67+
68+
This tutorial uses Vault in development mode (`server.dev.enabled=true`) with a
69+
static root token for simplicity. **Do not use this configuration in
70+
production**. For production deployments, follow the [Vault production hardening
71+
guide][vault-hardening].
72+
73+
:::
74+
75+
Wait for the Vault pod to be ready:
76+
77+
```bash
78+
kubectl wait --for=condition=ready pod vault-0 \
79+
--namespace vault \
80+
--timeout=300s
81+
```
82+
83+
### Configure Vault authentication
84+
85+
Configure Vault to authenticate Kubernetes service accounts:
86+
87+
```bash
88+
# Get the Vault pod name
89+
VAULT_POD=$(kubectl get pods --namespace vault \
90+
-l app.kubernetes.io/name=vault \
91+
-o jsonpath="{.items[0].metadata.name}")
92+
93+
# Enable Kubernetes auth method
94+
kubectl exec --namespace vault "$VAULT_POD" -- \
95+
vault auth enable kubernetes
96+
97+
# Configure Kubernetes auth
98+
kubectl exec --namespace vault "$VAULT_POD" -- \
99+
vault write auth/kubernetes/config \
100+
kubernetes_host="https://kubernetes.default.svc:443" \
101+
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
102+
token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token
103+
```
104+
105+
### Set up secrets engine and policies
106+
107+
Enable a key-value secrets engine and create the necessary policies:
108+
109+
```bash
110+
# Enable KV secrets engine
111+
kubectl exec --namespace vault "$VAULT_POD" -- \
112+
vault secrets enable -path=workload-secrets kv-v2
113+
114+
# Create Vault policy for MCP workloads
115+
kubectl exec --namespace vault "$VAULT_POD" -- \
116+
sh -c 'vault policy write toolhive-workload-secrets - << EOF
117+
path "auth/token/lookup-self" { capabilities = ["read"] }
118+
path "auth/token/renew-self" { capabilities = ["update"] }
119+
path "workload-secrets/data/github-mcp/*" { capabilities = ["read"] }
120+
EOF'
121+
122+
# Create Kubernetes auth role
123+
kubectl exec --namespace vault "$VAULT_POD" -- \
124+
vault write auth/kubernetes/role/toolhive-mcp-workloads \
125+
bound_service_account_names="*-proxy-runner,mcp-*" \
126+
bound_service_account_namespaces="toolhive-system" \
127+
policies="toolhive-workload-secrets" \
128+
audience="https://kubernetes.default.svc.cluster.local" \
129+
ttl="1h" \
130+
max_ttl="4h"
131+
```
132+
133+
## Step 2: Store secrets in Vault
134+
135+
Create secrets for your MCP servers in Vault. This example shows how to store a
136+
GitHub personal access token:
137+
138+
```bash
139+
# Store GitHub MCP server configuration
140+
kubectl exec --namespace vault "$VAULT_POD" -- \
141+
vault kv put workload-secrets/github-mcp/config \
142+
token="ghp_your_github_token_here" \
143+
organization="your-org"
144+
```
145+
146+
You can verify the secret was stored correctly:
147+
148+
```bash
149+
kubectl exec --namespace vault "$VAULT_POD" -- \
150+
vault kv get workload-secrets/github-mcp/config
151+
```
152+
153+
## Step 3: Configure your MCPServer resource
154+
155+
Create an MCPServer resource with Vault annotations to enable automatic secret
156+
injection. The key is using the `podTemplateMetadataOverrides` field to add
157+
annotations to the proxy runner pods:
158+
159+
```yaml title="github-mcp-with-vault.yaml"
160+
apiVersion: toolhive.stacklok.dev/v1alpha1
161+
kind: MCPServer
162+
metadata:
163+
name: github-vault
164+
namespace: toolhive-system
165+
spec:
166+
image: ghcr.io/github/github-mcp-server:latest
167+
transport: stdio
168+
port: 9095
169+
permissionProfile:
170+
type: builtin
171+
name: network
172+
resources:
173+
limits:
174+
cpu: '100m'
175+
memory: '128Mi'
176+
requests:
177+
cpu: '50m'
178+
memory: '64Mi'
179+
resourceOverrides:
180+
proxyDeployment:
181+
podTemplateMetadataOverrides:
182+
annotations:
183+
# Enable Vault Agent injection
184+
vault.hashicorp.com/agent-inject: 'true'
185+
vault.hashicorp.com/role: 'toolhive-mcp-workloads'
186+
187+
# Inject GitHub configuration secret
188+
vault.hashicorp.com/agent-inject-secret-github-config:
189+
\ "workload-secrets/data/github-mcp/config"
190+
vault.hashicorp.com/agent-inject-template-github-config: |
191+
{{- with secret "workload-secrets/data/github-mcp/config" -}}
192+
GITHUB_PERSONAL_ACCESS_TOKEN={{ .Data.data.token }}
193+
{{- end -}}
194+
```
195+
196+
### Understanding the annotations
197+
198+
The key annotations that enable Vault integration are:
199+
200+
- `vault.hashicorp.com/agent-inject: "true"` - Enables Vault Agent injection for
201+
this pod
202+
- `vault.hashicorp.com/role: "toolhive-mcp-workloads"` - Specifies the Vault
203+
role to use for authentication
204+
- `vault.hashicorp.com/agent-inject-secret-github-config` - Tells Vault to
205+
retrieve a secret and make it available as a file
206+
- `vault.hashicorp.com/agent-inject-template-github-config` - Uses a Vault
207+
template to format the secret as environment variables
208+
209+
When ToolHive detects the `vault.hashicorp.com/agent-inject` annotation, it
210+
automatically configures the proxy runner to read environment variables from the
211+
`/vault/secrets/` directory where the Vault Agent writes the rendered templates.
212+
213+
## Step 4: Deploy your MCPServer
214+
215+
Apply your MCPServer configuration:
216+
217+
```bash
218+
kubectl apply -f github-mcp-with-vault.yaml
219+
```
220+
221+
Monitor the deployment to ensure both the Vault Agent and ToolHive proxy runner
222+
start successfully:
223+
224+
```bash
225+
# Watch the pod start up
226+
kubectl get pods -n toolhive-system -w
227+
228+
# Check pod logs (replace POD_NAME with actual pod name)
229+
kubectl logs -n toolhive-system POD_NAME -c vault-agent
230+
kubectl logs -n toolhive-system POD_NAME -c proxy-runner
231+
```
232+
233+
You should see the Vault Agent successfully authenticate and retrieve secrets,
234+
and the ToolHive proxy runner start with the injected environment variables.
235+
236+
## Step 5: Verify the integration
237+
238+
Test that your MCP server has access to the secrets by checking the running pod:
239+
240+
```bash
241+
# Get the pod name
242+
POD_NAME=$(kubectl get pods -n toolhive-system \
243+
-l app.kubernetes.io/instance=github-vault \
244+
-o jsonpath="{.items[0].metadata.name}")
245+
246+
# Verify the Vault Agent wrote the secret file
247+
kubectl exec -n toolhive-system "$POD_NAME" -c toolhive -- \
248+
cat /vault/secrets/github-config
249+
250+
# Check that the environment variable is available to the MCP server
251+
kubectl get pod github-vault-0 -n toolhive-system -o jsonpath='{range .spec.containers[?(@.name=="mcp")].env[*]}{.name}{"="}{.value}{"\n"}{end}'
252+
```
253+
254+
## Security best practices
255+
256+
:::tip[Production recommendations]
257+
258+
- Use Vault in production mode with proper TLS certificates
259+
- Implement least-privilege policies for secret access
260+
- Enable audit logging in Vault
261+
- Regularly rotate Vault tokens and secrets
262+
- Monitor Vault Agent logs for authentication issues
263+
- Use namespace isolation for different environments
264+
265+
:::
266+
267+
## Related information
268+
269+
- [Kubernetes quickstart guide](./quickstart-k8s.mdx)
270+
- [Secrets management guide](../guides-cli/secrets-management.mdx)
271+
- [HashiCorp Vault documentation](https://developer.hashicorp.com/vault/docs)
272+
- [Vault Agent Injector documentation][vault-injector]
273+
274+
[vault-hardening]:
275+
https://developer.hashicorp.com/vault/tutorials/operations/production-hardening
276+
[vault-injector]:
277+
https://developer.hashicorp.com/vault/docs/platform/k8s/injector

sidebars.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ const sidebars: SidebarsConfig = {
160160
label: 'Quickstart guides',
161161
},
162162
'toolhive/tutorials/custom-registry',
163+
'toolhive/tutorials/vault-integration',
163164
],
164165
},
165166

0 commit comments

Comments
 (0)