Skip to content

Conversation

@abr-egn
Copy link
Contributor

@abr-egn abr-egn commented Jul 7, 2022

RUST-1382

This allows creating a Client with auto-encryption enabled, and spins up the various bits of internal state that are needed for that.

pub type KmsProvidersTlsOptions = HashMap<KmsProvider, TlsOptions>;

impl AutoEncryptionOpts {
pub(crate) fn extra_option<'a, Opt: ExtraOption<'a, Bson>>(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This machinery incurs a bit of extra boilerplate here but saves it at the usage sites; it also ensures that code using extra_options didn't accidentally get the field type wrong.

Copy link
Contributor

Choose a reason for hiding this comment

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

I really like this approach--it makes the requirement of having to use a map for this stuff not nearly as bad.

options: ClientOptions,
session_pool: ServerSessionPool,
#[cfg(feature = "fle")]
fle: std::sync::Mutex<Option<fle::ClientState>>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a little awkward to have to wrap this in a Mutex since it's not mutated after client construction, but since making the state for FLE needs the main Client as part of its own initialization parameters, I didn't see a way around this.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could potentially get around this by creating a WeakClient type or some such, but I think we're going to need some form of lock anyways in order to invoke the mutable mongocrypt methods, so it's not a big deal. Since ClientState stores Clients within it, we need to be really careful not to use them while holding the std lock guard, as we can easily deadlock (maybe the compiler will enforce this?). To avoid this, we could introduce a wrapper type or something that manages handling the lock, or alternatively just use an async one.

Also, we may want a RwLock, where the read portion can be used to access the clients and the write portion for mutating the state. Though maybe its best to defer a decision on these questions until the implementation moves a little further and the use cases are clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call on the deadlock risk - updated to use an async mutex. Agreed that it'll be easier to see whether Mutex or RwLock is better based on usage, so I've left it as that for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I realized this needed to be a weak ref in any case, since otherwise there's a potential for creating a reference loop with the main Client holding a ref to itself. I also updated it to a RwLock because even very early further implementation points to that being the way to go.

/// Creates a new `Client` connected to the cluster specified by `options` with auto-encryption
/// enabled.
#[cfg(feature = "fle")]
pub async fn with_fle_options(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ideally AutoEncryptionOpts could have just been another field in ClientOptions. Unfortunately, initializing FLE state requires async because it has to construct a client to the mongocryptd from an arbitrary URI. Since there needs to be a distinct creation method to accommodate that, it seemed worth keeping the options distinct to make it clear that if you're setting those options you have to use this method.

pub type KmsProvidersTlsOptions = HashMap<KmsProvider, TlsOptions>;

impl AutoEncryptionOpts {
pub(crate) fn extra_option<'a, Opt: ExtraOption<'a, Bson>>(
Copy link
Contributor

Choose a reason for hiding this comment

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

I really like this approach--it makes the requirement of having to use a map for this stuff not nearly as bad.

options: ClientOptions,
session_pool: ServerSessionPool,
#[cfg(feature = "fle")]
fle: std::sync::Mutex<Option<fle::ClientState>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

We could potentially get around this by creating a WeakClient type or some such, but I think we're going to need some form of lock anyways in order to invoke the mutable mongocrypt methods, so it's not a big deal. Since ClientState stores Clients within it, we need to be really careful not to use them while holding the std lock guard, as we can easily deadlock (maybe the compiler will enforce this?). To avoid this, we could introduce a wrapper type or something that manages handling the lock, or alternatively just use an async one.

Also, we may want a RwLock, where the read portion can be used to access the clients and the write portion for mutating the state. Though maybe its best to defer a decision on these questions until the implementation moves a little further and the use cases are clearer.

@abr-egn abr-egn force-pushed the RUST-1382/fle-client-init branch 2 times, most recently from af42cad to d4aa608 Compare July 12, 2022 17:00
Copy link
Contributor

@patrickfreed patrickfreed left a comment

Choose a reason for hiding this comment

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

One more naming nitpick, but otherwise LGTM!

/// Creates a new `Client` connected to the cluster specified by `options` with auto-encryption
/// enabled.
#[cfg(feature = "csfle")]
pub async fn with_encryption_opts(
Copy link
Contributor

Choose a reason for hiding this comment

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

for consistency with with_options, i think we might want to name this with_encryption_options actually.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

hmac = "0.12.1"
lazy_static = "1.4.0"
md-5 = "0.10.1"
mongocrypt = { git = "https://github.com/mongodb/libmongocrypt-rust.git", branch = "main", optional = true }
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like we might need to add a cargo-deny ignore for this in particular. I imagine we have one for bson already, otherwise that would trigger too right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, fixed.

}

impl ClientState {
pub(super) async fn new(client: &Client, opts: AutoEncryptionOpts) -> Result<Option<Self>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this need to return a Result<Option<Self>>? Is there any case in which Ok(None) should be returned?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch - that was left over from earlier structure. It now just returns Result<Self>.

Copy link
Contributor

@kmahar kmahar left a comment

Choose a reason for hiding this comment

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

looking good! a few minor comments.

hmac = "0.12.1"
lazy_static = "1.4.0"
md-5 = "0.10.1"
mongocrypt = { git = "https://github.com/mongodb/libmongocrypt-rust.git", branch = "main", optional = true }
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like adding this dependency breaks MSRV compilation, since libmongocrypt-rust uses Rust 2021 edition and Rust 1.53 isn't aware of that existing. based on the data I shared, I think we're safe to bump our MSRV to 1.56. should we either go ahead with that or temporarily downgrade libmongocrypt to Rust 2018 to avoid breaking our MSRV compile tasks when this merged?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm inclined to go ahead with the MSRV bump - I'll send that in its own PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM

@abr-egn abr-egn force-pushed the RUST-1382/fle-client-init branch from 2b6f72f to 13a16a5 Compare July 15, 2022 14:41
Copy link
Contributor

@kmahar kmahar left a comment

Choose a reason for hiding this comment

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

LGTM!

@abr-egn abr-egn requested a review from isabelatkinson July 25, 2022 14:49
@abr-egn abr-egn force-pushed the RUST-1382/fle-client-init branch from 13a16a5 to 6e4f1b1 Compare July 25, 2022 16:02
@abr-egn abr-egn merged commit 9692371 into mongodb:main Jul 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants