Skip to content

Commit 2fd9ed9

Browse files
committed
feat(jwt-bearer): Add support for JWT Bearer grants
Fixes #208. This change introduces support for JWT Bearer grants (RFC 7523). The Auth Manager already has support fur JWT client auth, also introduced by RFC 7523. In order to clarify the configuration, the client auth part of RFC 7523 has been relocated from `rest.auth.oauth2.client-assertion.jwt.*` to `rest.auth.oauth2.client-auth.jwt.*`.
1 parent fe42bd3 commit 2fd9ed9

43 files changed

Lines changed: 1607 additions & 237 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,34 @@ The Dremio AuthManager for Apache Iceberg supports several client authentication
5252
[Client Authentication](./client-authentication.md) section for more details on how to configure
5353
client authentication.
5454

55-
## Impersonation & Delegation
55+
## Grant Types
56+
57+
The Dremio AuthManager for Apache Iceberg supports several OAuth2 grant types:
58+
59+
* Client Credentials Grant ([RFC 6749, Section 4.4](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4))
60+
* Authorization Code Grant ([RFC 6749, Section 4.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1))
61+
* Device Authorization Grant ([RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628))
62+
* [Token Exchange Grant](./token-exchange.md) ([RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693))
63+
* [JWT Bearer Grant](./jwt-bearer.md) ([RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523))
64+
65+
The Dremio AuthManager also supports the Resource Owner Password Credentials Grant
66+
([RFC 6749, Section 4.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3)), but this grant
67+
type is deprecated and should be avoided if possible.
68+
69+
See the [Configuration](./configuration.md) section for more details on how to configure grant
70+
types.
71+
72+
### Impersonation & Delegation
5673

5774
The Dremio AuthManager for Apache Iceberg supports impersonation and delegation using the token
5875
exchange grant type. See the [Token Exchange](./token-exchange.md) section for more details on how
5976
to configure impersonation and delegation.
6077

78+
### Assertion Grants
79+
80+
The Dremio AuthManager for Apache Iceberg supports JWT assertion grants with static or dynamic
81+
assertions. See the [JWT Bearer Grant](./jwt-bearer.md) section for more details.
82+
6183
## Migration From Iceberg's Built-In OAuth2 `AuthManager`
6284

6385
Migrating from Iceberg's built-in OAuth2 `AuthManager` to the Dremio AuthManager for Apache Iceberg

docs/client-authentication.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Example configuration:
8989
rest.auth.oauth2.client-auth=client_secret_jwt
9090
rest.auth.oauth2.client-id=my-client
9191
rest.auth.oauth2.client-secret=my-secret
92-
rest.auth.oauth2.client-assertion.jwt.algorithm=HMAC_SHA256
92+
rest.auth.oauth2.client-auth.jwt.algorithm=HMAC_SHA256
9393
```
9494

9595
#### `private_key_jwt`
@@ -101,11 +101,11 @@ Example configuration:
101101
```properties
102102
rest.auth.oauth2.client-auth=private_key_jwt
103103
rest.auth.oauth2.client-id=my-client
104-
rest.auth.oauth2.client-assertion.jwt.algorithm=RSA_SHA256
105-
rest.auth.oauth2.client-assertion.jwt.private-key=/path/to/private_key.pem
104+
rest.auth.oauth2.client-auth.jwt.algorithm=RSA_SHA256
105+
rest.auth.oauth2.client-auth.jwt.private-key=/path/to/private_key.pem
106106
```
107107

108-
When using this method, the private key file must be provided using the `rest.auth.oauth2.client-assertion.jwt.private-key` property.
108+
When using this method, the private key file must be provided using the `rest.auth.oauth2.client-auth.jwt.private-key` property.
109109

110110
The file must be in PEM format. It may contain multiple objects; the first private key encountered
111111
in the file is used.
@@ -134,16 +134,16 @@ The JWT assertion includes the following claims:
134134
Each of these claims (except `iat`) can be customized using the following configuration properties:
135135

136136
```properties
137-
rest.auth.oauth2.client-assertion.jwt.issuer=my-issuer
138-
rest.auth.oauth2.client-assertion.jwt.subject=my-subject
139-
rest.auth.oauth2.client-assertion.jwt.audience=https://example.com/token
140-
rest.auth.oauth2.client-assertion.jwt.token-lifespan=PT10M
137+
rest.auth.oauth2.client-auth.jwt.issuer=my-issuer
138+
rest.auth.oauth2.client-auth.jwt.subject=my-subject
139+
rest.auth.oauth2.client-auth.jwt.audience=https://example.com/token
140+
rest.auth.oauth2.client-auth.jwt.token-lifespan=PT10M
141141
```
142142

143-
The signing algorithm can be specified using the `rest.auth.oauth2.client-assertion.jwt.algorithm` property. The default is `HMAC_SHA512` for `client_secret_jwt` and `RSA_SHA512` for `private_key_jwt` (algorithm names are case-insensitive). Example:
143+
The signing algorithm can be specified using the `rest.auth.oauth2.client-auth.jwt.algorithm` property. The default is `HMAC_SHA512` for `client_secret_jwt` and `RSA_SHA512` for `private_key_jwt` (algorithm names are case-insensitive). Example:
144144

145145
```properties
146-
rest.auth.oauth2.client-assertion.jwt.algorithm=HMAC_SHA384
146+
rest.auth.oauth2.client-auth.jwt.algorithm=HMAC_SHA384
147147
```
148148

149149
Supported algorithms are:
@@ -165,8 +165,8 @@ For `private_key_jwt`:
165165
| `RSA_SHA512` | `RS512`, `SHA512withRSA` |
166166

167167

168-
And finally, extra claims can be added to the JWT assertion using the `rest.auth.oauth2.client-assertion.jwt.extra-claims.*` prefix property. Example:
168+
And finally, extra claims can be added to the JWT assertion using the `rest.auth.oauth2.client-auth.jwt.extra-claims.*` prefix property. Example:
169169

170170
```properties
171-
rest.auth.oauth2.client-assertion.jwt.extra-claims.my-claim=my-value
171+
rest.auth.oauth2.client-auth.jwt.extra-claims.my-claim=my-value
172172
```

docs/configuration.md

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ The grant type to use when authenticating against the OAuth2 server. Valid value
103103
- `authorization_code`
104104
- `urn:ietf:params:oauth:grant-type:device_code`
105105
- `urn:ietf:params:oauth:grant-type:token-exchange`
106+
- `urn:ietf:params:oauth:grant-type:jwt-bearer`
106107

107108
Optional, defaults to `client_credentials`.
108109

@@ -393,43 +394,65 @@ The logical name of the target service(s) where the client intends to use the re
393394

394395
Optional. Can be a single value or a comma-separated list of values.
395396

396-
## Client Assertion Settings
397+
## Jwt Bearer Settings
398+
399+
Configuration properties for the JWT bearer grant as specified in [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523).
400+
401+
The assertion can be supplied statically or fetched dynamically using a nested OAuth2 configuration.
402+
403+
### `rest.auth.oauth2.jwt-bearer.assertion`
404+
405+
The assertion to exchange.
406+
407+
If this value is present, the assertion is used as-is. If this value is not present, the assertion may be read from the file specified by `rest.auth.oauth2.jwt-bearer.assertion-file`, or dynamically fetched using the configuration provided under the `rest.auth.oauth2.jwt-bearer.assertion` prefix.
408+
409+
### `rest.auth.oauth2.jwt-bearer.assertion-file`
410+
411+
Path to a file containing the assertion. The file content is read and trimmed to obtain the assertion value. Ignored if `rest.auth.oauth2.jwt-bearer.assertion` is set.
412+
413+
### `rest.auth.oauth2.jwt-bearer.assertion.*`
414+
415+
The configuration to use for fetching the assertion dynamically.
416+
417+
This is a prefix property; any property that can be set under the `rest.auth.oauth2.` prefix can also be set under this prefix.
418+
419+
## Jwt Client Auth Settings
397420

398421
Configuration properties for JWT client assertion as specified in [JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants](https://datatracker.ietf.org/doc/html/rfc7523).
399422

400423
These properties allow the client to authenticate using the `client_secret_jwt` or `private_key_jwt` authentication methods.
401424

402-
### `rest.auth.oauth2.client-assertion.jwt.issuer`
425+
### `rest.auth.oauth2.client-auth.jwt.issuer`
403426

404427
The issuer of the client assertion JWT. Optional. The default is the client ID.
405428

406-
### `rest.auth.oauth2.client-assertion.jwt.subject`
429+
### `rest.auth.oauth2.client-auth.jwt.subject`
407430

408431
The subject of the client assertion JWT. Optional. The default is the client ID.
409432

410-
### `rest.auth.oauth2.client-assertion.jwt.audience`
433+
### `rest.auth.oauth2.client-auth.jwt.audience`
411434

412435
The audience of the client assertion JWT. Optional. The default is the token endpoint. Can be a single audience or a comma-separated list of audiences.
413436

414-
### `rest.auth.oauth2.client-assertion.jwt.token-lifespan`
437+
### `rest.auth.oauth2.client-auth.jwt.token-lifespan`
415438

416439
The expiration time of the client assertion JWT. Optional. The default is 5 minutes.
417440

418-
### `rest.auth.oauth2.client-assertion.jwt.algorithm`
441+
### `rest.auth.oauth2.client-auth.jwt.algorithm`
419442

420443
The signing algorithm to use for the client assertion JWT. Optional. The default is `HS512` if the authentication method is `client_secret_jwt`, or `RS512` if the authentication method is `private_key_jwt`.
421444

422445
Algorithm names must match the "alg" Param Value as described in [RFC 7518 Section 3.1](https://datatracker.ietf.org/doc/html/rfc7518#section-3.1).
423446

424-
### `rest.auth.oauth2.client-assertion.jwt.key-id`
447+
### `rest.auth.oauth2.client-auth.jwt.key-id`
425448

426449
The key ID (kid) to include in the JWT header. Optional.
427450

428451
If specified, this will be included in the "kid" header parameter of the JWT assertion. This is useful when the authorization server needs to identify which key to use for verification from a set of keys.
429452

430453
This setting is only supported when using the `private_key_jwt` authentication method. It is ignored when using `client_secret_jwt`.
431454

432-
### `rest.auth.oauth2.client-assertion.jwt.private-key`
455+
### `rest.auth.oauth2.client-auth.jwt.private-key`
433456

434457
The path on the local filesystem to the private key to use for signing the client assertion JWT. Required if the authentication method is `private_key_jwt`.
435458

@@ -443,7 +466,7 @@ Supported key formats are:
443466

444467
Only unencrypted keys are supported currently.
445468

446-
### `rest.auth.oauth2.client-assertion.jwt.extra-claims.*`
469+
### `rest.auth.oauth2.client-auth.jwt.extra-claims.*`
447470

448471
Extra claims to include in the client assertion JWT. This is a prefix property, and multiple values can be set, each with a different key and value.
449472

docs/jwt-bearer.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<!--
2+
Copyright (C) 2025 Dremio Corporation
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
# Dremio AuthManager for Apache Iceberg - JWT Bearer Grant
17+
18+
## Overview
19+
20+
The Dremio AuthManager for Apache Iceberg supports the
21+
[JWT Bearer Grant](https://datatracker.ietf.org/doc/html/rfc7523).
22+
23+
Assertions can be provided in two methods:
24+
25+
* Static assertions: assertions acquired externally and directly included in the configuration.
26+
* Dynamic assertions: assertions are fetched dynamically by the AuthManager, using the same or
27+
different credentials and possibly a different IDP.
28+
29+
### Using Static Assertions
30+
31+
Static assertions are provided using the following properties:
32+
33+
* `rest.auth.oauth2.jwt-bearer.assertion`: the inline assertion value.
34+
* `rest.auth.oauth2.jwt-bearer.assertion-file`: path to a file whose content (read and
35+
trimmed) is used as the assertion; ignored if `assertion` is set.
36+
37+
The assertion is taken from the inline `assertion` if set, otherwise from the file at
38+
`assertion-file` if set, otherwise from dynamic configuration under `assertion.*`.
39+
40+
Here is an example of using a static assertion:
41+
42+
```properties
43+
rest.auth.type=com.dremio.iceberg.authmgr.oauth2.OAuth2Manager
44+
45+
rest.auth.oauth2.issuer-url=https://$IDP/realms/main
46+
rest.auth.oauth2.grant-type=urn:ietf:params:oauth:grant-type:jwt-bearer
47+
rest.auth.oauth2.client-id=Client1
48+
rest.auth.oauth2.client-secret=$CLIENT1_SECRET
49+
rest.auth.oauth2.scope=catalog1
50+
51+
rest.auth.oauth2.jwt-bearer.assertion=$ASSERTION
52+
```
53+
54+
### Using Dynamic Assertions
55+
56+
To enable dynamic fetching of assertions, the `rest.auth.oauth2.jwt-bearer.assertion` and
57+
`rest.auth.oauth2.jwt-bearer.assertion-file` properties must _not_ be set.
58+
59+
Then, details for fetching the assertion must be provided under:
60+
61+
* `rest.auth.oauth2.jwt-bearer.assertion.*`
62+
63+
Any property that can be set under the `rest.auth.oauth2.` prefix can also be set under this
64+
prefix, and will be used to configure a secondary agent for fetching the assertion.
65+
66+
```properties
67+
rest.auth.type=com.dremio.iceberg.authmgr.oauth2.OAuth2Manager
68+
69+
rest.auth.oauth2.issuer-url=https://$PRIMARY_IDP/realms/primary
70+
rest.auth.oauth2.grant-type=urn:ietf:params:oauth:grant-type:jwt-bearer
71+
rest.auth.oauth2.client-id=Client1
72+
rest.auth.oauth2.client-secret=$CLIENT1_SECRET
73+
rest.auth.oauth2.scope=catalog1
74+
75+
rest.auth.oauth2.jwt-bearer.assertion.issuer-url=https://$SECONDARY_IDP/realms/secondary
76+
rest.auth.oauth2.jwt-bearer.assertion.grant-type=authorization_code
77+
rest.auth.oauth2.jwt-bearer.assertion.client-id=Client2
78+
rest.auth.oauth2.jwt-bearer.assertion.client-secret=$CLIENT2_SECRET
79+
rest.auth.oauth2.jwt-bearer.assertion.scope=catalog2
80+
```
81+
82+
For Microsoft Entra ID on-behalf-of requests, configure the assertion using one of the above
83+
methods and add vendor-specific parameters with `rest.auth.oauth2.extra-params.*`, for example:
84+
85+
```properties
86+
rest.auth.oauth2.extra-params.requested_token_use=on_behalf_of
87+
```

docs/token-exchange.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ rest.auth.oauth2.token-exchange.actor-token.grant-type=client_credentials
160160
rest.auth.oauth2.token-exchange.actor-token.scope=catalog3
161161
rest.auth.oauth2.token-exchange.actor-token.client-id=Client3
162162
rest.auth.oauth2.token-exchange.actor-token.client-auth=private_key_jwt
163-
rest.auth.oauth2.token-exchange.actor-token.client-assertion.jwt.private-key=/path/to/private_key.pem
163+
rest.auth.oauth2.token-exchange.actor-token.client-auth.jwt.private-key=/path/to/private_key.pem
164164
```
165165

166166
In this example:

oauth2/core/src/docs/java/com/dremio/iceberg/authmgr/oauth2/docs/DocumentationGenerator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public class DocumentationGenerator {
6262
refs.put("GrantType#AUTHORIZATION_CODE", "authorization_code");
6363
refs.put("GrantType#REFRESH_TOKEN", "refresh_token");
6464
refs.put("GrantType#DEVICE_CODE", "urn:ietf:params:oauth:grant-type:device_code");
65+
refs.put("GrantType#JWT_BEARER", "urn:ietf:params:oauth:grant-type:jwt-bearer");
6566
refs.put("GrantType#TOKEN_EXCHANGE", "urn:ietf:params:oauth:grant-type:token-exchange");
6667
refs.put("JWSAlgorithm#HS512", "HS512");
6768
refs.put("JWSAlgorithm#RS512", "RS512");
@@ -229,7 +230,14 @@ private String resolveReference(Section section, String ref, String text) {
229230
String className = section.configClass.getPackageName() + "." + parts[0];
230231
String fieldName = parts[1];
231232
JavaClass classRef = builder.getClassByName(className);
233+
if (classRef == null) {
234+
throw new IllegalArgumentException("Unresolved documentation reference: " + ref);
235+
}
232236
Section refSection = sections.get(classRef.getFullyQualifiedName());
237+
if (refSection == null) {
238+
throw new IllegalArgumentException(
239+
"Unsupported external documentation reference: " + ref);
240+
}
233241
refTarget = refSection.refs.get(fieldName);
234242
}
235243
}

0 commit comments

Comments
 (0)