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
61 changes: 61 additions & 0 deletions pkg/kubernetes/accesscontrol_restclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package kubernetes

import (
"fmt"
"net/http"
"strings"

"k8s.io/apimachinery/pkg/runtime/schema"
)

type AccessControlRoundTripper struct {
delegate http.RoundTripper
accessControlRESTMapper *AccessControlRESTMapper
}

func (rt *AccessControlRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
gvr, err := parseURLToGVR(req.URL.Path)
if err != nil {
return nil, fmt.Errorf("failed to make request: AccessControlRoundTripper failed to parse url: %w", err)
}

_, err = rt.accessControlRESTMapper.KindFor(gvr)
if err != nil {
return nil, fmt.Errorf("not allowed to access resource: %v", gvr)
}

return rt.delegate.RoundTrip(req)
}

func parseURLToGVR(path string) (schema.GroupVersionResource, error) {
parts := strings.Split(strings.Trim(path, "/"), "/")

if len(parts) < 3 {
return schema.GroupVersionResource{}, fmt.Errorf("not an api path: %s", path)
}

gvr := schema.GroupVersionResource{}

switch parts[0] {
case "api":
gvr.Group = ""
gvr.Version = parts[1]
if parts[2] == "namespaces" && len(parts) > 4 {
gvr.Resource = parts[4]
} else {
gvr.Resource = parts[2]
}
case "apis":
gvr.Group = parts[1]
gvr.Version = parts[2]
if parts[3] == "namespaces" && len(parts) > 5 {
gvr.Resource = parts[5]
} else {
gvr.Resource = parts[3]
}
default:
return schema.GroupVersionResource{}, fmt.Errorf("unknown prefix: %s", parts[0])
}

return gvr, nil
}
31 changes: 28 additions & 3 deletions pkg/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package kubernetes

import (
"k8s.io/apimachinery/pkg/runtime"
"net/http"

"github.com/containers/kubernetes-mcp-server/pkg/helm"
"github.com/containers/kubernetes-mcp-server/pkg/kiali"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"

_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"

"github.com/containers/kubernetes-mcp-server/pkg/helm"
"github.com/containers/kubernetes-mcp-server/pkg/kiali"
)

type HeaderKey string
Expand All @@ -25,6 +28,28 @@ type Kubernetes struct {
manager *Manager
}

// AccessControlRestClient returns the access-controlled rest.Interface
// This ensures that any denied resources configured in the system are properly enforced
func (k *Kubernetes) AccessControlRestClient() (rest.Interface, error) {
config, err := k.manager.ToRESTConfig()
if err != nil {
return nil, err
}
config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
return &AccessControlRoundTripper{
delegate: rt,
accessControlRESTMapper: k.manager.accessControlRESTMapper,
}
}

client, err := rest.RESTClientFor(config)
if err != nil {
return nil, err
}

return client, nil
}

// AccessControlClientset returns the access-controlled clientset
// This ensures that any denied resources configured in the system are properly enforced
func (k *Kubernetes) AccessControlClientset() *AccessControlClientset {
Expand Down