Skip to content

Commit 1a2c6c5

Browse files
committed
Add OIDC k8s provider
1 parent 0e25654 commit 1a2c6c5

File tree

7 files changed

+195
-15
lines changed

7 files changed

+195
-15
lines changed

.evergreen/.evg.yml

+83-9
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,30 @@ functions:
837837
${PREPARE_SHELL}
838838
MONGODB_URI="${MONGODB_URI}" JAVA_VERSION="${JAVA_VERSION}" .evergreen/run-graalvm-native-image-app.sh
839839
840+
"oidc-auth-test-k8s-func":
841+
- command: shell.exec
842+
type: test
843+
params:
844+
shell: bash
845+
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
846+
script: |-
847+
set -o errexit
848+
${PREPARE_SHELL}
849+
export K8S_VARIANT=${VARIANT}
850+
cd src
851+
git add .
852+
git commit --allow-empty -m "add files"
853+
# uncompressed tar used to allow appending .git folder
854+
export K8S_DRIVERS_TAR_FILE=/tmp/mongo-java-driver.tar
855+
git archive -o $K8S_DRIVERS_TAR_FILE HEAD
856+
tar -rf $K8S_DRIVERS_TAR_FILE .git
857+
export K8S_TEST_CMD="OIDC_ENV=k8s VARIANT=${VARIANT} ./.evergreen/run-mongodb-oidc-test.sh"
858+
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/setup-pod.sh
859+
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/run-self-test.sh
860+
source $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/secrets-export.sh
861+
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/run-driver-test.sh
862+
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/teardown-pod.sh
863+
840864
# Anchors
841865

842866
pre:
@@ -960,6 +984,22 @@ tasks:
960984
export GCPOIDC_TEST_CMD="OIDC_ENV=gcp ./.evergreen/run-mongodb-oidc-test.sh"
961985
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/run-driver-test.sh
962986
987+
- name: "oidc-auth-test-k8s"
988+
commands:
989+
- command: ec2.assume_role
990+
params:
991+
role_arn: ${aws_test_secrets_role}
992+
duration_seconds: 1800
993+
- func: "oidc-auth-test-k8s-func"
994+
vars:
995+
VARIANT: eks
996+
- func: "oidc-auth-test-k8s-func"
997+
vars:
998+
VARIANT: aks
999+
- func: "oidc-auth-test-k8s-func"
1000+
vars:
1001+
VARIANT: gke
1002+
9631003
- name: serverless-test
9641004
commands:
9651005
- func: "run serverless"
@@ -2050,7 +2090,7 @@ task_groups:
20502090
tasks:
20512091
- test-aws-lambda-deployed
20522092

2053-
- name: testoidc_task_group
2093+
- name: test-oidc-task-group
20542094
setup_group:
20552095
- func: fetch source
20562096
- func: prepare resources
@@ -2075,7 +2115,7 @@ task_groups:
20752115
tasks:
20762116
- oidc-auth-test
20772117

2078-
- name: testazureoidc_task_group
2118+
- name: test-oidc-azure-task-group
20792119
setup_group:
20802120
- func: fetch source
20812121
- func: prepare resources
@@ -2098,7 +2138,7 @@ task_groups:
20982138
tasks:
20992139
- oidc-auth-test-azure
21002140

2101-
- name: testgcpoidc_task_group
2141+
- name: test-oidc-gcp-task-group
21022142
setup_group:
21032143
- func: fetch source
21042144
- func: prepare resources
@@ -2122,6 +2162,33 @@ task_groups:
21222162
tasks:
21232163
- oidc-auth-test-gcp
21242164

2165+
- name: test-oidc-k8s-task-group
2166+
setup_group_can_fail_task: true
2167+
setup_group_timeout_secs: 1800
2168+
teardown_task_can_fail_task: true
2169+
teardown_group_timeout_secs: 180
2170+
setup_group:
2171+
- func: fetch source
2172+
- func: prepare resources
2173+
- func: fix absolute paths
2174+
- command: ec2.assume_role
2175+
params:
2176+
role_arn: ${aws_test_secrets_role}
2177+
- command: subprocess.exec
2178+
params:
2179+
binary: bash
2180+
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
2181+
args:
2182+
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh
2183+
teardown_group:
2184+
- command: subprocess.exec
2185+
params:
2186+
binary: bash
2187+
args:
2188+
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh
2189+
tasks:
2190+
- oidc-auth-test-k8s
2191+
21252192
buildvariants:
21262193

21272194
# Test packaging and other release related routines
@@ -2301,25 +2368,32 @@ buildvariants:
23012368
tasks:
23022369
- name: "test_atlas_task_group_search_indexes"
23032370

2304-
- name: "oidc-auth-test"
2371+
- name: oidc-auth-test
23052372
display_name: "OIDC Auth"
23062373
run_on: ubuntu2204-small
23072374
tasks:
2308-
- name: testoidc_task_group
2375+
- name: test-oidc-task-group
23092376
batchtime: 20160 # 14 days
23102377

2311-
- name: testazureoidc-variant
2378+
- name: test-oidc-azure-variant
23122379
display_name: "OIDC Auth Azure"
23132380
run_on: ubuntu2204-small
23142381
tasks:
2315-
- name: testazureoidc_task_group
2382+
- name: test-oidc-azure-task-group
23162383
batchtime: 20160 # 14 days
23172384

2318-
- name: testgcpoidc-variant
2385+
- name: test-oidc-gcp-variant
23192386
display_name: "OIDC Auth GCP"
23202387
run_on: ubuntu2204-small
23212388
tasks:
2322-
- name: testgcpoidc_task_group
2389+
- name: test-oidc-gcp-task-group
2390+
batchtime: 20160 # 14 days
2391+
2392+
- name: test-oidc-k8s-variant
2393+
display_name: "OIDC Auth K8S"
2394+
run_on: ubuntu2204-small
2395+
tasks:
2396+
- name: test-oidc-k8s-task-group
23232397
batchtime: 20160 # 14 days
23242398

23252399
- matrix_name: "aws-auth-test"

.evergreen/run-mongodb-oidc-test.sh

+40
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,46 @@ elif [ $OIDC_ENV == "azure" ]; then
1919
source ./env.sh
2020
elif [ $OIDC_ENV == "gcp" ]; then
2121
source ./secrets-export.sh
22+
elif [ $OIDC_ENV == "k8s" ]; then
23+
# Make sure K8S_VARIANT is set.
24+
if [ -z "$K8S_VARIANT" ]; then
25+
echo "Must specify K8S_VARIANT"
26+
popd
27+
exit 1
28+
fi
29+
30+
if [ -z "${VARIANT}" ]; then
31+
echo "VARIANT is not set"
32+
exit 1
33+
elif [ $VARIANT == "eks" ]; then
34+
path="${AWS_WEB_IDENTITY_TOKEN_FILE}"
35+
elif [ $VARIANT == "aks" ]; then
36+
path="${AZURE_FEDERATED_TOKEN_FILE}"
37+
elif [ $VARIANT == "gke" ]; then
38+
path="/var/run/secrets/kubernetes.io/serviceaccount/token"
39+
else
40+
echo "Unrecognized k8s VARIANT: $VARIANT"
41+
exit 1
42+
fi
43+
44+
# Print the file
45+
if [ -f "$path" ]; then
46+
file_size=$(stat -c%s "$path")
47+
echo "VARIANT: $VARIANT"
48+
echo "Token file path: $path"
49+
echo "Token file size: $file_size bytes"
50+
else
51+
echo "Error: Token file not found at $path" >&2
52+
exit 1
53+
fi
54+
55+
if [ $VARIANT == "gke" ]; then
56+
echo "Skipping gke test to avoid error code 137 when running gradle"
57+
exit 0
58+
fi
59+
60+
# fix for git permissions issue:
61+
git config --global --add safe.directory /tmp/test
2262
else
2363
echo "Unrecognized OIDC_ENV $OIDC_ENV"
2464
exit 1

driver-core/src/main/com/mongodb/MongoCredential.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public final class MongoCredential {
189189
/**
190190
* Mechanism property key for specifying the environment for OIDC, which is
191191
* the name of a built-in OIDC application environment integration to use
192-
* to obtain credentials. The value must be either "gcp" or "azure".
192+
* to obtain credentials. The value must be either "k8s", "gcp", or "azure".
193193
* This is an alternative to supplying a callback.
194194
* <p>
195195
* The "gcp" and "azure" environments require
@@ -199,6 +199,11 @@ public final class MongoCredential {
199199
* {@link MongoCredential#OIDC_CALLBACK_KEY} and
200200
* {@link MongoCredential#OIDC_HUMAN_CALLBACK_KEY}
201201
* must not be provided.
202+
* <p>
203+
* The "k8s" environment will check the env vars
204+
* {@code AZURE_FEDERATED_TOKEN_FILE}, and then {@code AWS_WEB_IDENTITY_TOKEN_FILE},
205+
* for the token file path, and if neither is set will then use the path
206+
* {@code /var/run/secrets/kubernetes.io/serviceaccount/token}.
202207
*
203208
* @see #createOidcCredential(String)
204209
* @see MongoCredential#TOKEN_RESOURCE_KEY
@@ -265,7 +270,7 @@ public final class MongoCredential {
265270
"*.mongodb.net", "*.mongodb-qa.net", "*.mongodb-dev.net", "*.mongodbgov.net", "localhost", "127.0.0.1", "::1"));
266271

267272
/**
268-
* Mechanism property key for specifying he URI of the target resource (sometimes called the audience),
273+
* Mechanism property key for specifying the URI of the target resource (sometimes called the audience),
269274
* used in some OIDC environments.
270275
*
271276
* <p>A TOKEN_RESOURCE with a comma character must be given as a `MongoClient` configuration and not as

driver-core/src/main/com/mongodb/internal/connection/OidcAuthenticator.java

+33-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.bson.BsonDocument;
3939
import org.bson.BsonString;
4040
import org.bson.RawBsonDocument;
41+
import org.jetbrains.annotations.NotNull;
4142

4243
import javax.security.sasl.SaslClient;
4344
import java.io.IOException;
@@ -76,10 +77,11 @@ public final class OidcAuthenticator extends SaslAuthenticator {
7677
private static final String TEST_ENVIRONMENT = "test";
7778
private static final String AZURE_ENVIRONMENT = "azure";
7879
private static final String GCP_ENVIRONMENT = "gcp";
80+
private static final String K8S_ENVIRONMENT = "k8s";
7981
private static final List<String> IMPLEMENTED_ENVIRONMENTS = Arrays.asList(
80-
AZURE_ENVIRONMENT, GCP_ENVIRONMENT, TEST_ENVIRONMENT);
82+
AZURE_ENVIRONMENT, GCP_ENVIRONMENT, K8S_ENVIRONMENT, TEST_ENVIRONMENT);
8183
private static final List<String> USER_SUPPORTED_ENVIRONMENTS = Arrays.asList(
82-
AZURE_ENVIRONMENT, GCP_ENVIRONMENT);
84+
AZURE_ENVIRONMENT, GCP_ENVIRONMENT, K8S_ENVIRONMENT);
8385
private static final List<String> REQUIRES_TOKEN_RESOURCE = Arrays.asList(
8486
AZURE_ENVIRONMENT, GCP_ENVIRONMENT);
8587
private static final List<String> ALLOWS_USERNAME = Arrays.asList(
@@ -90,6 +92,10 @@ public final class OidcAuthenticator extends SaslAuthenticator {
9092

9193
public static final String OIDC_TOKEN_FILE = "OIDC_TOKEN_FILE";
9294

95+
private static final String K8S_FALLBACK_FILE = "/var/run/secrets/kubernetes.io/serviceaccount/token";
96+
private static final String K8S_AZURE_FILE = "AZURE_FEDERATED_TOKEN_FILE";
97+
private static final String K8S_AWS_FILE = "AWS_WEB_IDENTITY_TOKEN_FILE";
98+
9399
private static final int CALLBACK_API_VERSION_NUMBER = 1;
94100

95101
@Nullable
@@ -192,6 +198,8 @@ private OidcCallback getRequestCallback() {
192198
machine = getAzureCallback(getMongoCredential());
193199
} else if (GCP_ENVIRONMENT.equals(environment)) {
194200
machine = getGcpCallback(getMongoCredential());
201+
} else if (K8S_ENVIRONMENT.equals(environment)) {
202+
machine = getK8sCallback();
195203
} else {
196204
machine = getOidcCallbackMechanismProperty(OIDC_CALLBACK_KEY);
197205
}
@@ -206,6 +214,24 @@ private static OidcCallback getTestCallback() {
206214
};
207215
}
208216

217+
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
218+
static OidcCallback getK8sCallback() {
219+
return (context) -> {
220+
String azure = System.getenv(K8S_AZURE_FILE);
221+
String aws = System.getenv(K8S_AWS_FILE);
222+
String path;
223+
if (azure != null) {
224+
path = azure;
225+
} else if (aws != null) {
226+
path = aws;
227+
} else {
228+
path = K8S_FALLBACK_FILE;
229+
}
230+
String accessToken = readTokenFromFile(path);
231+
return new OidcCallbackResult(accessToken);
232+
};
233+
}
234+
209235
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
210236
static OidcCallback getAzureCallback(final MongoCredential credential) {
211237
return (context) -> {
@@ -499,6 +525,11 @@ private static String readTokenFromFile() {
499525
throw new MongoClientException(
500526
format("Environment variable must be specified: %s", OIDC_TOKEN_FILE));
501527
}
528+
return readTokenFromFile(path);
529+
}
530+
531+
@NotNull
532+
private static String readTokenFromFile(final String path) {
502533
try {
503534
return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
504535
} catch (IOException e) {

driver-core/src/test/functional/com/mongodb/ClusterFixture.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,14 @@ static class ShutdownHook extends Thread {
261261
@Override
262262
public void run() {
263263
if (cluster != null) {
264-
new DropDatabaseOperation(getDefaultDatabaseName(), WriteConcern.ACKNOWLEDGED).execute(getBinding());
264+
try {
265+
new DropDatabaseOperation(getDefaultDatabaseName(), WriteConcern.ACKNOWLEDGED).execute(getBinding());
266+
} catch (MongoCommandException e) {
267+
// if we do not have permission to drop the database, assume it is cleaned up in some other way
268+
if (!e.getMessage().contains("Command dropDatabase requires authentication")) {
269+
throw e;
270+
}
271+
}
265272
cluster.close();
266273
}
267274
}

driver-core/src/test/resources/auth/legacy/connection-string.json

+21-1
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@
481481
},
482482
{
483483
"description": "should throw an exception if username is specified for test (MONGODB-OIDC)",
484-
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&ENVIRONMENT:test",
484+
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
485485
"valid": false,
486486
"credential": null
487487
},
@@ -631,6 +631,26 @@
631631
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp",
632632
"valid": false,
633633
"credential": null
634+
},
635+
{
636+
"description": "should recognise the mechanism with k8s provider (MONGODB-OIDC)",
637+
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s",
638+
"valid": true,
639+
"credential": {
640+
"username": null,
641+
"password": null,
642+
"source": "$external",
643+
"mechanism": "MONGODB-OIDC",
644+
"mechanism_properties": {
645+
"ENVIRONMENT": "k8s"
646+
}
647+
}
648+
},
649+
{
650+
"description": "should throw an error for a username and password with k8s provider (MONGODB-OIDC)",
651+
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s",
652+
"valid": false,
653+
"credential": null
634654
}
635655
]
636656
}

driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java

+3
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ private MongoClientSettings createSettings(
826826
String cleanedConnectionString = callback == null ? connectionString : connectionString
827827
.replace("ENVIRONMENT:azure,", "")
828828
.replace("ENVIRONMENT:gcp,", "")
829+
.replace("&authMechanismProperties=ENVIRONMENT:k8s", "")
829830
.replace("ENVIRONMENT:test,", "");
830831
return createSettings(cleanedConnectionString, callback, commandListener, OIDC_CALLBACK_KEY);
831832
}
@@ -1042,6 +1043,8 @@ private OidcCallbackResult callback(final OidcCallbackContext context) {
10421043
c = OidcAuthenticator.getAzureCallback(credential);
10431044
} else if (oidcEnv.contains("gcp")) {
10441045
c = OidcAuthenticator.getGcpCallback(credential);
1046+
} else if (oidcEnv.contains("k8s")) {
1047+
c = OidcAuthenticator.getK8sCallback();
10451048
} else {
10461049
c = getProseTestCallback();
10471050
}

0 commit comments

Comments
 (0)