Skip to content

CSHARP-5581: Test MONGODB-X509 on cloud-dev #1757

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 12 commits into from
Aug 21, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,15 @@ Task("TestCsfleWithGcpKms")
action: (BuildConfig buildConfig, Path testProject) =>
RunTests(buildConfig, testProject, filter: "Category=\"CsfleGCPKMS\""));

Task("TestX509")
.IsDependentOn("Build")
.DoesForEach(
items: GetFiles("./**/MongoDB.Driver.Tests.csproj"),
action: (BuildConfig buildConfig, Path testProject) =>
RunTests(buildConfig, testProject, filter: "Category=\"X509\""));

Task("TestX509Net60").IsDependentOn("TestX509");

Task("Package")
.IsDependentOn("PackageNugetPackages");

Expand Down
46 changes: 31 additions & 15 deletions evergreen/convert-client-cert-to-pkcs12.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,39 @@
set -o errexit # Exit the script with an error if any of the commands fail

# Environment variables used as input:
# CLIENT_PEM Path to mongo -orchestration's client.pem: must be set.
# MONGO_X509_CLIENT_P12 Filename for client certificate in p12 format
# CLIENT_PEM Path to mongo client.pem: must be set
# P12_FILENAME Filename for client certificate in p12 format
# P12_PASSWORD Password for client certificate in p12 format
# P12_FRIENDLY_NAME Friendly name for client certificate in p12 format
# OUT_CLIENT_PATH_VAR Name of the output variable containing the path of the p12 certificate
# OUT_CLIENT_PASSWORD_VAR Name of the output variable containing the password for the p12 certificate
#
# Environment variables produced as output:
# MONGODB_X509_CLIENT_P12_PATH Absolute path to client certificate in p12 format
# MONGO_X509_CLIENT_CERTIFICATE_PASSWORD Password for client certificate
# ${OUT_CLIENT_PATH_VAR} Absolute path to client certificate in p12 format (OUT_CLIENT_PATH_VAR contains the actual variable being exported)
# ${OUT_CLIENT_PASSWORD_VAR} Password for client certificate (OUT_CLIENT_PASSWORD_VAR contains the actual variable being exported)


CLIENT_PEM=${CLIENT_PEM:-nil}
MONGO_X509_CLIENT_P12=${MONGO_X509_CLIENT_P12:-client.p12}
MONGO_X509_CLIENT_CERTIFICATE_PASSWORD=${MONGO_X509_CLIENT_CERTIFICATE_PASSWORD:-Picard-Alpha-Alpha-3-0-5}
# Input environment variables and default values
: "${CLIENT_PEM:=nil}"
: "${P12_FRIENDLY_NAME:="Drivers Client Certificate"}"
: "${P12_FILENAME:="client.p12"}"
: "${P12_PASSWORD:="Picard-Alpha-Alpha-3-0-5"}"
: "${OUT_CLIENT_PATH_VAR:="MONGO_X509_CLIENT_CERTIFICATE_PATH"}"
: "${OUT_CLIENT_PASSWORD_VAR:="MONGO_X509_CLIENT_CERTIFICATE_PASSWORD"}"

if [[ "$CLIENT_PEM" == "nil" ]]; then
echo "Error: CLIENT_PEM must be set."
exit 1
fi

openssl pkcs12 -export -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1 -in "${CLIENT_PEM}" \
-out "${MONGO_X509_CLIENT_P12}" \
-name "Drivers Client Certificate" \
-password "pass:${MONGO_X509_CLIENT_CERTIFICATE_PASSWORD}"
-out "${P12_FILENAME}" \
-name "${P12_FRIENDLY_NAME}" \
-password "pass:${P12_PASSWORD}"

# Determine path using realpath (compatible across macOS, Linux, and Windows)
if [[ "$OS" =~ MAC|Mac|mac ]]; then
# this function is not available on mac OS
# Functionality to mimic `realpath` on macOS
function realpath() {
OURPWD=$PWD
cd "$(dirname "$1")"
Expand All @@ -39,11 +49,17 @@ if [[ "$OS" =~ MAC|Mac|mac ]]; then
echo "$REALPATH"
}
fi
MONGO_X509_CLIENT_CERTIFICATE_PATH=$(realpath "${MONGO_X509_CLIENT_P12}")

CERT_PATH=$(realpath "${P12_FILENAME}")

if [[ "$OS" =~ Windows|windows ]]; then
MONGO_X509_CLIENT_CERTIFICATE_PATH=$(cygpath -w "${MONGO_X509_CLIENT_CERTIFICATE_PATH}")
CERT_PATH=$(cygpath -w "${CERT_PATH}")
fi

export MONGO_X509_CLIENT_CERTIFICATE_PATH
export MONGO_X509_CLIENT_CERTIFICATE_PASSWORD
# Output environment variables
export "${OUT_CLIENT_PASSWORD_VAR}"="${P12_PASSWORD}"
export "${OUT_CLIENT_PATH_VAR}"="${CERT_PATH}"

echo "Exported variables:"
echo "${OUT_CLIENT_PASSWORD_VAR}=${!OUT_CLIENT_PASSWORD_VAR}"
echo "${OUT_CLIENT_PATH_VAR}=${!OUT_CLIENT_PATH_VAR}"
54 changes: 54 additions & 0 deletions evergreen/evergreen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,46 @@ functions:
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/run-driver-test.sh
bash $DRIVERS_TOOLS/.evergreen/auth_oidc/k8s/teardown-pod.sh

setup-x509-tests:
- command: shell.exec
params:
shell: "bash"
include_expansions_in_env:
- "AWS_ACCESS_KEY_ID"
- "AWS_SECRET_ACCESS_KEY"
- "AWS_SESSION_TOKEN"
script: |
${DRIVERS_TOOLS}/.evergreen/secrets_handling/setup-secrets.sh drivers/atlas_connect
source secrets-export.sh

echo $ATLAS_X509_DEV_CERT_BASE64 | base64 --decode > ${DRIVERS_TOOLS}/CLIENT_CERT.pem
echo $ATLAS_X509_DEV_CERT_NOUSER_BASE64 | base64 --decode > ${DRIVERS_TOOLS}/CLIENT_NO_USER_CERT.pem

run-x509-tests:
- command: shell.exec
type: test
params:
shell: "bash"
working_dir: mongo-csharp-driver
script: |
source ../secrets-export.sh
${PREPARE_SHELL}
OS=${OS} \
evergreen/add-ca-certs.sh
P12_FRIENDLY_NAME="Drivers No-User Client Certificate" \
P12_FILENAME="client_no_user.p12" \
OUT_CLIENT_PASSWORD_VAR="MONGO_X509_CLIENT_NO_USER_CERTIFICATE_PASSWORD" \
OUT_CLIENT_PATH_VAR="MONGO_X509_CLIENT_NO_USER_CERTIFICATE_PATH" \
CLIENT_PEM=${DRIVERS_TOOLS}/CLIENT_NO_USER_CERT.pem \
source evergreen/convert-client-cert-to-pkcs12.sh
MONGODB_URI="$ATLAS_X509_DEV" \
CLIENT_PEM=${DRIVERS_TOOLS}/CLIENT_CERT.pem \
TOPOLOGY=${TOPOLOGY} \
OS=${OS} \
FRAMEWORK=${FRAMEWORK} \
TARGET="TestX509" \
evergreen/run-tests.sh

pre:
- func: fetch-source
- func: prepare-resources
Expand Down Expand Up @@ -1720,6 +1760,14 @@ tasks:
${PREPARE_SHELL}
CRYPT_SHARED_LIB_PATH="${CRYPT_SHARED_LIB_PATH}" DRIVER_VERSION="${PACKAGE_VERSION}" MONGODB_VERSION="${VERSION}" bash ./evergreen/run-tests.sh

- name: x509-auth-tests
commands:
- func: assume-ec2-role
- func: setup-x509-tests
- func: run-x509-tests
vars:
FRAMEWORK: net60

axes:
- id: version
display_name: MongoDB Version
Expand Down Expand Up @@ -2417,6 +2465,12 @@ buildvariants:
- name: test-gssapi-netstandard21
- name: test-gssapi-net60

- matrix_name: "x509-tests"
matrix_spec: { os: ["ubuntu-2004", "macos-14", "windows-64"], ssl: ["ssl"], version: ["latest"], topology: ["standalone"] }
display_name: "X509 tests ${version} ${os}"
tasks:
- name: x509-auth-tests

# Load balancer tests
- matrix_name: load-balancer-tests
matrix_spec: { version: ["5.0", "6.0", "7.0", "8.0", "rapid", "latest"], auth: "noauth", ssl: "nossl", topology: "sharded-cluster", os: "ubuntu-2004" }
Expand Down
125 changes: 125 additions & 0 deletions tests/MongoDB.Driver.Tests/X509Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/* Copyright 2010-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.
*/

using System;
using System.Security.Cryptography.X509Certificates;
using FluentAssertions;
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
using MongoDB.TestHelpers.XunitExtensions;
using Xunit;

namespace MongoDB.Driver.Tests;

[Trait("Category", "Integration")]
[Trait("Category", "X509")]
public class X509Tests
{
const string MONGODB_X509_CLIENT_CERTIFICATE_PATH = "MONGO_X509_CLIENT_CERTIFICATE_PATH";
const string MONGODB_X509_CLIENT_CERTIFICATE_PASSWORD = "MONGO_X509_CLIENT_CERTIFICATE_PASSWORD";

const string MONGO_X509_CLIENT_NO_USER_CERTIFICATE_PATH = "MONGO_X509_CLIENT_NO_USER_CERTIFICATE_PATH";
const string MONGO_X509_CLIENT_NO_USER_CERTIFICATE_PASSWORD = "MONGO_X509_CLIENT_NO_USER_CERTIFICATE_PASSWORD";

[Fact]
public void Authentication_succeeds_with_MONGODB_X509_mechanism()
{
var clientCertificate = GetClientCertificate(CertificateType.MONGO_X509);

var settings = DriverTestConfiguration.GetClientSettings();
settings.SslSettings.ClientCertificates = [clientCertificate];

AssertAuthenticationSucceeds(settings);
}

[Fact]
public void Authentication_fails_with_MONGODB_X509_mechanism_when_username_is_wrong()
{
var clientCertificate = GetClientCertificate(CertificateType.MONGO_X509);

var settings = DriverTestConfiguration.GetClientSettings();
settings.Credential = MongoCredential.CreateMongoX509Credential("wrong_username");
settings.SslSettings.ClientCertificates = [clientCertificate];

AssertAuthenticationFails(settings);
}

[Fact]
public void Authentication_fails_with_MONGODB_X509_mechanism_when_user_is_not_in_database()
{
var noUserClientCertificate = GetClientCertificate(CertificateType.MONGO_X509_CLIENT_NO_USER);

var settings = DriverTestConfiguration.GetClientSettings();
settings.SslSettings.ClientCertificates = [noUserClientCertificate];

AssertAuthenticationFails(settings, "Could not find user");
}

private void AssertAuthenticationSucceeds(MongoClientSettings settings)
{
using var client = DriverTestConfiguration.CreateMongoClient(settings);
_ = client.ListDatabaseNames().ToList();
}

private void AssertAuthenticationFails(MongoClientSettings settings, string innerExceptionMessage = null)
{
using var client = DriverTestConfiguration.CreateMongoClient(settings);
var exception = Record.Exception(() => client.ListDatabaseNames().ToList());
exception.Should().BeOfType<MongoAuthenticationException>();

if (innerExceptionMessage != null)
{
var innerException = exception.InnerException;
innerException.Should().BeOfType<MongoCommandException>();
innerException.Message.Should().Contain(innerExceptionMessage);
}
}

private enum CertificateType
{
MONGO_X509,
MONGO_X509_CLIENT_NO_USER
}

private X509Certificate2 GetClientCertificate(CertificateType certificateType)
{
RequireServer.Check().Tls(required: true);

string pathVariable = null;
string passwordVariable = null;

switch (certificateType)
{
case CertificateType.MONGO_X509:
pathVariable = MONGODB_X509_CLIENT_CERTIFICATE_PATH;
passwordVariable = MONGODB_X509_CLIENT_CERTIFICATE_PASSWORD;
break;
case CertificateType.MONGO_X509_CLIENT_NO_USER:
pathVariable = MONGO_X509_CLIENT_NO_USER_CERTIFICATE_PATH;
passwordVariable = MONGO_X509_CLIENT_NO_USER_CERTIFICATE_PASSWORD;
break;
default:
throw new ArgumentException("Wrong certificate type specified.", nameof(certificateType));
}

RequireEnvironment.Check()
.EnvironmentVariable(pathVariable, isDefined: true)
.EnvironmentVariable(passwordVariable, isDefined: true);

var path = Environment.GetEnvironmentVariable(pathVariable);
var password = Environment.GetEnvironmentVariable(passwordVariable);

return new X509Certificate2(path, password);
}
}