@@ -19,11 +19,11 @@ import (
19
19
"testing"
20
20
"time"
21
21
22
+ "github.com/containers/kubernetes-mcp-server/internal/test"
22
23
"github.com/coreos/go-oidc/v3/oidc"
23
24
"github.com/coreos/go-oidc/v3/oidc/oidctest"
24
25
"golang.org/x/sync/errgroup"
25
26
"k8s.io/client-go/tools/clientcmd"
26
- "k8s.io/client-go/tools/clientcmd/api"
27
27
"k8s.io/klog/v2"
28
28
"k8s.io/klog/v2/textlogger"
29
29
@@ -33,6 +33,7 @@ import (
33
33
34
34
type httpContext struct {
35
35
klogState klog.State
36
+ mockServer * test.MockServer
36
37
LogBuffer bytes.Buffer
37
38
HttpAddress string // HTTP server address
38
39
timeoutCancel context.CancelFunc // Release resources if test completes before the timeout
@@ -42,21 +43,31 @@ type httpContext struct {
42
43
OidcProvider * oidc.Provider
43
44
}
44
45
46
+ const tokenReviewSuccessful = `
47
+ {
48
+ "kind": "TokenReview",
49
+ "apiVersion": "authentication.k8s.io/v1",
50
+ "spec": {"token": "valid-token"},
51
+ "status": {
52
+ "authenticated": true,
53
+ "user": {
54
+ "username": "test-user",
55
+ "groups": ["system:authenticated"]
56
+ }
57
+ }
58
+ }`
59
+
45
60
func (c * httpContext ) beforeEach (t * testing.T ) {
46
61
t .Helper ()
47
62
http .DefaultClient .Timeout = 10 * time .Second
48
63
if c .StaticConfig == nil {
49
64
c .StaticConfig = & config.StaticConfig {}
50
65
}
66
+ c .mockServer = test .NewMockServer ()
51
67
// Fake Kubernetes configuration
52
- fakeConfig := api .NewConfig ()
53
- fakeConfig .Clusters ["fake" ] = api .NewCluster ()
54
- fakeConfig .Clusters ["fake" ].Server = "https://example.com"
55
- fakeConfig .Contexts ["fake-context" ] = api .NewContext ()
56
- fakeConfig .Contexts ["fake-context" ].Cluster = "fake"
57
- fakeConfig .CurrentContext = "fake-context"
68
+ mockKubeConfig := c .mockServer .KubeConfig ()
58
69
kubeConfig := filepath .Join (t .TempDir (), "config" )
59
- _ = clientcmd .WriteToFile (* fakeConfig , kubeConfig )
70
+ _ = clientcmd .WriteToFile (* mockKubeConfig , kubeConfig )
60
71
_ = os .Setenv ("KUBECONFIG" , kubeConfig )
61
72
// Capture logging
62
73
c .klogState = klog .CaptureState ()
@@ -100,6 +111,7 @@ func (c *httpContext) beforeEach(t *testing.T) {
100
111
101
112
func (c * httpContext ) afterEach (t * testing.T ) {
102
113
t .Helper ()
114
+ c .mockServer .Close ()
103
115
c .StopServer ()
104
116
err := c .WaitForShutdown ()
105
117
if err != nil {
@@ -546,3 +558,81 @@ func TestAuthorizationUnauthorized(t *testing.T) {
546
558
})
547
559
})
548
560
}
561
+
562
+ // TestAuthorizationRequireOAuthFalse tests the scenario where OAuth is not required.
563
+ func TestAuthorizationRequireOAuthFalse (t * testing.T ) {
564
+ testCaseWithContext (t , & httpContext {StaticConfig : & config.StaticConfig {RequireOAuth : false }}, func (ctx * httpContext ) {
565
+ resp , err := http .Get (fmt .Sprintf ("http://%s/mcp" , ctx .HttpAddress ))
566
+ if err != nil {
567
+ t .Fatalf ("Failed to get protected endpoint: %v" , err )
568
+ }
569
+ t .Cleanup (func () { _ = resp .Body .Close () })
570
+ t .Run ("Protected resource with MISSING Authorization header returns 200 - OK)" , func (t * testing.T ) {
571
+ if resp .StatusCode != http .StatusOK {
572
+ t .Errorf ("Expected HTTP 200 OK, got %d" , resp .StatusCode )
573
+ }
574
+ })
575
+ })
576
+ }
577
+
578
+ func TestAuthorizationRawToken (t * testing.T ) {
579
+ testCaseWithContext (t , & httpContext {StaticConfig : & config.StaticConfig {RequireOAuth : true }}, func (ctx * httpContext ) {
580
+ ctx .mockServer .Handle (http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
581
+ if req .URL .EscapedPath () == "/apis/authentication.k8s.io/v1/tokenreviews" {
582
+ w .Header ().Set ("Content-Type" , "application/json" )
583
+ _ , _ = w .Write ([]byte (tokenReviewSuccessful ))
584
+ return
585
+ }
586
+ }))
587
+ req , err := http .NewRequest ("GET" , fmt .Sprintf ("http://%s/mcp" , ctx .HttpAddress ), nil )
588
+ if err != nil {
589
+ t .Fatalf ("Failed to create request: %v" , err )
590
+ }
591
+ req .Header .Set ("Authorization" , "Bearer " + tokenBasicNotExpired )
592
+ resp , err := http .DefaultClient .Do (req )
593
+ if err != nil {
594
+ t .Fatalf ("Failed to get protected endpoint: %v" , err )
595
+ }
596
+ t .Cleanup (func () { _ = resp .Body .Close () })
597
+ t .Run ("Protected resource with VALID Authorization header returns 200 - OK" , func (t * testing.T ) {
598
+ if resp .StatusCode != http .StatusOK {
599
+ t .Errorf ("Expected HTTP 200 OK, got %d" , resp .StatusCode )
600
+ }
601
+ })
602
+ })
603
+ }
604
+
605
+ func TestAuthorizationOidcToken (t * testing.T ) {
606
+ key , oidcProvider , httpServer := NewOidcTestServer (t )
607
+ t .Cleanup (httpServer .Close )
608
+ rawClaims := `{
609
+ "iss": "` + httpServer .URL + `",
610
+ "exp": ` + strconv .FormatInt (time .Now ().Add (time .Hour ).Unix (), 10 ) + `,
611
+ "aud": "mcp-server"
612
+ }`
613
+ validOidcToken := oidctest .SignIDToken (key , "test-oidc-key-id" , oidc .RS256 , rawClaims )
614
+ testCaseWithContext (t , & httpContext {StaticConfig : & config.StaticConfig {RequireOAuth : true }, OidcProvider : oidcProvider }, func (ctx * httpContext ) {
615
+ ctx .mockServer .Handle (http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
616
+ if req .URL .EscapedPath () == "/apis/authentication.k8s.io/v1/tokenreviews" {
617
+ w .Header ().Set ("Content-Type" , "application/json" )
618
+ _ , _ = w .Write ([]byte (tokenReviewSuccessful ))
619
+ return
620
+ }
621
+ }))
622
+ req , err := http .NewRequest ("GET" , fmt .Sprintf ("http://%s/mcp" , ctx .HttpAddress ), nil )
623
+ if err != nil {
624
+ t .Fatalf ("Failed to create request: %v" , err )
625
+ }
626
+ req .Header .Set ("Authorization" , "Bearer " + validOidcToken )
627
+ resp , err := http .DefaultClient .Do (req )
628
+ if err != nil {
629
+ t .Fatalf ("Failed to get protected endpoint: %v" , err )
630
+ }
631
+ t .Cleanup (func () { _ = resp .Body .Close () })
632
+ t .Run ("Protected resource with VALID OIDC Authorization header returns 200 - OK" , func (t * testing.T ) {
633
+ if resp .StatusCode != http .StatusOK {
634
+ t .Errorf ("Expected HTTP 200 OK, got %d" , resp .StatusCode )
635
+ }
636
+ })
637
+ })
638
+ }
0 commit comments