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

Add OIDC k8s provider #1576

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
74 changes: 74 additions & 0 deletions .evergreen/.evg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,30 @@ functions:
${PREPARE_SHELL}
MONGODB_URI="${MONGODB_URI}" JAVA_VERSION="${JAVA_VERSION}" .evergreen/run-graalvm-native-image-app.sh

"oidc-auth-test-k8s-func":
- command: shell.exec
type: test
params:
shell: bash
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
script: |-
set -o errexit
${PREPARE_SHELL}
export K8S_VARIANT=${VARIANT}
cd src
git add .
git commit --allow-empty -m "add files"
# uncompressed tar used to allow appending .git folder
export K8S_DRIVERS_TAR_FILE=/tmp/mongo-java-driver.tar
git archive -o $K8S_DRIVERS_TAR_FILE HEAD
tar -rf $K8S_DRIVERS_TAR_FILE .git
export K8S_TEST_CMD="OIDC_ENV=k8s ./.evergreen/run-mongodb-oidc-test.sh"
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/setup-pod.sh
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/run-self-test.sh
source $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/secrets-export.sh
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/run-driver-test.sh
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/teardown-pod.sh

# Anchors

pre:
Expand Down Expand Up @@ -960,6 +984,22 @@ tasks:
export GCPOIDC_TEST_CMD="OIDC_ENV=gcp ./.evergreen/run-mongodb-oidc-test.sh"
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/gcp/run-driver-test.sh

- name: "oidc-auth-test-k8s"
commands:
- command: ec2.assume_role
params:
role_arn: ${aws_test_secrets_role}
duration_seconds: 1800
- func: "oidc-auth-test-k8s-func"
vars:
VARIANT: eks
# - func: "oidc-auth-test-k8s-func" TODO disabled, memory issue, consider forking and increasing here: https://github.com/mongodb-labs/drivers-evergreen-tools/commit/4bc3e500b6f0e8ab01f052c4a1bfb782d6a29b4e
# vars:
# VARIANT: gke
Comment on lines +987 to +998
Copy link
Contributor Author

@katcharov katcharov Dec 5, 2024

Choose a reason for hiding this comment

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

Variant is disabled due to failure:

 [2024/12/05 13:34:17.109] Compiling build file '/tmp/test/build.gradle' using BuildScriptTransformer.
 [2024/12/05 13:34:17.109] Starting process 'command 'git''. Working directory: /tmp/test Command: git describe --tags --always --dirty
 [2024/12/05 13:34:22.782] debconf: delaying package configuration, since apt-utils is not installed
 [2024/12/05 13:34:22.782] command terminated with exit code 137

See note in TODO comment (must be removed before merging).

Otherwise, eks and aks are both passing.

- func: "oidc-auth-test-k8s-func"
vars:
VARIANT: aks

- name: serverless-test
commands:
- func: "run serverless"
Expand Down Expand Up @@ -2122,6 +2162,33 @@ task_groups:
tasks:
- oidc-auth-test-gcp

- name: test_oidc_k8s_task_group
setup_group_can_fail_task: true
setup_group_timeout_secs: 1800
teardown_task_can_fail_task: true
teardown_group_timeout_secs: 180
setup_group:
- func: fetch source
- func: prepare resources
- func: fix absolute paths
- command: ec2.assume_role
params:
role_arn: ${aws_test_secrets_role}
- command: subprocess.exec
params:
binary: bash
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
args:
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh
teardown_group:
- command: subprocess.exec
params:
binary: bash
args:
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh
tasks:
- oidc-auth-test-k8s

buildvariants:

# Test packaging and other release related routines
Expand Down Expand Up @@ -2322,6 +2389,13 @@ buildvariants:
- name: testgcpoidc_task_group
batchtime: 20160 # 14 days

- name: testk8soidc-variant
display_name: "OIDC Auth K8S"
run_on: ubuntu2204-small
tasks:
- name: test_oidc_k8s_task_group
batchtime: 20160 # 14 days

- matrix_name: "aws-auth-test"
matrix_spec: { ssl: "nossl", jdk: ["jdk8", "jdk17", "jdk21"], version: ["4.4", "5.0", "6.0", "7.0", "8.0", "latest"], os: "ubuntu",
aws-credential-provider: "*" }
Expand Down
9 changes: 9 additions & 0 deletions .evergreen/run-mongodb-oidc-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ elif [ $OIDC_ENV == "azure" ]; then
source ./env.sh
elif [ $OIDC_ENV == "gcp" ]; then
source ./secrets-export.sh
elif [ $OIDC_ENV == "k8s" ]; then
# Make sure K8S_VARIANT is set.
if [ -z "$K8S_VARIANT" ]; then
echo "Must specify K8S_VARIANT"
popd
exit 1
fi
# fix for git permissions issue:
git config --global --add safe.directory /tmp/test
else
echo "Unrecognized OIDC_ENV $OIDC_ENV"
exit 1
Expand Down
9 changes: 7 additions & 2 deletions driver-core/src/main/com/mongodb/MongoCredential.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public final class MongoCredential {
/**
* Mechanism property key for specifying the environment for OIDC, which is
* the name of a built-in OIDC application environment integration to use
* to obtain credentials. The value must be either "gcp" or "azure".
* to obtain credentials. The value must be either "k8s", "gcp", or "azure".
* This is an alternative to supplying a callback.
* <p>
* The "gcp" and "azure" environments require
Expand All @@ -199,6 +199,11 @@ public final class MongoCredential {
* {@link MongoCredential#OIDC_CALLBACK_KEY} and
* {@link MongoCredential#OIDC_HUMAN_CALLBACK_KEY}
* must not be provided.
* <p>
* The "k8s" environment will check the env vars
* {@code AZURE_FEDERATED_TOKEN_FILE}, and then {@code AWS_WEB_IDENTITY_TOKEN_FILE},
* for the token file path, and if neither is set will then use the path
* {@code /var/run/secrets/kubernetes.io/serviceaccount/token}.
*
* @see #createOidcCredential(String)
* @see MongoCredential#TOKEN_RESOURCE_KEY
Expand Down Expand Up @@ -265,7 +270,7 @@ public final class MongoCredential {
"*.mongodb.net", "*.mongodb-qa.net", "*.mongodb-dev.net", "*.mongodbgov.net", "localhost", "127.0.0.1", "::1"));

/**
* Mechanism property key for specifying he URI of the target resource (sometimes called the audience),
* Mechanism property key for specifying the URI of the target resource (sometimes called the audience),
* used in some OIDC environments.
*
* <p>A TOKEN_RESOURCE with a comma character must be given as a `MongoClient` configuration and not as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.bson.RawBsonDocument;
import org.jetbrains.annotations.NotNull;

import javax.security.sasl.SaslClient;
import java.io.IOException;
Expand Down Expand Up @@ -76,10 +77,11 @@ public final class OidcAuthenticator extends SaslAuthenticator {
private static final String TEST_ENVIRONMENT = "test";
private static final String AZURE_ENVIRONMENT = "azure";
private static final String GCP_ENVIRONMENT = "gcp";
private static final String K8S_ENVIRONMENT = "k8s";
private static final List<String> IMPLEMENTED_ENVIRONMENTS = Arrays.asList(
AZURE_ENVIRONMENT, GCP_ENVIRONMENT, TEST_ENVIRONMENT);
AZURE_ENVIRONMENT, GCP_ENVIRONMENT, K8S_ENVIRONMENT, TEST_ENVIRONMENT);
private static final List<String> USER_SUPPORTED_ENVIRONMENTS = Arrays.asList(
AZURE_ENVIRONMENT, GCP_ENVIRONMENT);
AZURE_ENVIRONMENT, GCP_ENVIRONMENT, K8S_ENVIRONMENT);
private static final List<String> REQUIRES_TOKEN_RESOURCE = Arrays.asList(
AZURE_ENVIRONMENT, GCP_ENVIRONMENT);
private static final List<String> ALLOWS_USERNAME = Arrays.asList(
Expand All @@ -90,6 +92,10 @@ public final class OidcAuthenticator extends SaslAuthenticator {

public static final String OIDC_TOKEN_FILE = "OIDC_TOKEN_FILE";

private static final String K8S_FALLBACK_FILE = "/var/run/secrets/kubernetes.io/serviceaccount/token";
private static final String K8S_AZURE_FILE = "AZURE_FEDERATED_TOKEN_FILE";
private static final String K8S_AWS_FILE = "AWS_WEB_IDENTITY_TOKEN_FILE";

private static final int CALLBACK_API_VERSION_NUMBER = 1;

@Nullable
Expand Down Expand Up @@ -192,6 +198,8 @@ private OidcCallback getRequestCallback() {
machine = getAzureCallback(getMongoCredential());
} else if (GCP_ENVIRONMENT.equals(environment)) {
machine = getGcpCallback(getMongoCredential());
} else if (K8S_ENVIRONMENT.equals(environment)) {
machine = getK8sCallback();
} else {
machine = getOidcCallbackMechanismProperty(OIDC_CALLBACK_KEY);
}
Expand All @@ -206,6 +214,24 @@ private static OidcCallback getTestCallback() {
};
}

@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
static OidcCallback getK8sCallback() {
return (context) -> {
String azure = System.getenv(K8S_AZURE_FILE);
String aws = System.getenv(K8S_AWS_FILE);
String path;
if (azure != null) {
path = azure;
} else if (aws != null) {
path = aws;
} else {
path = K8S_FALLBACK_FILE;
}
String accessToken = readTokenFromFile(path);
return new OidcCallbackResult(accessToken);
};
}

@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
static OidcCallback getAzureCallback(final MongoCredential credential) {
return (context) -> {
Expand Down Expand Up @@ -499,6 +525,11 @@ private static String readTokenFromFile() {
throw new MongoClientException(
format("Environment variable must be specified: %s", OIDC_TOKEN_FILE));
}
return readTokenFromFile(path);
}

@NotNull
private static String readTokenFromFile(final String path) {
try {
return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,14 @@ static class ShutdownHook extends Thread {
@Override
public void run() {
if (cluster != null) {
new DropDatabaseOperation(getDefaultDatabaseName(), WriteConcern.ACKNOWLEDGED).execute(getBinding());
try {
new DropDatabaseOperation(getDefaultDatabaseName(), WriteConcern.ACKNOWLEDGED).execute(getBinding());
} catch (MongoCommandException e) {
// if we do not have permission to drop the database, assume it is cleaned up in some other way
if (!e.getMessage().contains("Command dropDatabase requires authentication")) {
throw e;
}
}
cluster.close();
Comment on lines -264 to 272
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As of this PR, we will sometimes lack permission to drop the database.

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@
},
{
"description": "should throw an exception if username is specified for test (MONGODB-OIDC)",
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&ENVIRONMENT:test",
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
"valid": false,
"credential": null
},
Expand Down Expand Up @@ -631,6 +631,26 @@
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp",
"valid": false,
"credential": null
},
{
"description": "should recognise the mechanism with k8s provider (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s",
"valid": true,
"credential": {
"username": null,
"password": null,
"source": "$external",
"mechanism": "MONGODB-OIDC",
"mechanism_properties": {
"ENVIRONMENT": "k8s"
}
}
},
{
"description": "should throw an error for a username and password with k8s provider (MONGODB-OIDC)",
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s",
"valid": false,
"credential": null
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,7 @@ private MongoClientSettings createSettings(
String cleanedConnectionString = callback == null ? connectionString : connectionString
.replace("ENVIRONMENT:azure,", "")
.replace("ENVIRONMENT:gcp,", "")
.replace("&authMechanismProperties=ENVIRONMENT:k8s", "")
.replace("ENVIRONMENT:test,", "");
return createSettings(cleanedConnectionString, callback, commandListener, OIDC_CALLBACK_KEY);
}
Expand Down Expand Up @@ -1042,6 +1043,8 @@ private OidcCallbackResult callback(final OidcCallbackContext context) {
c = OidcAuthenticator.getAzureCallback(credential);
} else if (oidcEnv.contains("gcp")) {
c = OidcAuthenticator.getGcpCallback(credential);
} else if (oidcEnv.contains("k8s")) {
c = OidcAuthenticator.getK8sCallback();
} else {
c = getProseTestCallback();
}
Expand Down