Skip to content

Commit f9e79db

Browse files
authored
RUST-1479 Add prose test for mongocrypt client bypass (#786)
1 parent 3140051 commit f9e79db

File tree

3 files changed

+143
-1
lines changed

3 files changed

+143
-1
lines changed

src/client/csfle/state_machine.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ impl CryptExecutor {
8383
self.mongocryptd.is_some()
8484
}
8585

86+
#[cfg(test)]
87+
pub(crate) fn has_mongocryptd_client(&self) -> bool {
88+
self.mongocryptd_client.is_some()
89+
}
90+
8691
pub(crate) async fn run_ctx(&self, ctx: Ctx, db: Option<&str>) -> Result<RawDocumentBuf> {
8792
let mut result = None;
8893
// This needs to be a `Result` so that the `Ctx` can be temporarily owned by the processing

src/client/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,16 @@ impl Client {
197197
.map_or(false, |cs| cs.exec().mongocryptd_spawned())
198198
}
199199

200+
#[cfg(all(test, feature = "csfle"))]
201+
pub(crate) async fn has_mongocryptd_client(&self) -> bool {
202+
self.inner
203+
.csfle
204+
.read()
205+
.await
206+
.as_ref()
207+
.map_or(false, |cs| cs.exec().has_mongocryptd_client())
208+
}
209+
200210
#[cfg(not(feature = "tracing-unstable"))]
201211
pub(crate) fn emit_command_event(&self, generate_event: impl FnOnce() -> CommandEvent) {
202212
if let Some(ref handler) = self.inner.options.command_event_handler {

src/test/csfle.rs

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
use std::{
22
collections::HashMap,
33
path::PathBuf,
4-
sync::{Arc, Mutex},
4+
sync::{
5+
atomic::{AtomicBool, Ordering},
6+
Arc,
7+
Mutex,
8+
},
59
time::Duration,
610
};
711

12+
#[cfg(not(feature = "tokio-runtime"))]
13+
use async_std::net::TcpListener;
814
use bson::{
915
doc,
1016
spec::{BinarySubtype, ElementType},
@@ -16,6 +22,8 @@ use bson::{
1622
use futures_util::TryStreamExt;
1723
use lazy_static::lazy_static;
1824
use mongocrypt::ctx::{Algorithm, KmsProvider};
25+
#[cfg(feature = "tokio-runtime")]
26+
use tokio::net::TcpListener;
1927

2028
use crate::{
2129
client::{auth::Credential, options::TlsOptions},
@@ -35,6 +43,7 @@ use crate::{
3543
CommandSucceededEvent,
3644
},
3745
options::{IndexOptions, ReadConcern, WriteConcern},
46+
runtime,
3847
test::{Event, EventHandler, SdamEvent},
3948
Client,
4049
Collection,
@@ -1313,6 +1322,62 @@ async fn custom_endpoint_kmip_invalid_endpoint() -> Result<()> {
13131322
Ok(())
13141323
}
13151324

1325+
// Prose test 8. Bypass Spawning mongocryptd (Via loading shared library)
1326+
#[cfg_attr(feature = "tokio-runtime", tokio::test)]
1327+
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
1328+
async fn bypass_mongocryptd_via_shared_library() -> Result<()> {
1329+
if !check_env("bypass_mongocryptd_via_shared_library", false) {
1330+
return Ok(());
1331+
}
1332+
let _guard = LOCK.run_exclusively().await;
1333+
1334+
if *DISABLE_CRYPT_SHARED {
1335+
log_uncaptured(
1336+
"Skipping bypass mongocryptd via shared library test: crypt_shared is disabled.",
1337+
);
1338+
return Ok(());
1339+
}
1340+
1341+
// Setup: encrypted client.
1342+
let client_encrypted = Client::encrypted_builder(
1343+
CLIENT_OPTIONS.get().await.clone(),
1344+
KV_NAMESPACE.clone(),
1345+
LOCAL_KMS.clone(),
1346+
)?
1347+
.schema_map({
1348+
[(
1349+
"db.coll".to_string(),
1350+
load_testdata("external-schema.json")?,
1351+
)]
1352+
.into_iter()
1353+
.collect::<HashMap<String, Document>>()
1354+
})
1355+
.extra_options(doc! {
1356+
"mongocryptdURI": "mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000",
1357+
"mongocryptdSpawnArgs": ["--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"],
1358+
"cryptSharedLibPath": EXTRA_OPTIONS.get("cryptSharedLibPath").unwrap(),
1359+
"cryptSharedRequired": true,
1360+
})
1361+
.build()
1362+
.await?;
1363+
1364+
// Test: insert succeeds.
1365+
client_encrypted
1366+
.database("db")
1367+
.collection::<Document>("coll")
1368+
.insert_one(doc! { "unencrypted": "test" }, None)
1369+
.await?;
1370+
// Test: mongocryptd not spawned.
1371+
assert!(!client_encrypted.mongocryptd_spawned().await);
1372+
// Test: attempting to connect fails.
1373+
let client =
1374+
Client::with_uri_str("mongodb://localhost:27021/?serverSelectionTimeoutMS=1000").await?;
1375+
let result = client.list_database_names(None, None).await;
1376+
assert!(result.unwrap_err().is_server_selection_error());
1377+
1378+
Ok(())
1379+
}
1380+
13161381
// Prose test 8. Bypass Spawning mongocryptd (Via mongocryptdBypassSpawn)
13171382
#[cfg_attr(feature = "tokio-runtime", tokio::test)]
13181383
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
@@ -2712,3 +2777,65 @@ impl CommandEventHandler for DecryptionEventsHandler {
27122777
// TODO RUST-1417: implement prose test 17. On-demand GCP Credentials
27132778

27142779
// TODO RUST-1442: implement prose test 18. Azure IMDS Credentials
2780+
2781+
// TODO RUST-1442: implement prose test 19. Azure IMDS Credentials Integration Test
2782+
2783+
// Prose test 20. Bypass creating mongocryptd client when shared library is loaded
2784+
#[cfg_attr(feature = "tokio-runtime", tokio::test)]
2785+
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
2786+
async fn bypass_mongocryptd_client() -> Result<()> {
2787+
if !check_env("bypass_mongocryptd_client", false) {
2788+
return Ok(());
2789+
}
2790+
let _guard = LOCK.run_exclusively().await;
2791+
2792+
if *DISABLE_CRYPT_SHARED {
2793+
log_uncaptured("Skipping bypass mongocryptd client test: crypt_shared is disabled.");
2794+
return Ok(());
2795+
}
2796+
2797+
let connected = Arc::new(AtomicBool::new(false));
2798+
{
2799+
let connected = Arc::clone(&connected);
2800+
let listener = bind("127.0.0.1:27021").await?;
2801+
runtime::spawn(async move {
2802+
let _ = listener.accept().await;
2803+
log_uncaptured("test failure: connection accepted");
2804+
connected.store(true, Ordering::SeqCst);
2805+
})
2806+
};
2807+
2808+
let client_encrypted = Client::encrypted_builder(
2809+
CLIENT_OPTIONS.get().await.clone(),
2810+
KV_NAMESPACE.clone(),
2811+
LOCAL_KMS.clone(),
2812+
)?
2813+
.extra_options({
2814+
let mut extra_options = EXTRA_OPTIONS.clone();
2815+
extra_options.insert("mongocryptdURI", "mongodb://localhost:27021");
2816+
extra_options
2817+
})
2818+
.build()
2819+
.await?;
2820+
client_encrypted
2821+
.database("db")
2822+
.collection::<Document>("coll")
2823+
.insert_one(doc! { "unencrypted": "test" }, None)
2824+
.await?;
2825+
2826+
assert!(!client_encrypted.has_mongocryptd_client().await);
2827+
assert!(!connected.load(Ordering::SeqCst));
2828+
2829+
Ok(())
2830+
}
2831+
2832+
async fn bind(addr: &str) -> Result<TcpListener> {
2833+
#[cfg(feature = "tokio-runtime")]
2834+
{
2835+
Ok(TcpListener::bind(addr.parse::<std::net::SocketAddr>()?).await?)
2836+
}
2837+
#[cfg(not(feature = "tokio-runtime"))]
2838+
{
2839+
Ok(TcpListener::bind(addr).await?)
2840+
}
2841+
}

0 commit comments

Comments
 (0)