-
Notifications
You must be signed in to change notification settings - Fork 177
feat(auth): introduce scoped based authorization #224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,8 @@ import ( | |
| "github.com/containers/kubernetes-mcp-server/pkg/version" | ||
| ) | ||
|
|
||
| const TokenScopesContextKey = "TokenScopesContextKey" | ||
|
|
||
| type Configuration struct { | ||
| Profile Profile | ||
| ListOutput output.Output | ||
|
|
@@ -45,20 +47,29 @@ func (c *Configuration) isToolApplicable(tool server.ServerTool) bool { | |
| type Server struct { | ||
| configuration *Configuration | ||
| server *server.MCPServer | ||
| enabledTools []string | ||
| k *internalk8s.Manager | ||
| } | ||
|
|
||
| func NewServer(configuration Configuration) (*Server, error) { | ||
| var serverOptions []server.ServerOption | ||
| serverOptions = append(serverOptions, | ||
| server.WithResourceCapabilities(true, true), | ||
| server.WithPromptCapabilities(true), | ||
| server.WithToolCapabilities(true), | ||
| server.WithLogging(), | ||
| server.WithToolHandlerMiddleware(toolCallLoggingMiddleware), | ||
| ) | ||
| if configuration.StaticConfig.RequireOAuth { | ||
| serverOptions = append(serverOptions, server.WithToolHandlerMiddleware(toolScopedAuthorizationMiddleware)) | ||
| } | ||
|
|
||
| s := &Server{ | ||
| configuration: &configuration, | ||
| server: server.NewMCPServer( | ||
| version.BinaryName, | ||
| version.Version, | ||
| server.WithResourceCapabilities(true, true), | ||
| server.WithPromptCapabilities(true), | ||
| server.WithToolCapabilities(true), | ||
| server.WithLogging(), | ||
| server.WithToolHandlerMiddleware(toolCallLoggingMiddleware), | ||
| serverOptions..., | ||
| ), | ||
| } | ||
| if err := s.reloadKubernetesClient(); err != nil { | ||
|
|
@@ -81,6 +92,7 @@ func (s *Server) reloadKubernetesClient() error { | |
| continue | ||
| } | ||
| applicableTools = append(applicableTools, tool) | ||
| s.enabledTools = append(s.enabledTools, tool.Tool.Name) | ||
| } | ||
| s.server.SetTools(applicableTools...) | ||
| return nil | ||
|
|
@@ -125,6 +137,10 @@ func (s *Server) GetKubernetesAPIServerHost() string { | |
| return s.k.GetAPIServerHost() | ||
| } | ||
|
|
||
| func (s *Server) GetEnabledTools() []string { | ||
| return s.enabledTools | ||
| } | ||
|
|
||
| func (s *Server) Close() { | ||
| if s.k != nil { | ||
| s.k.Close() | ||
|
|
@@ -181,3 +197,16 @@ func toolCallLoggingMiddleware(next server.ToolHandlerFunc) server.ToolHandlerFu | |
| return next(ctx, ctr) | ||
| } | ||
| } | ||
|
|
||
| func toolScopedAuthorizationMiddleware(next server.ToolHandlerFunc) server.ToolHandlerFunc { | ||
| return func(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||
| scopes, ok := ctx.Value(TokenScopesContextKey).([]string) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @manusa although we agreed that we'll add this scoped based validation in authorization.go rather than here, mcp clients all failed to handle the flow. I think, apart from here all the rest is a clear indication of model context protocol violation. After adding the scope based check in tool call middleware, it simply works. |
||
| if !ok { | ||
| return NewTextResult("", fmt.Errorf("Authorization failed: Access denied: Tool '%s' requires scope 'mcp:%s' but no scope is available", ctr.Params.Name, ctr.Params.Name)), nil | ||
| } | ||
| if !slices.Contains(scopes, "mcp:"+ctr.Params.Name) && !slices.Contains(scopes, ctr.Params.Name) { | ||
| return NewTextResult("", fmt.Errorf("Authorization failed: Access denied: Tool '%s' requires scope 'mcp:%s' but only scopes %s are available", ctr.Params.Name, ctr.Params.Name, scopes)), nil | ||
| } | ||
| return next(ctx, ctr) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, server url field is used for arbitrary audience rather than a structured URL format. I'm removing this validation (and its test). But if we decide to force URL format in the future, we'll need to revert this change (and its test).