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

CSHARP-4610: OIDC: Automatic token acquisition for GCP Identity Provider #1315

Merged
merged 1 commit into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 56 additions & 4 deletions evergreen/evergreen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1222,13 +1222,33 @@ tasks:
set -o errexit
${PREPARE_SHELL}

dotnet build ./tests/MongoDB.Driver.Tests/MongoDB.Driver.Tests.csproj
tar czf /tmp/mongo-csharp-driver.tgz ./tests/MongoDB.Driver.Tests/bin/Debug/net6.0 ./evergreen/run-mongodb-oidc-azure-tests.sh
dotnet build
tar czf /tmp/mongo-csharp-driver.tgz tests/*.Tests/bin/Debug/net6.0/ ./evergreen/run-mongodb-oidc-env-tests.sh

export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-csharp-driver.tgz
export AZUREOIDC_TEST_CMD="./evergreen/run-mongodb-oidc-azure-tests.sh"
export AZUREOIDC_TEST_CMD="OIDC_ENV=azure ./evergreen/run-mongodb-oidc-env-tests.sh"
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/azure/run-driver-test.sh

- name: test-oidc-gcp
commands:
- command: shell.exec
params:
shell: bash
working_dir: mongo-csharp-driver
script: |-
set -o errexit
${PREPARE_SHELL}

# Copy secrets-export.sh created by '${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/setup.sh' script to read secrets inside the vm
cp $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/secrets-export.sh ./secrets-export.sh

dotnet build
tar czf /tmp/mongo-csharp-driver.tgz tests/*.Tests/bin/Debug/net6.0/ ./evergreen/run-mongodb-oidc-env-tests.sh ./secrets-export.sh

export GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-csharp-driver.tgz
export GCPOIDC_TEST_CMD="OIDC_ENV=gcp ./evergreen/run-mongodb-oidc-env-tests.sh"
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/run-driver-test.sh

- name: test-serverless
exec_timeout_secs: 2700 # 45 minutes: 15 for setup + 30 for tests
commands:
Expand Down Expand Up @@ -2146,6 +2166,31 @@ task_groups:
tasks:
- test-oidc-azure

- name: oidc-auth-gcp-task-group
setup_group_can_fail_task: true
setup_group_timeout_secs: 1800 # 30 minutes
setup_group:
- func: fetch-source
- func: prepare-resources
- func: fix-absolute-paths
- func: make-files-executable
- func: install-dotnet
- command: subprocess.exec
params:
binary: bash
env:
GCPOIDC_VMNAME_PREFIX: "CSHARP_DRIVER"
args:
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/setup.sh
teardown_group:
- command: subprocess.exec
params:
binary: bash
args:
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/teardown.sh
tasks:
- test-oidc-gcp

- name: serverless-task-group
setup_group_can_fail_task: true
setup_group_timeout_secs: 1800 # 30 minutes
Expand Down Expand Up @@ -2293,7 +2338,7 @@ buildvariants:
- name: plain-auth-tests

- matrix_name: mongodb-oidc-test-tests
matrix_spec: { os: [ "ubuntu-2004", "macos-1100" ] }
matrix_spec: { os: [ "ubuntu-2004" ] }
display_name: "MongoDB-OIDC Auth (test) - ${os}"
batchtime: 20160 # 14 days
tasks:
Expand All @@ -2306,6 +2351,13 @@ buildvariants:
tasks:
- name: oidc-auth-azure-task-group

- matrix_name: mongodb-oidc-gcp-tests
matrix_spec: { os: [ "ubuntu-2004" ] }
display_name: "MongoDB-OIDC Auth (gcp) - ${os}"
batchtime: 20160 # 14 days
tasks:
- name: oidc-auth-gcp-task-group

- matrix_name: "ocsp-tests"
matrix_spec: { version: ["4.4", "5.0", "6.0", "7.0", "rapid", "latest"], auth: "noauth", ssl: "ssl", topology: "standalone", os: "windows-64" }
display_name: "OCSP ${version} ${os}"
Expand Down
17 changes: 0 additions & 17 deletions evergreen/run-mongodb-oidc-azure-tests.sh

This file was deleted.

34 changes: 34 additions & 0 deletions evergreen/run-mongodb-oidc-env-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash

# Don't trace since the URI contains a password that shouldn't show up in the logs
set -o errexit # Exit the script with error if any of the commands fail

DOTNET_SDK_PATH="$(pwd)/.dotnet"

echo "Downloading .NET SDK installer into $DOTNET_SDK_PATH folder..."
curl -Lfo ./dotnet-install.sh https://dot.net/v1/dotnet-install.sh
echo "Installing .NET LTS SDK..."
bash ./dotnet-install.sh --channel 6.0 --install-dir "$DOTNET_SDK_PATH" --no-path
export PATH=$DOTNET_SDK_PATH:$PATH

if [ "$OIDC_ENV" == "azure" ]; then
source ./env.sh
TOKEN_RESOURCE="$AZUREOIDC_RESOURCE"
elif [ "$OIDC_ENV" == "gcp" ]; then
source ./secrets-export.sh
TOKEN_RESOURCE="$GCPOIDC_AUDIENCE"
else
echo "Unrecognized OIDC_ENV $OIDC_ENV"
exit 1
fi

if [[ "$MONGODB_URI" =~ ^mongodb:.* ]]; then
MONGODB_URI="mongodb://${OIDC_ADMIN_USER}:${OIDC_ADMIN_PWD}@${MONGODB_URI:10}?authSource=admin"
elif [[ "$MONGODB_URI" =~ ^mongodb\+srv:.* ]]; then
MONGODB_URI="mongodb+srv://${OIDC_ADMIN_USER}:${OIDC_ADMIN_PWD}@${MONGODB_URI:14}?authSource=admin"
else
echo "Unexpected MONGODB_URI format: $MONGODB_URI"
exit 1
fi

dotnet test --no-build --framework net6.0 --filter Category=MongoDbOidc -e OIDC_ENV="$OIDC_ENV" -e TOKEN_RESOURCE="$TOKEN_RESOURCE" -e MONGODB_URI="$MONGODB_URI" --results-directory ./build/test-results --logger "console;verbosity=detailed" ./tests/**/*.Tests.dll
64 changes: 62 additions & 2 deletions specifications/auth/tests/legacy/connection-string.json
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@
}
},
{
"description": "should recognise the mechanism with test integration (MONGODB-OIDC)",
"description": "should recognise the mechanism with test environment (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
"valid": true,
"credential": {
Expand Down Expand Up @@ -569,6 +569,66 @@
}
}
},
{
"description": "should accept a url-encoded TOKEN_RESOURCE (MONGODB-OIDC)",
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb%3A%2F%2Ftest-cluster",
"valid": true,
"credential": {
"username": "user",
"password": null,
"source": "$external",
"mechanism": "MONGODB-OIDC",
"mechanism_properties": {
"ENVIRONMENT": "azure",
"TOKEN_RESOURCE": "mongodb://test-cluster"
}
}
},
{
"description": "should accept an un-encoded TOKEN_RESOURCE (MONGODB-OIDC)",
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb://test-cluster",
"valid": true,
"credential": {
"username": "user",
"password": null,
"source": "$external",
"mechanism": "MONGODB-OIDC",
"mechanism_properties": {
"ENVIRONMENT": "azure",
"TOKEN_RESOURCE": "mongodb://test-cluster"
}
}
},
{
"description": "should handle a complicated url-encoded TOKEN_RESOURCE (MONGODB-OIDC)",
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:abc%2Cd%25ef%3Ag%26hi",
"valid": true,
"credential": {
"username": "user",
"password": null,
"source": "$external",
"mechanism": "MONGODB-OIDC",
"mechanism_properties": {
"ENVIRONMENT": "azure",
"TOKEN_RESOURCE": "abc,d%ef:g&hi"
}
}
},
{
"description": "should url-encode a TOKEN_RESOURCE (MONGODB-OIDC)",
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:a$b",
"valid": true,
"credential": {
"username": "user",
"password": null,
"source": "$external",
"mechanism": "MONGODB-OIDC",
"mechanism_properties": {
"ENVIRONMENT": "azure",
"TOKEN_RESOURCE": "a$b"
}
}
},
{
"description": "should accept a username and throw an error for a password with azure provider (MONGODB-OIDC)",
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo",
Expand Down Expand Up @@ -609,4 +669,4 @@
"credential": null
}
]
}
}
46 changes: 45 additions & 1 deletion specifications/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 Expand Up @@ -412,6 +412,50 @@ tests:
mechanism_properties:
ENVIRONMENT: azure
TOKEN_RESOURCE: foo
- description: should accept a url-encoded TOKEN_RESOURCE (MONGODB-OIDC)
uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb%3A%2F%2Ftest-cluster
valid: true
credential:
username: user
password: null
source: $external
mechanism: MONGODB-OIDC
mechanism_properties:
ENVIRONMENT: azure
TOKEN_RESOURCE: 'mongodb://test-cluster'
- description: should accept an un-encoded TOKEN_RESOURCE (MONGODB-OIDC)
uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb://test-cluster
valid: true
credential:
username: user
password: null
source: $external
mechanism: MONGODB-OIDC
mechanism_properties:
ENVIRONMENT: azure
TOKEN_RESOURCE: 'mongodb://test-cluster'
- description: should handle a complicated url-encoded TOKEN_RESOURCE (MONGODB-OIDC)
uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:abc%2Cd%25ef%3Ag%26hi
valid: true
credential:
username: user
password: null
source: $external
mechanism: MONGODB-OIDC
mechanism_properties:
ENVIRONMENT: azure
TOKEN_RESOURCE: 'abc,d%ef:g&hi'
- description: should url-encode a TOKEN_RESOURCE (MONGODB-OIDC)
uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:a$b
valid: true
credential:
username: user
password: null
source: $external
mechanism: MONGODB-OIDC
mechanism_properties:
ENVIRONMENT: azure
TOKEN_RESOURCE: a$b
- description: should accept a username and throw an error for a password with azure provider (MONGODB-OIDC)
uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo
valid: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,13 @@

using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;

namespace MongoDB.Driver.Core.Authentication.Oidc
{
internal sealed class AzureOidcCallback : IOidcCallback
internal sealed class AzureOidcCallback : HttpRequestOidcCallback
{
private readonly string _tokenResource;

Expand All @@ -33,50 +30,20 @@ public AzureOidcCallback(string tokenResource)
_tokenResource = tokenResource;
}

public OidcAccessToken GetOidcAccessToken(OidcCallbackParameters parameters, CancellationToken cancellationToken)
protected override (Uri Uri, (string Key, string Value)[] headers) GetHttpRequestParams(OidcCallbackParameters parameters)
{
var request = CreateMetadataRequest(parameters);
using (cancellationToken.Register(() => request.Abort(), useSynchronizationContext: false))
{
var response = request.GetResponse();
return ParseMetadataResponse((HttpWebResponse)response);
}
}

public async Task<OidcAccessToken> GetOidcAccessTokenAsync(OidcCallbackParameters parameters, CancellationToken cancellationToken)
{
var request = CreateMetadataRequest(parameters);
using (cancellationToken.Register(() => request.Abort(), useSynchronizationContext: false))
{
var response = await request.GetResponseAsync().ConfigureAwait(false);
return ParseMetadataResponse((HttpWebResponse)response);
}
}

private HttpWebRequest CreateMetadataRequest(OidcCallbackParameters parameters)
{
var metadataUrl = $"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={_tokenResource}";
var metadataUrl = $"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={Uri.EscapeDataString(_tokenResource)}";
if (!string.IsNullOrEmpty(parameters.UserName))
{
metadataUrl += $"&client_id={parameters.UserName}";
metadataUrl += $"&client_id={Uri.EscapeDataString(parameters.UserName)}";
}

var request = WebRequest.CreateHttp(new Uri(metadataUrl));
request.Headers["Metadata"] = "true";
request.Accept = "application/json";
request.Method = "GET";

return request;
return (new Uri(metadataUrl), new [] { ("Accept", "application/json"), ("Metadata", "true") });
}

private OidcAccessToken ParseMetadataResponse(HttpWebResponse response)
protected override OidcAccessToken ProcessHttpResponse(Stream responseStream)
{
if (response.StatusCode != HttpStatusCode.OK)
{
throw new InvalidOperationException($"Response status code does not indicate success {response.StatusCode}:{response.StatusDescription}");
}

using var responseReader = new StreamReader(response.GetResponseStream());
using var responseReader = new StreamReader(responseStream);
using var jsonReader = new JsonReader(responseReader);

var context = BsonDeserializationContext.CreateRoot(jsonReader);
Expand Down