-
Notifications
You must be signed in to change notification settings - Fork 221
Expand file tree
/
Copy pathtoolhive.stacklok.dev_mcpexternalauthconfigs.yaml
More file actions
2640 lines (2626 loc) · 152 KB
/
toolhive.stacklok.dev_mcpexternalauthconfigs.yaml
File metadata and controls
2640 lines (2626 loc) · 152 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
labels:
toolhive.stacklok.dev/auto-migrate-storage-version: "true"
name: mcpexternalauthconfigs.toolhive.stacklok.dev
spec:
group: toolhive.stacklok.dev
names:
categories:
- toolhive
kind: MCPExternalAuthConfig
listKind: MCPExternalAuthConfigList
plural: mcpexternalauthconfigs
shortNames:
- extauth
- mcpextauth
singular: mcpexternalauthconfig
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.type
name: Type
type: string
- jsonPath: .status.conditions[?(@.type=='Valid')].status
name: Valid
type: string
- jsonPath: .status.referenceCount
name: References
type: integer
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
deprecated: true
deprecationWarning: toolhive.stacklok.dev/v1alpha1 is deprecated; use v1beta1
name: v1alpha1
schema:
openAPIV3Schema:
description: MCPExternalAuthConfig is the deprecated v1alpha1 version of the
MCPExternalAuthConfig resource.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: |-
MCPExternalAuthConfigSpec defines the desired state of MCPExternalAuthConfig.
MCPExternalAuthConfig resources are namespace-scoped and can only be referenced by
MCPServer resources in the same namespace.
properties:
awsSts:
description: |-
AWSSts configures AWS STS authentication with SigV4 request signing
Only used when Type is "awsSts"
properties:
fallbackRoleArn:
description: |-
FallbackRoleArn is the IAM role ARN to assume when no role mappings match
Used as the default role when RoleMappings is empty or no mapping matches
At least one of FallbackRoleArn or RoleMappings must be configured (enforced by webhook)
pattern: ^arn:(aws|aws-cn|aws-us-gov):iam::\d{12}:role/[\w+=,.@\-_/]+$
type: string
region:
description: Region is the AWS region for the STS endpoint and
service (e.g., "us-east-1", "eu-west-1")
minLength: 1
pattern: ^[a-z]{2}(-[a-z]+)+-\d+$
type: string
roleClaim:
default: groups
description: |-
RoleClaim is the JWT claim to use for role mapping evaluation
Defaults to "groups" to match common OIDC group claims
type: string
roleMappings:
description: |-
RoleMappings defines claim-based role selection rules
Allows mapping JWT claims (e.g., groups, roles) to specific IAM roles
Lower priority values are evaluated first (higher priority)
items:
description: |-
RoleMapping defines a rule for mapping JWT claims to IAM roles.
Mappings are evaluated in priority order (lower number = higher priority), and the first
matching rule determines which IAM role to assume.
Exactly one of Claim or Matcher must be specified.
properties:
claim:
description: |-
Claim is a simple claim value to match against
The claim type is specified by AWSStsConfig.RoleClaim
For example, if RoleClaim is "groups", this would be a group name
Internally compiled to a CEL expression: "<claim_value>" in claims["<role_claim>"]
Mutually exclusive with Matcher
minLength: 1
type: string
matcher:
description: |-
Matcher is a CEL expression for complex matching against JWT claims
The expression has access to a "claims" variable containing all JWT claims as map[string]any
Examples:
- "admins" in claims["groups"]
- claims["sub"] == "user123" && !("act" in claims)
Mutually exclusive with Claim
minLength: 1
type: string
priority:
description: |-
Priority determines evaluation order (lower values = higher priority)
Allows fine-grained control over role selection precedence
When omitted, this mapping has the lowest possible priority and
configuration order acts as tie-breaker via stable sort
format: int32
minimum: 0
type: integer
roleArn:
description: RoleArn is the IAM role ARN to assume when
this mapping matches
pattern: ^arn:(aws|aws-cn|aws-us-gov):iam::\d{12}:role/[\w+=,.@\-_/]+$
type: string
required:
- roleArn
type: object
type: array
x-kubernetes-list-type: atomic
service:
default: aws-mcp
description: |-
Service is the AWS service name for SigV4 signing
Defaults to "aws-mcp" for AWS MCP Server endpoints
type: string
sessionDuration:
default: 3600
description: |-
SessionDuration is the duration in seconds for the STS session
Must be between 900 (15 minutes) and 43200 (12 hours)
Defaults to 3600 (1 hour) if not specified
format: int32
maximum: 43200
minimum: 900
type: integer
sessionNameClaim:
default: sub
description: |-
SessionNameClaim is the JWT claim to use for role session name
Defaults to "sub" to use the subject claim
type: string
subjectProviderName:
description: |-
SubjectProviderName is the name of the upstream provider whose access token
is used as the web identity token for STS AssumeRoleWithWebIdentity.
This field is used exclusively by VirtualMCPServer, where there is no
upstream swap middleware to replace the bearer token before the strategy runs.
When left empty and an embedded authorization server is configured on the
VirtualMCPServer, the controller automatically populates this field with
the first configured upstream provider name. Set it explicitly to override
that default or to select a specific provider when multiple upstreams are
configured.
When no embedded auth server is present, the bearer token from the incoming
request's Authorization header is used instead.
type: string
required:
- region
type: object
bearerToken:
description: |-
BearerToken configures bearer token authentication
Only used when Type is "bearerToken"
properties:
tokenSecretRef:
description: TokenSecretRef references a Kubernetes Secret containing
the bearer token
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
required:
- tokenSecretRef
type: object
embeddedAuthServer:
description: |-
EmbeddedAuthServer configures an embedded OAuth2/OIDC authorization server
Only used when Type is "embeddedAuthServer"
properties:
authorizationEndpointBaseUrl:
description: |-
AuthorizationEndpointBaseURL overrides the base URL used for the authorization_endpoint
in the OAuth discovery document. When set, the discovery document will advertise
`{authorizationEndpointBaseUrl}/oauth/authorize` instead of `{issuer}/oauth/authorize`.
All other endpoints (token, registration, JWKS) remain derived from the issuer.
This is useful when the browser-facing authorization endpoint needs to be on a
different host than the issuer used for backend-to-backend calls.
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash.
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
baselineClientScopes:
description: |-
BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be
included in every client registration. The embedded auth server unions these
scopes into the registered set returned by RFC 7591 Dynamic Client
Registration, so a client that narrows the `scope` field at /oauth/register
can still request the baseline scopes at /oauth/authorize. All values must
be present in the upstream-derived scopesSupported set; the auth server
fails to start if any value is missing.
Security: every client registered via /oauth/register will gain the
ability to request these scopes at /oauth/authorize, regardless of what
the client itself requested. Keep the baseline narrow (typically
"openid" and "offline_access"). Adding a privileged scope here — e.g.
"admin:read" — would grant it to every DCR-registered client, including
public clients like Claude Code, Cursor, and VS Code.
When cimd.enabled is true, every dynamically resolved CIMD client will
also gain the ability to request these scopes, including third-party
clients resolved from arbitrary HTTPS URLs.
items:
minLength: 1
pattern: ^[\x21\x23-\x5B\x5D-\x7E]+$
type: string
maxItems: 10
type: array
x-kubernetes-list-type: atomic
cimd:
description: CIMD configures Client ID Metadata Document support.
When omitted, CIMD is disabled.
properties:
cacheFallbackTtl:
description: |-
CacheFallbackTTL is the fixed TTL applied to every cached CIMD document.
Cache-Control header parsing is not yet implemented; all entries use this value.
Format: Go duration string (e.g. "5m", "10m", "1h").
Defaults to 5 minutes when Enabled is true and this field is omitted.
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
cacheMaxSize:
description: |-
CacheMaxSize is the maximum number of CIMD documents held in the LRU cache.
Defaults to 256 when Enabled is true and this field is omitted.
minimum: 1
type: integer
enabled:
default: false
description: |-
Enabled activates CIMD client lookup. When false (the default), the AS only
accepts client_id values that were registered via DCR.
type: boolean
required:
- enabled
type: object
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
authorization codes and refresh tokens (opaque tokens).
Current secret must be at least 32 bytes and cryptographically random.
Supports secret rotation via multiple entries (first is current, rest are for verification).
If not specified, an ephemeral secret will be auto-generated (development only -
auth codes and refresh tokens will be invalid after restart).
items:
description: SecretKeyRef is a reference to a key within a Secret
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
type: array
x-kubernetes-list-type: atomic
issuer:
description: |-
Issuer is the issuer identifier for this authorization server.
This will be included in the "iss" claim of issued tokens.
Must be a valid HTTPS URL (or HTTP for localhost) without query, fragment, or trailing slash (per RFC 8414).
pattern: ^https?://[^\s?#]+[^/\s?#]$
type: string
primaryUpstreamProvider:
description: |-
PrimaryUpstreamProvider names the upstream IDP whose access token Cedar
should read claims from when authorising a request. Must match the name
of one of the entries in UpstreamProviders. When empty, the controller
auto-selects the first entry of UpstreamProviders.
Only meaningful on VirtualMCPServer, where multiple upstream providers
can be configured and Cedar needs to pick which token's claims to
evaluate. The VirtualMCPServer controller validates this field against
UpstreamProviders at admission and rejects unresolvable values.
On MCPServer and MCPRemoteProxy this field is structurally present (the
EmbeddedAuthServerConfig struct is shared) but has no runtime effect:
those CRDs are restricted to a single upstream so there is no choice to
make. Setting it on those CRDs is silently ignored.
maxLength: 63
minLength: 1
pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
type: string
signingKeySecretRefs:
description: |-
SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.
Supports key rotation by allowing multiple keys (oldest keys are used for verification only).
If not specified, an ephemeral signing key will be auto-generated (development only -
JWTs will be invalid after restart).
items:
description: SecretKeyRef is a reference to a key within a Secret
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
maxItems: 5
type: array
x-kubernetes-list-type: atomic
storage:
description: |-
Storage configures the storage backend for the embedded auth server.
If not specified, defaults to in-memory storage.
properties:
redis:
description: |-
Redis configures the Redis storage backend.
Required when type is "redis".
properties:
aclUserConfig:
description: ACLUserConfig configures Redis ACL user authentication.
properties:
passwordSecretRef:
description: PasswordSecretRef references a Secret
containing the Redis ACL password.
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
usernameSecretRef:
description: |-
UsernameSecretRef references a Secret containing the Redis ACL username.
When omitted, connections use legacy password-only AUTH. Omit for managed
Redis tiers that do not support ACL users (e.g. GCP Memorystore Basic/Standard
HA, Azure Cache for Redis). Set for services that support ACL users (e.g. AWS
ElastiCache non-cluster with Redis 6+ RBAC).
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
required:
- passwordSecretRef
type: object
addr:
description: |-
Addr is the Redis server address (host:port). Required for standalone and cluster modes.
Use for managed Redis services that expose a single endpoint (GCP Memorystore basic tier,
AWS ElastiCache without cluster mode, or cluster-mode services when clusterMode is true).
Mutually exclusive with sentinelConfig.
type: string
clusterMode:
description: |-
ClusterMode enables the Redis Cluster protocol. Set to true when addr points to a
Redis Cluster discovery endpoint (e.g., GCP Memorystore Cluster, AWS ElastiCache
cluster mode enabled). Requires addr to be set.
type: boolean
dialTimeout:
default: 5s
description: |-
DialTimeout is the timeout for establishing connections.
Format: Go duration string (e.g., "5s", "1m").
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
readTimeout:
default: 3s
description: |-
ReadTimeout is the timeout for socket reads.
Format: Go duration string (e.g., "3s", "1m").
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
sentinelConfig:
description: |-
SentinelConfig holds Redis Sentinel configuration.
Use for self-managed Redis with Sentinel-based HA. Mutually exclusive with addr.
properties:
db:
default: 0
description: DB is the Redis database number.
format: int32
type: integer
masterName:
description: MasterName is the name of the Redis master
monitored by Sentinel.
type: string
sentinelAddrs:
description: |-
SentinelAddrs is a list of Sentinel host:port addresses.
Mutually exclusive with SentinelService.
items:
type: string
type: array
x-kubernetes-list-type: atomic
sentinelService:
description: |-
SentinelService enables automatic discovery from a Kubernetes Service.
Mutually exclusive with SentinelAddrs.
properties:
name:
description: Name of the Sentinel Service.
type: string
namespace:
description: Namespace of the Sentinel Service
(defaults to same namespace).
type: string
port:
default: 26379
description: Port of the Sentinel service.
format: int32
type: integer
required:
- name
type: object
required:
- masterName
type: object
sentinelTls:
description: |-
SentinelTLS configures TLS for connections to Sentinel instances.
Only applies when sentinelConfig is set. Presence of this field enables TLS.
properties:
caCertSecretRef:
description: |-
CACertSecretRef references a Secret containing a PEM-encoded CA certificate
for verifying the server. When not specified, system root CAs are used.
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
insecureSkipVerify:
description: |-
InsecureSkipVerify skips TLS certificate verification.
Use when connecting to services with self-signed certificates.
type: boolean
type: object
tls:
description: |-
TLS configures TLS for connections to the Redis/Valkey master.
Presence of this field enables TLS. Omit to use plaintext.
properties:
caCertSecretRef:
description: |-
CACertSecretRef references a Secret containing a PEM-encoded CA certificate
for verifying the server. When not specified, system root CAs are used.
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
insecureSkipVerify:
description: |-
InsecureSkipVerify skips TLS certificate verification.
Use when connecting to services with self-signed certificates.
type: boolean
type: object
writeTimeout:
default: 3s
description: |-
WriteTimeout is the timeout for socket writes.
Format: Go duration string (e.g., "3s", "1m").
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
required:
- aclUserConfig
type: object
x-kubernetes-validations:
- message: exactly one of addr or sentinelConfig must be set
rule: (has(self.addr) && self.addr.size() > 0) != has(self.sentinelConfig)
- message: clusterMode requires addr to be set
rule: '!(has(self.clusterMode) && self.clusterMode) || (has(self.addr)
&& self.addr.size() > 0)'
type:
default: memory
description: |-
Type specifies the storage backend type.
Valid values: "memory" (default), "redis".
enum:
- memory
- redis
type: string
type: object
tokenLifespans:
description: |-
TokenLifespans configures the duration that various tokens are valid.
If not specified, defaults are applied (access: 1h, refresh: 7d, authCode: 10m).
properties:
accessTokenLifespan:
description: |-
AccessTokenLifespan is the duration that access tokens are valid.
Format: Go duration string (e.g., "1h", "30m", "24h").
If empty, defaults to 1 hour.
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
authCodeLifespan:
description: |-
AuthCodeLifespan is the duration that authorization codes are valid.
Format: Go duration string (e.g., "10m", "5m").
If empty, defaults to 10 minutes.
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
refreshTokenLifespan:
description: |-
RefreshTokenLifespan is the duration that refresh tokens are valid.
Format: Go duration string (e.g., "168h", "7d" as "168h").
If empty, defaults to 7 days (168h).
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
type: object
upstreamProviders:
description: |-
UpstreamProviders configures connections to upstream Identity Providers.
The embedded auth server delegates authentication to these providers.
MCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.
items:
description: |-
UpstreamProviderConfig defines configuration for an upstream Identity Provider.
Exactly one of OIDCConfig or OAuth2Config must be set and must match the
declared Type: oidc-typed providers set OIDCConfig, oauth2-typed providers
set OAuth2Config. The CEL rule below enforces the pairing at admission; the
matching Go-level check in validateUpstreamProvider provides defense-in-depth
for stored objects.
The rule is structured as a chain of equality checks ending in an explicit
`false`, so adding a new UpstreamProviderType value without extending this
rule fails admission instead of silently demanding the OAuth2 shape. When
adding a new type, extend both this rule and validateUpstreamProvider.
properties:
name:
description: |-
Name uniquely identifies this upstream provider.
Used for routing decisions and session binding in multi-upstream scenarios.
Must be lowercase alphanumeric with hyphens (DNS-label-like).
maxLength: 63
minLength: 1
pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
type: string
oauth2Config:
description: |-
OAuth2Config contains OAuth 2.0-specific configuration.
Required when Type is "oauth2", must be nil when Type is "oidc".
properties:
additionalAuthorizationParams:
additionalProperties:
type: string
description: |-
AdditionalAuthorizationParams are extra query parameters to include in
authorization requests sent to the upstream provider.
This is useful for providers that require custom parameters, such as
Google's access_type=offline for obtaining refresh tokens.
Framework-managed parameters (response_type, client_id, redirect_uri,
scope, state, code_challenge, code_challenge_method, nonce) are not allowed.
maxProperties: 16
type: object
authorizationEndpoint:
description: AuthorizationEndpoint is the URL for the
OAuth authorization endpoint.
pattern: ^https?://.*$
type: string
clientId:
description: |-
ClientID is the OAuth 2.0 client identifier registered with the upstream IDP.
Mutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained
at runtime via RFC 7591 Dynamic Client Registration and must be left empty.
type: string
clientSecretRef:
description: |-
ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.
Optional for public clients using PKCE instead of client secret.
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
dcrConfig:
description: |-
DCRConfig enables RFC 7591 Dynamic Client Registration against the upstream
authorization server. When set, the client credentials are obtained at
runtime rather than being pre-provisioned, and ClientID must be left empty.
Mutually exclusive with ClientID.
properties:
discoveryUrl:
description: |-
DiscoveryURL is the RFC 8414 / OIDC Discovery document URL. The resolver
issues a single GET against this URL (no well-known-path fallback) and
reads registration_endpoint, authorization_endpoint, token_endpoint,
token_endpoint_auth_methods_supported, and scopes_supported from the
response.
Mutually exclusive with RegistrationEndpoint.
HTTPS is required because the registration endpoint resolved from this
document carries the initial access token and the issued client_secret
(RFC 7591 §3, RFC 8414 §3). MaxLength is a defensive size cap (etcd
object budget, regex evaluation cost) and matches the conventional URL
length cap.
maxLength: 2048
pattern: ^https://[^\s?#]+[^/\s?#]$
type: string
initialAccessTokenRef:
description: |-
InitialAccessTokenRef is an optional reference to a Kubernetes Secret
carrying an RFC 7591 §3 initial access token. When set, the resolver
presents the token value as a Bearer credential on the registration
request. Mirrors the ClientSecretRef pattern.
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
registrationEndpoint:
description: |-
RegistrationEndpoint is the RFC 7591 registration endpoint URL used
directly, bypassing discovery. When using this field, the caller is
expected to also supply AuthorizationEndpoint, TokenEndpoint, and an
explicit Scopes list on the parent OAuth2UpstreamConfig.
Mutually exclusive with DiscoveryURL.
HTTPS is required because the registration endpoint carries the initial
access token and the issued client_secret (RFC 7591 §3, RFC 8414 §3).
MaxLength is a defensive size cap (etcd object budget, regex evaluation
cost) and matches the conventional URL length cap.
maxLength: 2048
pattern: ^https://[^\s?#]+[^/\s?#]$
type: string
softwareId:
description: |-
SoftwareID is the RFC 7591 "software_id" registration metadata value,
identifying the client software independent of any particular
registration instance. Typically a UUID or short identifier.
maxLength: 255
type: string
softwareStatement:
description: |-
SoftwareStatement is the RFC 7591 "software_statement" JWT asserting
metadata about the client software, signed by a party the authorization
server trusts.
Stored inline on the CR. The JWT is signed but not encrypted, so its
contents are visible to anyone with get/list/watch on this resource and
appear in etcd backups in plaintext. Treat the value as non-confidential
(signed attestation, not a secret). Operators that rotate software
statements like bearer credentials should keep them at the authorization
server side and rely on the registration endpoint's initial access
token (see InitialAccessTokenRef) instead of placing them on the CR.
Bounded to 16384 characters as a defensive size cap (etcd object
budget, regex evaluation cost). Real-world signed statements with
embedded x5c certificate chains, JWKS keys, or OIDC-Federation
trust-framework metadata routinely exceed 4 KB.
maxLength: 16384
type: string
type: object
x-kubernetes-validations:
- message: exactly one of discoveryUrl or registrationEndpoint
must be set
rule: has(self.discoveryUrl) != has(self.registrationEndpoint)
identityFromToken:
description: |-
IdentityFromToken extracts user identity (subject, name, email) directly
from the OAuth2 token-endpoint response body using gjson dot-notation paths.
When set, the embedded auth server skips the userinfo HTTP call entirely
and resolves identity from the token response. See IdentityFromTokenConfig
for trust-model and uniqueness considerations.
properties:
emailPath:
description: |-
EmailPath is the dot-notation path to the email address field in the token response.
If not specified or if the path does not resolve to a string, the email is omitted.
Omit the field entirely rather than setting it to an empty string.
maxLength: 256
minLength: 1
type: string
namePath:
description: |-
NamePath is the dot-notation path to the display name field in the token response.
If not specified or if the path does not resolve to a string, the display name is omitted.
Omit the field entirely rather than setting it to an empty string.
maxLength: 256
minLength: 1
type: string
subjectPath:
description: |-
SubjectPath is the dot-notation path to the subject (user ID) field in the token response.
Warning: claims read from the token response are trusted only via TLS, not
cryptographically verified; prefer OIDC ID tokens when verifiable claims are required.
Example: "authed_user.id" for Slack (top-level token-response field). For providers
whose token response embeds the access token as a JWT (e.g. Snowflake), use the
"@upstreamjwt" modifier to decode the payload, e.g. "access_token|@upstreamjwt|sub".
The "@upstreamjwt" modifier performs no signature verification either.
maxLength: 256
minLength: 1
type: string
required:
- subjectPath
type: object
redirectUri:
description: |-
RedirectURI is the callback URL where the upstream IDP will redirect after authentication.
When not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the
URL associated with the resource (e.g., MCPServer or vMCP) using this config.
type: string
scopes:
description: Scopes are the OAuth scopes to request
from the upstream IDP.
items:
type: string
type: array
x-kubernetes-list-type: atomic
tokenEndpoint:
description: TokenEndpoint is the URL for the OAuth
token endpoint.
pattern: ^https?://.*$
type: string
tokenResponseMapping:
description: |-
TokenResponseMapping configures custom field extraction from non-standard token responses.
Some OAuth providers (e.g., GovSlack) nest token fields under non-standard paths
instead of returning them at the top level. When set, ToolHive performs the token
exchange HTTP call directly and extracts fields using the configured dot-notation paths.
If nil, standard OAuth 2.0 token response parsing is used.
For extracting user identity from the token response, see IdentityFromToken.
properties:
accessTokenPath:
description: |-
AccessTokenPath is the dot-notation path to the access token in the response.
Example: "authed_user.access_token"
minLength: 1
type: string
expiresInPath:
description: |-
ExpiresInPath is the dot-notation path to the expires_in value (in seconds).
If not specified, defaults to "expires_in".
type: string
refreshTokenPath:
description: |-
RefreshTokenPath is the dot-notation path to the refresh token in the response.
If not specified, defaults to "refresh_token".
type: string
scopePath:
description: |-
ScopePath is the dot-notation path to the scope string in the response.
If not specified, defaults to "scope".
type: string
required:
- accessTokenPath
type: object
userInfo:
description: |-
UserInfo contains configuration for fetching user information from the upstream provider.
When omitted and IdentityFromToken is also unset, the embedded auth server runs in
synthesis mode for this upstream: a non-PII subject derived from the access token, no
Name/Email. Use this shape for upstreams with no userinfo surface and no identity in
the token response (e.g., MCP authorization servers per the MCP spec). When
IdentityFromToken is set instead, identity is resolved from the token response body
(e.g., Snowflake's "username" field, Slack's "authed_user.id"); the userinfo HTTP call
is skipped entirely.
properties:
additionalHeaders:
additionalProperties:
type: string
description: |-
AdditionalHeaders contains extra headers to include in the userinfo request.
Useful for providers that require specific headers (e.g., GitHub's Accept header).
type: object
endpointUrl:
description: EndpointURL is the URL of the userinfo
endpoint.
pattern: ^https?://.*$
type: string
fieldMapping:
description: |-
FieldMapping contains custom field mapping configuration for non-standard providers.
If nil, standard OIDC field names are used ("sub", "name", "email").
properties:
emailFields:
description: |-
EmailFields is an ordered list of field names to try for the email address.
The first non-empty value found will be used.
Default: ["email"]
items:
type: string
type: array
x-kubernetes-list-type: atomic
nameFields:
description: |-
NameFields is an ordered list of field names to try for the display name.
The first non-empty value found will be used.
Default: ["name"]
items:
type: string
type: array
x-kubernetes-list-type: atomic
subjectFields:
description: |-
SubjectFields is an ordered list of field names to try for the user ID.
The first non-empty value found will be used.
Default: ["sub"]
items:
type: string
type: array
x-kubernetes-list-type: atomic
type: object
httpMethod:
description: |-
HTTPMethod is the HTTP method to use for the userinfo request.
If not specified, defaults to GET.
enum:
- GET
- POST
type: string
required:
- endpointUrl
type: object
required:
- authorizationEndpoint
- tokenEndpoint
type: object
x-kubernetes-validations:
- message: exactly one of clientId or dcrConfig must be
set
rule: '(has(self.clientId) && size(self.clientId) > 0)
? !has(self.dcrConfig) : has(self.dcrConfig)'
- message: clientSecretRef must not be set when dcrConfig
is set; the client_secret is obtained at runtime via
Dynamic Client Registration
rule: '!(has(self.dcrConfig) && has(self.clientSecretRef))'
oidcConfig:
description: |-
OIDCConfig contains OIDC-specific configuration.
Required when Type is "oidc", must be nil when Type is "oauth2".
properties:
additionalAuthorizationParams:
additionalProperties:
type: string
description: |-
AdditionalAuthorizationParams are extra query parameters to include in
authorization requests sent to the upstream provider.
This is useful for providers that require custom parameters, such as
Google's access_type=offline for obtaining refresh tokens.
Note: when using access_type=offline, also set explicit scopes to avoid
the default offline_access scope being sent alongside it.
Framework-managed parameters (response_type, client_id, redirect_uri,
scope, state, code_challenge, code_challenge_method, nonce) are not allowed.
maxProperties: 16
type: object
clientId:
description: ClientID is the OAuth 2.0 client identifier
registered with the upstream IDP.
type: string
clientSecretRef:
description: |-
ClientSecretRef references a Kubernetes Secret containing the OAuth 2.0 client secret.
Optional for public clients using PKCE instead of client secret.
properties:
key:
description: Key is the key within the secret
type: string
name:
description: Name is the name of the secret
type: string
required:
- key
- name
type: object
issuerUrl:
description: |-
IssuerURL is the OIDC issuer URL for automatic endpoint discovery.
Must be a valid HTTPS URL.
pattern: ^https://.*$
type: string
redirectUri:
description: |-
RedirectURI is the callback URL where the upstream IDP will redirect after authentication.
When not specified, defaults to `{resourceUrl}/oauth/callback` where `resourceUrl` is the
URL associated with the resource (e.g., MCPServer or vMCP) using this config.
type: string
scopes:
description: |-
Scopes are the OAuth scopes to request from the upstream IDP.
If not specified, defaults to ["openid", "offline_access"].
When using additionalAuthorizationParams with provider-specific refresh token
mechanisms (e.g., Google's access_type=offline), set explicit scopes to avoid
sending both offline_access and the provider-specific parameter.
items:
type: string
type: array
x-kubernetes-list-type: atomic
userInfoOverride:
description: |-
UserInfoOverride allows customizing UserInfo fetching behavior for OIDC providers.
By default, the UserInfo endpoint is discovered automatically via OIDC discovery.
Use this to override the endpoint URL, HTTP method, or field mappings for providers
that return non-standard claim names in their UserInfo response.
properties:
additionalHeaders:
additionalProperties:
type: string
description: |-
AdditionalHeaders contains extra headers to include in the userinfo request.
Useful for providers that require specific headers (e.g., GitHub's Accept header).
type: object
endpointUrl:
description: EndpointURL is the URL of the userinfo
endpoint.
pattern: ^https?://.*$
type: string
fieldMapping:
description: |-
FieldMapping contains custom field mapping configuration for non-standard providers.
If nil, standard OIDC field names are used ("sub", "name", "email").
properties:
emailFields:
description: |-
EmailFields is an ordered list of field names to try for the email address.
The first non-empty value found will be used.
Default: ["email"]
items:
type: string
type: array
x-kubernetes-list-type: atomic
nameFields:
description: |-
NameFields is an ordered list of field names to try for the display name.
The first non-empty value found will be used.
Default: ["name"]
items:
type: string
type: array
x-kubernetes-list-type: atomic
subjectFields: