inejge / ldap3

A pure-Rust LDAP library using the Tokio stack
Apache License 2.0
226 stars 39 forks source link

GSSAPI appears to fail with fqdn #84

Closed 1Dragoon closed 2 years ago

1Dragoon commented 2 years ago

It seems the gssapi bit is failing if I pass the fqdn of the server I connected to:

Reason: GSSAPI operation error: ClientCtx::step failed The specified target is unknown or unreachable ', src\ad.rs:33:41

Could just be an environment specific thing? Or maybe just an active directory thing? Here's the code that actually works for me:

use crate::Duration;
use itertools::Itertools;
use ldap3::Ldap;
use anyhow::{bail, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use chrono::NaiveDateTime;
use ldap3::{SearchEntry, LdapConnAsync};
use trust_dns_resolver::TokioAsyncResolver;

pub(crate) async fn autoconnect_ldap() -> Result<Ldap, anyhow::Error> {
    let resolver = TokioAsyncResolver::tokio_from_system_conf()?;
    let lookup = resolver.srv_lookup("_ldap._tcp").await?;
    let records = lookup
        .into_iter()
        .sorted_by(|srv_a, srv_b| srv_a.priority().cmp(&srv_b.priority()))
        .collect::<Vec<_>>();
    let mut recs_iter = records.iter();
    loop {
        if let Some(srv) = recs_iter.next() {
            let mut server = srv.target().to_string();
            if server.ends_with('.') {
                server.truncate(server.len()-1);
            }
            let url = format!("ldaps://{server}:636");
            match LdapConnAsync::new(url.as_str()).await
            {
                Ok((conn, mut ldap)) => {
                    if let Some(first_label) = srv.target().iter().next() {
                        if let Ok(host) = std::str::from_utf8(first_label) {
                            ldap3::drive!(conn);
                            ldap.with_timeout(Duration::from_millis(100));
                            if let Err(err) = ldap.sasl_gssapi_bind(host).await {
                                bail!("Failed to authenticate to AD\nReason: {err}")
                            }
                            break Ok(ldap);
                        } else {
                            continue;
                        }

                    }
                },
                Err(err) => {
                    println!("Error: {server} {err}");
                    continue;
                },
            };
        } else {
            bail!("Couldn't find an appropriate domain controller to connect to.");
        }
    }
}

Also FWIW, native-tls fails without truncating that root dot (i.e. 'www.example.com.' needs to be 'www.example.com') Not sure whether the native-tls maintainers would consider that a feature or a bug.

inejge commented 2 years ago

It seems the gssapi bit is failing if I pass the fqdn of the server I connected to

As you surmised, it's probably environment-specific. Running klist should tell you the SPN of the domain controller in the ticket cache (ldap/dc.fq.dn); if the FQDN part is identical to what you get back from the SRV query, I can't explain it. Anyhow, a GSSAPI bind never failed for me in that way, neither on Linux nor on Windows.

Also FWIW, native-tls fails without truncating that root dot

Hostnames in TLS certificates don't have the final dot, so a straight comparison will fail. Whether that dot should be accomodated is debatable.

1Dragoon commented 2 years ago

Is it possible to request that info via ldap?

It seems the gssapi bit is failing if I pass the fqdn of the server I connected to

As you surmised, it's probably environment-specific. Running klist should tell you the SPN of the domain controller in the ticket cache (ldap/dc.fq.dn); if the FQDN part is identical to what you get back from the SRV query, I can't explain it. Anyhow, a GSSAPI bind never failed for me in that way, neither on Linux nor on Windows.

Is it possible to discover what it would need via the api? or would it just need to be trial and error?

Also FWIW, native-tls fails without truncating that root dot

Hostnames in TLS certificates don't have the final dot, so a straight comparison will fail. Whether that dot should be accomodated is debatable.

Yep: https://datatracker.ietf.org/doc/html/rfc3546#section-3.1

Would be nice if the error message somehow indicated what was being searched for vs what was found, as I literally had to guess that was the problem as I wasn't aware that the resolver crate returned the trailing dot. I'll submit a bug report to them and see if they deem it worth doing anything about.

One thing I'm really big on is offering as much as can be given about why something failed in my user facing code, though I don't force that philosophy on anybody else, merely suggesting it. Rustc itself is such an excellent example of error messages done right.

inejge commented 2 years ago

Is it possible to request that info [SPN] via ldap?

No, it's pure Kerberos.

Is it possible to discover what it would need [SPN format] via the api? or would it just need to be trial and error?

It shouldn't be trial and error, the FQDN ought to work. I'm not sure about cross-domain trusts and their interaction with Kerberos and DNS, but that's a very advanced topic and probably not the issue here.

inejge commented 2 years ago

Closing for lack of feedback and inability to reproduce.

1Dragoon commented 2 years ago

Oh sorry didn't have time to do additional tests and I just left that employer, so I'm not able to do so anymore.