Skip to content

Docker based examples #8

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

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "openldap"
version = "1.2.2"
authors = ["Josh Leverette <[email protected]>", "Ross Delinger <[email protected]>", "Stephen Holsapple <[email protected]>", "Yong Wen Chua <[email protected]>"]
authors = ["Josh Leverette <[email protected]>", "Ross Delinger <[email protected]>", "Stephen Holsapple <[email protected]>", "Yong Wen Chua <[email protected]>", "Mathias Myrland <[email protected]>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/coder543/rust-cldap"
Expand All @@ -11,3 +11,8 @@ description = "Straightforward Rust bindings to the C openldap library. This is

[dependencies]
libc = "0.2.10"

[workspace]
members = [
"examples/simple_bind_authentication"
]
33 changes: 33 additions & 0 deletions examples/simple_bind_authentication/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Simple Bind with start_tls

This example shows how to use simple_bind in combination with start_tls
and a manager account to do a typical user authentication lookup.

Start TLS is the recommended way to do secure LDAP; ldaps:// on port 636 is deprecated.

## Running

Start the example docker using the start_example_server.sh script from
the examples directory. Then, from the simple_bind directory, do

```shell script
cargo run -- -u fry -p fry
```

## Steps that are being performed

The first step is to set up the LDAPRust instance, and perform start_tls on it.
This ensures that our communication is encrypted. Note that we are not verifying
the server certificate in this example; this is something you should do in production.

The next step is to simple_bind using our manger accounts DN and password. This
will allow us to perform an ldap_search later on.

Now, we take the incoming user name string, and perform an ldap_search for it.
Note how we are matching either email or username. Our search yields the DN
for the provided credentials.

The last step is to attempt a simple bind with the discovered DN and provided
user password. If all goes well, we are authenticated, otherwise, something
went wrong.

132 changes: 132 additions & 0 deletions examples/simple_bind_authentication/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#[macro_use]
extern crate clap;

extern crate openldap;

use openldap::errors::*;
use openldap::*;
use std::ptr;

#[derive(Clap)]
#[clap(
name = "LDAP simple_bind_authentication with start_tls authentication example",
author = "Mathias Myrland <[email protected]>",
version = "0.1.0"
)]
struct AuthOpts {
#[clap(short = "u")]
user: String,

#[clap(short = "p")]
password: String,
}

fn ldap_with_start_tls(ldap_uri: &str) -> Result<RustLDAP, LDAPError> {
let ldap = RustLDAP::new(ldap_uri).unwrap();

ldap.set_option(
codes::options::LDAP_OPT_PROTOCOL_VERSION,
&codes::versions::LDAP_VERSION3,
);

// WARNING: Normally you would want to verify the server certificate to avoid
// man in the middle attacks, but for this testing scenario we're using a
// generated self signed certificate from the docker container.
//
// To set up certificate validation, use the LDAP_OPT_X_TLS_CACERT* options
ldap.set_option(
codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT,
&codes::options::LDAP_OPT_X_TLS_NEVER,
);

ldap.set_option(openldap::codes::options::LDAP_OPT_X_TLS_NEWCTX, &0);

ldap.start_tls(None, None)?;

Ok(ldap)
}

fn do_simple_bind(
ldap: &RustLDAP,
ldap_manager_user: &str,
ldap_manager_pass: &str,
) -> Result<(), LDAPError> {
let bind_result = ldap.simple_bind(ldap_manager_user, ldap_manager_pass)?;

match bind_result {
v if v == openldap::codes::results::LDAP_SUCCESS => Ok(()),
_ => Err(LDAPError::from(String::from(
"Authentication with simple bind failed",
))),
}
}

fn ldap_dn_lookup(ldap: &RustLDAP, who: &str) -> Result<String, LDAPError> {
// Show all DNs matching the description "Human"
// ldap_search is a powerful query language, look at
// https://confluence.atlassian.com/kb/how-to-write-ldap-search-filters-792496933.html
// for an overview
//
// This particular filter allows the user to sign in with either
// uid or email
let filter = format!("(|(uid={})(mail={}))", who, who);
Copy link

Choose a reason for hiding this comment

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

This is vulnerable to LDAP injection. It's actually a problem of the library since it does not provide the proper tools for escaping, so I opened #9 to track this issue.

Choose a reason for hiding this comment

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

I don’t think the C LDAP library provided this functionality, and since this is a thin wrapper around it it was never present. The C library likely assumed that you escaped your queries yourself, as I did when I was using it for the project the fork was made for.

That being said, I do not think it’s a bad idea to add that ability if there is a standardized way to perform it and it is not required to be done if you can prove that your queries are otherwise ‘safe’.


match ldap.ldap_search(
"ou=people,dc=planetexpress,dc=com",
codes::scopes::LDAP_SCOPE_SUBTREE,
Some(filter.as_str()),
Some(vec!["dn"]),
true,
None,
None,
ptr::null_mut(),
-1,
) {
Ok(search_results) => {
for result_map in search_results {
for result_tuple in result_map {
println!("Found result map with key {}", result_tuple.0);
for result_data in result_tuple.1 {
println!("\t {}", result_data);
return Ok(result_data);
}
}
}

Err(LDAPError::from(String::from(
"Authentication with simple bind failed",
)))
}
_ => Err(LDAPError::from(String::from(
"Authentication with simple bind failed",
))),
}
}

fn main() {
let options = AuthOpts::parse();
let user_to_authenticate = options.user;
let pwd_to_authenticate = options.password;

let ldap_uri = "ldap://localhost:389";
let ldap_manager_dn = "cn=Hubert J. Farnsworth,ou=people,dc=planetexpress,dc=com";
let ldap_manager_pass = "professor";

let ldap = ldap_with_start_tls(ldap_uri).unwrap();

// Bind to the LDAP server with the manager account,
// this is done to perform a search for the DN to
// use when authenticating the user attempting to
// sign in. Obviously, the manager credentials should
// be kept secret, and not be put under version control.
// In our test scenario, the professor is the manager.
do_simple_bind(&ldap, ldap_manager_dn, ldap_manager_pass).unwrap();

if let Ok(fry_dn) = ldap_dn_lookup(&ldap, user_to_authenticate.as_str()) {
// Now, perform a bind with the DN we found matching the user attempting to sign in
// and the password provided in the authentication request
do_simple_bind(&ldap, fry_dn.as_str(), pwd_to_authenticate.as_str()).unwrap();

println!("Successfully signed in as fry");
}
Copy link

Choose a reason for hiding this comment

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

This is vulnerable to a timing side-channel attack. An attacker that does not have a valid set of credentials should not be able to learn which user accounts exist. However, if the attacker tries to authenticate with a bunch of credentials, they may notice that certain authentication attempts take longer than others. That's because the second simple-bind is only performed if ldap_dn_lookup returns Some. Therefore, the attacker can infer that a long request duration means that the username exists, even if the password is still wrong. This allows the attacker to perform a brute-force search for valid credentials much more efficiently.

Since this is a very common problem with authentication code, the example should demonstrate how to do it more safely. The basic idea is that you do the second simple-bind even if ldap_dn_lookup does not return any results. But you set a flag to remind yourself that you have to ignore the result of that second simple-bind. Here's an example from one of my programs how that looks (although in Go, not in Rust): https://github.com/majewsky/alltag/blob/df161b55fa4c7eba0abec82d2cf0df34e49b0ad4/internal/auth/ldap.go#L96-L115

Copy link
Author

Choose a reason for hiding this comment

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

Good catch! I'll see if I get time to update the example this weekend!

}
3 changes: 3 additions & 0 deletions examples/start_example_server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#! /usr/bin/env bash

docker run -p 389:389 -p 636:636 rroemhild/test-openldap