Skip to content

RUST-2245 Implement GSSAPI auth support for Windows #1444

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 16 commits into
base: main
Choose a base branch
from

Conversation

mattChiaravalloti
Copy link
Collaborator

This PR implements GSSAPI auth support for Windows as described by the spec. Similar to the Linux and Mac implementation in #1413, this one took longer than expected. In the end, it turned out that the sspi crate was not as amenable to this task as initially thought at scope time. I decided to attempt to implement this using the native Windows api (via windows-sys) and modeling it exactly off of libmongoc since I know the C implementation works. Of course, that results in a bit more C-style code than Rust-style code, but after several weeks of investigating and implementing, this is finally working.

I updated the GSSAPI codepath to share common code between Linux/Mac and Windows as much as possible. I only separated out the platform-specific implementations and guarded each with the relevant cfg attributes.

This PR also updates the e2e tests to run on Windows. I observed this implementation passing my local testing on a Windows host, and the e2e tests passing on this patch. I anticipate the evergreen patch associated with this PR to succeed off the bat, as well.

Copy link

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

@@ -1405,7 +1413,7 @@ functions:
type: test
params:
binary: bash
working_dir: ${PROJECT_DIRECTORY}
working_dir: src
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Using ${PROJECT_DIRECTORY} on windows-64-vs2017-small was causing "working_dir could not be found" errors. I checked the rest of config.yml and it seems like the majority of working_dir values are set to src. I only chose ${PROJECT_DIRECTORY} initially since I based this function on the aws auth functions.

@@ -235,6 +234,13 @@ features = ["serde", "serde_json-1"]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true

# Target-specific dependencies for GSSAPI authentication
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To be honest, I've never had to do this before in a Cargo.toml file so I'm not sure if this is the idiomatic way.

let source = credential.source.as_deref().unwrap_or("$external");

#[cfg(target_os = "windows")]
let (mut authenticator, initial_token) = windows::SspiAuthenticator::init(
Copy link
Collaborator Author

@mattChiaravalloti mattChiaravalloti Aug 6, 2025

Choose a reason for hiding this comment

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

As you see in the windows.rs file, I created the SspiAuthenticator struct to mirror the GssapiAuthenticator struct. It has the same interface (although I did not go so far as to make them both implement a common trait since they'll never be compiled together). This allowed the core authentication steps in this function to remain unchanged.

@@ -0,0 +1,108 @@
use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step};
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 file is essentially just cut-pasted from the original gssapi.rs file. The only notable change is a minor tweak to the function signatures: they are no longer marked as async since that is not required. I left that in basically by accident after doing all the refactoring and trial-and-erroring the first time around.

user_principal: String,
}

impl SspiAuthenticator {
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 the core implementation for the SSPI auth protocol on Windows. It is based heavily on the C implementation. I truly tried (for weeks) to get a higher level, more Rust-like library to work first. At the end of the day, though, the best way to do this was to resort to the low-level Windows API. It is a bit harder to read, but fortunately the SSPI api is not something that changes often (or will/should change in the future). The C implementation has been largely untouched for years and it still works!

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm going to have to defer to @kevinAlbs to review the actual semantics of the implementation here :)

}
}

impl Drop for SspiAuthenticator {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Required since these handles are set and used basically exclusively in unsafe blocks.

@mattChiaravalloti mattChiaravalloti requested review from isabelatkinson and removed request for rishitb-mongodb August 6, 2025 19:40
@mattChiaravalloti
Copy link
Collaborator Author

Is it possible to make exceptions to the semgrep policy to allow for the use of these unsafe blocks, or will we need to get rid of them?

@mattChiaravalloti
Copy link
Collaborator Author

Is it possible to make exceptions to the semgrep policy to allow for the use of these unsafe blocks, or will we need to get rid of them?

I investigated further and am not confident SSPI support can be implemented the way our drivers require if we do not use these unsafe blocks.

There are limited crate options for kerberos auth. We've ruled out libgssapi-sys/libgssapi/cross-krb5 since they rely on MIT KRB5. I discovered sspi is insufficient for our needs (it is incapable of producing the token types needed for MongoDB's SASL auth flow when using GSSAPI). Looking through crates.io, there are not any other viable candidates for this, so the windows crate is likely our best option.

Using the native Windows API inherently requires unsafe code. Hopefully we can make an exception for this. I know in the ODBC driver, we have some required unsafe code that is permitted.

@abr-egn
Copy link
Contributor

abr-egn commented Aug 7, 2025

Is it possible to make exceptions to the semgrep policy to allow for the use of these unsafe blocks, or will we need to get rid of them?

I investigated further and am not confident SSPI support can be implemented the way our drivers require if we do not use these unsafe blocks.

There are limited crate options for kerberos auth. We've ruled out libgssapi-sys/libgssapi/cross-krb5 since they rely on MIT KRB5. I discovered sspi is insufficient for our needs (it is incapable of producing the token types needed for MongoDB's SASL auth flow when using GSSAPI). Looking through crates.io, there are not any other viable candidates for this, so the windows crate is likely our best option.

Using the native Windows API inherently requires unsafe code. Hopefully we can make an exception for this. I know in the ODBC driver, we have some required unsafe code that is permitted.

I haven't had the chance to look into this PR in detail, but it should work to use a nosemgrep comment, we've had to do that in a couple other places in the code base (example). My recollection of the policy (I'll verify later) is that as long as we've investigated and have manually validated that the thing semgrep would complain about is not, in fact, a problem it's fine.

@abr-egn abr-egn requested a review from kevinAlbs August 8, 2025 14:54
error::{Error, Result},
};

pub(super) struct GssapiAuthenticator {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not changed in this PR, but this being a fresh file made me realize - this is only ever either

GssapiAuthenticator { pending_ctx: Some(...), established_ctx: None, is_complete: false, ... }

or

GssapiAuthenticator { pending_ctx: None, established_ctx: Some(...), is_complete: true, ... }

right? That seems like it would be better modeled via a state enum, e.g.

enum CtxState {
  Pending(PendingClientCtx),
  Established(ClientCtx),
}

pub(super) struct GssapiAuthenticator {
  ctx: CtxState,
  user_principal: 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.

Ah that's a nice refactor. I'll make this change.

}

fn acquire_credentials_and_init(&mut self, password: Option<String>) -> Result<Vec<u8>> {
unsafe {
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the only unsafe portion of this is calling AcquireCredentialsHandleW, right? I'd rather see the unsafe blocks as narrowly scoped as possible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good point! I started broad to allow for whatever had to be done, but I'll narrow these down now like you suggested 👍

user_principal: String,
}

impl SspiAuthenticator {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm going to have to defer to @kevinAlbs to review the actual semantics of the implementation here :)


// Perform the final step of Kerberos authentication by gss_unwrap-ing the
// final server challenge, then wrapping the protocol bytes + user principal.
// The resulting token must be sent to the server.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is like a monad transformer 😂

@mattChiaravalloti mattChiaravalloti removed the request for review from isabelatkinson August 8, 2025 20:12
Copy link
Contributor

@isabelatkinson isabelatkinson left a comment

Choose a reason for hiding this comment

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

one tiny comment, I did a pass over the C-style code but will similarly defer to @kevinAlbs on the details

Copy link
Contributor

Choose a reason for hiding this comment

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

This filename should be able to stay the same - we stopped using mod.rs files a while ago (https://doc.rust-lang.org/reference/items/modules.html#module-source-filenames)

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