First impl of LDAP login
This commit is contained in:
parent
53881fdb21
commit
262eabdca9
5 changed files with 318 additions and 22 deletions
123
Cargo.lock
generated
123
Cargo.lock
generated
|
@ -342,6 +342,16 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||||
|
dependencies = [
|
||||||
|
"matches",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
|
@ -588,6 +598,17 @@ dependencies = [
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||||
|
dependencies = [
|
||||||
|
"matches",
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "im"
|
name = "im"
|
||||||
version = "15.1.0"
|
version = "15.1.0"
|
||||||
|
@ -668,6 +689,41 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lber"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a99b520993b21a6faab32643cf4726573dc18ca4cf2d48cbeb24d248c86c930"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ldap3"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef35dc747152dd47bdc6aaeb35a232f84cbc8d84ae4cb9673aea810a6570ab8f"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"bytes",
|
||||||
|
"futures",
|
||||||
|
"futures-util",
|
||||||
|
"lazy_static",
|
||||||
|
"lber",
|
||||||
|
"log",
|
||||||
|
"native-tls",
|
||||||
|
"nom",
|
||||||
|
"percent-encoding",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.126"
|
version = "0.2.126"
|
||||||
|
@ -708,6 +764,8 @@ dependencies = [
|
||||||
"im",
|
"im",
|
||||||
"itertools",
|
"itertools",
|
||||||
"k2v-client",
|
"k2v-client",
|
||||||
|
"ldap3",
|
||||||
|
"log",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"rand",
|
"rand",
|
||||||
"rmp-serde",
|
"rmp-serde",
|
||||||
|
@ -723,6 +781,12 @@ dependencies = [
|
||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matches"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
|
@ -770,6 +834,12 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
|
@ -1406,6 +1476,21 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.18.2"
|
version = "1.18.2"
|
||||||
|
@ -1446,6 +1531,17 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-stream"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -1519,12 +1615,39 @@ version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"matches",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
|
@ -15,6 +15,8 @@ clap = { version = "3.1.18", features = ["derive", "env"] }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
im = "15"
|
im = "15"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
|
ldap3 = { version = "0.10", default-features = false, features = ["tls"] }
|
||||||
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.4"
|
||||||
rusoto_core = "0.48.0"
|
rusoto_core = "0.48.0"
|
||||||
rusoto_credential = "0.48.0"
|
rusoto_credential = "0.48.0"
|
||||||
|
|
33
src/bayou.rs
33
src/bayou.rs
|
@ -1,6 +1,7 @@
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
use log::debug;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
|
@ -76,14 +77,14 @@ impl<S: BayouState> Bayou<S> {
|
||||||
pub async fn sync(&mut self) -> Result<()> {
|
pub async fn sync(&mut self) -> Result<()> {
|
||||||
// 1. List checkpoints
|
// 1. List checkpoints
|
||||||
let checkpoints = self.list_checkpoints().await?;
|
let checkpoints = self.list_checkpoints().await?;
|
||||||
eprintln!("(sync) listed checkpoints: {:?}", checkpoints);
|
debug!("(sync) listed checkpoints: {:?}", checkpoints);
|
||||||
|
|
||||||
// 2. Load last checkpoint if different from currently used one
|
// 2. Load last checkpoint if different from currently used one
|
||||||
let checkpoint = if let Some((ts, key)) = checkpoints.last() {
|
let checkpoint = if let Some((ts, key)) = checkpoints.last() {
|
||||||
if *ts == self.checkpoint.0 {
|
if *ts == self.checkpoint.0 {
|
||||||
(*ts, None)
|
(*ts, None)
|
||||||
} else {
|
} else {
|
||||||
eprintln!("(sync) loading checkpoint: {}", key);
|
debug!("(sync) loading checkpoint: {}", key);
|
||||||
|
|
||||||
let mut gor = GetObjectRequest::default();
|
let mut gor = GetObjectRequest::default();
|
||||||
gor.bucket = self.bucket.clone();
|
gor.bucket = self.bucket.clone();
|
||||||
|
@ -94,7 +95,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
let mut buf = Vec::with_capacity(obj_res.content_length.unwrap_or(128) as usize);
|
let mut buf = Vec::with_capacity(obj_res.content_length.unwrap_or(128) as usize);
|
||||||
obj_body.into_async_read().read_to_end(&mut buf).await?;
|
obj_body.into_async_read().read_to_end(&mut buf).await?;
|
||||||
|
|
||||||
eprintln!("(sync) checkpoint body length: {}", buf.len());
|
debug!("(sync) checkpoint body length: {}", buf.len());
|
||||||
|
|
||||||
let ck = open_deserialize::<S>(&buf, &self.key)?;
|
let ck = open_deserialize::<S>(&buf, &self.key)?;
|
||||||
(*ts, Some(ck))
|
(*ts, Some(ck))
|
||||||
|
@ -108,7 +109,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ck) = checkpoint.1 {
|
if let Some(ck) = checkpoint.1 {
|
||||||
eprintln!(
|
debug!(
|
||||||
"(sync) updating checkpoint to loaded state at {:?}",
|
"(sync) updating checkpoint to loaded state at {:?}",
|
||||||
checkpoint.0
|
checkpoint.0
|
||||||
);
|
);
|
||||||
|
@ -123,7 +124,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
|
|
||||||
// 3. List all operations starting from checkpoint
|
// 3. List all operations starting from checkpoint
|
||||||
let ts_ser = self.checkpoint.0.serialize();
|
let ts_ser = self.checkpoint.0.serialize();
|
||||||
eprintln!("(sync) looking up operations starting at {}", ts_ser);
|
debug!("(sync) looking up operations starting at {}", ts_ser);
|
||||||
let ops_map = self
|
let ops_map = self
|
||||||
.k2v
|
.k2v
|
||||||
.read_batch(&[BatchReadOp {
|
.read_batch(&[BatchReadOp {
|
||||||
|
@ -155,7 +156,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
match &val.value[0] {
|
match &val.value[0] {
|
||||||
K2vValue::Value(v) => {
|
K2vValue::Value(v) => {
|
||||||
let op = open_deserialize::<S::Op>(&v, &self.key)?;
|
let op = open_deserialize::<S::Op>(&v, &self.key)?;
|
||||||
eprintln!("(sync) operation {}: {} {:?}", tsstr, base64::encode(v), op);
|
debug!("(sync) operation {}: {} {:?}", tsstr, base64::encode(v), op);
|
||||||
ops.push((ts, op));
|
ops.push((ts, op));
|
||||||
}
|
}
|
||||||
K2vValue::Tombstone => {
|
K2vValue::Tombstone => {
|
||||||
|
@ -164,7 +165,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ops.sort_by_key(|(ts, _)| *ts);
|
ops.sort_by_key(|(ts, _)| *ts);
|
||||||
eprintln!("(sync) {} operations", ops.len());
|
debug!("(sync) {} operations", ops.len());
|
||||||
|
|
||||||
if ops.len() < self.history.len() {
|
if ops.len() < self.history.len() {
|
||||||
bail!("Some operations have disappeared from storage!");
|
bail!("Some operations have disappeared from storage!");
|
||||||
|
@ -239,7 +240,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
pub async fn push(&mut self, op: S::Op) -> Result<()> {
|
pub async fn push(&mut self, op: S::Op) -> Result<()> {
|
||||||
self.check_recent_sync().await?;
|
self.check_recent_sync().await?;
|
||||||
|
|
||||||
eprintln!("(push) add operation: {:?}", op);
|
debug!("(push) add operation: {:?}", op);
|
||||||
|
|
||||||
let ts = Timestamp::after(
|
let ts = Timestamp::after(
|
||||||
self.history
|
self.history
|
||||||
|
@ -302,18 +303,18 @@ impl<S: BayouState> Bayou<S> {
|
||||||
{
|
{
|
||||||
Some(i) => i,
|
Some(i) => i,
|
||||||
None => {
|
None => {
|
||||||
eprintln!("(cp) Oldest operation is too recent to trigger checkpoint");
|
debug!("(cp) Oldest operation is too recent to trigger checkpoint");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if i_cp < CHECKPOINT_MIN_OPS {
|
if i_cp < CHECKPOINT_MIN_OPS {
|
||||||
eprintln!("(cp) Not enough old operations to trigger checkpoint");
|
debug!("(cp) Not enough old operations to trigger checkpoint");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let ts_cp = self.history[i_cp].0;
|
let ts_cp = self.history[i_cp].0;
|
||||||
eprintln!(
|
debug!(
|
||||||
"(cp) we could checkpoint at time {} (index {} in history)",
|
"(cp) we could checkpoint at time {} (index {} in history)",
|
||||||
ts_cp.serialize(),
|
ts_cp.serialize(),
|
||||||
i_cp
|
i_cp
|
||||||
|
@ -321,13 +322,13 @@ impl<S: BayouState> Bayou<S> {
|
||||||
|
|
||||||
// Check existing checkpoints: if last one is too recent, don't checkpoint again.
|
// Check existing checkpoints: if last one is too recent, don't checkpoint again.
|
||||||
let existing_checkpoints = self.list_checkpoints().await?;
|
let existing_checkpoints = self.list_checkpoints().await?;
|
||||||
eprintln!("(cp) listed checkpoints: {:?}", existing_checkpoints);
|
debug!("(cp) listed checkpoints: {:?}", existing_checkpoints);
|
||||||
|
|
||||||
if let Some(last_cp) = existing_checkpoints.last() {
|
if let Some(last_cp) = existing_checkpoints.last() {
|
||||||
if (ts_cp.msec as i128 - last_cp.0.msec as i128)
|
if (ts_cp.msec as i128 - last_cp.0.msec as i128)
|
||||||
< CHECKPOINT_INTERVAL.as_millis() as i128
|
< CHECKPOINT_INTERVAL.as_millis() as i128
|
||||||
{
|
{
|
||||||
eprintln!(
|
debug!(
|
||||||
"(cp) last checkpoint is too recent: {}, not checkpointing",
|
"(cp) last checkpoint is too recent: {}, not checkpointing",
|
||||||
last_cp.0.serialize()
|
last_cp.0.serialize()
|
||||||
);
|
);
|
||||||
|
@ -335,7 +336,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("(cp) saving checkpoint at {}", ts_cp.serialize());
|
debug!("(cp) saving checkpoint at {}", ts_cp.serialize());
|
||||||
|
|
||||||
// Calculate state at time of checkpoint
|
// Calculate state at time of checkpoint
|
||||||
let mut last_known_state = (0, &self.checkpoint.1);
|
let mut last_known_state = (0, &self.checkpoint.1);
|
||||||
|
@ -351,7 +352,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
|
|
||||||
// Serialize and save checkpoint
|
// Serialize and save checkpoint
|
||||||
let cryptoblob = seal_serialize(&state_cp, &self.key)?;
|
let cryptoblob = seal_serialize(&state_cp, &self.key)?;
|
||||||
eprintln!("(cp) checkpoint body length: {}", cryptoblob.len());
|
debug!("(cp) checkpoint body length: {}", cryptoblob.len());
|
||||||
|
|
||||||
let mut por = PutObjectRequest::default();
|
let mut por = PutObjectRequest::default();
|
||||||
por.bucket = self.bucket.clone();
|
por.bucket = self.bucket.clone();
|
||||||
|
@ -366,7 +367,7 @@ impl<S: BayouState> Bayou<S> {
|
||||||
|
|
||||||
// Delete blobs
|
// Delete blobs
|
||||||
for (_ts, key) in existing_checkpoints[..last_to_keep].iter() {
|
for (_ts, key) in existing_checkpoints[..last_to_keep].iter() {
|
||||||
eprintln!("(cp) drop old checkpoint {}", key);
|
debug!("(cp) drop old checkpoint {}", key);
|
||||||
let mut dor = DeleteObjectRequest::default();
|
let mut dor = DeleteObjectRequest::default();
|
||||||
dor.bucket = self.bucket.clone();
|
dor.bucket = self.bucket.clone();
|
||||||
dor.key = key.to_string();
|
dor.key = key.to_string();
|
||||||
|
|
|
@ -41,8 +41,16 @@ pub struct LoginStaticUser {
|
||||||
pub struct LoginLdapConfig {
|
pub struct LoginLdapConfig {
|
||||||
pub ldap_server: String,
|
pub ldap_server: String,
|
||||||
|
|
||||||
pub search_dn: String,
|
#[serde(default)]
|
||||||
|
pub pre_bind_on_login: bool,
|
||||||
|
pub bind_dn: Option<String>,
|
||||||
|
pub bind_password: Option<String>,
|
||||||
|
|
||||||
|
pub search_base: String,
|
||||||
pub username_attr: String,
|
pub username_attr: String,
|
||||||
|
#[serde(default = "default_mail_attr")]
|
||||||
|
pub mail_attr: String,
|
||||||
|
|
||||||
pub aws_access_key_id_attr: String,
|
pub aws_access_key_id_attr: String,
|
||||||
pub aws_secret_access_key_attr: String,
|
pub aws_secret_access_key_attr: String,
|
||||||
pub user_secret_attr: String,
|
pub user_secret_attr: String,
|
||||||
|
@ -62,3 +70,7 @@ pub fn read_config(config_file: PathBuf) -> Result<Config> {
|
||||||
|
|
||||||
Ok(toml::from_str(&config)?)
|
Ok(toml::from_str(&config)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_mail_attr() -> String {
|
||||||
|
"mail".into()
|
||||||
|
}
|
||||||
|
|
|
@ -1,23 +1,181 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use ldap3::{LdapConnAsync, Scope, SearchEntry};
|
||||||
|
use log::debug;
|
||||||
use rusoto_signature::Region;
|
use rusoto_signature::Region;
|
||||||
|
|
||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
use crate::login::*;
|
use crate::login::*;
|
||||||
|
|
||||||
pub struct LdapLoginProvider {
|
pub struct LdapLoginProvider {
|
||||||
// TODO
|
k2v_region: Region,
|
||||||
|
s3_region: Region,
|
||||||
|
ldap_server: String,
|
||||||
|
|
||||||
|
pre_bind_on_login: bool,
|
||||||
|
bind_dn_and_pw: Option<(String, String)>,
|
||||||
|
|
||||||
|
search_base: String,
|
||||||
|
attrs_to_retrieve: Vec<String>,
|
||||||
|
username_attr: String,
|
||||||
|
mail_attr: String,
|
||||||
|
|
||||||
|
aws_access_key_id_attr: String,
|
||||||
|
aws_secret_access_key_attr: String,
|
||||||
|
user_secret_attr: String,
|
||||||
|
alternate_user_secrets_attr: Option<String>,
|
||||||
|
|
||||||
|
bucket_source: BucketSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BucketSource {
|
||||||
|
Constant(String),
|
||||||
|
Attr(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LdapLoginProvider {
|
impl LdapLoginProvider {
|
||||||
pub fn new(_config: LoginLdapConfig, _k2v_region: Region, _s3_region: Region) -> Result<Self> {
|
pub fn new(config: LoginLdapConfig, k2v_region: Region, s3_region: Region) -> Result<Self> {
|
||||||
unimplemented!()
|
let bind_dn_and_pw = match (config.bind_dn, config.bind_password) {
|
||||||
|
(Some(dn), Some(pw)) => Some((dn, pw)),
|
||||||
|
(None, None) => None,
|
||||||
|
_ => bail!(
|
||||||
|
"If either of `bind_dn` or `bind_password` is set, the other must be set as well."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bucket_source = match (config.bucket, config.bucket_attr) {
|
||||||
|
(Some(b), None) => BucketSource::Constant(b),
|
||||||
|
(None, Some(a)) => BucketSource::Attr(a),
|
||||||
|
_ => bail!("Must set `bucket` or `bucket_attr`, but not both"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if config.pre_bind_on_login && bind_dn_and_pw.is_none() {
|
||||||
|
bail!("Cannot use `pre_bind_on_login` without setting `bind_dn` and `bind_password`");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut attrs_to_retrieve = vec![
|
||||||
|
config.username_attr.clone(),
|
||||||
|
config.mail_attr.clone(),
|
||||||
|
config.aws_access_key_id_attr.clone(),
|
||||||
|
config.aws_secret_access_key_attr.clone(),
|
||||||
|
config.user_secret_attr.clone(),
|
||||||
|
];
|
||||||
|
if let Some(a) = &config.alternate_user_secrets_attr {
|
||||||
|
attrs_to_retrieve.push(a.clone());
|
||||||
|
}
|
||||||
|
if let BucketSource::Attr(a) = &bucket_source {
|
||||||
|
attrs_to_retrieve.push(a.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
k2v_region,
|
||||||
|
s3_region,
|
||||||
|
ldap_server: config.ldap_server,
|
||||||
|
pre_bind_on_login: config.pre_bind_on_login,
|
||||||
|
bind_dn_and_pw,
|
||||||
|
search_base: config.search_base,
|
||||||
|
attrs_to_retrieve,
|
||||||
|
username_attr: config.username_attr,
|
||||||
|
mail_attr: config.mail_attr,
|
||||||
|
aws_access_key_id_attr: config.aws_access_key_id_attr,
|
||||||
|
aws_secret_access_key_attr: config.aws_secret_access_key_attr,
|
||||||
|
user_secret_attr: config.user_secret_attr,
|
||||||
|
alternate_user_secrets_attr: config.alternate_user_secrets_attr,
|
||||||
|
bucket_source,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl LoginProvider for LdapLoginProvider {
|
impl LoginProvider for LdapLoginProvider {
|
||||||
async fn login(&self, _username: &str, _password: &str) -> Result<Credentials> {
|
async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
|
||||||
unimplemented!()
|
let (conn, mut ldap) = LdapConnAsync::new(&self.ldap_server).await?;
|
||||||
|
ldap3::drive!(conn);
|
||||||
|
|
||||||
|
if self.pre_bind_on_login {
|
||||||
|
let (dn, pw) = self.bind_dn_and_pw.as_ref().unwrap();
|
||||||
|
ldap.simple_bind(dn, pw).await?.success()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let username_is_ok = username
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_alphanumeric() || "-+_.@".contains(c));
|
||||||
|
if !username_is_ok {
|
||||||
|
bail!("Invalid username, must contain only a-z A-Z 0-9 - + _ . @");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (matches, _res) = ldap
|
||||||
|
.search(
|
||||||
|
&self.search_base,
|
||||||
|
Scope::Subtree,
|
||||||
|
&format!(
|
||||||
|
"(&(objectClass=inetOrgPerson)({}={}))",
|
||||||
|
self.username_attr, username
|
||||||
|
),
|
||||||
|
&self.attrs_to_retrieve,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.success()?;
|
||||||
|
|
||||||
|
if matches.is_empty() {
|
||||||
|
bail!("Invalid username");
|
||||||
|
}
|
||||||
|
if matches.len() > 1 {
|
||||||
|
bail!("Invalid username (multiple matching accounts)");
|
||||||
|
}
|
||||||
|
let user = SearchEntry::construct(matches.into_iter().next().unwrap());
|
||||||
|
debug!(
|
||||||
|
"Found matching LDAP user for username {}: {}",
|
||||||
|
username, user.dn
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to login against LDAP server with provided password
|
||||||
|
// to check user's password
|
||||||
|
ldap.simple_bind(&user.dn, password)
|
||||||
|
.await?
|
||||||
|
.success()
|
||||||
|
.context("Invalid password")?;
|
||||||
|
debug!("Ldap login with user name {} successfull", username);
|
||||||
|
|
||||||
|
let get_attr = |attr: &str| -> Result<String> {
|
||||||
|
Ok(user
|
||||||
|
.attrs
|
||||||
|
.get(attr)
|
||||||
|
.ok_or(anyhow!("Missing attr: {}", attr))?
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.ok_or(anyhow!("No value for attr: {}", attr))?
|
||||||
|
.clone())
|
||||||
|
};
|
||||||
|
let aws_access_key_id = get_attr(&self.aws_access_key_id_attr)?;
|
||||||
|
let aws_secret_access_key = get_attr(&self.aws_secret_access_key_attr)?;
|
||||||
|
let bucket = match &self.bucket_source {
|
||||||
|
BucketSource::Constant(b) => b.clone(),
|
||||||
|
BucketSource::Attr(a) => get_attr(a)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let storage = StorageCredentials {
|
||||||
|
k2v_region: self.k2v_region.clone(),
|
||||||
|
s3_region: self.s3_region.clone(),
|
||||||
|
aws_access_key_id,
|
||||||
|
aws_secret_access_key,
|
||||||
|
bucket,
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_secret = get_attr(&self.user_secret_attr)?;
|
||||||
|
let alternate_user_secrets = match &self.alternate_user_secrets_attr {
|
||||||
|
None => vec![],
|
||||||
|
Some(a) => user.attrs.get(a).cloned().unwrap_or_default(),
|
||||||
|
};
|
||||||
|
let user_secrets = UserSecrets {
|
||||||
|
user_secret,
|
||||||
|
alternate_user_secrets,
|
||||||
|
};
|
||||||
|
|
||||||
|
drop(ldap);
|
||||||
|
|
||||||
|
let keys = CryptoKeys::open(&storage, &user_secrets, password).await?;
|
||||||
|
|
||||||
|
Ok(Credentials { storage, keys })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue