-
Notifications
You must be signed in to change notification settings - Fork 181
RUST-2235 Implement GSSAPI auth support #1413
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
base: main
Are you sure you want to change the base?
Conversation
@@ -67,8 +69,7 @@ pub enum AuthMechanism { | |||
/// Kerberos authentication mechanism as defined in [RFC 4752](http://tools.ietf.org/html/rfc4752). | |||
/// | |||
/// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/kerberos/) for more information. | |||
/// | |||
/// Note: This mechanism is not currently supported by this driver but will be in the future. | |||
#[cfg(feature = "gssapi-auth")] |
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 followed the same pattern as the aws-auth
feature flag.
// Limit number of auth challenge steps (typically, only one step is needed, however | ||
// different configurations may require more). | ||
for _ in 0..10 { |
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 idea is taken directly from the python driver's implementation (link).
It seems there may be some circumstances where multiple challenges need to be stepped through, so we need to do this multiple times. However, based on the python implementation's comments, it seems there may be protocol and/or library issues that could result in endless looping so a limit is put in place (10 tries).
impl GssapiProperties { | ||
pub fn from_credential(credential: &Credential) -> Result<Self> { | ||
let mut properties = GssapiProperties { | ||
service_name: "mongodb".to_string(), |
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.
You may notice that in options.rs
, we also ensure that mongodb
is the default service name. That is required in options.rs
for spec compliance (as in, the spec tests cannot pass without doing that). At this point, although we know mechanism_properites.get(SERVICE_NAME)
must always return Some(...)
, it felt cleanest to me to implement this code the way you see here (including "mongodb"
as a default value).
} else if let Some(user_principal) = user_principal.as_ref() { | ||
if let Some(idx) = user_principal.find('@') { | ||
// If no SERVICE_REALM was specified, use realm specified in the | ||
// username. Note that `realm` starts with '@'. | ||
let (_, realm) = user_principal.split_at(idx); | ||
service_principal = format!("{}{}", service_principal, realm); | ||
} | ||
} |
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 is not mentioned in the spec but it did end up being required since there is no other way to specify a realm for the service principal if not done manually. Perhaps other language libraries default to this behavior, but not cross-krb5
/libgssapi
in Rust. I spoke to James about this and he said he had to do something similar in the C# driver's GSSAPI implementation.
} | ||
} | ||
|
||
fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result<String> { |
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 wrote some unit tests for this but ultimately removed them since they are trivial. The more interesting testing of this will be indirect via the end-to-end GSSAPI auth tests in the next ticket.
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.
Yea, I'm personally trying to move away from unit tests that just explicitly encode basic code.
@abr-egn I am not able to add other reviewers, so if you know who else could/should be tagged on this, can you add them? Thank you! |
Assigned |
Ok I believe I've addressed the majority of failures now. The two remaining issues are:
Beyond addressing those two issues, the last thing I need to do is double check the spec for how to handle password being provided on Windows. It looks like there is supposed to be some special handling that happens only on Windows if the password is provided as a uri option. I'll look into that and make any relevant changes. |
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 once everything is working on every OS/tests are passing. Good design. Just fix the format!s because clippy will start complaining whenever they update.
@@ -58,6 +58,9 @@ gcp-oidc = ["dep:reqwest"] | |||
# This can only be used with the tokio-runtime feature flag. |
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.
Unrelated to this PR, really (though we could fix it here), should we update this comment since async-std support was dropped?
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.
Hah, yes, good catch.
} | ||
|
||
if credential.source.as_deref().unwrap_or("$external") != "$external" { | ||
return Err(ErrorKind::InvalidArgument { |
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.
Hmm, just noticed that the "must be $external" message for OIDC is slightly different than everything else, maybe we should update it 🤔
hostname: &str, | ||
) -> Result<(Self, Vec<u8>)> { | ||
let service_name: &str = properties.service_name.as_ref(); | ||
let mut service_principal = format!("{}/{}", service_name, hostname); |
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.
let mut service_principal = format!("{}/{}", service_name, hostname); | |
let mut service_principal = format!("{service_name}/{hostname}"); |
Let's just fix this before clippy starts whining about it the next time they update it :)
let service_name: &str = properties.service_name.as_ref(); | ||
let mut service_principal = format!("{}/{}", service_name, hostname); | ||
if let Some(service_realm) = properties.service_realm.as_ref() { | ||
service_principal = format!("{}@{}", service_principal, service_realm); |
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.
service_principal = format!("{}@{}", service_principal, service_realm); | |
service_principal = format!("{service_principal}@{service_realm}"); |
ditto
// If no SERVICE_REALM was specified, use realm specified in the | ||
// username. Note that `realm` starts with '@'. | ||
let (_, realm) = user_principal.split_at(idx); | ||
service_principal = format!("{}{}", service_principal, realm); |
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.
service_principal = format!("{}{}", service_principal, realm); | |
service_principal = format!("{service_principal}{realm}"); |
} | ||
} | ||
|
||
fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result<String> { |
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.
Yea, I'm personally trying to move away from unit tests that just explicitly encode basic code.
@mattChiaravalloti all the failures atm seem to be from missing libclang, it may make sense to turn off the gssapi feature flag for those tests |
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 very reasonable!
The MSRV issue can be solved by just bumping up MSRV to 1.82 which introduced the unsafe extern
block syntax; that's well past our six month rolling window.
I agree with Patrick that it makes sense to turn off the GSSAPI feature flag for the Graviton variants; I don't think there's enough value in the marginal test coverage increase to justify the engineer time to figure out if it can be made to work.
@@ -58,6 +58,9 @@ gcp-oidc = ["dep:reqwest"] | |||
# This can only be used with the tokio-runtime feature flag. |
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.
Hah, yes, good catch.
derive_more = "0.99.17" | ||
derive-where = "1.2.7" | ||
dns-lookup = { version = "2.0", 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.
Why not use hickory-resolver
which we already have as an optional dep?
This PR implements the GSSAPI auth mechanism, as described by the spec. This took a bit longer than anticipated because manual end-to-end testing revealed that the initial implementation was not correct. Subsequent fixes made slow progress towards correctness. Ultimately, the implementation you see here has been manually tested on macOS (locally, against a local KDC and mongod) and an ubuntu2204-arm64-small evergreen host (against the LDAPTEST KDC/mongod). I still need to test this on Windows. I have a separate follow-up ticket to implement automated end-to-end testing, RUST-2236, which I will put up a PR for after confirming this works on Windows and actually writing end-to-end tests.
This implementation is guarded by a feature flag, and passes the existing spec tests when the feature flag is enabled.