-
Notifications
You must be signed in to change notification settings - Fork 190
RUST-1382 FLE-related Client initialization
#690
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
Conversation
src/client/fle/options.rs
Outdated
| pub type KmsProvidersTlsOptions = HashMap<KmsProvider, TlsOptions>; | ||
|
|
||
| impl AutoEncryptionOpts { | ||
| pub(crate) fn extra_option<'a, Opt: ExtraOption<'a, Bson>>( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/client/mod.rs
Outdated
| options: ClientOptions, | ||
| session_pool: ServerSessionPool, | ||
| #[cfg(feature = "fle")] | ||
| fle: std::sync::Mutex<Option<fle::ClientState>>, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/client/mod.rs
Outdated
| /// Creates a new `Client` connected to the cluster specified by `options` with auto-encryption | ||
| /// enabled. | ||
| #[cfg(feature = "fle")] | ||
| pub async fn with_fle_options( |
There was a problem hiding this comment.
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.
src/client/fle/options.rs
Outdated
| pub type KmsProvidersTlsOptions = HashMap<KmsProvider, TlsOptions>; | ||
|
|
||
| impl AutoEncryptionOpts { | ||
| pub(crate) fn extra_option<'a, Opt: ExtraOption<'a, Bson>>( |
There was a problem hiding this comment.
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.
src/client/mod.rs
Outdated
| options: ClientOptions, | ||
| session_pool: ServerSessionPool, | ||
| #[cfg(feature = "fle")] | ||
| fle: std::sync::Mutex<Option<fle::ClientState>>, |
There was a problem hiding this comment.
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.
af42cad to
d4aa608
Compare
There was a problem hiding this 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!
src/client/mod.rs
Outdated
| /// Creates a new `Client` connected to the cluster specified by `options` with auto-encryption | ||
| /// enabled. | ||
| #[cfg(feature = "csfle")] | ||
| pub async fn with_encryption_opts( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, fixed.
src/client/csfle.rs
Outdated
| } | ||
|
|
||
| impl ClientState { | ||
| pub(super) async fn new(client: &Client, opts: AutoEncryptionOpts) -> Result<Option<Self>> { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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>.
There was a problem hiding this 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 } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
2b6f72f to
13a16a5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
13a16a5 to
6e4f1b1
Compare
RUST-1382
This allows creating a
Clientwith auto-encryption enabled, and spins up the various bits of internal state that are needed for that.