Skip to content

Commit 1968652

Browse files
authored
test(config): extensive test suite for denied lists
1 parent f3915cd commit 1968652

File tree

12 files changed

+414
-44
lines changed

12 files changed

+414
-44
lines changed

pkg/mcp/common_test.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -124,23 +124,12 @@ func (c *mcpContext) beforeEach(t *testing.T) {
124124
if c.listOutput == nil {
125125
c.listOutput = output.Yaml
126126
}
127+
if c.staticConfig == nil {
128+
c.staticConfig = &config.StaticConfig{}
129+
}
127130
if c.before != nil {
128131
c.before(c)
129132
}
130-
if c.staticConfig == nil {
131-
c.staticConfig = &config.StaticConfig{
132-
DeniedResources: []config.GroupVersionKind{
133-
{
134-
Version: "v1",
135-
Kind: "Secret",
136-
},
137-
{
138-
Group: "rbac.authorization.k8s.io",
139-
Version: "v1",
140-
},
141-
},
142-
}
143-
}
144133
if c.mcpServer, err = NewServer(Configuration{
145134
Profile: c.profile,
146135
ListOutput: c.listOutput,
@@ -222,10 +211,6 @@ func (c *mcpContext) withKubeConfig(rc *rest.Config) *api.Config {
222211
return fakeConfig
223212
}
224213

225-
func (c *mcpContext) withStaticConfig(config *config.StaticConfig) {
226-
c.staticConfig = config
227-
}
228-
229214
// withEnvTest sets up the environment for kubeconfig to be used with envTest
230215
func (c *mcpContext) withEnvTest() {
231216
c.withKubeConfig(envTestRestConfig)

pkg/mcp/events_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mcp
22

33
import (
4+
"github.com/manusa/kubernetes-mcp-server/pkg/config"
45
"github.com/mark3labs/mcp-go/mcp"
56
v1 "k8s.io/api/core/v1"
67
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -93,3 +94,22 @@ func TestEventsList(t *testing.T) {
9394
})
9495
})
9596
}
97+
98+
func TestEventsListDenied(t *testing.T) {
99+
deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Version: "v1", Kind: "Event"}}}
100+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
101+
c.withEnvTest()
102+
eventList, _ := c.callTool("events_list", map[string]interface{}{})
103+
t.Run("events_list has error", func(t *testing.T) {
104+
if !eventList.IsError {
105+
t.Fatalf("call tool should fail")
106+
}
107+
})
108+
t.Run("events_list describes denial", func(t *testing.T) {
109+
expectedMessage := "failed to list events in all namespaces: resource not allowed: /v1, Kind=Event"
110+
if eventList.Content[0].(mcp.TextContent).Text != expectedMessage {
111+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, eventList.Content[0].(mcp.TextContent).Text)
112+
}
113+
})
114+
})
115+
}

pkg/mcp/helm_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package mcp
33
import (
44
"context"
55
"encoding/base64"
6+
"github.com/manusa/kubernetes-mcp-server/pkg/config"
67
"github.com/mark3labs/mcp-go/mcp"
78
corev1 "k8s.io/api/core/v1"
89
"k8s.io/apimachinery/pkg/api/errors"
@@ -57,6 +58,30 @@ func TestHelmInstall(t *testing.T) {
5758
})
5859
}
5960

61+
func TestHelmInstallDenied(t *testing.T) {
62+
t.Skip("To be implemented") // TODO: helm_install is not checking for denied resources
63+
deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Version: "v1", Kind: "Secret"}}}
64+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
65+
c.withEnvTest()
66+
_, file, _, _ := runtime.Caller(0)
67+
chartPath := filepath.Join(filepath.Dir(file), "testdata", "helm-chart-secret")
68+
helmInstall, _ := c.callTool("helm_install", map[string]interface{}{
69+
"chart": chartPath,
70+
})
71+
t.Run("helm_install has error", func(t *testing.T) {
72+
if !helmInstall.IsError {
73+
t.Fatalf("call tool should fail")
74+
}
75+
})
76+
t.Run("helm_install describes denial", func(t *testing.T) {
77+
expectedMessage := "failed to install helm chart: resource not allowed: /v1, Kind=Secret"
78+
if helmInstall.Content[0].(mcp.TextContent).Text != expectedMessage {
79+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, helmInstall.Content[0].(mcp.TextContent).Text)
80+
}
81+
})
82+
})
83+
}
84+
6085
func TestHelmList(t *testing.T) {
6186
testCase(t, func(c *mcpContext) {
6287
c.withEnvTest()
@@ -199,6 +224,10 @@ func TestHelmUninstall(t *testing.T) {
199224
})
200225
}
201226

227+
func TestHelmUninstallDenied(t *testing.T) {
228+
t.Skip("To be implemented") // TODO: helm_uninstall is not checking for denied resources
229+
}
230+
202231
func clearHelmReleases(ctx context.Context, kc *kubernetes.Clientset) {
203232
secrets, _ := kc.CoreV1().Secrets("default").List(ctx, metav1.ListOptions{})
204233
for _, secret := range secrets.Items {

pkg/mcp/mcp_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import (
1111

1212
"github.com/mark3labs/mcp-go/client"
1313
"github.com/mark3labs/mcp-go/mcp"
14-
15-
"github.com/manusa/kubernetes-mcp-server/pkg/config"
1614
)
1715

1816
func TestWatchKubeConfig(t *testing.T) {
@@ -99,7 +97,6 @@ func TestSseHeaders(t *testing.T) {
9997
defer mockServer.Close()
10098
before := func(c *mcpContext) {
10199
c.withKubeConfig(mockServer.config)
102-
c.withStaticConfig(&config.StaticConfig{})
103100
c.clientOptions = append(c.clientOptions, client.WithHeaders(map[string]string{"kubernetes-authorization": "Bearer a-token-from-mcp-client"}))
104101
}
105102
pathHeaders := make(map[string]http.Header, 0)

pkg/mcp/namespaces_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mcp
22

33
import (
4+
"github.com/manusa/kubernetes-mcp-server/pkg/config"
45
"github.com/manusa/kubernetes-mcp-server/pkg/output"
56
"github.com/mark3labs/mcp-go/mcp"
67
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -48,6 +49,25 @@ func TestNamespacesList(t *testing.T) {
4849
})
4950
}
5051

52+
func TestNamespacesListDenied(t *testing.T) {
53+
deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Version: "v1", Kind: "Namespace"}}}
54+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
55+
c.withEnvTest()
56+
namespacesList, _ := c.callTool("namespaces_list", map[string]interface{}{})
57+
t.Run("namespaces_list has error", func(t *testing.T) {
58+
if !namespacesList.IsError {
59+
t.Fatalf("call tool should fail")
60+
}
61+
})
62+
t.Run("namespaces_list describes denial", func(t *testing.T) {
63+
expectedMessage := "failed to list namespaces: resource not allowed: /v1, Kind=Namespace"
64+
if namespacesList.Content[0].(mcp.TextContent).Text != expectedMessage {
65+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, namespacesList.Content[0].(mcp.TextContent).Text)
66+
}
67+
})
68+
})
69+
}
70+
5171
func TestNamespacesListAsTable(t *testing.T) {
5272
testCaseWithContext(t, &mcpContext{listOutput: output.Table}, func(c *mcpContext) {
5373
c.withEnvTest()
@@ -133,3 +153,22 @@ func TestProjectsListInOpenShift(t *testing.T) {
133153
})
134154
})
135155
}
156+
157+
func TestProjectsListInOpenShiftDenied(t *testing.T) {
158+
deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Group: "project.openshift.io", Version: "v1"}}}
159+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer, before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) {
160+
c.withEnvTest()
161+
projectsList, _ := c.callTool("projects_list", map[string]interface{}{})
162+
t.Run("projects_list has error", func(t *testing.T) {
163+
if !projectsList.IsError {
164+
t.Fatalf("call tool should fail")
165+
}
166+
})
167+
t.Run("projects_list describes denial", func(t *testing.T) {
168+
expectedMessage := "failed to list projects: resource not allowed: project.openshift.io/v1, Kind=Project"
169+
if projectsList.Content[0].(mcp.TextContent).Text != expectedMessage {
170+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, projectsList.Content[0].(mcp.TextContent).Text)
171+
}
172+
})
173+
})
174+
}

pkg/mcp/pods.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ func (s *Server) podsRun(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.Cal
288288
}
289289
resources, err := s.k.Derived(ctx).PodsRun(ctx, ns.(string), name.(string), image.(string), int32(port.(float64)))
290290
if err != nil {
291-
return NewTextResult("", fmt.Errorf("failed to get pod %s log in namespace %s: %v", name, ns, err)), nil
291+
return NewTextResult("", fmt.Errorf("failed to run pod %s in namespace %s: %v", name, ns, err)), nil
292292
}
293293
marshalledYaml, err := output.MarshalYaml(resources)
294294
if err != nil {

pkg/mcp/pods_exec_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,7 @@ func TestPodsExec(t *testing.T) {
9999
})
100100
})
101101
}
102+
103+
func TestPodsExecDenied(t *testing.T) {
104+
t.Skip("To be implemented") // TODO: exec is not checking for denied resources
105+
}

pkg/mcp/pods_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mcp
22

33
import (
4+
"github.com/manusa/kubernetes-mcp-server/pkg/config"
45
"github.com/manusa/kubernetes-mcp-server/pkg/output"
56
"regexp"
67
"strings"
@@ -176,6 +177,37 @@ func TestPodsListInNamespace(t *testing.T) {
176177
})
177178
}
178179

180+
func TestPodsListDenied(t *testing.T) {
181+
deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Version: "v1", Kind: "Pod"}}}
182+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
183+
c.withEnvTest()
184+
podsList, _ := c.callTool("pods_list", map[string]interface{}{})
185+
t.Run("pods_list has error", func(t *testing.T) {
186+
if !podsList.IsError {
187+
t.Fatalf("call tool should fail")
188+
}
189+
})
190+
t.Run("pods_list describes denial", func(t *testing.T) {
191+
expectedMessage := "failed to list pods in all namespaces: resource not allowed: /v1, Kind=Pod"
192+
if podsList.Content[0].(mcp.TextContent).Text != expectedMessage {
193+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, podsList.Content[0].(mcp.TextContent).Text)
194+
}
195+
})
196+
podsListInNamespace, _ := c.callTool("pods_list_in_namespace", map[string]interface{}{"namespace": "ns-1"})
197+
t.Run("pods_list_in_namespace has error", func(t *testing.T) {
198+
if !podsListInNamespace.IsError {
199+
t.Fatalf("call tool should fail")
200+
}
201+
})
202+
t.Run("pods_list_in_namespace describes denial", func(t *testing.T) {
203+
expectedMessage := "failed to list pods in namespace ns-1: resource not allowed: /v1, Kind=Pod"
204+
if podsListInNamespace.Content[0].(mcp.TextContent).Text != expectedMessage {
205+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, podsListInNamespace.Content[0].(mcp.TextContent).Text)
206+
}
207+
})
208+
})
209+
}
210+
179211
func TestPodsListAsTable(t *testing.T) {
180212
testCaseWithContext(t, &mcpContext{listOutput: output.Table}, func(c *mcpContext) {
181213
c.withEnvTest()
@@ -380,6 +412,25 @@ func TestPodsGet(t *testing.T) {
380412
})
381413
}
382414

415+
func TestPodsGetDenied(t *testing.T) {
416+
deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Version: "v1", Kind: "Pod"}}}
417+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
418+
c.withEnvTest()
419+
podsGet, _ := c.callTool("pods_get", map[string]interface{}{"name": "a-pod-in-default"})
420+
t.Run("pods_get has error", func(t *testing.T) {
421+
if !podsGet.IsError {
422+
t.Fatalf("call tool should fail")
423+
}
424+
})
425+
t.Run("pods_get describes denial", func(t *testing.T) {
426+
expectedMessage := "failed to get pod a-pod-in-default in namespace : resource not allowed: /v1, Kind=Pod"
427+
if podsGet.Content[0].(mcp.TextContent).Text != expectedMessage {
428+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, podsGet.Content[0].(mcp.TextContent).Text)
429+
}
430+
})
431+
})
432+
}
433+
383434
func TestPodsDelete(t *testing.T) {
384435
testCase(t, func(c *mcpContext) {
385436
c.withEnvTest()
@@ -511,6 +562,26 @@ func TestPodsDelete(t *testing.T) {
511562
})
512563
}
513564

565+
func TestPodsDeleteDenied(t *testing.T) {
566+
t.Skip("To be implemented") // TODO: delete is not checking for denied resources
567+
deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Version: "v1", Kind: "Pod"}}}
568+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
569+
c.withEnvTest()
570+
podsDelete, _ := c.callTool("pods_delete", map[string]interface{}{"name": "a-pod-in-default"})
571+
t.Run("pods_delete has error", func(t *testing.T) {
572+
if !podsDelete.IsError {
573+
t.Fatalf("call tool should fail")
574+
}
575+
})
576+
t.Run("pods_delete describes denial", func(t *testing.T) {
577+
expectedMessage := "failed to delete pod a-pod-in-default in namespace : resource not allowed: /v1, Kind=Pod"
578+
if podsDelete.Content[0].(mcp.TextContent).Text != expectedMessage {
579+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, podsDelete.Content[0].(mcp.TextContent).Text)
580+
}
581+
})
582+
})
583+
}
584+
514585
func TestPodsDeleteInOpenShift(t *testing.T) {
515586
testCaseWithContext(t, &mcpContext{before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) {
516587
managedLabels := map[string]string{
@@ -651,6 +722,26 @@ func TestPodsLog(t *testing.T) {
651722
})
652723
}
653724

725+
func TestPodsLogDenied(t *testing.T) {
726+
t.Skip("To be implemented") // TODO: log is not checking for denied resources
727+
deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Version: "v1", Kind: "Pod"}}}
728+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
729+
c.withEnvTest()
730+
podsLog, _ := c.callTool("pods_log", map[string]interface{}{"name": "a-pod-in-default"})
731+
t.Run("pods_log has error", func(t *testing.T) {
732+
if !podsLog.IsError {
733+
t.Fatalf("call tool should fail")
734+
}
735+
})
736+
t.Run("pods_log describes denial", func(t *testing.T) {
737+
expectedMessage := "failed to log pod a-pod-in-default in namespace : resource not allowed: /v1, Kind=Pod"
738+
if podsLog.Content[0].(mcp.TextContent).Text != expectedMessage {
739+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, podsLog.Content[0].(mcp.TextContent).Text)
740+
}
741+
})
742+
})
743+
}
744+
654745
func TestPodsRun(t *testing.T) {
655746
testCase(t, func(c *mcpContext) {
656747
c.withEnvTest()
@@ -801,6 +892,25 @@ func TestPodsRun(t *testing.T) {
801892
})
802893
}
803894

895+
func TestPodsRunDenied(t *testing.T) {
896+
deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Version: "v1", Kind: "Pod"}}}
897+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
898+
c.withEnvTest()
899+
podsRun, _ := c.callTool("pods_run", map[string]interface{}{"image": "nginx"})
900+
t.Run("pods_run has error", func(t *testing.T) {
901+
if !podsRun.IsError {
902+
t.Fatalf("call tool should fail")
903+
}
904+
})
905+
t.Run("pods_run describes denial", func(t *testing.T) {
906+
expectedMessage := "failed to run pod in namespace : resource not allowed: /v1, Kind=Pod"
907+
if podsRun.Content[0].(mcp.TextContent).Text != expectedMessage {
908+
t.Fatalf("expected desciptive error '%s', got %v", expectedMessage, podsRun.Content[0].(mcp.TextContent).Text)
909+
}
910+
})
911+
})
912+
}
913+
804914
func TestPodsRunInOpenShift(t *testing.T) {
805915
testCaseWithContext(t, &mcpContext{before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) {
806916
t.Run("pods_run with image, namespace, and port returns route with port", func(t *testing.T) {

pkg/mcp/pods_top_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,7 @@ func TestPodsTopMetricsAvailable(t *testing.T) {
204204
})
205205
})
206206
}
207+
208+
func TestPodsTopDenied(t *testing.T) {
209+
t.Skip("To be implemented") // TODO: top is not checking for denied resources
210+
}

0 commit comments

Comments
 (0)