Skip to content

PYTHON-3467 OIDC: Automatic token acquisition for Azure Identity Provider #1443

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

Merged
merged 112 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
d7265bf
PYTHON-3845 OIDC: Implement Machine Callback Mechanism
blink1073 Oct 15, 2023
cc838c5
Merge branch 'master' of github.com:mongodb/mongo-python-driver into …
blink1073 Oct 18, 2023
4c22587
run unified tests in oidc test
blink1073 Oct 18, 2023
0485e82
fix path handling
blink1073 Oct 18, 2023
ae77133
debug
blink1073 Oct 18, 2023
e5b31cb
fix path handling
blink1073 Oct 18, 2023
fee9cf4
handle db_ip
blink1073 Oct 18, 2023
e11865a
handle srv hosts
blink1073 Oct 19, 2023
fb7470d
PYTHON-3845 OIDC: Implement Machine Callback Mechanism
blink1073 Oct 20, 2023
131de6a
undo test comment out
blink1073 Oct 20, 2023
1d7011f
change name to custom_token_callback
blink1073 Oct 31, 2023
36d6c8b
Merge branch 'master' of github.com:mongodb/mongo-python-driver into …
blink1073 Nov 1, 2023
9c961e9
simplify custom callback and start prose tests
blink1073 Nov 2, 2023
c0eed02
fix placeholder handling
blink1073 Nov 3, 2023
65e47a1
Merge branch 'master' of github.com:mongodb/mongo-python-driver into …
blink1073 Nov 7, 2023
f26c60a
wip implement OIDC tests
blink1073 Nov 8, 2023
7897053
Merge branch 'master' of github.com:mongodb/mongo-python-driver into …
blink1073 Nov 14, 2023
a95479c
Use callback class and update tests
blink1073 Nov 15, 2023
e55d34e
Fix typing and test
blink1073 Nov 15, 2023
480a648
Merge branch 'master' of github.com:mongodb/mongo-python-driver into …
blink1073 Nov 16, 2023
c085b50
fix tests
blink1073 Nov 16, 2023
07b8c6d
fix default port
blink1073 Nov 16, 2023
5b991c3
lint
blink1073 Nov 16, 2023
2cd3aa7
add reauth succeeds prose test for machine
blink1073 Nov 16, 2023
e221ebf
Merge branch 'master' of github.com:mongodb/mongo-python-driver into …
blink1073 Nov 17, 2023
5c0f770
use dataclasses in callbacks
blink1073 Nov 17, 2023
dae8124
Merge branch 'master' of github.com:mongodb/mongo-python-driver into …
blink1073 Nov 27, 2023
e563840
update for spec changes
blink1073 Nov 28, 2023
9a4d6b0
add updated spec tests
blink1073 Nov 28, 2023
789a55c
fix test and typing
blink1073 Nov 29, 2023
ac8e90e
PYTHON-3467 OIDC: Automatic token acquisition for Azure Identity Prov…
blink1073 Nov 29, 2023
f35a316
wip
blink1073 Nov 30, 2023
45ba0af
fix handling of oidc admin user
blink1073 Nov 30, 2023
d6e8df6
fix uri
blink1073 Nov 30, 2023
ce8527c
fix handling of token aud
blink1073 Nov 30, 2023
abcc9b6
try using parse uri
blink1073 Nov 30, 2023
d9e6d85
fix lookup
blink1073 Nov 30, 2023
744a24e
try with token client
blink1073 Nov 30, 2023
7b92310
add azure support in unified tests
blink1073 Nov 30, 2023
7ecbf61
fix provider name
blink1073 Nov 30, 2023
d591efe
fix auth mech name
blink1073 Nov 30, 2023
c63c795
fix response handling
blink1073 Nov 30, 2023
ddcc27b
skip prose tests for now
blink1073 Nov 30, 2023
fded240
add debug print
blink1073 Nov 30, 2023
39a26bc
fix aud
blink1073 Nov 30, 2023
c522503
add human tests back
blink1073 Nov 30, 2023
d30d099
add test for multiple client_ids
blink1073 Nov 30, 2023
fa0839a
update prose test
blink1073 Nov 30, 2023
30b1230
fixes
blink1073 Dec 1, 2023
9bf112a
updates based on spec discussion
blink1073 Dec 1, 2023
6f315ce
try with another &&
blink1073 Dec 1, 2023
10319dd
debug
blink1073 Dec 1, 2023
51955e6
make sure files are up to date
blink1073 Dec 1, 2023
4026097
fix delay logic
blink1073 Dec 1, 2023
9519880
enable oidc auth mech
blink1073 Dec 4, 2023
d0b8575
Merge branch 'master' of github.com:mongodb/mongo-python-driver into …
blink1073 Dec 8, 2023
385b1d1
wip implement prose tests
blink1073 Dec 8, 2023
b0b5ee5
update azure handling
blink1073 Dec 19, 2023
59c31ff
fix handling of azure username
blink1073 Dec 19, 2023
0caba87
add debug info
blink1073 Dec 19, 2023
df8a069
clear username and pwd
blink1073 Dec 19, 2023
317bb44
try with username
blink1073 Dec 19, 2023
4dbcafc
force the username
blink1073 Dec 19, 2023
0e6f0e1
fix username
blink1073 Dec 19, 2023
a6f5211
clean up
blink1073 Dec 19, 2023
908d367
debug
blink1073 Dec 19, 2023
8f719d5
cleanup
blink1073 Dec 19, 2023
cf8c7a4
fix test
blink1073 Dec 19, 2023
966145a
Update connection string tests
blink1073 Dec 19, 2023
1daaf8c
fix typing
blink1073 Dec 19, 2023
ea46340
fix handling of username and passwd
blink1073 Dec 19, 2023
bd4d320
fix token name
blink1073 Dec 19, 2023
c2e6749
update typings
blink1073 Dec 19, 2023
770124e
fix token handling
blink1073 Dec 20, 2023
14da786
consolidate callbacks and update mech property names
blink1073 Jan 5, 2024
e7b9208
cleanup
blink1073 Jan 5, 2024
b5c16de
fix test
blink1073 Jan 5, 2024
93960c8
add new unified test
blink1073 Jan 5, 2024
7e1d685
update prose tests
blink1073 Jan 5, 2024
5ff9c05
fix azure vm teardown
blink1073 Jan 19, 2024
0dec6b3
Update unified tests
blink1073 Jan 19, 2024
528f9e1
Cleanup
blink1073 Jan 19, 2024
53ae91f
update for spec changes
blink1073 Jan 19, 2024
88bea64
more updates for spec changes
blink1073 Jan 19, 2024
ea75285
wip oidc updates
blink1073 Jan 19, 2024
b8b31a1
Refactor auth logic
blink1073 Jan 20, 2024
80713b4
more refactor
blink1073 Jan 20, 2024
7a7ce6f
more refactor and prose implementation
blink1073 Jan 21, 2024
ac8c6ae
more refactor and prose implementation
blink1073 Jan 22, 2024
70707c0
update based on discussion today
blink1073 Jan 25, 2024
b72d0ab
more cleanup
blink1073 Jan 25, 2024
b81f405
simplify spec auth
blink1073 Jan 25, 2024
1bb217f
address failing tests
blink1073 Jan 26, 2024
64022c7
more updates
blink1073 Jan 26, 2024
6866f64
try pinning pyopenssl
blink1073 Jan 26, 2024
1b043a4
try pinning service_identity
blink1073 Jan 26, 2024
9eb2d40
update prose tests
blink1073 Jan 27, 2024
c9e3d98
update for spec changes
blink1073 Jan 30, 2024
4776a1d
update for spec changes
blink1073 Jan 31, 2024
f012dd4
Fix test
blink1073 Jan 31, 2024
93620d4
Merge branch 'master' of github.com:mongodb/mongo-python-driver into …
blink1073 Jan 31, 2024
3624a8f
undo resync-specs change
blink1073 Jan 31, 2024
dc5a239
undo change to oscp reqs
blink1073 Jan 31, 2024
55ee678
restore GSSAPI tests
blink1073 Jan 31, 2024
170272b
undo changes to specs
blink1073 Jan 31, 2024
7040640
fix typing
blink1073 Jan 31, 2024
87151bf
Update pymongo/auth_oidc.py
blink1073 Feb 1, 2024
b3decdd
Update test/auth_oidc/test_auth_oidc.py
blink1073 Feb 1, 2024
23ae825
Update test/auth_oidc/test_auth_oidc.py
blink1073 Feb 1, 2024
2051aad
Update test/auth_oidc/test_auth_oidc.py
blink1073 Feb 1, 2024
586f67a
Update test/auth_oidc/test_auth_oidc.py
blink1073 Feb 1, 2024
628bd03
address review
blink1073 Feb 1, 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
49 changes: 49 additions & 0 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,32 @@ task_groups:
tasks:
- testazurekms-task

- name: testazureoidc_task_group
setup_group:
- func: fetch source
- func: prepare resources
- func: fix absolute paths
- func: make files executable
- command: shell.exec
params:
shell: bash
script: |-
set -o errexit
${PREPARE_SHELL}
export AZUREOIDC_VMNAME_PREFIX="PYTHON_DRIVER"
$DRIVERS_TOOLS/.evergreen/auth_oidc/azure/create-and-setup-vm.sh
teardown_task:
- command: shell.exec
params:
shell: bash
script: |-
${PREPARE_SHELL}
$DRIVERS_TOOLS/.evergreen/auth_oidc/azure/delete-vm.sh
setup_group_can_fail_task: true
setup_group_timeout_secs: 1800
tasks:
- oidc-auth-test-azure-latest

- name: test_aws_lambda_task_group
setup_group:
- func: fetch source
Expand Down Expand Up @@ -1978,6 +2004,22 @@ tasks:
- func: "run load-balancer"
- func: "run tests"

- name: "oidc-auth-test-azure-latest"
commands:
- command: shell.exec
params:
shell: bash
script: |-
set -o errexit
${PREPARE_SHELL}
cd src
git add .
Copy link
Contributor

Choose a reason for hiding this comment

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

Humor me if this is a silly question. Is the ${PREPARE_SHELL} command responsible for adding in the new files for the commit?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it sets up common env variables.

git commit -m "add files"
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-python-driver.tgz
git archive -o $AZUREOIDC_DRIVERS_TAR_FILE HEAD
export AZUREOIDC_TEST_CMD="source ./env.sh && export OIDC_PROVIDER_NAME=azure && ./.evergreen/run-mongodb-oidc-test.sh"
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/azure/run-driver-test.sh

- name: "test-fips-standalone"
tags: ["fips"]
commands:
Expand Down Expand Up @@ -3036,6 +3078,13 @@ buildvariants:
tasks:
- name: "oidc-auth-test-latest"

- name: testazureoidc-variant
display_name: "Azure OIDC"
run_on: ubuntu2004-small
tasks:
- name: testazureoidc_task_group
batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README

- matrix_name: "aws-auth-test"
matrix_spec:
platform: [ubuntu-20.04]
Expand Down
83 changes: 54 additions & 29 deletions .evergreen/run-mongodb-oidc-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,69 @@ set -o errexit # Exit the script with error if any of the commands fail

echo "Running MONGODB-OIDC authentication tests"

# Make sure DRIVERS_TOOLS is set.
if [ -z "$DRIVERS_TOOLS" ]; then
echo "Must specify DRIVERS_TOOLS"
exit 1
fi
OIDC_PROVIDER_NAME=${OIDC_PROVIDER_NAME:-"aws"}

# Get the drivers secrets. Use an existing secrets file first.
if [ ! -f "./secrets-export.sh" ]; then
bash ${DRIVERS_TOOLS}/.evergreen/auth_aws/setup_secrets.sh drivers/oidc
fi
source ./secrets-export.sh
if [ $OIDC_PROVIDER_NAME == "aws" ]; then
# Make sure DRIVERS_TOOLS is set.
if [ -z "$DRIVERS_TOOLS" ]; then
echo "Must specify DRIVERS_TOOLS"
exit 1
fi

# # If the file did not have our creds, get them from the vault.
if [ -z "$OIDC_ATLAS_URI_SINGLE" ]; then
bash ${DRIVERS_TOOLS}/.evergreen/auth_aws/setup_secrets.sh drivers/oidc
# Get the drivers secrets. Use an existing secrets file first.
if [ ! -f "./secrets-export.sh" ]; then
bash ${DRIVERS_TOOLS}/.evergreen/auth_aws/setup_secrets.sh drivers/oidc
fi
source ./secrets-export.sh
fi

# Make the OIDC tokens.
set -x
pushd ${DRIVERS_TOOLS}/.evergreen/auth_oidc
. ./oidc_get_tokens.sh
popd
# # If the file did not have our creds, get them from the vault.
if [ -z "$OIDC_ATLAS_URI_SINGLE" ]; then
bash ${DRIVERS_TOOLS}/.evergreen/auth_aws/setup_secrets.sh drivers/oidc
source ./secrets-export.sh
fi

# Set up variables and run the test.
if [ -n "$LOCAL_OIDC_SERVER" ]; then
export MONGODB_URI=${MONGODB_URI:-"mongodb://localhost"}
export MONGODB_URI_SINGLE="${MONGODB_URI}/?authMechanism=MONGODB-OIDC"
export MONGODB_URI_MULTI="${MONGODB_URI}:27018/?authMechanism=MONGODB-OIDC&directConnection=true"
else
# Make the OIDC tokens.
set -x
pushd ${DRIVERS_TOOLS}/.evergreen/auth_oidc
. ./oidc_get_tokens.sh
popd

# Set up variables and run the test.
if [ -n "$LOCAL_OIDC_SERVER" ]; then
export MONGODB_URI=${MONGODB_URI:-"mongodb://localhost"}
export MONGODB_URI_SINGLE="${MONGODB_URI}/?authMechanism=MONGODB-OIDC"
export MONGODB_URI_MULTI="${MONGODB_URI}:27018/?authMechanism=MONGODB-OIDC&directConnection=true"
else
set +x # turn off xtrace for this portion
export MONGODB_URI="$OIDC_ATLAS_URI_SINGLE"
export MONGODB_URI_SINGLE="$OIDC_ATLAS_URI_SINGLE/?authMechanism=MONGODB-OIDC"
export MONGODB_URI_MULTI="$OIDC_ATLAS_URI_MULTI/?authMechanism=MONGODB-OIDC"
set -x
fi
export AWS_WEB_IDENTITY_TOKEN_FILE="$OIDC_TOKEN_DIR/test_user1"
export OIDC_ADMIN_USER=$OIDC_ALTAS_USER
export OIDC_ADMIN_PWD=$OIDC_ATLAS_PASSWORD

elif [ $OIDC_PROVIDER_NAME == "azure" ]; then
if [ -z "${AZUREOIDC_AUDIENCE}" ]; then
echo "Must specify an AZUREOIDC_AUDIENCE"
exit 1
fi
set +x # turn off xtrace for this portion
export MONGODB_URI="$OIDC_ATLAS_URI_SINGLE"
export MONGODB_URI_SINGLE="$OIDC_ATLAS_URI_SINGLE/?authMechanism=MONGODB-OIDC"
export MONGODB_URI_MULTI="$OIDC_ATLAS_URI_MULTI/?authMechanism=MONGODB-OIDC"
export OIDC_ADMIN_USER=$AZUREOIDC_USERNAME
export OIDC_ADMIN_PWD=pwd123
set -x
export MONGODB_URI=${MONGODB_URI:-"mongodb://localhost"}
MONGODB_URI_SINGLE="${MONGODB_URI}/?authMechanism=MONGODB-OIDC"
MONGODB_URI_SINGLE="${MONGODB_URI_SINGLE}&authMechanismProperties=PROVIDER_NAME:azure"
export MONGODB_URI_SINGLE="${MONGODB_URI_SINGLE},TOKEN_AUDIENCE:${AZUREOIDC_AUDIENCE}"
export MONGODB_URI_MULTI=$MONGODB_URI_SINGLE
else
echo "Unrecognized OIDC_PROVIDER_NAME $OIDC_PROVIDER_NAME"
exit 1
fi

export TEST_AUTH_OIDC=1
export COVERAGE=1
export AUTH="auth"
bash ./.evergreen/tox.sh -m test-eg
bash ./.evergreen/tox.sh -m test-eg -- "${@:1}"
8 changes: 4 additions & 4 deletions .evergreen/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ set -o xtrace

AUTH=${AUTH:-noauth}
SSL=${SSL:-nossl}
TEST_ARGS="$1"
TEST_ARGS="${*:1}"
PYTHON=$(which python)
export PIP_QUIET=1 # Quiet by default

Expand All @@ -50,8 +50,9 @@ if [ "$AUTH" != "noauth" ]; then
export DB_USER=$SERVERLESS_ATLAS_USER
export DB_PASSWORD=$SERVERLESS_ATLAS_PASSWORD
elif [ ! -z "$TEST_AUTH_OIDC" ]; then
export DB_USER=$OIDC_ALTAS_USER
export DB_PASSWORD=$OIDC_ATLAS_PASSWORD
export DB_USER=$OIDC_ADMIN_USER
export DB_PASSWORD=$OIDC_ADMIN_PWD
export DB_IP="$MONGODB_URI"
else
export DB_USER="bob"
export DB_PASSWORD="pwd123"
Expand Down Expand Up @@ -205,7 +206,6 @@ fi

if [ -n "$TEST_AUTH_OIDC" ]; then
python -m pip install ".[aws]"

TEST_ARGS="test/auth_oidc/test_auth_oidc.py"
fi

Expand Down
56 changes: 56 additions & 0 deletions pymongo/_azure_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright 2023-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Azure helpers."""
from __future__ import annotations

import json
from typing import Any, Optional
from urllib.request import Request, urlopen


def _get_azure_response(
resource: str, object_id: Optional[str] = None, timeout: float = 5
) -> dict[str, Any]:
url = "http://169.254.169.254/metadata/identity/oauth2/token"
url += "?api-version=2018-02-01"
url += f"&resource={resource}"
if object_id:
url += f"&object_id={object_id}"
headers = {"Metadata": "true", "Accept": "application/json"}
request = Request(url, headers=headers) # noqa: S310
print("fetching url", url) # noqa: T201
try:
with urlopen(request, timeout=timeout) as response: # noqa: S310
status = response.status
body = response.read().decode("utf8")
except Exception as e:
msg = "Failed to acquire IMDS access token: %s" % e
raise ValueError(msg) from None

if status != 200:
msg = "Failed to acquire IMDS access token."
raise ValueError(msg)
try:
data = json.loads(body)
except Exception:
raise ValueError("Azure IMDS response must be in JSON format.") from None

for key in ["access_token", "expires_in"]:
if not data.get(key):
msg = "Azure IMDS response must contain %s, but was %s."
msg = msg % (key, body)
raise ValueError(msg)

return data
56 changes: 46 additions & 10 deletions pymongo/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@

from bson.binary import Binary
from pymongo.auth_aws import _authenticate_aws
from pymongo.auth_oidc import _authenticate_oidc, _get_authenticator, _OIDCProperties
from pymongo.auth_oidc import (
_authenticate_oidc,
_get_authenticator,
_OIDCAWSCallback,
_OIDCAzureCallback,
_OIDCProperties,
)
from pymongo.errors import ConfigurationError, OperationFailure
from pymongo.saslprep import saslprep

Expand Down Expand Up @@ -162,8 +168,10 @@ def _build_credentials_tuple(
return MongoCredential(mech, "$external", user, passwd, aws_props, None)
elif mech == "MONGODB-OIDC":
properties = extra.get("authmechanismproperties", {})
request_token_callback = properties.get("request_token_callback")
provider_name = properties.get("PROVIDER_NAME", "")
callback = properties.get("OIDC_CALLBACK")
human_callback = properties.get("OIDC_HUMAN_CALLBACK")
provider_name = properties.get("PROVIDER_NAME")
token_audience = properties.get("TOKEN_AUDIENCE", "")
default_allowed = [
"*.mongodb.net",
"*.mongodb-dev.net",
Expand All @@ -173,13 +181,40 @@ def _build_credentials_tuple(
"127.0.0.1",
"::1",
]
allowed_hosts = properties.get("allowed_hosts", default_allowed)
if not request_token_callback and provider_name != "aws":
raise ConfigurationError(
"authentication with MONGODB-OIDC requires providing an request_token_callback or a provider_name of 'aws'"
)
allowed_hosts = properties.get("ALLOWED_HOSTS", default_allowed)
msg = "authentication with MONGODB-OIDC requires providing either a callback or a provider_name"
if passwd is not None:
msg = "password is not supported by MONGODB-OIDC"
raise ConfigurationError(msg)
if callback or human_callback:
if provider_name is not None:
raise ConfigurationError(msg)
if callback and human_callback:
msg = "cannot set both OIDC_CALLBACK and OIDC_HUMAN_CALLBACK"
raise ConfigurationError(msg)
elif provider_name is not None:
if provider_name == "aws":
if user is not None:
msg = "AWS provider for MONGODB-OIDC does not support username"
raise ConfigurationError(msg)
callback = _OIDCAWSCallback()
elif provider_name == "azure":
passwd = None
if not token_audience:
raise ConfigurationError(
"Azure provider for MONGODB-OIDC requires a TOKEN_AUDIENCE auth mechanism property"
)
callback = _OIDCAzureCallback(token_audience, user)
else:
raise ConfigurationError(
f"unrecognized provider_name for MONGODB-OIDC: {provider_name}"
)
else:
raise ConfigurationError(msg)

oidc_props = _OIDCProperties(
request_token_callback=request_token_callback,
callback=callback,
human_callback=human_callback,
provider_name=provider_name,
allowed_hosts=allowed_hosts,
)
Expand Down Expand Up @@ -522,6 +557,7 @@ def _authenticate_default(credentials: MongoCredential, conn: Connection) -> Non
"MONGODB-CR": _authenticate_mongo_cr,
"MONGODB-X509": _authenticate_x509,
"MONGODB-AWS": _authenticate_aws,
"MONGODB-OIDC": _authenticate_oidc, # type:ignore[dict-item]
"PLAIN": _authenticate_plain,
"SCRAM-SHA-1": functools.partial(_authenticate_scram, mechanism="SCRAM-SHA-1"),
"SCRAM-SHA-256": functools.partial(_authenticate_scram, mechanism="SCRAM-SHA-256"),
Expand Down Expand Up @@ -582,7 +618,7 @@ def speculate_command(self) -> MutableMapping[str, Any]:
class _OIDCContext(_AuthContext):
def speculate_command(self) -> Optional[MutableMapping[str, Any]]:
authenticator = _get_authenticator(self.credentials, self.address)
cmd = authenticator.auth_start_cmd(False)
cmd = authenticator.get_spec_auth_cmd()
if cmd is None:
return None
cmd["db"] = self.credentials.source
Expand Down
Loading