@@ -3,71 +3,103 @@ package kubernetes
33import (
44 "context"
55 "fmt"
6+ "net/http"
67
7- authenticationv1api "k8s.io/api/authentication/v1"
8- authorizationv1api "k8s.io/api/authorization/v1"
8+ "github.com/containers/kubernetes-mcp-server/pkg/config"
99 v1 "k8s.io/api/core/v1"
10+ "k8s.io/apimachinery/pkg/api/meta"
1011 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11- "k8s.io/apimachinery/pkg/runtime/schema"
1212 "k8s.io/apimachinery/pkg/util/httpstream"
1313 "k8s.io/client-go/discovery"
14+ "k8s.io/client-go/discovery/cached/memory"
15+ "k8s.io/client-go/dynamic"
1416 "k8s.io/client-go/kubernetes"
1517 authenticationv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
1618 authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
1719 corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
1820 "k8s.io/client-go/rest"
21+ "k8s.io/client-go/restmapper"
1922 "k8s.io/client-go/tools/remotecommand"
2023 "k8s.io/metrics/pkg/apis/metrics"
2124 metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
2225 metricsv1beta1 "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1"
23-
24- "github.com/containers/kubernetes-mcp-server/pkg/config"
2526)
2627
2728// AccessControlClientset is a limited clientset delegating interface to the standard kubernetes.Clientset
2829// Only a limited set of functions are implemented with a single point of access to the kubernetes API where
2930// apiVersion and kinds are checked for allowed access
3031type AccessControlClientset struct {
31- cfg * rest.Config
32- delegate kubernetes.Interface
33- discoveryClient discovery.DiscoveryInterface
32+ cfg * rest.Config
33+ kubernetes.Interface
34+ discoveryClient discovery.CachedDiscoveryInterface
35+ dynamicClient * dynamic.DynamicClient
36+ restMapper meta.ResettableRESTMapper
3437 metricsV1beta1 * metricsv1beta1.MetricsV1beta1Client
35- staticConfig * config.StaticConfig // TODO: maybe just store the denied resource slice
3638}
3739
38- func (a * AccessControlClientset ) DiscoveryClient () discovery.DiscoveryInterface {
40+ func NewAccessControlClientset (staticConfig * config.StaticConfig , restConfig * rest.Config ) (* AccessControlClientset , error ) {
41+ configShallowCopy := & (* restConfig )
42+ acc := & AccessControlClientset {
43+ cfg : configShallowCopy ,
44+ }
45+ discoveryClient , err := discovery .NewDiscoveryClientForConfig (acc .cfg )
46+ if err != nil {
47+ return nil , fmt .Errorf ("failed to create discovery client: %v" , err )
48+ }
49+ acc .discoveryClient = memory .NewMemCacheClient (discoveryClient )
50+ acc .restMapper = restmapper .NewDeferredDiscoveryRESTMapper (acc .discoveryClient )
51+ acc .cfg .Wrap (func (original http.RoundTripper ) http.RoundTripper {
52+ return & AccessControlRoundTripper {
53+ delegate : original ,
54+ staticConfig : staticConfig ,
55+ restMapper : acc .restMapper ,
56+ }
57+ })
58+ acc .Interface , err = kubernetes .NewForConfig (acc .cfg )
59+ if err != nil {
60+ return nil , err
61+ }
62+ acc .dynamicClient , err = dynamic .NewForConfig (acc .cfg )
63+ if err != nil {
64+ return nil , err
65+ }
66+ acc .metricsV1beta1 , err = metricsv1beta1 .NewForConfig (acc .cfg )
67+ if err != nil {
68+ return nil , err
69+ }
70+ return acc , nil
71+ }
72+
73+ func (a * AccessControlClientset ) DiscoveryClient () discovery.CachedDiscoveryInterface {
3974 return a .discoveryClient
4075}
4176
77+ func (a * AccessControlClientset ) DynamicClient () dynamic.Interface {
78+ return a .dynamicClient
79+ }
80+
81+ func (a * AccessControlClientset ) RESTMapper () meta.ResettableRESTMapper {
82+ return a .restMapper
83+ }
84+
85+ // Nodes returns NodeInterface
86+ // Deprecated: use CoreV1().Nodes() directly
4287func (a * AccessControlClientset ) Nodes () (corev1.NodeInterface , error ) {
43- gvk := & schema.GroupVersionKind {Group : "" , Version : "v1" , Kind : "Node" }
44- if ! isAllowed (a .staticConfig , gvk ) {
45- return nil , isNotAllowedError (gvk )
46- }
47- return a .delegate .CoreV1 ().Nodes (), nil
88+ return a .CoreV1 ().Nodes (), nil
4889}
4990
5091func (a * AccessControlClientset ) NodesLogs (ctx context.Context , name string ) (* rest.Request , error ) {
51- gvk := & schema.GroupVersionKind {Group : "" , Version : "v1" , Kind : "Node" }
52- if ! isAllowed (a .staticConfig , gvk ) {
53- return nil , isNotAllowedError (gvk )
54- }
55-
56- if _ , err := a .delegate .CoreV1 ().Nodes ().Get (ctx , name , metav1.GetOptions {}); err != nil {
92+ if _ , err := a .CoreV1 ().Nodes ().Get (ctx , name , metav1.GetOptions {}); err != nil {
5793 return nil , fmt .Errorf ("failed to get node %s: %w" , name , err )
5894 }
5995
6096 url := []string {"api" , "v1" , "nodes" , name , "proxy" , "logs" }
61- return a .delegate . CoreV1 ().RESTClient ().
97+ return a .CoreV1 ().RESTClient ().
6298 Get ().
6399 AbsPath (url ... ), nil
64100}
65101
66102func (a * AccessControlClientset ) NodesMetricses (ctx context.Context , name string , listOptions metav1.ListOptions ) (* metrics.NodeMetricsList , error ) {
67- gvk := & schema.GroupVersionKind {Group : metrics .GroupName , Version : metricsv1beta1api .SchemeGroupVersion .Version , Kind : "NodeMetrics" }
68- if ! isAllowed (a .staticConfig , gvk ) {
69- return nil , isNotAllowedError (gvk )
70- }
71103 versionedMetrics := & metricsv1beta1api.NodeMetricsList {}
72104 var err error
73105 if name != "" {
@@ -87,37 +119,26 @@ func (a *AccessControlClientset) NodesMetricses(ctx context.Context, name string
87119}
88120
89121func (a * AccessControlClientset ) NodesStatsSummary (ctx context.Context , name string ) (* rest.Request , error ) {
90- gvk := & schema.GroupVersionKind {Group : "" , Version : "v1" , Kind : "Node" }
91- if ! isAllowed (a .staticConfig , gvk ) {
92- return nil , isNotAllowedError (gvk )
93- }
94-
95- if _ , err := a .delegate .CoreV1 ().Nodes ().Get (ctx , name , metav1.GetOptions {}); err != nil {
122+ if _ , err := a .CoreV1 ().Nodes ().Get (ctx , name , metav1.GetOptions {}); err != nil {
96123 return nil , fmt .Errorf ("failed to get node %s: %w" , name , err )
97124 }
98125
99126 url := []string {"api" , "v1" , "nodes" , name , "proxy" , "stats" , "summary" }
100- return a .delegate . CoreV1 ().RESTClient ().
127+ return a .CoreV1 ().RESTClient ().
101128 Get ().
102129 AbsPath (url ... ), nil
103130}
104131
132+ // Pods returns PodInterface
133+ // Deprecated: use CoreV1().Pods(namespace) directly
105134func (a * AccessControlClientset ) Pods (namespace string ) (corev1.PodInterface , error ) {
106- gvk := & schema.GroupVersionKind {Group : "" , Version : "v1" , Kind : "Pod" }
107- if ! isAllowed (a .staticConfig , gvk ) {
108- return nil , isNotAllowedError (gvk )
109- }
110- return a .delegate .CoreV1 ().Pods (namespace ), nil
135+ return a .CoreV1 ().Pods (namespace ), nil
111136}
112137
113138func (a * AccessControlClientset ) PodsExec (namespace , name string , podExecOptions * v1.PodExecOptions ) (remotecommand.Executor , error ) {
114- gvk := & schema.GroupVersionKind {Group : "" , Version : "v1" , Kind : "Pod" }
115- if ! isAllowed (a .staticConfig , gvk ) {
116- return nil , isNotAllowedError (gvk )
117- }
118139 // Compute URL
119140 // https://github.com/kubernetes/kubectl/blob/5366de04e168bcbc11f5e340d131a9ca8b7d0df4/pkg/cmd/exec/exec.go#L382-L397
120- execRequest := a .delegate . CoreV1 ().RESTClient ().
141+ execRequest := a .CoreV1 ().RESTClient ().
121142 Post ().
122143 Resource ("pods" ).
123144 Namespace (namespace ).
@@ -138,10 +159,6 @@ func (a *AccessControlClientset) PodsExec(namespace, name string, podExecOptions
138159}
139160
140161func (a * AccessControlClientset ) PodsMetricses (ctx context.Context , namespace , name string , listOptions metav1.ListOptions ) (* metrics.PodMetricsList , error ) {
141- gvk := & schema.GroupVersionKind {Group : metrics .GroupName , Version : metricsv1beta1api .SchemeGroupVersion .Version , Kind : "PodMetrics" }
142- if ! isAllowed (a .staticConfig , gvk ) {
143- return nil , isNotAllowedError (gvk )
144- }
145162 versionedMetrics := & metricsv1beta1api.PodMetricsList {}
146163 var err error
147164 if name != "" {
@@ -160,45 +177,20 @@ func (a *AccessControlClientset) PodsMetricses(ctx context.Context, namespace, n
160177 return convertedMetrics , metricsv1beta1api .Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList (versionedMetrics , convertedMetrics , nil )
161178}
162179
180+ // Services returns ServiceInterface
181+ // Deprecated: use CoreV1().Services(namespace) directly
163182func (a * AccessControlClientset ) Services (namespace string ) (corev1.ServiceInterface , error ) {
164- gvk := & schema.GroupVersionKind {Group : "" , Version : "v1" , Kind : "Service" }
165- if ! isAllowed (a .staticConfig , gvk ) {
166- return nil , isNotAllowedError (gvk )
167- }
168- return a .delegate .CoreV1 ().Services (namespace ), nil
183+ return a .CoreV1 ().Services (namespace ), nil
169184}
170185
186+ // SelfSubjectAccessReviews returns SelfSubjectAccessReviewInterface
187+ // Deprecated: use AuthorizationV1().SelfSubjectAccessReviews() directly
171188func (a * AccessControlClientset ) SelfSubjectAccessReviews () (authorizationv1.SelfSubjectAccessReviewInterface , error ) {
172- gvk := & schema.GroupVersionKind {Group : authorizationv1api .GroupName , Version : authorizationv1api .SchemeGroupVersion .Version , Kind : "SelfSubjectAccessReview" }
173- if ! isAllowed (a .staticConfig , gvk ) {
174- return nil , isNotAllowedError (gvk )
175- }
176- return a .delegate .AuthorizationV1 ().SelfSubjectAccessReviews (), nil
189+ return a .AuthorizationV1 ().SelfSubjectAccessReviews (), nil
177190}
178191
179192// TokenReview returns TokenReviewInterface
193+ // Deprecated: use AuthenticationV1().TokenReviews() directly
180194func (a * AccessControlClientset ) TokenReview () (authenticationv1.TokenReviewInterface , error ) {
181- gvk := & schema.GroupVersionKind {Group : authenticationv1api .GroupName , Version : authorizationv1api .SchemeGroupVersion .Version , Kind : "TokenReview" }
182- if ! isAllowed (a .staticConfig , gvk ) {
183- return nil , isNotAllowedError (gvk )
184- }
185- return a .delegate .AuthenticationV1 ().TokenReviews (), nil
186- }
187-
188- func NewAccessControlClientset (cfg * rest.Config , staticConfig * config.StaticConfig ) (* AccessControlClientset , error ) {
189- clientSet , err := kubernetes .NewForConfig (cfg )
190- if err != nil {
191- return nil , err
192- }
193- metricsClient , err := metricsv1beta1 .NewForConfig (cfg )
194- if err != nil {
195- return nil , err
196- }
197- return & AccessControlClientset {
198- cfg : cfg ,
199- delegate : clientSet ,
200- discoveryClient : clientSet .DiscoveryClient ,
201- metricsV1beta1 : metricsClient ,
202- staticConfig : staticConfig ,
203- }, nil
195+ return a .AuthenticationV1 ().TokenReviews (), nil
204196}
0 commit comments