Skip to content

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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

mattChiaravalloti
Copy link
Collaborator

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.

@mattChiaravalloti mattChiaravalloti requested a review from a team as a code owner July 8, 2025 15:42
@mattChiaravalloti mattChiaravalloti requested a review from abr-egn July 8, 2025 15:42
@@ -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")]
Copy link
Collaborator Author

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.

Comment on lines +73 to +75
// Limit number of auth challenge steps (typically, only one step is needed, however
// different configurations may require more).
for _ in 0..10 {
Copy link
Collaborator Author

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(),
Copy link
Collaborator Author

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).

Comment on lines +206 to +213
} 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);
}
}
Copy link
Collaborator Author

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> {
Copy link
Collaborator Author

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.

Copy link
Contributor

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
Copy link
Collaborator Author

@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!

Copy link

codeowners-service-app bot commented Jul 9, 2025

Assigned rishitb-mongodb for team dbx-rust because abr-egn is out of office.
Assigned rishitb-mongodb for team dbx-rust because abr-egn is out of office.
Assigned rishitb-mongodb for team dbx-rust because abr-egn is out of office.
Assigned rishitb-mongodb for team dbx-rust because abr-egn is out of office.

@mattChiaravalloti mattChiaravalloti removed the request for review from rishitb-mongodb July 9, 2025 17:02
@mattChiaravalloti
Copy link
Collaborator Author

Ok I believe I've addressed the majority of failures now. The two remaining issues are:

  1. Compiling with the MSRV (1.81.0) fails because libgssapi-sys (a deeply nested indirect dependency) uses unsafe extern "C" blocks, which are disallowed in that version. I'm still trying to figure out how best to address this. Any ideas from reviewers are appreciated.
  2. It seems the Graviton tests are failing because libgssapi-sys cannot compile without setting an ENV variable. The relevant error seems to be: Unable to find libclang: "couldn't find any valid shared libraries matching: ['libclang.so', 'libclang-*.so', 'libclang.so.*', 'libclang-*.so.*'], set the LIBCLANG_PATH environment variable to a path where one of these files can be found (invalid: [])". So we should be able to set LIBCLANG_PATH for that distro and be good to go. I know nothing about this distro though. Perhaps @abr-egn can help out with this one when he's back!

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.

@mattChiaravalloti mattChiaravalloti removed the request for review from rishitb-mongodb July 10, 2025 20:09
@rishitb-mongodb rishitb-mongodb removed their request for review July 10, 2025 20:42
Copy link
Contributor

@pmeredit pmeredit left a 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

@abr-egn

Unrelated to this PR, really (though we could fix it here), should we update this comment since async-std support was dropped?

Copy link
Contributor

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 {
Copy link
Contributor

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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
service_principal = format!("{}{}", service_principal, realm);
service_principal = format!("{service_principal}{realm}");

}
}

fn canonicalize_hostname(hostname: &str, mode: &CanonicalizeHostName) -> Result<String> {
Copy link
Contributor

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.

@pmeredit
Copy link
Contributor

@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

@abr-egn abr-egn removed the request for review from rishitb-mongodb July 14, 2025 08:38
Copy link
Contributor

@abr-egn abr-egn left a 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.
Copy link
Contributor

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 }
Copy link
Contributor

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?

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.

3 participants