Skip to content

Commit

Permalink
DRIVERS-2836 OIDC Spec Cleanup (#1551)
Browse files Browse the repository at this point in the history
  • Loading branch information
blink1073 authored Apr 22, 2024
1 parent 47c97fb commit e62b205
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 337 deletions.
58 changes: 32 additions & 26 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 @@ -1222,8 +1222,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 @@ -1265,9 +1264,9 @@ purposes, and is not meant to be documented as a user-facing feature.

If enabled, drivers MUST generate a token using a script in the `auth_oidc`
[folder](https://github.com/mongodb-labs/drivers-evergreen-tools/tree/master/.evergreen/auth_oidc#readme) in Drivers
Evergreen Tools. The must then set the `OIDC_TOKEN_FILE` environment variable to the path to that file. At runtime, the
driver MUST use the `OIDC_TOKEN_FILE` environment variable and read the OIDC access token from that path. The driver
MUST use the contents of that file as value in the `jwt` field of the `saslStart` payload.
Evergreen Tools. The driver MUST then set the `OIDC_TOKEN_FILE` environment variable to the path to that file. At
runtime, the driver MUST use the `OIDC_TOKEN_FILE` environment variable and read the OIDC access token from that path.
The driver MUST use the contents of that file as value in the `jwt` field of the `saslStart` payload.

Drivers MAY implement the "test" integration so that it conforms to the function signature of the
[OIDC Callback](#oidc-callback) to prevent having to re-implement the "test" integration logic in the OIDC prose tests.
Expand Down Expand Up @@ -1426,10 +1425,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 @@ -1483,7 +1482,7 @@ An example human callback API might look like:
```typescript
interface IdpInfo {
issuer: string;
clientId: string;
clientId: Optional<string>;
requestScopes: Optional<Array<string>>;
}

Expand Down Expand Up @@ -1544,6 +1543,7 @@ An example OIDC one-step SASL conversation with access token string "abcd1234" l
{
saslStart: 1,
mechanism: "MONGODB-OIDC",
db: "$external"
// payload is a BSON generic binary field containing a JwtStepRequest BSON
// document: {"jwt": "abcd1234"}
payload: BinData(0, "FwAAAAJqd3QACQAAAGFiY2QxMjM0AAA=")
Expand Down Expand Up @@ -1618,6 +1618,7 @@ An example OIDC two-step SASL conversation with username "myidp" and access toke
{
saslStart: 1,
mechanism: "MONGODB-OIDC",
db: "$external",
// payload is a BSON generic binary field containing a PrincipalStepRequest
// BSON document: {"n": "myidp"}
payload: BinData(0, "EgAAAAJuAAYAAABteWlkcAAA")
Expand Down Expand Up @@ -1712,9 +1713,10 @@ Use the following algorithm to authenticate a new connection:

- Check if the the *Client Cache* has an access token.
- 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.
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 @@ -1725,18 +1727,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 @@ -1747,14 +1750,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 @@ -1776,7 +1780,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 @@ -2053,6 +2057,8 @@ to EC2 instance metadata in ECS, for security reasons, Amazon states it's best p

## Changelog

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

- 2024-04-22: Clarify that driver should not validate `saslSupportedMechs` content.

- 2024-04-03: Added GCP built-in OIDC provider integration.
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
Loading

0 comments on commit e62b205

Please sign in to comment.