@@ -19,11 +19,11 @@ import (
1919 "testing"
2020 "time"
2121
22+ "github.com/containers/kubernetes-mcp-server/internal/test"
2223 "github.com/coreos/go-oidc/v3/oidc"
2324 "github.com/coreos/go-oidc/v3/oidc/oidctest"
2425 "golang.org/x/sync/errgroup"
2526 "k8s.io/client-go/tools/clientcmd"
26- "k8s.io/client-go/tools/clientcmd/api"
2727 "k8s.io/klog/v2"
2828 "k8s.io/klog/v2/textlogger"
2929
@@ -33,6 +33,7 @@ import (
3333
3434type httpContext struct {
3535 klogState klog.State
36+ mockServer * test.MockServer
3637 LogBuffer bytes.Buffer
3738 HttpAddress string // HTTP server address
3839 timeoutCancel context.CancelFunc // Release resources if test completes before the timeout
@@ -42,21 +43,31 @@ type httpContext struct {
4243 OidcProvider * oidc.Provider
4344}
4445
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+
4560func (c * httpContext ) beforeEach (t * testing.T ) {
4661 t .Helper ()
4762 http .DefaultClient .Timeout = 10 * time .Second
4863 if c .StaticConfig == nil {
4964 c .StaticConfig = & config.StaticConfig {}
5065 }
66+ c .mockServer = test .NewMockServer ()
5167 // 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 ()
5869 kubeConfig := filepath .Join (t .TempDir (), "config" )
59- _ = clientcmd .WriteToFile (* fakeConfig , kubeConfig )
70+ _ = clientcmd .WriteToFile (* mockKubeConfig , kubeConfig )
6071 _ = os .Setenv ("KUBECONFIG" , kubeConfig )
6172 // Capture logging
6273 c .klogState = klog .CaptureState ()
@@ -100,6 +111,7 @@ func (c *httpContext) beforeEach(t *testing.T) {
100111
101112func (c * httpContext ) afterEach (t * testing.T ) {
102113 t .Helper ()
114+ c .mockServer .Close ()
103115 c .StopServer ()
104116 err := c .WaitForShutdown ()
105117 if err != nil {
@@ -546,3 +558,81 @@ func TestAuthorizationUnauthorized(t *testing.T) {
546558 })
547559 })
548560}
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