Skip to content

Commit 69c50a6

Browse files
authored
feat: add auth can I operation (#12)
Signed-off-by: Calum Murray <cmurray@redhat.com>
1 parent fc7f7fc commit 69c50a6

8 files changed

Lines changed: 482 additions & 6 deletions

File tree

go.mod

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.25.5
55
require (
66
github.com/google/jsonschema-go v0.4.2
77
github.com/mcpchecker/mcpchecker v0.0.3
8+
k8s.io/api v0.35.0
89
k8s.io/apimachinery v0.35.0
910
k8s.io/client-go v0.35.0
1011
)
@@ -13,13 +14,30 @@ require (
1314
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1415
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
1516
github.com/go-logr/logr v1.4.3 // indirect
17+
github.com/go-openapi/jsonpointer v0.22.4 // indirect
18+
github.com/go-openapi/jsonreference v0.21.4 // indirect
19+
github.com/go-openapi/swag v0.25.4 // indirect
20+
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
21+
github.com/go-openapi/swag/conv v0.25.4 // indirect
22+
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
23+
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
24+
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
25+
github.com/go-openapi/swag/loading v0.25.4 // indirect
26+
github.com/go-openapi/swag/mangling v0.25.4 // indirect
27+
github.com/go-openapi/swag/netutils v0.25.4 // indirect
28+
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
29+
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
30+
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
31+
github.com/google/gnostic-models v0.7.0 // indirect
1632
github.com/json-iterator/go v1.1.12 // indirect
33+
github.com/kr/text v0.2.0 // indirect
1734
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
1835
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
1936
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
2037
github.com/spf13/pflag v1.0.10 // indirect
2138
github.com/x448/float16 v0.8.4 // indirect
2239
go.yaml.in/yaml/v2 v2.4.3 // indirect
40+
go.yaml.in/yaml/v3 v3.0.4 // indirect
2341
golang.org/x/exp/event v0.0.0-20260112195511-716be5621a96 // indirect
2442
golang.org/x/exp/jsonrpc2 v0.0.0-20260112195511-716be5621a96 // indirect
2543
golang.org/x/net v0.49.0 // indirect
@@ -29,6 +47,8 @@ require (
2947
golang.org/x/text v0.33.0 // indirect
3048
golang.org/x/time v0.9.0 // indirect
3149
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
50+
google.golang.org/protobuf v1.36.11 // indirect
51+
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
3252
gopkg.in/inf.v0 v0.9.1 // indirect
3353
k8s.io/klog/v2 v2.130.1 // indirect
3454
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect

go.sum

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
2+
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
3+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
14
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
25
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
36
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -24,6 +27,8 @@ github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ98
2427
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
2528
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
2629
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
30+
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
31+
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
2732
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
2833
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
2934
github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
@@ -36,17 +41,29 @@ github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv4
3641
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
3742
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
3843
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
44+
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
45+
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
46+
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
47+
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
48+
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
49+
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
3950
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
4051
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
4152
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
4253
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
4354
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
4455
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
4556
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
57+
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
58+
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
4659
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
4760
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
4861
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
4962
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
63+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
64+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
65+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
66+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
5067
github.com/mcpchecker/mcpchecker v0.0.3 h1:/6V+6a1vvkjjoebmP9V2gl3sh2phnaUbMb9hQnt5rsU=
5168
github.com/mcpchecker/mcpchecker v0.0.3/go.mod h1:15Hh69Ou6hXPW/Y4anmZs1goa/EdU+QBKGJvhsrcfXE=
5269
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -57,9 +74,15 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
5774
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
5875
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
5976
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
77+
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
78+
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
79+
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
80+
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
6081
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6182
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
6283
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
84+
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
85+
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
6386
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
6487
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
6588
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -78,10 +101,14 @@ golang.org/x/exp/event v0.0.0-20260112195511-716be5621a96 h1:l+bY+u9cx/1NImWfu0O
78101
golang.org/x/exp/event v0.0.0-20260112195511-716be5621a96/go.mod h1:Mdr2zZUK+6kOEaz94oXdRj8dk4gD0X6uJ5tlEy7hG04=
79102
golang.org/x/exp/jsonrpc2 v0.0.0-20260112195511-716be5621a96 h1:cN9X2vSBmT3Ruw2UlbJNLJh0iBqTmtSB0dRfh5aumiY=
80103
golang.org/x/exp/jsonrpc2 v0.0.0-20260112195511-716be5621a96/go.mod h1:FGo2nTjljKiUliOH66kiDGWw1Hl9sPoyb/bFuIxjzeQ=
104+
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
105+
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
81106
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
82107
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
83108
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
84109
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
110+
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
111+
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
85112
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
86113
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
87114
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
@@ -90,12 +117,15 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
90117
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
91118
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
92119
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
120+
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
121+
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
93122
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
94123
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
95124
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
96125
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
97-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
98126
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
127+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
128+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
99129
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
100130
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
101131
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=

pkg/extension/authcani.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package extension
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/mcpchecker/mcpchecker/pkg/extension/sdk"
8+
)
9+
10+
func (e *Extension) handleAuthCanI(ctx context.Context, req *sdk.OperationRequest) (*sdk.OperationResult, error) {
11+
if e.client == nil {
12+
return sdk.Failure(fmt.Errorf("kubernetes client not initialized")), nil
13+
}
14+
15+
args, ok := req.Args.(map[string]any)
16+
if !ok {
17+
return sdk.Failure(fmt.Errorf("args must be an object")), nil
18+
}
19+
20+
verb, _ := args["verb"].(string)
21+
resource, _ := args["resource"].(string)
22+
as, _ := args["as"].(string)
23+
namespace, _ := args["namespace"].(string)
24+
apiGroup, _ := args["apiGroup"].(string)
25+
resourceName, _ := args["resourceName"].(string)
26+
27+
if verb == "" {
28+
return sdk.Failure(fmt.Errorf("verb is required")), nil
29+
}
30+
if resource == "" {
31+
return sdk.Failure(fmt.Errorf("resource is required")), nil
32+
}
33+
if as == "" {
34+
return sdk.Failure(fmt.Errorf("as is required")), nil
35+
}
36+
37+
e.LogInfo(ctx, "Checking permissions", map[string]any{
38+
"verb": verb,
39+
"resource": resource,
40+
"as": as,
41+
"namespace": namespace,
42+
"apiGroup": apiGroup,
43+
"resourceName": resourceName,
44+
})
45+
46+
allowed, reason, err := e.client.CheckAccess(ctx, as, verb, resource, apiGroup, namespace, resourceName)
47+
if err != nil {
48+
e.LogError(ctx, "Failed to check permissions", map[string]any{
49+
"error": err.Error(),
50+
})
51+
return sdk.Failure(fmt.Errorf("failed to check permissions: %w", err)), nil
52+
}
53+
54+
e.LogInfo(ctx, "Permission check completed", map[string]any{
55+
"allowed": allowed,
56+
"reason": reason,
57+
})
58+
59+
// Handle expect.allowed verification
60+
if expectArg, hasExpect := args["expect"]; hasExpect {
61+
expect, ok := expectArg.(map[string]any)
62+
if !ok {
63+
return sdk.Failure(fmt.Errorf("expect must be an object")), nil
64+
}
65+
66+
if expectedAllowed, hasAllowed := expect["allowed"]; hasAllowed {
67+
expectedBool, ok := expectedAllowed.(bool)
68+
if !ok {
69+
return sdk.Failure(fmt.Errorf("expect.allowed must be a boolean")), nil
70+
}
71+
72+
if allowed != expectedBool {
73+
return sdk.FailureWithMessage(
74+
fmt.Sprintf("permission check failed: expected allowed=%v but got allowed=%v", expectedBool, allowed),
75+
fmt.Errorf("permission expectation not met"),
76+
), nil
77+
}
78+
}
79+
}
80+
81+
msg := fmt.Sprintf("%s can %s %s", as, verb, resource)
82+
if namespace != "" {
83+
msg += fmt.Sprintf(" in namespace %s", namespace)
84+
} else {
85+
msg += " cluster-wide"
86+
}
87+
88+
if allowed {
89+
return sdk.SuccessWithOutputs(msg+": allowed", map[string]string{
90+
"allowed": "true",
91+
"reason": reason,
92+
}), nil
93+
}
94+
95+
return sdk.SuccessWithOutputs(msg+": denied", map[string]string{
96+
"allowed": "false",
97+
"reason": reason,
98+
}), nil
99+
}

0 commit comments

Comments
 (0)