mirror of
https://github.com/containers/kubernetes-mcp-server.git
synced 2025-10-23 01:22:57 +03:00
feat(auth): implement proxied /.well-known/oauth-authorization-server (#244)
Signed-off-by: Marc Nuri <marc@marcnuri.com>
This commit is contained in:
@@ -22,7 +22,7 @@ const (
|
|||||||
func AuthorizationMiddleware(requireOAuth bool, serverURL string, oidcProvider *oidc.Provider, mcpServer *mcp.Server) func(http.Handler) http.Handler {
|
func AuthorizationMiddleware(requireOAuth bool, serverURL string, oidcProvider *oidc.Provider, mcpServer *mcp.Server) func(http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == healthEndpoint || r.URL.Path == oauthProtectedResourceEndpoint {
|
if r.URL.Path == healthEndpoint || r.URL.Path == oauthProtectedResourceEndpoint || r.URL.Path == oauthAuthorizationServerEndpoint {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func Serve(ctx context.Context, mcpServer *mcp.Server, staticConfig *config.Stat
|
|||||||
mux.HandleFunc(healthEndpoint, func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc(healthEndpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
|
mux.HandleFunc(oauthAuthorizationServerEndpoint, OAuthAuthorizationServerHandler(staticConfig))
|
||||||
mux.HandleFunc(oauthProtectedResourceEndpoint, OAuthProtectedResourceHandler(mcpServer, staticConfig))
|
mux.HandleFunc(oauthProtectedResourceEndpoint, OAuthProtectedResourceHandler(mcpServer, staticConfig))
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|||||||
@@ -286,6 +286,36 @@ func TestHealthCheck(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWellKnownOAuthAuthorizationServer(t *testing.T) {
|
||||||
|
// Simple http server to mock the authorization server
|
||||||
|
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/.well-known/oauth-authorization-server" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"issuer": "https://example.com"}`))
|
||||||
|
}))
|
||||||
|
t.Cleanup(testServer.Close)
|
||||||
|
testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{AuthorizationURL: testServer.URL, RequireOAuth: true}}, func(ctx *httpContext) {
|
||||||
|
resp, err := http.Get(fmt.Sprintf("http://%s/.well-known/oauth-authorization-server", ctx.HttpAddress))
|
||||||
|
t.Cleanup(func() { _ = resp.Body.Close() })
|
||||||
|
t.Run("Exposes .well-known/oauth-authorization-server endpoint", func(t *testing.T) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get .well-known/oauth-authorization-server endpoint: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Expected HTTP 200 OK, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run(".well-known/oauth-authorization-server returns application/json content type", func(t *testing.T) {
|
||||||
|
if resp.Header.Get("Content-Type") != "application/json" {
|
||||||
|
t.Errorf("Expected Content-Type application/json, got %s", resp.Header.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestWellKnownOAuthProtectedResource(t *testing.T) {
|
func TestWellKnownOAuthProtectedResource(t *testing.T) {
|
||||||
testCase(t, func(ctx *httpContext) {
|
testCase(t, func(ctx *httpContext) {
|
||||||
resp, err := http.Get(fmt.Sprintf("http://%s/.well-known/oauth-protected-resource", ctx.HttpAddress))
|
resp, err := http.Get(fmt.Sprintf("http://%s/.well-known/oauth-protected-resource", ctx.HttpAddress))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/containers/kubernetes-mcp-server/pkg/config"
|
"github.com/containers/kubernetes-mcp-server/pkg/config"
|
||||||
@@ -9,9 +10,42 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
oauthProtectedResourceEndpoint = "/.well-known/oauth-protected-resource"
|
oauthAuthorizationServerEndpoint = "/.well-known/oauth-authorization-server"
|
||||||
|
oauthProtectedResourceEndpoint = "/.well-known/oauth-protected-resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func OAuthAuthorizationServerHandler(staticConfig *config.StaticConfig) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if staticConfig.AuthorizationURL == "" {
|
||||||
|
http.Error(w, "Authorization URL is not configured", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(r.Method, staticConfig.AuthorizationURL+oauthAuthorizationServerEndpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to create request: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(req.WithContext(r.Context()))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to perform request: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to read response body: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for key, values := range resp.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
w.Header().Add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
_, _ = w.Write(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func OAuthProtectedResourceHandler(mcpServer *mcp.Server, staticConfig *config.StaticConfig) http.HandlerFunc {
|
func OAuthProtectedResourceHandler(mcpServer *mcp.Server, staticConfig *config.StaticConfig) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|||||||
Reference in New Issue
Block a user