Skip to content

Commit 4c4cc0e

Browse files
authored
RUST-1314 Support on-demand AWS credentials for in-use encryption (#831) (#835)
1 parent 1e06db6 commit 4c4cc0e

File tree

8 files changed

+135
-25
lines changed

8 files changed

+135
-25
lines changed

.evergreen/run-csfle-tests.sh

+7-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ source ./.evergreen/env.sh
77

88
set -o xtrace
99

10-
FEATURE_FLAGS="in-use-encryption-unstable,${TLS_FEATURE}"
10+
FEATURE_FLAGS="in-use-encryption-unstable,aws-auth,${TLS_FEATURE}"
1111
OPTIONS="-- -Z unstable-options --format json --report-time"
1212

1313
if [ "$SINGLE_THREAD" = true ]; then
@@ -38,6 +38,11 @@ set +o errexit
3838
cargo_test test::csfle > prose.xml
3939
cargo_test test::spec::client_side_encryption > spec.xml
4040

41-
junit-report-merger results.xml prose.xml spec.xml
41+
# Unset variables for on-demand credential failure tests.
42+
unset AWS_ACCESS_KEY_ID
43+
unset AWS_SECRET_ACCESS_KEY
44+
cargo_test test::csfle::on_demand_aws_failure > failure.xml
45+
46+
junit-report-merger results.xml prose.xml spec.xml failure.xml
4247

4348
exit ${CARGO_RESULT}

src/client/auth/aws.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ pub(super) async fn authenticate_stream(
115115

116116
/// Contains the credentials for MONGODB-AWS authentication.
117117
#[derive(Debug, Deserialize)]
118-
struct AwsCredential {
118+
pub(crate) struct AwsCredential {
119119
#[serde(rename = "AccessKeyId")]
120120
access_key: String,
121121

@@ -129,7 +129,7 @@ struct AwsCredential {
129129
impl AwsCredential {
130130
/// Derives the credentials for an authentication attempt given the set of credentials the user
131131
/// passed in.
132-
async fn get(credential: &Credential, http_client: &HttpClient) -> Result<Self> {
132+
pub(crate) async fn get(credential: &Credential, http_client: &HttpClient) -> Result<Self> {
133133
let access_key = credential
134134
.username
135135
.clone()
@@ -348,6 +348,18 @@ impl AwsCredential {
348348

349349
Ok(auth_header)
350350
}
351+
352+
pub(crate) fn access_key(&self) -> &str {
353+
&self.access_key
354+
}
355+
356+
pub(crate) fn secret_key(&self) -> &str {
357+
&self.secret_key
358+
}
359+
360+
pub(crate) fn session_token(&self) -> Option<&str> {
361+
self.session_token.as_deref()
362+
}
351363
}
352364

353365
/// The response from the server to the `saslStart` command in a MONGODB-AWS authentication attempt.

src/client/auth/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! [`Client`](struct.Client.html).
33
44
#[cfg(feature = "aws-auth")]
5-
mod aws;
5+
pub(crate) mod aws;
66
mod plain;
77
mod sasl;
88
mod scram;

src/client/csfle.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl ClientState {
6969
let exec = CryptExecutor::new_implicit(
7070
aux_clients.key_vault_client,
7171
opts.key_vault_namespace.clone(),
72-
opts.kms_providers.tls_options().clone(),
72+
opts.kms_providers.clone(),
7373
mongocryptd_opts,
7474
mongocryptd_client,
7575
aux_clients.metadata_client,

src/client/csfle/client_encryption.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,12 @@ impl ClientEncryption {
6060
let kms_providers = KmsProviders::new(kms_providers)?;
6161
let crypt = Crypt::builder()
6262
.kms_providers(&kms_providers.credentials_doc()?)?
63+
.use_need_kms_credentials_state()
6364
.build()?;
6465
let exec = CryptExecutor::new_explicit(
6566
key_vault_client.weak(),
6667
key_vault_namespace.clone(),
67-
kms_providers.tls_options().clone(),
68+
kms_providers,
6869
)?;
6970
let key_vault = key_vault_client
7071
.database(&key_vault_namespace.db)

src/client/csfle/options.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,10 @@ impl KmsProviders {
122122
Ok(bson::to_document(&self.credentials)?)
123123
}
124124

125-
pub(crate) fn tls_options(&self) -> &Option<KmsProvidersTlsOptions> {
126-
&self.tls_options
125+
pub(crate) fn tls_options(&self) -> Option<&KmsProvidersTlsOptions> {
126+
self.tls_options.as_ref()
127127
}
128128

129-
#[cfg(test)]
130129
pub(crate) fn credentials(&self) -> &HashMap<KmsProvider, Document> {
131130
&self.credentials
132131
}

src/client/csfle/state_machine.rs

+44-14
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,33 @@ use std::{
44
path::{Path, PathBuf},
55
};
66

7-
use bson::{Document, RawDocument, RawDocumentBuf};
7+
use bson::{rawdoc, Document, RawDocument, RawDocumentBuf};
88
use futures_util::{stream, TryStreamExt};
9-
use mongocrypt::ctx::{Ctx, State};
9+
use mongocrypt::ctx::{Ctx, KmsProvider, State};
1010
use rayon::ThreadPool;
1111
use tokio::{
1212
io::{AsyncReadExt, AsyncWriteExt},
1313
sync::{oneshot, Mutex},
1414
};
1515

1616
use crate::{
17-
client::{options::ServerAddress, WeakClient},
17+
client::{auth::Credential, options::ServerAddress, WeakClient},
1818
coll::options::FindOptions,
1919
error::{Error, Result},
2020
operation::{RawOutput, RunCommand},
2121
options::ReadConcern,
22-
runtime::{AsyncStream, Process, TlsConfig},
22+
runtime::{AsyncStream, HttpClient, Process, TlsConfig},
2323
Client,
2424
Namespace,
2525
};
2626

27-
use super::options::KmsProvidersTlsOptions;
27+
use super::options::KmsProviders;
2828

2929
#[derive(Debug)]
3030
pub(crate) struct CryptExecutor {
3131
key_vault_client: WeakClient,
3232
key_vault_namespace: Namespace,
33-
tls_options: Option<KmsProvidersTlsOptions>,
33+
kms_providers: KmsProviders,
3434
crypto_threads: ThreadPool,
3535
mongocryptd: Option<Mongocryptd>,
3636
mongocryptd_client: Option<Client>,
@@ -41,7 +41,7 @@ impl CryptExecutor {
4141
pub(crate) fn new_explicit(
4242
key_vault_client: WeakClient,
4343
key_vault_namespace: Namespace,
44-
tls_options: Option<KmsProvidersTlsOptions>,
44+
kms_providers: KmsProviders,
4545
) -> Result<Self> {
4646
// TODO RUST-1492: Replace num_cpus with std::thread::available_parallelism.
4747
let crypto_threads = rayon::ThreadPoolBuilder::new()
@@ -51,7 +51,7 @@ impl CryptExecutor {
5151
Ok(Self {
5252
key_vault_client,
5353
key_vault_namespace,
54-
tls_options,
54+
kms_providers,
5555
crypto_threads,
5656
mongocryptd: None,
5757
mongocryptd_client: None,
@@ -62,7 +62,7 @@ impl CryptExecutor {
6262
pub(crate) async fn new_implicit(
6363
key_vault_client: WeakClient,
6464
key_vault_namespace: Namespace,
65-
tls_options: Option<KmsProvidersTlsOptions>,
65+
kms_providers: KmsProviders,
6666
mongocryptd_opts: Option<MongocryptdOptions>,
6767
mongocryptd_client: Option<Client>,
6868
metadata_client: Option<WeakClient>,
@@ -71,7 +71,7 @@ impl CryptExecutor {
7171
Some(opts) => Some(Mongocryptd::new(opts).await?),
7272
None => None,
7373
};
74-
let mut exec = Self::new_explicit(key_vault_client, key_vault_namespace, tls_options)?;
74+
let mut exec = Self::new_explicit(key_vault_client, key_vault_namespace, kms_providers)?;
7575
exec.mongocryptd = mongocryptd;
7676
exec.mongocryptd_client = mongocryptd_client;
7777
exec.metadata_client = metadata_client;
@@ -185,8 +185,8 @@ impl CryptExecutor {
185185
let addr = ServerAddress::parse(endpoint)?;
186186
let provider = kms_ctx.kms_provider()?;
187187
let tls_options = self
188-
.tls_options
189-
.as_ref()
188+
.kms_providers
189+
.tls_options()
190190
.and_then(|tls| tls.get(&provider))
191191
.cloned()
192192
.unwrap_or_default();
@@ -208,8 +208,38 @@ impl CryptExecutor {
208208
.await?;
209209
}
210210
State::NeedKmsCredentials => {
211-
// TODO(RUST-1314, RUST-1417): support fetching KMS credentials.
212-
return Err(Error::internal("KMS credentials are not yet supported"));
211+
let ctx = result_mut(&mut ctx)?;
212+
let mut out = rawdoc! {};
213+
if self
214+
.kms_providers
215+
.credentials()
216+
.get(&KmsProvider::Aws)
217+
.map_or(false, |d| d.is_empty())
218+
{
219+
#[cfg(feature = "aws-auth")]
220+
{
221+
let aws_creds = crate::client::auth::aws::AwsCredential::get(
222+
&Credential::default(),
223+
&HttpClient::default(),
224+
)
225+
.await?;
226+
let mut creds = rawdoc! {
227+
"accessKeyId": aws_creds.access_key(),
228+
"secretAccessKey": aws_creds.secret_key(),
229+
};
230+
if let Some(token) = aws_creds.session_token() {
231+
creds.append("sessionToken", token);
232+
}
233+
out.append("aws", creds);
234+
}
235+
#[cfg(not(feature = "aws-auth"))]
236+
{
237+
return Err(Error::invalid_argument(
238+
"On-demand AWS KMS credentials require the `aws-auth` feature.",
239+
));
240+
}
241+
}
242+
ctx.provide_kms_providers(&out)?;
213243
}
214244
State::Ready => {
215245
let (tx, rx) = oneshot::channel();

src/test/csfle.rs

+64-1
Original file line numberDiff line numberDiff line change
@@ -2749,7 +2749,70 @@ impl CommandEventHandler for DecryptionEventsHandler {
27492749
}
27502750
}
27512751

2752-
// TODO RUST-1314: implement prose test 15. On-demand AWS Credentials
2752+
// Prose test 15. On-demand AWS Credentials (failure)
2753+
#[cfg(feature = "aws-auth")]
2754+
#[cfg_attr(feature = "tokio-runtime", tokio::test)]
2755+
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
2756+
async fn on_demand_aws_failure() -> Result<()> {
2757+
if !check_env("on_demand_aws_failure", false) {
2758+
return Ok(());
2759+
}
2760+
if std::env::var("AWS_ACCESS_KEY_ID").is_ok() && std::env::var("AWS_SECRET_ACCESS_KEY").is_ok()
2761+
{
2762+
log_uncaptured("Skipping on_demand_aws_failure: credentials set");
2763+
return Ok(());
2764+
}
2765+
let _guard = LOCK.run_exclusively().await;
2766+
2767+
let ce = ClientEncryption::new(
2768+
Client::test_builder().build().await.into_client(),
2769+
KV_NAMESPACE.clone(),
2770+
[(KmsProvider::Aws, doc! {}, None)],
2771+
)?;
2772+
let result = ce
2773+
.create_data_key(MasterKey::Aws {
2774+
region: "us-east-1".to_string(),
2775+
key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0"
2776+
.to_string(),
2777+
endpoint: None,
2778+
})
2779+
.run()
2780+
.await;
2781+
assert!(
2782+
result.as_ref().unwrap_err().is_auth_error(),
2783+
"Expected auth error, got {:?}",
2784+
result
2785+
);
2786+
2787+
Ok(())
2788+
}
2789+
2790+
// Prose test 15. On-demand AWS Credentials (success)
2791+
#[cfg(feature = "aws-auth")]
2792+
#[cfg_attr(feature = "tokio-runtime", tokio::test)]
2793+
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
2794+
async fn on_demand_aws_success() -> Result<()> {
2795+
if !check_env("on_demand_aws_success", false) {
2796+
return Ok(());
2797+
}
2798+
let _guard = LOCK.run_exclusively().await;
2799+
2800+
let ce = ClientEncryption::new(
2801+
Client::test_builder().build().await.into_client(),
2802+
KV_NAMESPACE.clone(),
2803+
[(KmsProvider::Aws, doc! {}, None)],
2804+
)?;
2805+
ce.create_data_key(MasterKey::Aws {
2806+
region: "us-east-1".to_string(),
2807+
key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0"
2808+
.to_string(),
2809+
endpoint: None,
2810+
})
2811+
.run()
2812+
.await?;
2813+
2814+
Ok(())
2815+
}
27532816

27542817
// TODO RUST-1441: implement prose test 16. Rewrap
27552818

0 commit comments

Comments
 (0)