Skip to content

Commit 91fa4f4

Browse files
committed
Traverse the resources in helm chart for client access
1 parent 9136f04 commit 91fa4f4

File tree

4 files changed

+186
-22
lines changed

4 files changed

+186
-22
lines changed

pkg/helm/helm.go

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ package helm
33
import (
44
"context"
55
"fmt"
6+
"log"
7+
"time"
8+
69
"helm.sh/helm/v3/pkg/action"
710
"helm.sh/helm/v3/pkg/chart/loader"
811
"helm.sh/helm/v3/pkg/cli"
912
"helm.sh/helm/v3/pkg/registry"
1013
"helm.sh/helm/v3/pkg/release"
1114
"k8s.io/cli-runtime/pkg/genericclioptions"
12-
"log"
1315
"sigs.k8s.io/yaml"
14-
"time"
1516
)
1617

1718
type Kubernetes interface {
@@ -62,6 +63,57 @@ func (h *Helm) Install(ctx context.Context, chart string, values map[string]inte
6263
return string(ret), nil
6364
}
6465

66+
// RenderTemplateDryRun renders a Helm chart template using dry-run mode to see what resources will be created
67+
func (h *Helm) RenderTemplateDryRun(ctx context.Context, chart string, values map[string]interface{}, name string, namespace string) (string, error) {
68+
cfg, err := h.newAction(h.kubernetes.NamespaceOrDefault(namespace), false)
69+
if err != nil {
70+
return "", err
71+
}
72+
install := action.NewInstall(cfg)
73+
if name == "" {
74+
install.GenerateName = true
75+
install.ReleaseName, _, _ = install.NameAndChart([]string{chart})
76+
} else {
77+
install.ReleaseName = name
78+
}
79+
install.Namespace = h.kubernetes.NamespaceOrDefault(namespace)
80+
install.Wait = false
81+
install.Timeout = 5 * time.Minute
82+
install.DryRun = true
83+
84+
chartRequested, err := install.ChartPathOptions.LocateChart(chart, cli.New())
85+
if err != nil {
86+
return "", err
87+
}
88+
chartLoaded, err := loader.Load(chartRequested)
89+
if err != nil {
90+
return "", err
91+
}
92+
93+
dryRunRelease, err := install.RunWithContext(ctx, chartLoaded, values)
94+
if err != nil {
95+
return "", err
96+
}
97+
98+
return dryRunRelease.Manifest, nil
99+
}
100+
101+
// GetReleaseManifests gets the manifests for an existing Helm release
102+
func (h *Helm) GetReleaseManifests(name string, namespace string) (string, error) {
103+
cfg, err := h.newAction(h.kubernetes.NamespaceOrDefault(namespace), false)
104+
if err != nil {
105+
return "", err
106+
}
107+
108+
get := action.NewGet(cfg)
109+
release, err := get.Run(name)
110+
if err != nil {
111+
return "", err
112+
}
113+
114+
return release.Manifest, nil
115+
}
116+
65117
// List lists all the releases for the specified namespace (or current namespace if). Or allNamespaces is true, it lists all releases across all namespaces.
66118
func (h *Helm) List(namespace string, allNamespaces bool) (string, error) {
67119
cfg, err := h.newAction(namespace, allNamespaces)

pkg/kubernetes/pods.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (k *Kubernetes) PodsDelete(ctx context.Context, namespace, name string) (st
6161

6262
// Delete managed service
6363
if isManaged {
64-
if err = k.canClientAccess(ctx, &schema.GroupVersionResource{
64+
if err = k.CanClientAccess(ctx, &schema.GroupVersionResource{
6565
Group: "",
6666
Version: "v1",
6767
Resource: "services",
@@ -77,7 +77,7 @@ func (k *Kubernetes) PodsDelete(ctx context.Context, namespace, name string) (st
7777
LabelSelector: managedLabelSelector.String(),
7878
}); sl != nil {
7979
for _, svc := range sl.Items {
80-
if err = k.canClientAccess(ctx, &schema.GroupVersionResource{
80+
if err = k.CanClientAccess(ctx, &schema.GroupVersionResource{
8181
Group: "",
8282
Version: "v1",
8383
Resource: "services",
@@ -91,7 +91,7 @@ func (k *Kubernetes) PodsDelete(ctx context.Context, namespace, name string) (st
9191

9292
// Delete managed Route
9393
if isManaged && k.supportsGroupVersion("route.openshift.io/v1") {
94-
if err = k.canClientAccess(ctx, &schema.GroupVersionResource{
94+
if err = k.CanClientAccess(ctx, &schema.GroupVersionResource{
9595
Group: "route.openshift.io",
9696
Version: "v1",
9797
Resource: "routes",
@@ -106,7 +106,7 @@ func (k *Kubernetes) PodsDelete(ctx context.Context, namespace, name string) (st
106106
LabelSelector: managedLabelSelector.String(),
107107
}); rl != nil {
108108
for _, route := range rl.Items {
109-
if err = k.canClientAccess(ctx, &schema.GroupVersionResource{
109+
if err = k.CanClientAccess(ctx, &schema.GroupVersionResource{
110110
Group: "route.openshift.io",
111111
Version: "v1",
112112
Resource: "routes",
@@ -124,7 +124,7 @@ func (k *Kubernetes) PodsDelete(ctx context.Context, namespace, name string) (st
124124

125125
func (k *Kubernetes) PodsLog(ctx context.Context, namespace, name, container string) (string, error) {
126126
tailLines := int64(256)
127-
if err := k.canClientAccess(ctx, &schema.GroupVersionResource{
127+
if err := k.CanClientAccess(ctx, &schema.GroupVersionResource{
128128
Group: "",
129129
Version: "v1",
130130
Resource: "pods",
@@ -135,7 +135,7 @@ func (k *Kubernetes) PodsLog(ctx context.Context, namespace, name, container str
135135
if err != nil {
136136
return "", err
137137
}
138-
if err := k.canClientAccess(ctx, &schema.GroupVersionResource{
138+
if err := k.CanClientAccess(ctx, &schema.GroupVersionResource{
139139
Group: "",
140140
Version: "v1",
141141
Resource: "pods",
@@ -249,7 +249,7 @@ func (k *Kubernetes) PodsTop(ctx context.Context, options PodsTopOptions) (*metr
249249
namespace = k.NamespaceOrDefault(namespace)
250250
}
251251

252-
if err := k.canClientAccess(ctx, &schema.GroupVersionResource{
252+
if err := k.CanClientAccess(ctx, &schema.GroupVersionResource{
253253
Group: "metrics.k8s.io",
254254
Version: "v1beta1",
255255
Resource: "podmetrics",
@@ -266,7 +266,7 @@ func (k *Kubernetes) PodsExec(ctx context.Context, namespace, name, container st
266266
if err != nil {
267267
return "", err
268268
}
269-
if err := k.canClientAccess(ctx, &schema.GroupVersionResource{
269+
if err := k.CanClientAccess(ctx, &schema.GroupVersionResource{
270270
Group: "",
271271
Version: "v1",
272272
Resource: "pods",
@@ -290,7 +290,7 @@ func (k *Kubernetes) PodsExec(ctx context.Context, namespace, name, container st
290290
Stdout: true,
291291
Stderr: true,
292292
}
293-
if err := k.canClientAccess(ctx, &schema.GroupVersionResource{
293+
if err := k.CanClientAccess(ctx, &schema.GroupVersionResource{
294294
Group: "",
295295
Version: "v1",
296296
Resource: "pods",

pkg/kubernetes/resources.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88

99
"k8s.io/apimachinery/pkg/runtime"
10+
"k8s.io/klog/v2"
1011

1112
"github.com/manusa/kubernetes-mcp-server/pkg/version"
1213
authv1 "k8s.io/api/authorization/v1"
@@ -30,7 +31,7 @@ type ResourceListOptions struct {
3031
}
3132

3233
func (k *Kubernetes) ResourcesList(ctx context.Context, gvk *schema.GroupVersionKind, namespace string, options ResourceListOptions) (runtime.Unstructured, error) {
33-
gvr, err := k.resourceFor(gvk)
34+
gvr, err := k.ResourceFor(gvk)
3435
if err != nil {
3536
return nil, err
3637
}
@@ -41,7 +42,7 @@ func (k *Kubernetes) ResourcesList(ctx context.Context, gvk *schema.GroupVersion
4142
namespace = k.manager.configuredNamespace()
4243
}
4344

44-
if err := k.canClientAccess(ctx, gvr, "", namespace, "list", ""); err != nil {
45+
if err := k.CanClientAccess(ctx, gvr, "", namespace, "list", ""); err != nil {
4546
return nil, err
4647
}
4748

@@ -52,7 +53,7 @@ func (k *Kubernetes) ResourcesList(ctx context.Context, gvk *schema.GroupVersion
5253
}
5354

5455
func (k *Kubernetes) ResourcesGet(ctx context.Context, gvk *schema.GroupVersionKind, namespace, name string) (*unstructured.Unstructured, error) {
55-
gvr, err := k.resourceFor(gvk)
56+
gvr, err := k.ResourceFor(gvk)
5657
if err != nil {
5758
return nil, err
5859
}
@@ -62,7 +63,7 @@ func (k *Kubernetes) ResourcesGet(ctx context.Context, gvk *schema.GroupVersionK
6263
namespace = k.NamespaceOrDefault(namespace)
6364
}
6465

65-
if err := k.canClientAccess(ctx, gvr, name, namespace, "get", ""); err != nil {
66+
if err := k.CanClientAccess(ctx, gvr, name, namespace, "get", ""); err != nil {
6667
return nil, err
6768
}
6869

@@ -84,7 +85,7 @@ func (k *Kubernetes) ResourcesCreateOrUpdate(ctx context.Context, resource strin
8485
}
8586

8687
func (k *Kubernetes) ResourcesDelete(ctx context.Context, gvk *schema.GroupVersionKind, namespace, name string) error {
87-
gvr, err := k.resourceFor(gvk)
88+
gvr, err := k.ResourceFor(gvk)
8889
if err != nil {
8990
return err
9091
}
@@ -94,7 +95,7 @@ func (k *Kubernetes) ResourcesDelete(ctx context.Context, gvk *schema.GroupVersi
9495
namespace = k.NamespaceOrDefault(namespace)
9596
}
9697

97-
if err := k.canClientAccess(ctx, gvr, name, namespace, "delete", ""); err != nil {
98+
if err := k.CanClientAccess(ctx, gvr, name, namespace, "delete", ""); err != nil {
9899
return err
99100
}
100101

@@ -151,7 +152,7 @@ func (k *Kubernetes) resourcesListAsTable(ctx context.Context, gvk *schema.Group
151152
func (k *Kubernetes) resourcesCreateOrUpdate(ctx context.Context, resources []*unstructured.Unstructured) ([]*unstructured.Unstructured, error) {
152153
for i, obj := range resources {
153154
gvk := obj.GroupVersionKind()
154-
gvr, rErr := k.resourceFor(&gvk)
155+
gvr, rErr := k.ResourceFor(&gvk)
155156
if rErr != nil {
156157
return nil, rErr
157158
}
@@ -162,7 +163,7 @@ func (k *Kubernetes) resourcesCreateOrUpdate(ctx context.Context, resources []*u
162163
namespace = k.NamespaceOrDefault(namespace)
163164
}
164165

165-
if err := k.canClientAccess(ctx, gvr, obj.GetName(), namespace, "patch", ""); err != nil {
166+
if err := k.CanClientAccess(ctx, gvr, obj.GetName(), namespace, "patch", ""); err != nil {
166167
return nil, err
167168
}
168169

@@ -180,7 +181,7 @@ func (k *Kubernetes) resourcesCreateOrUpdate(ctx context.Context, resources []*u
180181
return resources, nil
181182
}
182183

183-
func (k *Kubernetes) resourceFor(gvk *schema.GroupVersionKind) (*schema.GroupVersionResource, error) {
184+
func (k *Kubernetes) ResourceFor(gvk *schema.GroupVersionKind) (*schema.GroupVersionResource, error) {
184185
m, err := k.manager.accessControlRESTMapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
185186
if err != nil {
186187
return nil, err
@@ -208,7 +209,7 @@ func (k *Kubernetes) supportsGroupVersion(groupVersion string) bool {
208209
return true
209210
}
210211

211-
func (k *Kubernetes) canClientAccess(ctx context.Context, gvr *schema.GroupVersionResource, resourceName, namespace, verb, subResource string) error {
212+
func (k *Kubernetes) CanClientAccess(ctx context.Context, gvr *schema.GroupVersionResource, resourceName, namespace, verb, subResource string) error {
212213
if !k.manager.staticConfig.RequireOAuth {
213214
return nil
214215
}
@@ -223,7 +224,7 @@ func (k *Kubernetes) canClientAccess(ctx context.Context, gvr *schema.GroupVersi
223224
}
224225
groups := ctx.Value("X-User-Groups")
225226
if groups == nil {
226-
return fmt.Errorf("user groups are not set")
227+
groups = []string{}
227228
}
228229
userGroups := strings.Split(groups.(string), ",")
229230

@@ -254,6 +255,7 @@ func (k *Kubernetes) canClientAccess(ctx context.Context, gvr *schema.GroupVersi
254255
if !response.Status.Allowed {
255256
return fmt.Errorf("user %q does not have permission to %s %s", userName, verb, resourceName)
256257
}
258+
klog.V(2).Infof("User %q has permission to %s %s in namespace %s", userName, verb, resourceName, namespace)
257259
return nil
258260
}
259261

0 commit comments

Comments
 (0)