@@ -3,71 +3,106 @@ 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+ rest .CopyConfig (restConfig )
42+ acc := & AccessControlClientset {
43+ cfg : rest .CopyConfig (restConfig ),
44+ }
45+ if acc .cfg .UserAgent == "" {
46+ acc .cfg .UserAgent = rest .DefaultKubernetesUserAgent ()
47+ }
48+ acc .cfg .Wrap (func (original http.RoundTripper ) http.RoundTripper {
49+ return & AccessControlRoundTripper {
50+ delegate : original ,
51+ staticConfig : staticConfig ,
52+ restMapper : acc .restMapper ,
53+ }
54+ })
55+ discoveryClient , err := discovery .NewDiscoveryClientForConfig (acc .cfg )
56+ if err != nil {
57+ return nil , fmt .Errorf ("failed to create discovery client: %v" , err )
58+ }
59+ acc .discoveryClient = memory .NewMemCacheClient (discoveryClient )
60+ acc .restMapper = restmapper .NewDeferredDiscoveryRESTMapper (acc .discoveryClient )
61+ acc .Interface , err = kubernetes .NewForConfig (acc .cfg )
62+ if err != nil {
63+ return nil , err
64+ }
65+ acc .dynamicClient , err = dynamic .NewForConfig (acc .cfg )
66+ if err != nil {
67+ return nil , err
68+ }
69+ acc .metricsV1beta1 , err = metricsv1beta1 .NewForConfig (acc .cfg )
70+ if err != nil {
71+ return nil , err
72+ }
73+ return acc , nil
74+ }
75+
76+ func (a * AccessControlClientset ) DiscoveryClient () discovery.CachedDiscoveryInterface {
3977 return a .discoveryClient
4078}
4179
80+ func (a * AccessControlClientset ) DynamicClient () dynamic.Interface {
81+ return a .dynamicClient
82+ }
83+
84+ func (a * AccessControlClientset ) RESTMapper () meta.ResettableRESTMapper {
85+ return a .restMapper
86+ }
87+
88+ // Nodes returns NodeInterface
89+ // Deprecated: use CoreV1().Nodes() directly
4290func (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
91+ return a .CoreV1 ().Nodes (), nil
4892}
4993
5094func (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 {
95+ if _ , err := a .CoreV1 ().Nodes ().Get (ctx , name , metav1.GetOptions {}); err != nil {
5796 return nil , fmt .Errorf ("failed to get node %s: %w" , name , err )
5897 }
5998
6099 url := []string {"api" , "v1" , "nodes" , name , "proxy" , "logs" }
61- return a .delegate . CoreV1 ().RESTClient ().
100+ return a .CoreV1 ().RESTClient ().
62101 Get ().
63102 AbsPath (url ... ), nil
64103}
65104
66105func (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- }
71106 versionedMetrics := & metricsv1beta1api.NodeMetricsList {}
72107 var err error
73108 if name != "" {
@@ -87,37 +122,26 @@ func (a *AccessControlClientset) NodesMetricses(ctx context.Context, name string
87122}
88123
89124func (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 {
125+ if _ , err := a .CoreV1 ().Nodes ().Get (ctx , name , metav1.GetOptions {}); err != nil {
96126 return nil , fmt .Errorf ("failed to get node %s: %w" , name , err )
97127 }
98128
99129 url := []string {"api" , "v1" , "nodes" , name , "proxy" , "stats" , "summary" }
100- return a .delegate . CoreV1 ().RESTClient ().
130+ return a .CoreV1 ().RESTClient ().
101131 Get ().
102132 AbsPath (url ... ), nil
103133}
104134
135+ // Pods returns PodInterface
136+ // Deprecated: use CoreV1().Pods(namespace) directly
105137func (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
138+ return a .CoreV1 ().Pods (namespace ), nil
111139}
112140
113141func (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- }
118142 // Compute URL
119143 // https://github.com/kubernetes/kubectl/blob/5366de04e168bcbc11f5e340d131a9ca8b7d0df4/pkg/cmd/exec/exec.go#L382-L397
120- execRequest := a .delegate . CoreV1 ().RESTClient ().
144+ execRequest := a .CoreV1 ().RESTClient ().
121145 Post ().
122146 Resource ("pods" ).
123147 Namespace (namespace ).
@@ -138,10 +162,6 @@ func (a *AccessControlClientset) PodsExec(namespace, name string, podExecOptions
138162}
139163
140164func (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- }
145165 versionedMetrics := & metricsv1beta1api.PodMetricsList {}
146166 var err error
147167 if name != "" {
@@ -160,45 +180,20 @@ func (a *AccessControlClientset) PodsMetricses(ctx context.Context, namespace, n
160180 return convertedMetrics , metricsv1beta1api .Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList (versionedMetrics , convertedMetrics , nil )
161181}
162182
183+ // Services returns ServiceInterface
184+ // Deprecated: use CoreV1().Services(namespace) directly
163185func (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
186+ return a .CoreV1 ().Services (namespace ), nil
169187}
170188
189+ // SelfSubjectAccessReviews returns SelfSubjectAccessReviewInterface
190+ // Deprecated: use AuthorizationV1().SelfSubjectAccessReviews() directly
171191func (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
192+ return a .AuthorizationV1 ().SelfSubjectAccessReviews (), nil
177193}
178194
179195// TokenReview returns TokenReviewInterface
196+ // Deprecated: use AuthenticationV1().TokenReviews() directly
180197func (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
198+ return a .AuthenticationV1 ().TokenReviews (), nil
204199}
0 commit comments