Skip to content

Commit 0ad8726

Browse files
authored
feat(auth): introduce jwks url flag to be published in oauth metadata (#197)
1 parent ca0aa46 commit 0ad8726

File tree

3 files changed

+83
-23
lines changed

3 files changed

+83
-23
lines changed

pkg/config/config.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ type StaticConfig struct {
1919
// When true, expose only tools annotated with readOnlyHint=true
2020
ReadOnly bool `toml:"read_only,omitempty"`
2121
// When true, disable tools annotated with destructiveHint=true
22-
DisableDestructive bool `toml:"disable_destructive,omitempty"`
23-
EnabledTools []string `toml:"enabled_tools,omitempty"`
24-
DisabledTools []string `toml:"disabled_tools,omitempty"`
25-
RequireOAuth bool `toml:"require_oauth,omitempty"`
26-
AuthorizationURL string `toml:"authorization_url,omitempty"`
27-
ServerURL string `toml:"server_url,omitempty"`
22+
DisableDestructive bool `toml:"disable_destructive,omitempty"`
23+
EnabledTools []string `toml:"enabled_tools,omitempty"`
24+
DisabledTools []string `toml:"disabled_tools,omitempty"`
25+
RequireOAuth bool `toml:"require_oauth,omitempty"`
26+
AuthorizationURL string `toml:"authorization_url,omitempty"`
27+
JwksURL string `toml:"jwks_url,omitempty"`
28+
CertificateAuthority string `toml:"certificate_authority,omitempty"`
29+
ServerURL string `toml:"server_url,omitempty"`
2830
}
2931

3032
type GroupVersionKind struct {

pkg/http/http.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func Serve(ctx context.Context, mcpServer *mcp.Server, staticConfig *config.Stat
6060

6161
response := map[string]interface{}{
6262
"authorization_servers": authServers,
63+
"authorization_server": authServers[0],
6364
"scopes_supported": []string{},
6465
"bearer_methods_supported": []string{"header"},
6566
}
@@ -68,6 +69,10 @@ func Serve(ctx context.Context, mcpServer *mcp.Server, staticConfig *config.Stat
6869
response["resource"] = staticConfig.ServerURL
6970
}
7071

72+
if staticConfig.JwksURL != "" {
73+
response["jwks_uri"] = staticConfig.JwksURL
74+
}
75+
7176
w.WriteHeader(http.StatusOK)
7277
if err := json.NewEncoder(w).Encode(response); err != nil {
7378
http.Error(w, err.Error(), http.StatusInternalServerError)

pkg/kubernetes-mcp-server/cmd/root.go

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package cmd
22

33
import (
44
"context"
5+
"crypto/tls"
6+
"crypto/x509"
57
"errors"
68
"flag"
79
"fmt"
10+
"net/http"
811
"net/url"
12+
"os"
913
"strconv"
1014
"strings"
1115

@@ -46,20 +50,22 @@ kubernetes-mcp-server --port 8443 --sse-base-url https://example.com:8443
4650
)
4751

4852
type MCPServerOptions struct {
49-
Version bool
50-
LogLevel int
51-
Port string
52-
SSEPort int
53-
HttpPort int
54-
SSEBaseUrl string
55-
Kubeconfig string
56-
Profile string
57-
ListOutput string
58-
ReadOnly bool
59-
DisableDestructive bool
60-
RequireOAuth bool
61-
AuthorizationURL string
62-
ServerURL string
53+
Version bool
54+
LogLevel int
55+
Port string
56+
SSEPort int
57+
HttpPort int
58+
SSEBaseUrl string
59+
Kubeconfig string
60+
Profile string
61+
ListOutput string
62+
ReadOnly bool
63+
DisableDestructive bool
64+
RequireOAuth bool
65+
AuthorizationURL string
66+
JwksURL string
67+
CertificateAuthority string
68+
ServerURL string
6369

6470
ConfigPath string
6571
StaticConfig *config.StaticConfig
@@ -116,8 +122,13 @@ func NewMCPServer(streams genericiooptions.IOStreams) *cobra.Command {
116122
_ = cmd.Flags().MarkHidden("require-oauth")
117123
cmd.Flags().StringVar(&o.AuthorizationURL, "authorization-url", o.AuthorizationURL, "OAuth authorization server URL for protected resource endpoint. If not provided, the Kubernetes API server host will be used. Only valid if require-oauth is enabled.")
118124
_ = cmd.Flags().MarkHidden("authorization-url")
125+
cmd.Flags().StringVar(&o.JwksURL, "jwks-url", o.JwksURL, "OAuth JWKS server URL for protected resource endpoint. Only valid if require-oauth is enabled.")
126+
_ = cmd.Flags().MarkHidden("jwks-url")
119127
cmd.Flags().StringVar(&o.ServerURL, "server-url", o.ServerURL, "Server URL of this application. Optional. If set, this url will be served in protected resource metadata endpoint and tokens will be validated with this audience. If not set, expected audience is kubernetes-mcp-server. Only valid if require-oauth is enabled.")
120128
_ = cmd.Flags().MarkHidden("server-url")
129+
cmd.Flags().StringVar(&o.CertificateAuthority, "certificate-authority", o.CertificateAuthority, "Certificate authority path to verify certificates. Optional. Only valid if require-oauth is enabled.")
130+
_ = cmd.Flags().MarkHidden("certificate-authority")
131+
121132
return cmd
122133
}
123134

@@ -174,9 +185,15 @@ func (m *MCPServerOptions) loadFlags(cmd *cobra.Command) {
174185
if cmd.Flag("authorization-url").Changed {
175186
m.StaticConfig.AuthorizationURL = m.AuthorizationURL
176187
}
188+
if cmd.Flag("jwks-url").Changed {
189+
m.StaticConfig.JwksURL = m.JwksURL
190+
}
177191
if cmd.Flag("server-url").Changed {
178192
m.StaticConfig.ServerURL = m.ServerURL
179193
}
194+
if cmd.Flag("certificate-authority").Changed {
195+
m.StaticConfig.CertificateAuthority = m.CertificateAuthority
196+
}
180197
}
181198

182199
func (m *MCPServerOptions) initializeLogging() {
@@ -195,8 +212,8 @@ func (m *MCPServerOptions) Validate() error {
195212
if m.Port != "" && (m.SSEPort > 0 || m.HttpPort > 0) {
196213
return fmt.Errorf("--port is mutually exclusive with deprecated --http-port and --sse-port flags")
197214
}
198-
if !m.StaticConfig.RequireOAuth && (m.StaticConfig.AuthorizationURL != "" || m.StaticConfig.ServerURL != "") {
199-
return fmt.Errorf("authorization-url and server-url are only valid if require-oauth is enabled")
215+
if !m.StaticConfig.RequireOAuth && (m.StaticConfig.AuthorizationURL != "" || m.StaticConfig.ServerURL != "" || m.StaticConfig.JwksURL != "" || m.StaticConfig.CertificateAuthority != "") {
216+
return fmt.Errorf("authorization-url, server-url, certificate-authority and jwks-url are only valid if require-oauth is enabled. Missing --port may implicitly set require-oauth to false")
200217
}
201218
if m.StaticConfig.AuthorizationURL != "" {
202219
u, err := url.Parse(m.StaticConfig.AuthorizationURL)
@@ -222,6 +239,18 @@ func (m *MCPServerOptions) Validate() error {
222239
klog.Warningf("server-url is using http://, this is not recommended production use")
223240
}
224241
}
242+
if m.StaticConfig.JwksURL != "" {
243+
u, err := url.Parse(m.StaticConfig.JwksURL)
244+
if err != nil {
245+
return err
246+
}
247+
if u.Scheme != "https" && u.Scheme != "http" {
248+
return fmt.Errorf("--jwks-url must be a valid URL")
249+
}
250+
if u.Scheme == "http" {
251+
klog.Warningf("jwks-url is using http://, this is not recommended production use")
252+
}
253+
}
225254
return nil
226255
}
227256

@@ -248,7 +277,31 @@ func (m *MCPServerOptions) Run() error {
248277

249278
var oidcProvider *oidc.Provider
250279
if m.StaticConfig.AuthorizationURL != "" {
251-
provider, err := oidc.NewProvider(context.TODO(), m.StaticConfig.AuthorizationURL)
280+
ctx := context.Background()
281+
if m.StaticConfig.CertificateAuthority != "" {
282+
httpClient := &http.Client{}
283+
caCert, err := os.ReadFile(m.StaticConfig.CertificateAuthority)
284+
if err != nil {
285+
return fmt.Errorf("failed to read CA certificate from %s: %w", m.StaticConfig.CertificateAuthority, err)
286+
}
287+
caCertPool := x509.NewCertPool()
288+
if !caCertPool.AppendCertsFromPEM(caCert) {
289+
return fmt.Errorf("failed to append CA certificate from %s to pool", m.StaticConfig.CertificateAuthority)
290+
}
291+
292+
if caCertPool.Equal(x509.NewCertPool()) {
293+
caCertPool = nil
294+
}
295+
296+
transport := &http.Transport{
297+
TLSClientConfig: &tls.Config{
298+
RootCAs: caCertPool,
299+
},
300+
}
301+
httpClient.Transport = transport
302+
ctx = oidc.ClientContext(ctx, httpClient)
303+
}
304+
provider, err := oidc.NewProvider(ctx, m.StaticConfig.AuthorizationURL)
252305
if err != nil {
253306
return fmt.Errorf("unable to setup OIDC provider: %w", err)
254307
}

0 commit comments

Comments
 (0)