Skip to content

Commit eb59132

Browse files
author
Oleksandr Poliakov
committed
CSHARP-4610: OIDC: Automatic token acquisition for GCP Identity Provider
1 parent 757a238 commit eb59132

File tree

13 files changed

+364
-76
lines changed

13 files changed

+364
-76
lines changed

evergreen/evergreen.yml

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,13 +1222,33 @@ tasks:
12221222
set -o errexit
12231223
${PREPARE_SHELL}
12241224
1225-
dotnet build ./tests/MongoDB.Driver.Tests/MongoDB.Driver.Tests.csproj
1226-
tar czf /tmp/mongo-csharp-driver.tgz ./tests/MongoDB.Driver.Tests/bin/Debug/net6.0 ./evergreen/run-mongodb-oidc-azure-tests.sh
1225+
dotnet build
1226+
tar czf /tmp/mongo-csharp-driver.tgz tests/*.Tests/bin/Debug/net6.0/ ./evergreen/run-mongodb-oidc-env-tests.sh
12271227
12281228
export AZUREOIDC_DRIVERS_TAR_FILE=/tmp/mongo-csharp-driver.tgz
1229-
export AZUREOIDC_TEST_CMD="./evergreen/run-mongodb-oidc-azure-tests.sh"
1229+
export AZUREOIDC_TEST_CMD="OIDC_ENV=azure ./evergreen/run-mongodb-oidc-env-tests.sh"
12301230
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/azure/run-driver-test.sh
12311231
1232+
- name: test-oidc-gcp
1233+
commands:
1234+
- command: shell.exec
1235+
params:
1236+
shell: bash
1237+
working_dir: mongo-csharp-driver
1238+
script: |-
1239+
set -o errexit
1240+
${PREPARE_SHELL}
1241+
1242+
# Copy secrets-export.sh created by '${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/setup.sh' script to read secrets inside the vm
1243+
cp $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/secrets-export.sh ./secrets-export.sh
1244+
1245+
dotnet build
1246+
tar czf /tmp/mongo-csharp-driver.tgz tests/*.Tests/bin/Debug/net6.0/ ./evergreen/run-mongodb-oidc-env-tests.sh ./secrets-export.sh
1247+
1248+
export GCPOIDC_DRIVERS_TAR_FILE=/tmp/mongo-csharp-driver.tgz
1249+
export GCPOIDC_TEST_CMD="OIDC_ENV=gcp ./evergreen/run-mongodb-oidc-env-tests.sh"
1250+
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/run-driver-test.sh
1251+
12321252
- name: test-serverless
12331253
exec_timeout_secs: 2700 # 45 minutes: 15 for setup + 30 for tests
12341254
commands:
@@ -2146,6 +2166,31 @@ task_groups:
21462166
tasks:
21472167
- test-oidc-azure
21482168

2169+
- name: oidc-auth-gcp-task-group
2170+
setup_group_can_fail_task: true
2171+
setup_group_timeout_secs: 1800 # 30 minutes
2172+
setup_group:
2173+
- func: fetch-source
2174+
- func: prepare-resources
2175+
- func: fix-absolute-paths
2176+
- func: make-files-executable
2177+
- func: install-dotnet
2178+
- command: subprocess.exec
2179+
params:
2180+
binary: bash
2181+
env:
2182+
GCPOIDC_VMNAME_PREFIX: "CSHARP_DRIVER"
2183+
args:
2184+
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/setup.sh
2185+
teardown_group:
2186+
- command: subprocess.exec
2187+
params:
2188+
binary: bash
2189+
args:
2190+
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/teardown.sh
2191+
tasks:
2192+
- test-oidc-gcp
2193+
21492194
- name: serverless-task-group
21502195
setup_group_can_fail_task: true
21512196
setup_group_timeout_secs: 1800 # 30 minutes
@@ -2293,7 +2338,7 @@ buildvariants:
22932338
- name: plain-auth-tests
22942339

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

2354+
- matrix_name: mongodb-oidc-gcp-tests
2355+
matrix_spec: { os: [ "ubuntu-2004" ] }
2356+
display_name: "MongoDB-OIDC Auth (gcp) - ${os}"
2357+
batchtime: 20160 # 14 days
2358+
tasks:
2359+
- name: oidc-auth-gcp-task-group
2360+
23092361
- matrix_name: "ocsp-tests"
23102362
matrix_spec: { version: ["4.4", "5.0", "6.0", "7.0", "rapid", "latest"], auth: "noauth", ssl: "ssl", topology: "standalone", os: "windows-64" }
23112363
display_name: "OCSP ${version} ${os}"

evergreen/run-mongodb-oidc-azure-tests.sh

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env bash
2+
3+
# Don't trace since the URI contains a password that shouldn't show up in the logs
4+
set -o errexit # Exit the script with error if any of the commands fail
5+
6+
DOTNET_SDK_PATH="$(pwd)/.dotnet"
7+
8+
echo "Downloading .NET SDK installer into $DOTNET_SDK_PATH folder..."
9+
curl -Lfo ./dotnet-install.sh https://dot.net/v1/dotnet-install.sh
10+
echo "Installing .NET LTS SDK..."
11+
bash ./dotnet-install.sh --channel 6.0 --install-dir "$DOTNET_SDK_PATH" --no-path
12+
export PATH=$DOTNET_SDK_PATH:$PATH
13+
14+
if [ "$OIDC_ENV" == "azure" ]; then
15+
source ./env.sh
16+
TOKEN_RESOURCE="$AZUREOIDC_RESOURCE"
17+
elif [ "$OIDC_ENV" == "gcp" ]; then
18+
source ./secrets-export.sh
19+
TOKEN_RESOURCE="$GCPOIDC_AUDIENCE"
20+
else
21+
echo "Unrecognized OIDC_ENV $OIDC_ENV"
22+
exit 1
23+
fi
24+
25+
if [[ "$MONGODB_URI" =~ ^mongodb:.* ]]; then
26+
MONGODB_URI="mongodb://${OIDC_ADMIN_USER}:${OIDC_ADMIN_PWD}@${MONGODB_URI:10}?authSource=admin"
27+
elif [[ "$MONGODB_URI" =~ ^mongodb\+srv:.* ]]; then
28+
MONGODB_URI="mongodb+srv://${OIDC_ADMIN_USER}:${OIDC_ADMIN_PWD}@${MONGODB_URI:14}?authSource=admin"
29+
else
30+
echo "Unexpected MONGODB_URI format: $MONGODB_URI"
31+
exit 1
32+
fi
33+
34+
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

specifications/auth/tests/legacy/connection-string.json

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@
482482
}
483483
},
484484
{
485-
"description": "should recognise the mechanism with test integration (MONGODB-OIDC)",
485+
"description": "should recognise the mechanism with test environment (MONGODB-OIDC)",
486486
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
487487
"valid": true,
488488
"credential": {
@@ -569,6 +569,66 @@
569569
}
570570
}
571571
},
572+
{
573+
"description": "should accept a url-encoded TOKEN_RESOURCE (MONGODB-OIDC)",
574+
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb%3A%2F%2Ftest-cluster",
575+
"valid": true,
576+
"credential": {
577+
"username": "user",
578+
"password": null,
579+
"source": "$external",
580+
"mechanism": "MONGODB-OIDC",
581+
"mechanism_properties": {
582+
"ENVIRONMENT": "azure",
583+
"TOKEN_RESOURCE": "mongodb://test-cluster"
584+
}
585+
}
586+
},
587+
{
588+
"description": "should accept an un-encoded TOKEN_RESOURCE (MONGODB-OIDC)",
589+
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb://test-cluster",
590+
"valid": true,
591+
"credential": {
592+
"username": "user",
593+
"password": null,
594+
"source": "$external",
595+
"mechanism": "MONGODB-OIDC",
596+
"mechanism_properties": {
597+
"ENVIRONMENT": "azure",
598+
"TOKEN_RESOURCE": "mongodb://test-cluster"
599+
}
600+
}
601+
},
602+
{
603+
"description": "should handle a complicated url-encoded TOKEN_RESOURCE (MONGODB-OIDC)",
604+
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:abc%2Cd%25ef%3Ag%26hi",
605+
"valid": true,
606+
"credential": {
607+
"username": "user",
608+
"password": null,
609+
"source": "$external",
610+
"mechanism": "MONGODB-OIDC",
611+
"mechanism_properties": {
612+
"ENVIRONMENT": "azure",
613+
"TOKEN_RESOURCE": "abc,d%ef:g&hi"
614+
}
615+
}
616+
},
617+
{
618+
"description": "should url-encode a TOKEN_RESOURCE (MONGODB-OIDC)",
619+
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:a$b",
620+
"valid": true,
621+
"credential": {
622+
"username": "user",
623+
"password": null,
624+
"source": "$external",
625+
"mechanism": "MONGODB-OIDC",
626+
"mechanism_properties": {
627+
"ENVIRONMENT": "azure",
628+
"TOKEN_RESOURCE": "a$b"
629+
}
630+
}
631+
},
572632
{
573633
"description": "should accept a username and throw an error for a password with azure provider (MONGODB-OIDC)",
574634
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo",
@@ -609,4 +669,4 @@
609669
"credential": null
610670
}
611671
]
612-
}
672+
}

specifications/auth/tests/legacy/connection-string.yml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ tests:
374374
uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test
375375
valid: false
376376
credential:
377-
- description: should throw an exception if username is specified for aws (MONGODB-OIDC)
377+
- description: should throw an exception if username is specified for test (MONGODB-OIDC)
378378
uri: mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&ENVIRONMENT:test
379379
valid: false
380380
credential:
@@ -412,6 +412,50 @@ tests:
412412
mechanism_properties:
413413
ENVIRONMENT: azure
414414
TOKEN_RESOURCE: foo
415+
- description: should accept a url-encoded TOKEN_RESOURCE (MONGODB-OIDC)
416+
uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb%3A%2F%2Ftest-cluster
417+
valid: true
418+
credential:
419+
username: user
420+
password: null
421+
source: $external
422+
mechanism: MONGODB-OIDC
423+
mechanism_properties:
424+
ENVIRONMENT: azure
425+
TOKEN_RESOURCE: 'mongodb://test-cluster'
426+
- description: should accept an un-encoded TOKEN_RESOURCE (MONGODB-OIDC)
427+
uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb://test-cluster
428+
valid: true
429+
credential:
430+
username: user
431+
password: null
432+
source: $external
433+
mechanism: MONGODB-OIDC
434+
mechanism_properties:
435+
ENVIRONMENT: azure
436+
TOKEN_RESOURCE: 'mongodb://test-cluster'
437+
- description: should handle a complicated url-encoded TOKEN_RESOURCE (MONGODB-OIDC)
438+
uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:abc%2Cd%25ef%3Ag%26hi
439+
valid: true
440+
credential:
441+
username: user
442+
password: null
443+
source: $external
444+
mechanism: MONGODB-OIDC
445+
mechanism_properties:
446+
ENVIRONMENT: azure
447+
TOKEN_RESOURCE: 'abc,d%ef:g&hi'
448+
- description: should url-encode a TOKEN_RESOURCE (MONGODB-OIDC)
449+
uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:a$b
450+
valid: true
451+
credential:
452+
username: user
453+
password: null
454+
source: $external
455+
mechanism: MONGODB-OIDC
456+
mechanism_properties:
457+
ENVIRONMENT: azure
458+
TOKEN_RESOURCE: a$b
415459
- description: should accept a username and throw an error for a password with azure provider (MONGODB-OIDC)
416460
uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo
417461
valid: false

src/MongoDB.Driver.Core/Core/Authentication/Oidc/AzureOidcCallback.cs

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,13 @@
1515

1616
using System;
1717
using System.IO;
18-
using System.Net;
19-
using System.Threading;
20-
using System.Threading.Tasks;
2118
using MongoDB.Bson.IO;
2219
using MongoDB.Bson.Serialization;
2320
using MongoDB.Bson.Serialization.Serializers;
2421

2522
namespace MongoDB.Driver.Core.Authentication.Oidc
2623
{
27-
internal sealed class AzureOidcCallback : IOidcCallback
24+
internal sealed class AzureOidcCallback : HttpRequestOidcCallback
2825
{
2926
private readonly string _tokenResource;
3027

@@ -33,50 +30,20 @@ public AzureOidcCallback(string tokenResource)
3330
_tokenResource = tokenResource;
3431
}
3532

36-
public OidcAccessToken GetOidcAccessToken(OidcCallbackParameters parameters, CancellationToken cancellationToken)
33+
protected override (Uri Uri, (string Key, string Value)[] headers) GetHttpRequestParams(OidcCallbackParameters parameters)
3734
{
38-
var request = CreateMetadataRequest(parameters);
39-
using (cancellationToken.Register(() => request.Abort(), useSynchronizationContext: false))
40-
{
41-
var response = request.GetResponse();
42-
return ParseMetadataResponse((HttpWebResponse)response);
43-
}
44-
}
45-
46-
public async Task<OidcAccessToken> GetOidcAccessTokenAsync(OidcCallbackParameters parameters, CancellationToken cancellationToken)
47-
{
48-
var request = CreateMetadataRequest(parameters);
49-
using (cancellationToken.Register(() => request.Abort(), useSynchronizationContext: false))
50-
{
51-
var response = await request.GetResponseAsync().ConfigureAwait(false);
52-
return ParseMetadataResponse((HttpWebResponse)response);
53-
}
54-
}
55-
56-
private HttpWebRequest CreateMetadataRequest(OidcCallbackParameters parameters)
57-
{
58-
var metadataUrl = $"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={_tokenResource}";
35+
var metadataUrl = $"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={Uri.EscapeDataString(_tokenResource)}";
5936
if (!string.IsNullOrEmpty(parameters.UserName))
6037
{
61-
metadataUrl += $"&client_id={parameters.UserName}";
38+
metadataUrl += $"&client_id={Uri.EscapeDataString(parameters.UserName)}";
6239
}
6340

64-
var request = WebRequest.CreateHttp(new Uri(metadataUrl));
65-
request.Headers["Metadata"] = "true";
66-
request.Accept = "application/json";
67-
request.Method = "GET";
68-
69-
return request;
41+
return (new Uri(metadataUrl), new [] { ("Accept", "application/json"), ("Metadata", "true") });
7042
}
7143

72-
private OidcAccessToken ParseMetadataResponse(HttpWebResponse response)
44+
protected override OidcAccessToken ProcessHttpResponse(Stream responseStream)
7345
{
74-
if (response.StatusCode != HttpStatusCode.OK)
75-
{
76-
throw new InvalidOperationException($"Response status code does not indicate success {response.StatusCode}:{response.StatusDescription}");
77-
}
78-
79-
using var responseReader = new StreamReader(response.GetResponseStream());
46+
using var responseReader = new StreamReader(responseStream);
8047
using var jsonReader = new JsonReader(responseReader);
8148

8249
var context = BsonDeserializationContext.CreateRoot(jsonReader);

0 commit comments

Comments
 (0)