Skip to content

Commit 948ad66

Browse files
committed
Add MCP auth header secret support
Introduce Secret-backed MCP auth header configuration Support both normal and discovery-time auth headers Add env and volume mounts for file-based header injection Include templating, validation, and Helm tests Bump chart version to 0.1.1
1 parent 99bc0eb commit 948ad66

8 files changed

Lines changed: 242 additions & 3 deletions

File tree

charts/openai-mcp-tunnel/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v2
22
name: openai-mcp-tunnel
33
description: A Helm chart for running the OpenAI Secure MCP Tunnel client in Kubernetes.
44
type: application
5-
version: 0.1.0
5+
version: 0.1.1
66
appVersion: "v0.0.9"
77
keywords:
88
- openai

charts/openai-mcp-tunnel/README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# openai-mcp-tunnel
22

3-
![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.0.9](https://img.shields.io/badge/AppVersion-v0.0.9-informational?style=flat-square)
3+
![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.0.9](https://img.shields.io/badge/AppVersion-v0.0.9-informational?style=flat-square)
44

55
A Helm chart for running the OpenAI Secure MCP Tunnel client in Kubernetes.
66

@@ -60,6 +60,29 @@ For Kubernetes production use, `mcp.serverUrl` is the safer default. OpenAI docu
6060

6161
The bootstrap stub is useful only as a first-step bring-up mechanism. It lets ChatGPT connect to the tunnel, but it does not expose your real MCP tools. Switch to `mcp.serverUrl` or `mcp.command` once the backend exists.
6262

63+
## Internal MCP Authentication
64+
65+
For internal MCP servers that require static technical authentication, prefer the dedicated Secret-backed header settings instead of raw `extraEnv`:
66+
67+
- `mcp.auth.extraHeaders` for normal MCP HTTP requests
68+
- `mcp.auth.discoveryExtraHeaders` for startup probes, OAuth discovery, and MCP initialize probing
69+
70+
The expected secret values are complete header values. For a bearer token, store the full string such as `Bearer eyJ...` in a secret key, then map the header name to that key.
71+
72+
Example:
73+
74+
```yaml
75+
mcp:
76+
serverUrl: https://internal-mcp.example.local/mcp
77+
auth:
78+
extraHeaders:
79+
existingSecretName: internal-mcp-auth
80+
headers:
81+
Authorization: authorization
82+
```
83+
84+
If the MCP also requires auth during discovery or initialize probing, mirror the same mapping under `mcp.auth.discoveryExtraHeaders`.
85+
6386
## Production Notes
6487

6588
For production rollouts, prefer one of these image supply-chain patterns:
@@ -119,10 +142,16 @@ For production rollouts, prefer one of these image supply-chain patterns:
119142
| controlPlane.tunnelId | string | `""` | Tunnel ID created in the OpenAI tunnels management UI or API. |
120143
| controlPlane.baseUrl | string | `""` | Optional override for the control plane base URL. Leave empty to let `tunnel-client` use its built-in default (`https://api.openai.com`). |
121144
| controlPlane.urlPath | string | `""` | Optional URL path prefix inserted before `/v1/...` routes, for example `/workspace/dev/us` behind an enterprise gateway. |
122-
| mcp | object | `{"bootstrap":{"enabled":false},"command":"","connectionMaxTTL":"10m","maxConcurrentRequests":10,"serverUrl":""}` | Upstream MCP configuration. Set either `serverUrl` or `command`. |
145+
| mcp | object | `{"auth":{"discoveryExtraHeaders":{"createSecret":false,"existingSecretName":"","headers":{},"values":{}},"extraHeaders":{"createSecret":false,"existingSecretName":"","headers":{},"values":{}}},"bootstrap":{"enabled":false},"command":"","connectionMaxTTL":"10m","maxConcurrentRequests":10,"serverUrl":""}` | Upstream MCP configuration. Set either `serverUrl` or `command`. |
123146
| mcp.bootstrap.enabled | bool | `false` | Start `tunnel-client` with the built-in stub MCP so the tunnel can come up before a real MCP backend is available. When `mcp.serverUrl` and `mcp.command` are both empty, the chart falls back to this stub automatically. |
124147
| mcp.serverUrl | string | `""` | URL of the private MCP server reachable from the pod. |
125148
| mcp.command | string | `""` | Command to launch a local stdio MCP server. `stdio` does not support MCP sessions. |
149+
| mcp.auth.extraHeaders.existingSecretName | string | `""` | Existing Secret whose keys contain complete header values such as `Bearer ...`. |
150+
| mcp.auth.extraHeaders.createSecret | bool | `false` | Create the Secret from values in this chart. |
151+
| mcp.auth.extraHeaders.values | object | `{}` | Secret data written when `createSecret=true`. Each key becomes one mounted file. |
152+
| mcp.auth.discoveryExtraHeaders.existingSecretName | string | `""` | Existing Secret whose keys contain complete discovery/probe header values. |
153+
| mcp.auth.discoveryExtraHeaders.createSecret | bool | `false` | Create the Secret from values in this chart. |
154+
| mcp.auth.discoveryExtraHeaders.values | object | `{}` | Secret data written when `createSecret=true`. Each key becomes one mounted file. |
126155
| mcp.connectionMaxTTL | string | `"10m"` | Maximum lifetime of an upstream MCP connection. |
127156
| mcp.maxConcurrentRequests | int | `10` | Maximum number of concurrent upstream MCP requests. |
128157
| log | object | `{"format":"json","level":"info"}` | Runtime logging configuration. |

charts/openai-mcp-tunnel/README.md.gotmpl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ For Kubernetes production use, `mcp.serverUrl` is the safer default. OpenAI docu
5555

5656
The bootstrap stub is useful only as a first-step bring-up mechanism. It lets ChatGPT connect to the tunnel, but it does not expose your real MCP tools. Switch to `mcp.serverUrl` or `mcp.command` once the backend exists.
5757

58+
## Internal MCP Authentication
59+
60+
For internal MCP servers that require static technical authentication, prefer the dedicated Secret-backed header settings instead of raw `extraEnv`:
61+
62+
- `mcp.auth.extraHeaders` for normal MCP HTTP requests
63+
- `mcp.auth.discoveryExtraHeaders` for startup probes, OAuth discovery, and MCP initialize probing
64+
65+
The expected secret values are complete header values. For a bearer token, store the full string such as `Bearer eyJ...` in a secret key, then map the header name to that key.
66+
67+
Example:
68+
69+
```yaml
70+
mcp:
71+
serverUrl: https://internal-mcp.example.local/mcp
72+
auth:
73+
extraHeaders:
74+
existingSecretName: internal-mcp-auth
75+
headers:
76+
Authorization: authorization
77+
```
78+
79+
If the MCP also requires auth during discovery or initialize probing, mirror the same mapping under `mcp.auth.discoveryExtraHeaders`.
80+
5881
## Production Notes
5982

6083
For production rollouts, prefer one of these image supply-chain patterns:

charts/openai-mcp-tunnel/templates/_helpers.tpl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ Validate supported runtime configuration.
9494
{{- if and .Values.mcp.serverUrl .Values.mcp.command -}}
9595
{{- fail "set only one of mcp.serverUrl or mcp.command in this chart" -}}
9696
{{- end -}}
97+
{{- if and .Values.mcp.auth.extraHeaders.createSecret (not .Values.mcp.auth.extraHeaders.values) -}}
98+
{{- fail "mcp.auth.extraHeaders.values is required when mcp.auth.extraHeaders.createSecret=true" -}}
99+
{{- end -}}
100+
{{- if and .Values.mcp.auth.discoveryExtraHeaders.createSecret (not .Values.mcp.auth.discoveryExtraHeaders.values) -}}
101+
{{- fail "mcp.auth.discoveryExtraHeaders.values is required when mcp.auth.discoveryExtraHeaders.createSecret=true" -}}
102+
{{- end -}}
103+
{{- if and .Values.mcp.auth.extraHeaders.createSecret .Values.mcp.auth.extraHeaders.existingSecretName -}}
104+
{{- fail "set only one of mcp.auth.extraHeaders.createSecret or mcp.auth.extraHeaders.existingSecretName" -}}
105+
{{- end -}}
106+
{{- if and .Values.mcp.auth.discoveryExtraHeaders.createSecret .Values.mcp.auth.discoveryExtraHeaders.existingSecretName -}}
107+
{{- fail "set only one of mcp.auth.discoveryExtraHeaders.createSecret or mcp.auth.discoveryExtraHeaders.existingSecretName" -}}
108+
{{- end -}}
97109
{{- if and .Values.credentials.createSecret (not .Values.credentials.apiKey) -}}
98110
{{- fail "credentials.apiKey is required when credentials.createSecret=true" -}}
99111
{{- end -}}
@@ -110,3 +122,37 @@ Decide whether to run with the embedded MCP stub.
110122
true
111123
{{- end -}}
112124
{{- end }}
125+
126+
{{- define "openai-mcp-tunnel.mcpExtraHeadersSecretName" -}}
127+
{{- if .Values.mcp.auth.extraHeaders.existingSecretName }}
128+
{{- .Values.mcp.auth.extraHeaders.existingSecretName }}
129+
{{- else }}
130+
{{- printf "%s-mcp-extra-headers" (include "openai-mcp-tunnel.fullname" .) }}
131+
{{- end }}
132+
{{- end }}
133+
134+
{{- define "openai-mcp-tunnel.mcpDiscoveryExtraHeadersSecretName" -}}
135+
{{- if .Values.mcp.auth.discoveryExtraHeaders.existingSecretName }}
136+
{{- .Values.mcp.auth.discoveryExtraHeaders.existingSecretName }}
137+
{{- else }}
138+
{{- printf "%s-mcp-discovery-extra-headers" (include "openai-mcp-tunnel.fullname" .) }}
139+
{{- end }}
140+
{{- end }}
141+
142+
{{- define "openai-mcp-tunnel.mcpExtraHeadersEnvValue" -}}
143+
{{- $path := printf "/etc/openai-mcp-tunnel/mcp-extra-headers" -}}
144+
{{- $entries := list -}}
145+
{{- range $header, $secretKey := .Values.mcp.auth.extraHeaders.headers }}
146+
{{- $entries = append $entries (printf "%s: file:%s/%s" $header $path $secretKey) -}}
147+
{{- end -}}
148+
{{- join ", " $entries -}}
149+
{{- end }}
150+
151+
{{- define "openai-mcp-tunnel.mcpDiscoveryExtraHeadersEnvValue" -}}
152+
{{- $path := printf "/etc/openai-mcp-tunnel/mcp-discovery-extra-headers" -}}
153+
{{- $entries := list -}}
154+
{{- range $header, $secretKey := .Values.mcp.auth.discoveryExtraHeaders.headers }}
155+
{{- $entries = append $entries (printf "%s: file:%s/%s" $header $path $secretKey) -}}
156+
{{- end -}}
157+
{{- join ", " $entries -}}
158+
{{- end }}

charts/openai-mcp-tunnel/templates/deployment.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ spec:
8585
- name: MCP_COMMAND
8686
value: {{ .Values.mcp.command | quote }}
8787
{{- end }}
88+
{{- if .Values.mcp.auth.extraHeaders.headers }}
89+
- name: MCP_EXTRA_HEADERS
90+
value: {{ include "openai-mcp-tunnel.mcpExtraHeadersEnvValue" . | quote }}
91+
{{- end }}
92+
{{- if .Values.mcp.auth.discoveryExtraHeaders.headers }}
93+
- name: MCP_DISCOVERY_EXTRA_HEADERS
94+
value: {{ include "openai-mcp-tunnel.mcpDiscoveryExtraHeadersEnvValue" . | quote }}
95+
{{- end }}
8896
{{- with .Values.extraEnv }}
8997
{{- toYaml . | nindent 12 }}
9098
{{- end }}
@@ -95,6 +103,16 @@ spec:
95103
volumeMounts:
96104
- name: tmp
97105
mountPath: /tmp
106+
{{- if .Values.mcp.auth.extraHeaders.headers }}
107+
- name: mcp-extra-headers
108+
mountPath: /etc/openai-mcp-tunnel/mcp-extra-headers
109+
readOnly: true
110+
{{- end }}
111+
{{- if .Values.mcp.auth.discoveryExtraHeaders.headers }}
112+
- name: mcp-discovery-extra-headers
113+
mountPath: /etc/openai-mcp-tunnel/mcp-discovery-extra-headers
114+
readOnly: true
115+
{{- end }}
98116
{{- with .Values.extraVolumeMounts }}
99117
{{- toYaml . | nindent 12 }}
100118
{{- end }}
@@ -108,6 +126,16 @@ spec:
108126
volumes:
109127
- name: tmp
110128
emptyDir: {}
129+
{{- if .Values.mcp.auth.extraHeaders.headers }}
130+
- name: mcp-extra-headers
131+
secret:
132+
secretName: {{ include "openai-mcp-tunnel.mcpExtraHeadersSecretName" . }}
133+
{{- end }}
134+
{{- if .Values.mcp.auth.discoveryExtraHeaders.headers }}
135+
- name: mcp-discovery-extra-headers
136+
secret:
137+
secretName: {{ include "openai-mcp-tunnel.mcpDiscoveryExtraHeadersSecretName" . }}
138+
{{- end }}
111139
{{- with .Values.extraVolumes }}
112140
{{- toYaml . | nindent 8 }}
113141
{{- end }}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{{- if .Values.mcp.auth.extraHeaders.createSecret }}
2+
apiVersion: v1
3+
kind: Secret
4+
metadata:
5+
name: {{ include "openai-mcp-tunnel.mcpExtraHeadersSecretName" . }}
6+
namespace: {{ include "openai-mcp-tunnel.namespace" . }}
7+
labels:
8+
{{- include "openai-mcp-tunnel.labels" . | nindent 4 }}
9+
type: Opaque
10+
stringData:
11+
{{- toYaml .Values.mcp.auth.extraHeaders.values | nindent 2 }}
12+
{{- end }}
13+
---
14+
{{- if .Values.mcp.auth.discoveryExtraHeaders.createSecret }}
15+
apiVersion: v1
16+
kind: Secret
17+
metadata:
18+
name: {{ include "openai-mcp-tunnel.mcpDiscoveryExtraHeadersSecretName" . }}
19+
namespace: {{ include "openai-mcp-tunnel.namespace" . }}
20+
labels:
21+
{{- include "openai-mcp-tunnel.labels" . | nindent 4 }}
22+
type: Opaque
23+
stringData:
24+
{{- toYaml .Values.mcp.auth.discoveryExtraHeaders.values | nindent 2 }}
25+
{{- end }}

charts/openai-mcp-tunnel/tests/openai_mcp_tunnel_test.yaml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ release:
44
templates:
55
- templates/serviceaccount.yaml
66
- templates/secret.yaml
7+
- templates/secret.mcp-extra-headers.yaml
78
- templates/deployment.yaml
89
- templates/service.yaml
910
- templates/pdb.yaml
@@ -119,6 +120,61 @@ tests:
119120
value: sk-example-runtime-key
120121
template: templates/secret.yaml
121122

123+
- it: should mount secret-backed MCP auth headers and expose file-based env references
124+
set:
125+
credentials.existingSecretName: openai-tunnel-secret
126+
controlPlane.tunnelId: tunnel_0123456789abcdef0123456789abcdef
127+
mcp.serverUrl: https://internal-mcp.example.local/mcp
128+
mcp.auth.extraHeaders.existingSecretName: internal-mcp-auth
129+
mcp.auth.extraHeaders.headers.Authorization: authorization
130+
mcp.auth.discoveryExtraHeaders.existingSecretName: internal-mcp-auth
131+
mcp.auth.discoveryExtraHeaders.headers.Authorization: authorization
132+
asserts:
133+
- contains:
134+
path: spec.template.spec.containers[0].env
135+
content:
136+
name: MCP_EXTRA_HEADERS
137+
value: "Authorization: file:/etc/openai-mcp-tunnel/mcp-extra-headers/authorization"
138+
template: templates/deployment.yaml
139+
- contains:
140+
path: spec.template.spec.containers[0].env
141+
content:
142+
name: MCP_DISCOVERY_EXTRA_HEADERS
143+
value: "Authorization: file:/etc/openai-mcp-tunnel/mcp-discovery-extra-headers/authorization"
144+
template: templates/deployment.yaml
145+
- contains:
146+
path: spec.template.spec.containers[0].volumeMounts
147+
content:
148+
name: mcp-extra-headers
149+
mountPath: /etc/openai-mcp-tunnel/mcp-extra-headers
150+
readOnly: true
151+
template: templates/deployment.yaml
152+
- contains:
153+
path: spec.template.spec.volumes
154+
content:
155+
name: mcp-extra-headers
156+
secret:
157+
secretName: internal-mcp-auth
158+
template: templates/deployment.yaml
159+
160+
- it: should create dedicated mcp auth header secrets when requested
161+
set:
162+
credentials.existingSecretName: openai-tunnel-secret
163+
controlPlane.tunnelId: tunnel_0123456789abcdef0123456789abcdef
164+
mcp.serverUrl: https://internal-mcp.example.local/mcp
165+
mcp.auth.extraHeaders.createSecret: true
166+
mcp.auth.extraHeaders.values.authorization: Bearer example-token
167+
mcp.auth.extraHeaders.headers.Authorization: authorization
168+
asserts:
169+
- hasDocuments:
170+
count: 1
171+
template: templates/secret.mcp-extra-headers.yaml
172+
- equal:
173+
path: stringData.authorization
174+
value: Bearer example-token
175+
template: templates/secret.mcp-extra-headers.yaml
176+
documentIndex: 0
177+
122178
- it: should fail when serviceaccount rendering is attempted without tunnel credentials
123179
asserts:
124180
- failedTemplate:
@@ -145,6 +201,17 @@ tests:
145201
errorMessage: mcp.bootstrap.enabled cannot be combined with mcp.serverUrl or mcp.command
146202
template: templates/serviceaccount.yaml
147203

204+
- it: should fail when extra header secret creation is enabled without values
205+
set:
206+
credentials.existingSecretName: openai-tunnel-secret
207+
controlPlane.tunnelId: tunnel_0123456789abcdef0123456789abcdef
208+
mcp.serverUrl: https://internal-mcp.example.local/mcp
209+
mcp.auth.extraHeaders.createSecret: true
210+
asserts:
211+
- failedTemplate:
212+
errorMessage: mcp.auth.extraHeaders.values is required when mcp.auth.extraHeaders.createSecret=true
213+
template: templates/serviceaccount.yaml
214+
148215
- it: should not render a service when service is disabled
149216
set:
150217
credentials.existingSecretName: openai-tunnel-secret

charts/openai-mcp-tunnel/values.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,27 @@ mcp:
124124
serverUrl: ""
125125
# -- Command to launch a local stdio MCP server. `stdio` does not support MCP sessions.
126126
command: ""
127+
auth:
128+
extraHeaders:
129+
# -- Existing Secret whose keys contain complete header values such as `Bearer ...`.
130+
existingSecretName: ""
131+
# -- Create the Secret from values in this chart.
132+
createSecret: false
133+
# -- Secret data written when `createSecret=true`. Each key becomes one mounted file.
134+
values: {}
135+
headers:
136+
# -- Map HTTP header names to secret keys. Example: `Authorization: authorization`.
137+
{}
138+
discoveryExtraHeaders:
139+
# -- Existing Secret whose keys contain complete discovery/probe header values.
140+
existingSecretName: ""
141+
# -- Create the Secret from values in this chart.
142+
createSecret: false
143+
# -- Secret data written when `createSecret=true`. Each key becomes one mounted file.
144+
values: {}
145+
headers:
146+
# -- Map HTTP header names to secret keys for MCP discovery/probe requests.
147+
{}
127148
# -- Maximum lifetime of an upstream MCP connection.
128149
connectionMaxTTL: "10m"
129150
# -- Maximum number of concurrent upstream MCP requests.

0 commit comments

Comments
 (0)