Skip to content

Commit 6e0e96c

Browse files
yroblataskbot
andauthored
fix: fail when discover of auth creds fail (#2758)
instead of falling back to unauthenticated, make the process fail when there is a problem in discovering auth config Co-authored-by: taskbot <taskbot@users.noreply.github.com>
1 parent 49812fa commit 6e0e96c

File tree

3 files changed

+57
-10
lines changed

3 files changed

+57
-10
lines changed

cmd/thv-operator/controllers/virtualmcpserver_discover_backends_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ func TestVirtualMCPServerDiscoverBackends(t *testing.T) {
4444
vmcp *mcpv1alpha1.VirtualMCPServer
4545
mcpGroup *mcpv1alpha1.MCPGroup
4646
mcpServers []mcpv1alpha1.MCPServer
47+
authConfigs []mcpv1alpha1.MCPExternalAuthConfig // Auth configs referenced by MCPServers
48+
secrets []corev1.Secret // Secrets referenced by auth configs
4749
expectedBackends int
4850
expectedStatuses map[string]string // backend name -> status
4951
expectedAuthConfigs map[string]string // backend name -> authConfigRef
@@ -153,6 +155,37 @@ func TestVirtualMCPServerDiscoverBackends(t *testing.T) {
153155
},
154156
},
155157
},
158+
authConfigs: []mcpv1alpha1.MCPExternalAuthConfig{
159+
{
160+
ObjectMeta: metav1.ObjectMeta{
161+
Name: "my-auth-config",
162+
Namespace: "default",
163+
},
164+
Spec: mcpv1alpha1.MCPExternalAuthConfigSpec{
165+
Type: mcpv1alpha1.ExternalAuthTypeTokenExchange,
166+
TokenExchange: &mcpv1alpha1.TokenExchangeConfig{
167+
TokenURL: "https://auth.example.com/token",
168+
ClientID: "test-client",
169+
ClientSecretRef: &mcpv1alpha1.SecretKeyRef{
170+
Name: "my-auth-secret",
171+
Key: "client-secret",
172+
},
173+
Audience: "test-audience",
174+
},
175+
},
176+
},
177+
},
178+
secrets: []corev1.Secret{
179+
{
180+
ObjectMeta: metav1.ObjectMeta{
181+
Name: "my-auth-secret",
182+
Namespace: "default",
183+
},
184+
Data: map[string][]byte{
185+
"client-secret": []byte("test-secret-value"),
186+
},
187+
},
188+
},
156189
expectedBackends: 1,
157190
expectedStatuses: map[string]string{
158191
"backend-1": "ready",
@@ -319,12 +352,19 @@ func TestVirtualMCPServerDiscoverBackends(t *testing.T) {
319352

320353
scheme := runtime.NewScheme()
321354
_ = mcpv1alpha1.AddToScheme(scheme)
355+
_ = corev1.AddToScheme(scheme)
322356

323357
// Build objects list for fake client
324358
objects := []client.Object{tt.vmcp, tt.mcpGroup}
325359
for i := range tt.mcpServers {
326360
objects = append(objects, &tt.mcpServers[i])
327361
}
362+
for i := range tt.authConfigs {
363+
objects = append(objects, &tt.authConfigs[i])
364+
}
365+
for i := range tt.secrets {
366+
objects = append(objects, &tt.secrets[i])
367+
}
328368

329369
fakeClient := fake.NewClientBuilder().
330370
WithScheme(scheme).

pkg/vmcp/workloads/k8s.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ func (d *k8sDiscoverer) GetWorkloadAsVMCPBackend(ctx context.Context, workloadNa
9898
// Convert MCPServer to Backend
9999
backend := d.mcpServerToBackend(ctx, mcpServer)
100100

101+
// If auth discovery failed, mcpServerToBackend returns nil
102+
if backend == nil {
103+
logger.Warnf("Skipping workload %s due to auth discovery failure", workloadName)
104+
return nil, nil
105+
}
106+
101107
// Skip workloads without a URL (not accessible)
102108
if backend.BaseURL == "" {
103109
logger.Debugf("Skipping workload %s without URL", workloadName)
@@ -182,8 +188,11 @@ func (d *k8sDiscoverer) mcpServerToBackend(ctx context.Context, mcpServer *mcpv1
182188

183189
// Discover and populate authentication configuration from MCPServer
184190
if err := d.discoverAuthConfig(ctx, mcpServer, backend); err != nil {
185-
// Log warning but don't fail - backend can still be used without auth
186-
logger.Warnf("Failed to discover auth config for MCPServer %s: %v", mcpServer.Name, err)
191+
// If auth discovery fails, we must fail - don't silently allow unauthorized access
192+
// This is a security-critical operation: if auth is configured but fails to load,
193+
// we should not proceed without it
194+
logger.Errorf("Failed to discover auth config for MCPServer %s: %v", mcpServer.Name, err)
195+
return nil
187196
}
188197

189198
return backend

pkg/vmcp/workloads/k8s_test.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,10 @@ func TestDiscoverAuth_AuthConfigNotFound(t *testing.T) {
255255
ctx := context.Background()
256256
backend, err := discoverer.GetWorkloadAsVMCPBackend(ctx, "test-server")
257257

258-
// Should still return the backend but without auth (logs warning)
258+
// Should return nil backend when auth config is referenced but not found
259+
// This is security-critical: fail closed rather than allowing unauthorized access
259260
require.NoError(t, err)
260-
require.NotNil(t, backend)
261-
assert.Empty(t, backend.AuthStrategy)
262-
assert.Nil(t, backend.AuthMetadata)
261+
require.Nil(t, backend, "Should return nil backend when auth config is missing")
263262
}
264263

265264
func TestDiscoverAuth_SecretNotFound(t *testing.T) {
@@ -312,11 +311,10 @@ func TestDiscoverAuth_SecretNotFound(t *testing.T) {
312311
ctx := context.Background()
313312
backend, err := discoverer.GetWorkloadAsVMCPBackend(ctx, "test-server")
314313

315-
// Should still return the backend but without auth (logs warning)
314+
// Should return nil backend when secret is missing
315+
// This is security-critical: fail closed rather than allowing unauthorized access
316316
require.NoError(t, err)
317-
require.NotNil(t, backend)
318-
assert.Empty(t, backend.AuthStrategy)
319-
assert.Nil(t, backend.AuthMetadata)
317+
require.Nil(t, backend, "Should return nil backend when secret is missing")
320318
}
321319

322320
func TestMCPServerToBackend_BasicFields(t *testing.T) {

0 commit comments

Comments
 (0)