Skip to content
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

DRIVERS-2836 OIDC Spec Cleanup #1551

Merged
merged 31 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6e09e49
DRIVERS-2836 Only invalidate cached OIDC access tokens if the server …
blink1073 Mar 19, 2024
ed669eb
regenerate json
blink1073 Mar 19, 2024
a25518c
restore comment
blink1073 Mar 19, 2024
06e0110
add spec test for Refresh Token Is Passed To The Callback
blink1073 Mar 20, 2024
fd9481a
Merge branch 'master' of github.com:mongodb/specifications into DRIVE…
blink1073 Mar 21, 2024
739ae61
address review
blink1073 Mar 21, 2024
6ed2c94
Update source/auth/auth.md
blink1073 Apr 3, 2024
cef1ad2
Merge branch 'master' of github.com:mongodb/specifications into DRIVE…
blink1073 Apr 3, 2024
7ae7fea
Merge branch 'DRIVERS-2836' of github.com:blink1073/specifications in…
blink1073 Apr 3, 2024
958148e
Update source/auth/tests/mongodb-oidc.md
blink1073 Apr 3, 2024
83952e7
OIDC spec updates
blink1073 Apr 8, 2024
9027606
Merge branch 'master' of github.com:mongodb/specifications into DRIVE…
blink1073 Apr 8, 2024
3d98b29
Merge branch 'DRIVERS-2836' of github.com:blink1073/specifications in…
blink1073 Apr 8, 2024
b4e7905
update auth logic
blink1073 Apr 9, 2024
6815eb4
update error handling
blink1073 Apr 9, 2024
5967353
Update source/auth/auth.md
blink1073 Apr 10, 2024
3242bea
Update source/auth/auth.md
blink1073 Apr 10, 2024
ddd4054
Update source/auth/auth.md
blink1073 Apr 10, 2024
c263ad2
Update source/auth/auth.md
blink1073 Apr 10, 2024
119fce1
address review
blink1073 Apr 10, 2024
0f6f3bc
Update source/auth/tests/mongodb-oidc.md
blink1073 Apr 11, 2024
94ef18d
fix connection string tests
blink1073 Apr 11, 2024
13dc54c
clarify test conditions
blink1073 Apr 16, 2024
134c1f4
clarify spec test
blink1073 Apr 17, 2024
f24a075
fix SASL conversation
blink1073 Apr 19, 2024
5f63fea
fix grammar
blink1073 Apr 19, 2024
9e268e7
convert two unified tests to prose tests
blink1073 Apr 22, 2024
8cef9de
Update source/auth/tests/mongodb-oidc.md
blink1073 Apr 22, 2024
8471762
Update source/auth/tests/mongodb-oidc.md
blink1073 Apr 22, 2024
1716f23
Merge branch 'master' into DRIVERS-2836
blink1073 Apr 22, 2024
531753e
lint
blink1073 Apr 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 27 additions & 23 deletions source/auth/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Drivers SHOULD contain a type called `MongoCredential`. It SHOULD contain some o
- username (string)

- Applies to all mechanisms.
- Optional for MONGODB-X509 and MONGODB-AWS.
- Optional for MONGODB-X509, MONGODB-AWS, and MONGODB-OIDC.

- source (string)

Expand Down Expand Up @@ -1219,8 +1219,7 @@ in the MONGODB-OIDC specification, including sections or blocks that specificall
the same `MongoClient`, the driver MUST raise an error.

- TOKEN_RESOURCE\
The URI of the target resource. This property is currently only used and required by the Azure
built-in OIDC provider integration. If `TOKEN_RESOURCE` is provided and `ENVIRONMENT` is not one of
The URI of the target resource. If `TOKEN_RESOURCE` is provided and `ENVIRONMENT` is not one of
`["azure", "gcp"]` or `TOKEN_RESOURCE` is not provided and `ENVIRONMENT` is one of `["azure", "gcp"]`, the driver
MUST raise an error.

Expand Down Expand Up @@ -1423,10 +1422,10 @@ The driver MUST pass the following information to the callback:
The callback MUST be able to return the following information:

- `accessToken`: An OIDC access token string. The driver MUST NOT attempt to validate `accessToken` directly.
- `expiresIn`: An optional expiry duration for the access token. Drivers MUST interpret the value 0 as an infinite
duration and error if a negative value is returned. Drivers SHOULD use the most idiomatic type for representing a
duration in the driver's language. Note that the access token expiry value is currently not used in
[Credential Caching](#credential-caching), but is intended to support future caching optimizations.
- `expiresIn`: An optional expiry duration for the access token. Drivers with optional parameters MAY interpret a
missing value as infinite. Drivers MUST error if a negative value is returned. Drivers SHOULD use the most idiomatic
type for representing a duration in the driver's language. Note that the access token expiry value is currently not
used in [Credential Caching](#credential-caching), but is intended to support future caching optimizations.

The signature and naming of the callback API is up to the driver's discretion. Drivers MUST ensure that additional
optional input parameters and return values can be added to the callback signature in the future without breaking
Expand Down Expand Up @@ -1480,7 +1479,7 @@ An example human callback API might look like:
```typescript
interface IdpInfo {
issuer: string;
clientId: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intended behavior when clientId is None? The code I have written on client side assumes the clientId will always exist.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be None if the user used a Human Callback and the IdP is configured on the server with supportsHumanFlows:False. They shouldn't really be doing that, but it is allowed. See https://jira.mongodb.org/browse/DRIVERS-2773 for details.

clientId: Optional<string>;
requestScopes: Optional<Array<string>>;
}

Expand Down Expand Up @@ -1709,9 +1708,10 @@ Use the following algorithm to authenticate a new connection:

- Check if the the *Client Cache* has an access token.
katcharov marked this conversation as resolved.
Show resolved Hide resolved
- If it does, cache the access token in the *Connection Cache* and perform a `One-Step` SASL conversation using the
access token in the *Client Cache*. If the server returns an error, invalidate that access token, sleep 100ms then
continue.
- Call the configured built-in provider integration or the OIDC callback to retrieve a new access token.
access token in the *Client Cache*. If the server returns a Authentication error (18), invalidate that access token.
blink1073 marked this conversation as resolved.
Show resolved Hide resolved
Raise any other errors to the user. On success, exit the algorithm.
- Call the configured built-in provider integration or the OIDC callback to retrieve a new access token. Wait until it
has been at least 100ms since the last callback invocation, to avoid overloading the callback.
- Cache the new access token in the *Client Cache* and *Connection Cache*.
- Perform a `One-Step` SASL conversation using the new access token. Raise any errors to the user.

Expand All @@ -1722,18 +1722,19 @@ def auth(connection):
access_token, is_cache = get_access_token()

# If there is a cached access token, try to authenticate with it. If
# authentication fails, it's possible the cached access token is expired. In
# that case, invalidate the access token, fetch a new access token, and try
# authentication fails with an Authentication error (18),
# invalidate the access token, fetch a new access token, and try
# to authenticate again.
# If the server fails for any other reason, do not clear the cache.
if is_cache:
try:
connection.oidc_cache.access_token = access_token
sasl_start(connection, payload={"jwt": access_token})
return
except ServerError:
invalidate(access_token)
sleep(0.1)
access_token, _ = get_access_token()
except ServerError as e:
if e.code == 18:
invalidate(access_token)
access_token, _ = get_access_token()

connection.oidc_cache.access_token = access_token
sasl_start(connection, payload={"jwt": access_token})
Expand All @@ -1744,14 +1745,15 @@ authenticate a new connection when a [OIDC Human Callback](#oidc-human-callback)

- Check if the *Client Cache* has an access token.
- If it does, cache the access token in the *Connection Cache* and perform a [One-Step](#one-step) SASL conversation
using the access token. If the server returns an error, invalidate the access token token from the *Client Cache*,
clear the *Connection Cache*, and continue.
using the access token. If the server returns an Authentication error (18), invalidate the access token token from
the *Client Cache*, clear the *Connection Cache*, and restart the authentication flow. Raise any other errors to the
user. On success, exit the algorithm.
- Check if the *Client Cache* has a refresh token.
- If it does, call the [OIDC Human Callback](#oidc-human-callback) with the cached refresh token and `IdpInfo` to get
a new access token. Cache the new access token in the *Client Cache* and *Connection Cache*. Perform a
[One-Step](#one-step) SASL conversation using the new access token. If the
[OIDC Human Callback](#oidc-human-callback) or the server return an error, invalidate the access token from the
*Client Cache*, clear the *Connection Cache*, and continue.
[One-Step](#one-step) SASL conversation using the new access token. If the the server returns an Authentication
error (18), clear the refresh token, invalidate the access token from the *Client Cache*, clear the *Connection
Cache*, and restart the authentication flow. Raise any other errors to the user. On success, exit the algorithm.
- Start a new [Two-Step](#two-step) SASL conversation.
- Run a `PrincipalStepRequest` to get the `IdpInfo`.
- Call the [OIDC Human Callback](#oidc-human-callback) with the new `IdpInfo` to get a new access token and optional
Expand All @@ -1773,7 +1775,7 @@ Use the following algorithm to perform speculative authentication:
- If it does, cache the access token in the *Connection Cache* and send a `JwtStepRequest` with the cached access
token in the speculative authentication SASL payload. If the response is missing a speculative authentication
document or the speculative authentication document indicates authentication was not successful, clear the the
*Connection Cache* and continue.
*Connection Cache* and proceed to the next step.
- Authenticate with the standard authentication handshake.

Example code for speculative authentication using the `auth` function described above:
Expand Down Expand Up @@ -2050,6 +2052,8 @@ to EC2 instance metadata in ECS, for security reasons, Amazon states it's best p

## Changelog

- 2024-04-09: Updated OIDC authentication flow and prose tests.

- 2024-04-03: Added GCP built-in OIDC provider integration.

- 2024-03-29: Updated OIDC test setup and descriptions.
Expand Down
4 changes: 2 additions & 2 deletions source/auth/tests/legacy/connection-string.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion source/auth/tests/legacy/connection-string.yml
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ tests:
uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test
valid: false
credential:
- description: should throw an exception if username is specified for aws (MONGODB-OIDC)
- description: should throw an exception if username is specified for test (MONGODB-OIDC)
uri: mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&ENVIRONMENT:test
valid: false
credential:
Expand Down
100 changes: 87 additions & 13 deletions source/auth/tests/mongodb-oidc.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,32 @@ gets the appropriate token for the given environment.
- Assert that the callback was called 1 time.
- Close the client.

**3.3 Unexpected error code does not clear the cache**

- Create a `MongoClient` with a human callback that returns a valid token.
- Set a fail point for `saslStart` commands of the form:

```javascript
{
configureFailPoint: "failCommand",
mode: {
times: 1
},
data: {
failCommands: [
"saslStart"
],
errorCode: 20 // IllegalOperation
}
}
```

- Perform a `find` operation that fails.
- Assert that the human callback has been called once.
- Perform a `find` operation that succeeds.
- Assert that the human callback has been called once.
- Close the client.

### (4) Reauthentication

- Create an OIDC configured client.
Expand Down Expand Up @@ -200,6 +226,24 @@ Drivers MUST be able to authenticate using OIDC callback(s) when there is one pr
- Assert that a `find` operation fails with a client-side error.
- Close the client.

**1.7 Allowed Hosts in Connection String Ignored**

- Create an OIDC configured client with the connection string:
`mongodb+srv://example.com/?authMechanism=MONGODB-OIDC&authMechanismProperties=ALLOWED_HOSTS:%5B%22example.com%22%5D`
and a Human Callback.
- Assert that the creation of the client raises a configuration error.

**1.8 Machine IdP with Human Callback**

This test MUST only be run when `OIDC_IS_LOCAL` is set. This indicates that the server is local and not using Atlas. In
this case, `MONGODB_URI_SINGLE` will be configured with a human user `test_user1`, and a machine user `test_machine`.
This test uses the machine user with a human callback, ensuring that the missing `clientId` in the
`PrincipalStepRequest` response is handled by the driver.

- Create an OIDC configured client with `MONGODB_URI_SINGLE` and a username of `test_machine`.
blink1073 marked this conversation as resolved.
Show resolved Hide resolved
- Perform a find operation that succeeds.
- Close the client.

### (2) OIDC Human Callback Validation

**2.1 Valid Callback Inputs**
Expand All @@ -209,13 +253,38 @@ Drivers MUST be able to authenticate using OIDC callback(s) when there is one pr
including the timeout parameter if possible.
- Close the client.

**2.3 Human Callback Returns Missing Data**
**2.2 Human Callback Returns Missing Data**

- Create an OIDC configured client with a human callback that returns data not conforming to the `OIDCCredential` with
missing fields.
- Perform a `find` operation that fails.
- Close the client.

**2.3 Refresh Token Is Passed To The Callback**

- Create a `MongoClient` with a human callback that checks for the presence of a refresh token.
- Perform a find operation that succeeds.
- Set a fail point for `find` commands of the form:

```javascript
{
configureFailPoint: "failCommand",
mode: {
times: 1
},
data: {
failCommands: [
"find"
],
errorCode: 391
}
}
```

- Perform a `find` operation that succeeds.
- Assert that the callback has been called twice.
- Assert that the refresh token was provided to the callback once.

### (3) Speculative Authentication

**3.1 Uses speculative authentication if there is a cached token**
Expand Down Expand Up @@ -244,12 +313,14 @@ Drivers MUST be able to authenticate using OIDC callback(s) when there is one pr
```javascript
{
configureFailPoint: "failCommand",
mode: "alwaysOn",
mode: {
times: 1
},
data: {
failCommands: [
"saslStart"
],
errorCode: 20
errorCode: 18
}
}
```
Expand All @@ -265,12 +336,14 @@ Drivers MUST be able to authenticate using OIDC callback(s) when there is one pr
```javascript
{
configureFailPoint: "failCommand",
mode: "alwaysOn",
mode: {
times: 1
},
data: {
failCommands: [
"saslStart"
],
errorCode: 20 // IllegalOperation
errorCode: 18
}
}
```
Expand Down Expand Up @@ -341,7 +414,7 @@ Drivers MUST be able to authenticate using OIDC callback(s) when there is one pr

**4.3 Succeeds after refresh fails**

- Create an OIDC configured client.
- Create an OIDC configured client with a callback that returns the `test_user1` access token and a bad refresh token.
- Perform a `find` operation that succeeds.
- Assert that the human callback has been called once.
- Force a reauthenication using a fail point of the form:
Expand All @@ -350,24 +423,25 @@ Drivers MUST be able to authenticate using OIDC callback(s) when there is one pr
{
configureFailPoint: "failCommand",
mode: {
times: 2
times: 1
},
data: {
failCommands: [
"find", "saslStart"
"find",
],
errorCode: 391 // ReauthenticationRequired
}
}
```

- Perform a `find` operation that succeeds.
- Assert that the human callback has been called 3 times.
- Assert that the human callback has been called 2 times.
- Close the client.

**4.4 Fails**

- Create an OIDC configured client.
- Create an OIDC configured client that returns invalid refresh tokens and returns invalid access tokens after the first
access.
- Perform a find operation that succeeds (to force a speculative auth).
- Assert that the human callback has been called once.
- Force a reauthenication using a failCommand of the form:
Expand All @@ -376,17 +450,17 @@ Drivers MUST be able to authenticate using OIDC callback(s) when there is one pr
{
configureFailPoint: "failCommand",
mode: {
times: 3
times: 1
},
data: {
failCommands: [
"find", "saslStart"
"find",
],
errorCode: 391 // ReauthenticationRequired
}
}
```

- Perform a find operation that fails.
- Assert that the human callback has been called twice.
- Assert that the human callback has been called three times.
- Close the client.
Loading
Loading