Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion pkg/toolsets/netedge/coredns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
)

func (s *NetEdgeTestSuite) TestGetCoreDNSConfig() {

tests := []struct {
name string
configMap *corev1.ConfigMap
Expand Down
104 changes: 104 additions & 0 deletions pkg/toolsets/netedge/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package netedge

import (
"fmt"

"github.com/containers/kubernetes-mcp-server/pkg/api"
"github.com/google/jsonschema-go/jsonschema"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/ptr"
"sigs.k8s.io/yaml"
)

func initRoutes() []api.ServerTool {
return []api.ServerTool{
{
Tool: api.Tool{
Name: "inspect_route",
Description: "Inspect an OpenShift Route to view its full configuration and status.",
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"namespace": {
Type: "string",
Description: "Route namespace",
},
"route": {
Type: "string",
Description: "Route name",
},
},
Required: []string{"namespace", "route"},
},
Annotations: api.ToolAnnotations{
Title: "Inspect Route",
ReadOnlyHint: ptr.To(true),
DestructiveHint: ptr.To(false),
OpenWorldHint: ptr.To(true),
},
},
Handler: inspectRoute,
},
}
}

func inspectRoute(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
namespace, err := api.RequiredString(params, "namespace")
if err != nil {
return api.NewToolCallResult("", err), nil
}
routeName, err := api.RequiredString(params, "route")
if err != nil {
return api.NewToolCallResult("", err), nil
}

gvr := schema.GroupVersionResource{
Group: "route.openshift.io",
Version: "v1",
Resource: "routes",
}

route, err := params.DynamicClient().Resource(gvr).Namespace(namespace).Get(params.Context, routeName, metav1.GetOptions{})
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to get route %s/%s: %w", namespace, routeName, err)), nil
}

keyFields := map[string]interface{}{
"Name": route.GetName(),
"Namespace": route.GetNamespace(),
}

if host, found, err := unstructured.NestedString(route.Object, "spec", "host"); found && err == nil {
keyFields["Host"] = host
}

if tls, found, err := unstructured.NestedMap(route.Object, "spec", "tls"); found && err == nil {
keyFields["TLS"] = tls
}
Comment on lines +77 to +79
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Redact sensitive TLS fields before returning KeyFields/RawRoute.

At Line 77 and Line 95, the response can expose spec.tls.key (private key) to tool output/logs. Please sanitize before marshaling.

🔒 Suggested fix
 func inspectRoute(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
@@
-	keyFields := map[string]interface{}{
+	sanitizedRoute := route.DeepCopy()
+	if _, found, _ := unstructured.NestedString(sanitizedRoute.Object, "spec", "tls", "key"); found {
+		if err := unstructured.SetNestedField(sanitizedRoute.Object, "<redacted>", "spec", "tls", "key"); err != nil {
+			return api.NewToolCallResult("", fmt.Errorf("failed to sanitize route tls key: %w", err)), nil
+		}
+	}
+
+	keyFields := map[string]interface{}{
 		"Name":      route.GetName(),
 		"Namespace": route.GetNamespace(),
 	}
@@
-	if tls, found, err := unstructured.NestedMap(route.Object, "spec", "tls"); found && err == nil {
+	if tls, found, err := unstructured.NestedMap(sanitizedRoute.Object, "spec", "tls"); found && err == nil {
 		keyFields["TLS"] = tls
 	}
@@
 	resultObj := map[string]interface{}{
 		"KeyFields": keyFields,
-		"RawRoute":  route.Object,
+		"RawRoute":  sanitizedRoute.Object,
 	}

Also applies to: 93-96

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/toolsets/netedge/routes.go` around lines 77 - 79, The code currently
copies the TLS map from unstructured.NestedMap(route.Object, "spec", "tls")
directly into keyFields["TLS"] and into the RawRoute output, which can expose
sensitive values like spec.tls.key; before assigning the TLS map to keyFields or
RawRoute, detect and remove or redact sensitive keys (e.g., "key", "privateKey",
"tls.key", "tls.privateKey") from the tls map returned by unstructured.NestedMap
and then assign the sanitized map to keyFields["TLS"] and the RawRoute field(s)
that currently receive the raw tls map so no private key material is included in
responses or logs.


if to, found, err := unstructured.NestedMap(route.Object, "spec", "to"); found && err == nil {
keyFields["To"] = to
}

if port, found, err := unstructured.NestedMap(route.Object, "spec", "port"); found && err == nil {
keyFields["Port"] = port
}

if ingress, found, err := unstructured.NestedSlice(route.Object, "status", "ingress"); found && err == nil {
keyFields["IngressStatus"] = ingress
}

resultObj := map[string]interface{}{
"KeyFields": keyFields,
"RawRoute": route.Object,
}

data, err := yaml.Marshal(resultObj)
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to marshal route as yaml: %w", err)), nil
}

return api.NewToolCallResult(string(data), nil), nil
}
104 changes: 104 additions & 0 deletions pkg/toolsets/netedge/routes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package netedge

import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic/fake"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/yaml"
)

func (s *NetEdgeTestSuite) TestInspectRoute() {
tests := []struct {
name string
namespace string
route string
existingObjs []runtime.Object
expectedError string
validate func(result string)
}{
{
name: "successful retrieval",
namespace: "default",
route: "my-route",
existingObjs: []runtime.Object{
&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "route.openshift.io/v1",
"kind": "Route",
"metadata": map[string]interface{}{
"name": "my-route",
"namespace": "default",
},
"spec": map[string]interface{}{
"host": "example.com",
},
},
},
},
validate: func(result string) {
var r map[string]interface{}
err := yaml.Unmarshal([]byte(result), &r)
s.Require().NoError(err)
rawRoute := r["RawRoute"].(map[string]interface{})
keyFields := r["KeyFields"].(map[string]interface{})
s.Assert().Equal("my-route", rawRoute["metadata"].(map[string]interface{})["name"])
s.Assert().Equal("example.com", rawRoute["spec"].(map[string]interface{})["host"])
s.Assert().Equal("my-route", keyFields["Name"])
s.Assert().Equal("default", keyFields["Namespace"])
s.Assert().Equal("example.com", keyFields["Host"])
},
},
{
name: "route not found",
namespace: "default",
route: "missing",
existingObjs: []runtime.Object{},
expectedError: "failed to get route",
},
{
name: "missing arguments",
namespace: "",
route: "",
expectedError: "parameter required",
},
}

for _, tt := range tests {
s.Run(tt.name, func() {
// Create fake dynamic client
scheme := runtime.NewScheme()
err := clientgoscheme.AddToScheme(scheme)
s.Require().NoError(err)
dynClient := fake.NewSimpleDynamicClient(scheme, tt.existingObjs...)

// Create mock params
args := make(map[string]any)
if tt.namespace != "" {
args["namespace"] = tt.namespace
}
if tt.route != "" {
args["route"] = tt.route
}

s.SetArgs(args)
s.SetDynamicClient(dynClient)

result, err := inspectRoute(s.params)

if tt.expectedError != "" {
s.Assert().NoError(err)
s.Require().NotNil(result)
s.Require().Error(result.Error)
s.Assert().Contains(result.Error.Error(), tt.expectedError)
} else {
s.Assert().NoError(err)
s.Require().NotNil(result)
s.Assert().NoError(result.Error)
if tt.validate != nil {
tt.validate(result.Content)
}
}
})
}
}
1 change: 1 addition & 0 deletions pkg/toolsets/netedge/toolset.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func (t *Toolset) GetTools(_ api.Openshift) []api.ServerTool {
initCoreDNS(),
initEndpoints(),
initProbeDNSLocal(),
initRoutes(),
)
}

Expand Down