199 ldap gateway (#246)

adds an LDAP gateway to the server. It supports TLS if configured for the webserver, using the same parameters. It is a read only interface, only supporting bind via the configured posix password.
This commit is contained in:
Firstyear 2020-06-10 12:07:43 +10:00 committed by GitHub
parent ff9238b7ee
commit 70fa17f3a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1864 additions and 877 deletions

121
Cargo.lock generated
View file

@ -365,9 +365,9 @@ dependencies = [
[[package]]
name = "arc-swap"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62"
checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
[[package]]
name = "arrayref"
@ -383,9 +383,9 @@ checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "async-trait"
version = "0.1.31"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b"
checksum = "89cb5d814ab2a47fd66d3266e9efccb53ca4c740b7451043b8ffcf9a6208f3f8"
dependencies = [
"proc-macro2",
"quote",
@ -556,9 +556,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.3.0"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]]
name = "byte-tools"
@ -647,10 +647,11 @@ dependencies = [
[[package]]
name = "concread"
version = "0.1.12"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f9eb637262518ed76134ca6ce47088c60e514c330f89f32d88ac72b10458bb"
checksum = "7ca3d5adc408121d96cb5e3ca77656ca0b6e96429c244086d300ca9ed6a2fd1d"
dependencies = [
"crossbeam",
"crossbeam-epoch",
"crossbeam-utils",
"num",
@ -701,9 +702,9 @@ dependencies = [
[[package]]
name = "copyless"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127"
checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
[[package]]
name = "core-foundation"
@ -950,11 +951,10 @@ dependencies = [
[[package]]
name = "dirs-sys"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
dependencies = [
"cfg-if",
"libc",
"redox_users",
"winapi 0.3.8",
@ -1420,9 +1420,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.3.2"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
dependencies = [
"autocfg",
]
@ -1499,9 +1499,11 @@ dependencies = [
"crossbeam",
"env_logger",
"futures",
"futures-util",
"idlset",
"kanidm_proto",
"lazy_static",
"ldap3_server",
"libsqlite3-sys",
"log",
"num_cpus",
@ -1520,6 +1522,8 @@ dependencies = [
"structopt",
"time 0.1.43",
"tokio",
"tokio-openssl",
"tokio-util 0.2.0",
"uuid",
"zxcvbn",
]
@ -1623,6 +1627,28 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lber"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a749954d43fcfb8d4381aa0c6cf291065053e0590d622f4f830393a9bd8278a5"
dependencies = [
"byteorder",
"bytes",
"nom 2.2.1",
]
[[package]]
name = "ldap3_server"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b32e8fda95d5e76c853678774ba647797b13632e41fbc7cfd9afe0869a232d"
dependencies = [
"bytes",
"lber",
"tokio-util 0.2.0",
]
[[package]]
name = "libc"
version = "0.2.71"
@ -1768,7 +1794,7 @@ checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
dependencies = [
"log",
"mio",
"miow 0.3.4",
"miow 0.3.5",
"winapi 0.3.8",
]
@ -1797,9 +1823,9 @@ dependencies = [
[[package]]
name = "miow"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22dfdd1d51b2639a5abd17ed07005c3af05fb7a2a3b1a1d0d7af1000a520c1c7"
checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
dependencies = [
"socket2",
"winapi 0.3.8",
@ -1840,6 +1866,12 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff"
[[package]]
name = "nom"
version = "4.2.3"
@ -1997,9 +2029,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.57"
version = "0.9.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7410fef80af8ac071d4f63755c0ab89ac3df0fd1ea91f1d1f37cf5cec4395990"
checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
dependencies = [
"autocfg",
"cc",
@ -2045,9 +2077,9 @@ dependencies = [
[[package]]
name = "paste"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53181dcd37421c08d3b69f887784956674d09c3f9a47a04fece2b130a5b346b"
checksum = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec"
dependencies = [
"paste-impl",
"proc-macro-hack",
@ -2055,9 +2087,9 @@ dependencies = [
[[package]]
name = "paste-impl"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ca490fa1c034a71412b4d1edcb904ec5a0981a4426c9eb2128c0fda7a68d17"
checksum = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32"
dependencies = [
"proc-macro-hack",
"proc-macro2",
@ -2079,18 +2111,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project"
version = "0.4.17"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791"
checksum = "e75373ff9037d112bb19bc61333a06a159eaeb217660dcfbea7d88e1db823919"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "0.4.17"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40"
checksum = "10b4b44893d3c370407a1d6a5cfde7c41ae0478e31c516c85f67eb3adc51be6d"
dependencies = [
"proc-macro2",
"quote",
@ -2099,9 +2131,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df32da11d84f3a7d70205549562966279adb900e080fad3dccd8e64afccf0ad"
checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715"
[[package]]
name = "pin-utils"
@ -2167,9 +2199,9 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
[[package]]
name = "proc-macro-nested"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
checksum = "0afe1bd463b9e9ed51d0e0f0b50b6b146aec855c56fd182bb242388710a9b6de"
[[package]]
name = "proc-macro2"
@ -2201,9 +2233,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
@ -2578,9 +2610,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.53"
version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2"
checksum = "cfe4c1f6427dbc29329c6288e9e748b8b8e0ea42a0aab733e887fa72c22e965d"
dependencies = [
"itoa",
"ryu",
@ -2683,9 +2715,12 @@ dependencies = [
[[package]]
name = "standback"
version = "0.2.8"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e4b8c631c998468961a9ea159f064c5c8499b95b5e4a34b77849d45949d540"
checksum = "b0437cfb83762844799a60e1e3b489d5ceb6a650fbacb86437badc1b6d87b246"
dependencies = [
"version_check 0.9.2",
]
[[package]]
name = "stdweb"
@ -2790,9 +2825,9 @@ dependencies = [
[[package]]
name = "synstructure"
version = "0.12.3"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2",
"quote",
@ -3175,7 +3210,7 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae"
dependencies = [
"nom",
"nom 4.2.3",
"proc-macro2",
"quote",
"syn",
@ -3316,9 +3351,9 @@ dependencies = [
[[package]]
name = "widestring"
version = "0.4.0"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6"
checksum = "a763e303c0e0f23b0da40888724762e802a8ffefbc22de4127ef42493c2ea68c"
[[package]]
name = "winapi"

View file

@ -1,4 +1,7 @@
[profile.release]
debug = true
[workspace]
members = [
"kanidm_proto",

View file

@ -28,23 +28,24 @@ pub fn run_test(test_fn: fn(KanidmClient) -> ()) {
admin_password: ADMIN_TEST_PASSWORD.to_string(),
});
// Setup the config ...
let mut config = Configuration::new();
config.address = format!("127.0.0.1:{}", port);
config.secure_cookies = false;
config.integration_test_config = Some(int_config);
// Setup the config ...
thread::spawn(move || {
// Spawn a thread for the test runner, this should have a unique
// port....
System::run(move || {
let sctx = create_server_core(config);
let system = System::new("test-rctx");
// This appears to be bind random ...
// let srv = srv.bind("127.0.0.1:0").unwrap();
let rctx = async move {
let sctx = create_server_core(config).await;
let _ = tx.send(sctx);
})
.expect("unable to start system");
};
Arbiter::spawn(rctx);
system.run().expect("Failed to start thread");
});
let sctx = rx.recv().unwrap().expect("failed to start ctx");
System::set_current(sctx.current());

View file

@ -152,6 +152,7 @@ pub struct UserAuthToken {
// may depend on the client application.
// pub expiry: DateTime,
pub name: String,
pub spn: String,
pub displayname: String,
pub uuid: String,
pub application: Option<Application>,
@ -163,6 +164,7 @@ pub struct UserAuthToken {
impl fmt::Display for UserAuthToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "name: {}", self.name)?;
writeln!(f, "spn: {}", self.spn)?;
writeln!(f, "display: {}", self.displayname)?;
writeln!(f, "uuid: {}", self.uuid)?;
writeln!(f, "groups: {:?}", self.groups)?;

View file

@ -28,22 +28,26 @@ fn run_test(fix_fn: fn(&KanidmClient) -> (), test_fn: fn(CacheLayer, KanidmAsync
admin_password: ADMIN_TEST_PASSWORD.to_string(),
});
// Setup the config ...
let mut config = Configuration::new();
config.address = format!("127.0.0.1:{}", port);
config.secure_cookies = false;
config.integration_test_config = Some(int_config);
// Setup the config ...
thread::spawn(move || {
// Spawn a thread for the test runner, this should have a unique
// port....
System::run(move || {
let sctx = create_server_core(config);
let system = System::new("test-rctx");
let rctx = async move {
let sctx = create_server_core(config).await;
let _ = tx.send(sctx);
})
.expect("Failed to start system");
};
Arbiter::spawn(rctx);
system.run().expect("Failed to start thread");
});
let sctx = rx.recv().unwrap().expect("Failed to start server core");
let sctx = rx.recv().unwrap().expect("failed to start ctx");
System::set_current(sctx.current());
// Setup the client, and the address we selected.

View file

@ -77,6 +77,12 @@ idlset = { version = "0.1" , features = ["use_smallvec"] }
zxcvbn = "2.0"
base64 = "0.12"
ldap3_server = "0.1"
# ldap3_server = { path = "../../ldap3_server" }
futures-util = "0.3"
tokio-util = { version = "0.2", features = ["codec"] }
tokio-openssl = "0.4"
[features]
default = [ "libsqlite3-sys/bundled", "openssl/vendored" ]

View file

@ -42,7 +42,7 @@ RUN zypper mr -d repo-non-oss && \
COPY --from=builder /usr/src/kanidm/target/release/kanidmd /sbin/
EXPOSE 8443
EXPOSE 8443 3636
VOLUME /data
ENV RUST_BACKTRACE 1

View file

@ -359,6 +359,7 @@ pub trait AccessControlsTransaction {
se: &SearchEvent,
entries: Vec<Entry<EntrySealed, EntryCommitted>>,
) -> Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> {
lperf_segment!(audit, "access::search_filter_entries", || {
lsecurity_access!(audit, "Access check for event: {:?}", se);
// If this is an internal search, return our working set.
@ -489,6 +490,7 @@ pub trait AccessControlsTransaction {
}
Ok(allowed_entries)
})
}
fn search_filter_entry_attributes(
@ -497,6 +499,7 @@ pub trait AccessControlsTransaction {
se: &SearchEvent,
entries: Vec<Entry<EntrySealed, EntryCommitted>>,
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
lperf_segment!(audit, "access::search_filter_entry_attributes", || {
/*
* Super similar to above (could even re-use some parts). Given a set of entries,
* reduce the attribute sets on them to "what is visible". This is ONLY called on
@ -652,6 +655,7 @@ pub trait AccessControlsTransaction {
allowed_entries.len()
);
Ok(allowed_entries)
})
}
fn modify_allow_operation(
@ -660,6 +664,7 @@ pub trait AccessControlsTransaction {
me: &ModifyEvent,
entries: &[Entry<EntrySealed, EntryCommitted>],
) -> Result<bool, OperationError> {
lperf_segment!(audit, "access::modify_allow_operation", || {
lsecurity_access!(audit, "Access check for event: {:?}", me);
let rec_entry: &Entry<EntrySealed, EntryCommitted> = match &me.event.origin {
@ -839,7 +844,12 @@ pub trait AccessControlsTransaction {
}
if !requested_classes.is_subset(&allowed_classes) {
lsecurity_access!(audit, "requested_classes is not a subset of allowed");
lsecurity_access!(audit, "{:?} !⊆ {:?}", requested_classes, allowed_classes);
lsecurity_access!(
audit,
"{:?} !⊆ {:?}",
requested_classes,
allowed_classes
);
result = false;
}
lsecurity_access!(audit, "passed pres, rem, classes check.");
@ -852,6 +862,7 @@ pub trait AccessControlsTransaction {
lsecurity_access!(audit, "denied ❌");
}
Ok(r)
})
}
fn create_allow_operation(
@ -860,6 +871,7 @@ pub trait AccessControlsTransaction {
ce: &CreateEvent,
entries: &[Entry<EntryInit, EntryNew>],
) -> Result<bool, OperationError> {
lperf_segment!(audit, "access::create_allow_operation", || {
lsecurity_access!(audit, "Access check for event: {:?}", ce);
let rec_entry: &Entry<EntrySealed, EntryCommitted> = match &ce.event.origin {
@ -1024,6 +1036,7 @@ pub trait AccessControlsTransaction {
}
Ok(r)
})
}
fn delete_allow_operation(
@ -1032,6 +1045,7 @@ pub trait AccessControlsTransaction {
de: &DeleteEvent,
entries: &[Entry<EntrySealed, EntryCommitted>],
) -> Result<bool, OperationError> {
lperf_segment!(audit, "access::delete_allow_operation", || {
lsecurity_access!(audit, "Access check for event: {:?}", de);
let rec_entry: &Entry<EntrySealed, EntryCommitted> = match &de.event.origin {
@ -1130,6 +1144,7 @@ pub trait AccessControlsTransaction {
lsecurity_access!(audit, "denied ❌");
}
Ok(r)
})
}
}

View file

@ -12,6 +12,7 @@ use kanidm_proto::v1::{OperationError, RadiusAuthToken};
use crate::filter::{Filter, FilterInvalid};
use crate::idm::server::IdmServer;
use crate::ldap::{LdapBoundToken, LdapResponseState, LdapServer};
use crate::server::{QueryServer, QueryServerTransaction};
use kanidm_proto::v1::Entry as ProtoEntry;
@ -24,6 +25,9 @@ use actix::prelude::*;
use std::time::SystemTime;
use uuid::Uuid;
use ldap3_server::simple::*;
use std::convert::TryFrom;
// These are used when the request (IE Get) has no intrising request
// type. Additionally, they are used in some requests where we need
// to supplement extra server state (IE userauthtokens) to a request.
@ -183,6 +187,7 @@ pub struct QueryServerReadV1 {
log: Sender<Option<AuditScope>>,
qs: QueryServer,
idms: Arc<IdmServer>,
ldap: Arc<LdapServer>,
}
impl Actor for QueryServerReadV1 {
@ -194,19 +199,35 @@ impl Actor for QueryServerReadV1 {
}
impl QueryServerReadV1 {
pub fn new(log: Sender<Option<AuditScope>>, qs: QueryServer, idms: Arc<IdmServer>) -> Self {
pub fn new(
log: Sender<Option<AuditScope>>,
qs: QueryServer,
idms: Arc<IdmServer>,
ldap: Arc<LdapServer>,
) -> Self {
info!("Starting query server v1 worker ...");
QueryServerReadV1 { log, qs, idms }
QueryServerReadV1 {
log,
qs,
idms,
ldap,
}
}
pub fn start(
log: Sender<Option<AuditScope>>,
query_server: QueryServer,
idms: Arc<IdmServer>,
ldap: Arc<LdapServer>,
threads: usize,
) -> actix::Addr<QueryServerReadV1> {
SyncArbiter::start(threads, move || {
QueryServerReadV1::new(log.clone(), query_server.clone(), idms.clone())
QueryServerReadV1::new(
log.clone(),
query_server.clone(),
idms.clone(),
ldap.clone(),
)
})
}
}
@ -857,3 +878,58 @@ impl Handler<IdmAccountUnixAuthMessage> for QueryServerReadV1 {
res
}
}
#[derive(Message)]
#[rtype(result = "Option<LdapResponseState>")]
pub struct LdapRequestMessage {
pub eventid: Uuid,
pub protomsg: LdapMsg,
pub uat: Option<LdapBoundToken>,
}
impl Handler<LdapRequestMessage> for QueryServerReadV1 {
type Result = Option<LdapResponseState>;
fn handle(&mut self, msg: LdapRequestMessage, _: &mut Self::Context) -> Self::Result {
let LdapRequestMessage {
eventid,
protomsg,
uat,
} = msg;
let mut audit = AuditScope::new("ldap_request_message", eventid.clone());
let res = lperf_segment!(
&mut audit,
"actors::v1_read::handle<LdapRequestMessage>",
|| {
let server_op = match ServerOps::try_from(protomsg) {
Ok(v) => v,
Err(_) => {
return LdapResponseState::Disconnect(DisconnectionNotice::gen(
LdapResultCode::ProtocolError,
format!("Invalid Request {:?}", &eventid).as_str(),
));
}
};
self.ldap
.do_op(&mut audit, &self.idms, server_op, uat, &eventid)
.unwrap_or_else(|e| {
ladmin_error!(&mut audit, "do_op failed -> {:?}", e);
LdapResponseState::Disconnect(DisconnectionNotice::gen(
LdapResultCode::Other,
format!("Internal Server Error {:?}", &eventid).as_str(),
))
})
}
);
if self.log.send(Some(audit)).is_err() {
error!("Unable to commit log -> {:?}", &eventid);
Some(LdapResponseState::Disconnect(DisconnectionNotice::gen(
LdapResultCode::Other,
format!("Internal Server Error {:?}", &eventid).as_str(),
)))
} else {
Some(res)
}
}
}

View file

@ -87,15 +87,19 @@ macro_rules! ltrace {
macro_rules! lfilter {
($au:expr, $($arg:tt)*) => ({
if log_enabled!(log::Level::Info) {
lqueue!($au, LogTag::Filter, $($arg)*)
}
})
}
/*
macro_rules! lfilter_warning {
($au:expr, $($arg:tt)*) => ({
lqueue!($au, LogTag::FilterWarning, $($arg)*)
})
}
*/
macro_rules! lfilter_error {
($au:expr, $($arg:tt)*) => ({
@ -111,13 +115,17 @@ macro_rules! ladmin_error {
macro_rules! ladmin_warning {
($au:expr, $($arg:tt)*) => ({
if log_enabled!(log::Level::Warn) {
lqueue!($au, LogTag::AdminWarning, $($arg)*)
}
})
}
macro_rules! ladmin_info {
($au:expr, $($arg:tt)*) => ({
if log_enabled!(log::Level::Info) {
lqueue!($au, LogTag::AdminInfo, $($arg)*)
}
})
}
@ -141,6 +149,7 @@ macro_rules! lsecurity_access {
macro_rules! lperf_segment {
($au:expr, $id:expr, $fun:expr) => {{
if log_enabled!(log::Level::Debug) {
use std::time::Instant;
// start timer.
@ -163,6 +172,9 @@ macro_rules! lperf_segment {
// Return the result. Hope this works!
r
} else {
$fun()
}
}};
}
@ -295,7 +307,7 @@ impl PartialEq for PerfProcessed {
impl PerfProcessed {
fn int_write_fmt(&self, parents: usize, uuid: &HyphenatedRef) {
let mut prefix = String::new();
prefix.push_str(format!("[- {} perf::trace] ", uuid).as_str());
prefix.push_str("[- perf::trace] ");
let d = &self.duration;
let df = d.as_secs() as f64 + d.subsec_nanos() as f64 * 1e-9;
if parents > 0 {
@ -355,19 +367,19 @@ impl AuditScope {
pub fn write_log(self) {
let uuid_ref = self.uuid.to_hyphenated_ref();
error!("[- event::start] {}", uuid_ref);
self.events.iter().for_each(|e| match e.tag {
LogTag::AdminError | LogTag::RequestError | LogTag::FilterError => {
error!("[{} {} {}] {}", e.time, uuid_ref, e.tag, e.data)
error!("[{} {}] {}", e.time, e.tag, e.data)
}
LogTag::AdminWarning
| LogTag::Security
| LogTag::SecurityAccess
| LogTag::FilterWarning => warn!("[{} {} {}] {}", e.time, uuid_ref, e.tag, e.data),
LogTag::AdminInfo | LogTag::Filter => {
info!("[{} {} {}] {}", e.time, uuid_ref, e.tag, e.data)
}
LogTag::Trace => debug!("[{} {} {}] {}", e.time, uuid_ref, e.tag, e.data),
| LogTag::FilterWarning => warn!("[{} {}] {}", e.time, e.tag, e.data),
LogTag::AdminInfo | LogTag::Filter => info!("[{} {}] {}", e.time, e.tag, e.data),
LogTag::Trace => debug!("[{} {}] {}", e.time, e.tag, e.data),
});
error!("[- event::end] {}", uuid_ref);
// First, we pre-process all the perf events to order them
let mut proc_perf: Vec<_> = self.perf.iter().map(|pe| pe.process()).collect();
@ -377,7 +389,8 @@ impl AuditScope {
// Now write the perf events
proc_perf
.iter()
.for_each(|pe| pe.int_write_fmt(0, &uuid_ref))
.for_each(|pe| pe.int_write_fmt(0, &uuid_ref));
error!("[- perf::end] {}", uuid_ref);
}
pub fn log_event(&mut self, tag: LogTag, data: String) {

View file

@ -14,8 +14,8 @@ use uuid::Uuid;
// use std::borrow::Borrow;
const DEFAULT_CACHE_TARGET: usize = 1024;
const DEFAULT_IDL_CACHE_RATIO: usize = 16;
const DEFAULT_CACHE_TARGET: usize = 10240;
const DEFAULT_IDL_CACHE_RATIO: usize = 32;
const DEFAULT_CACHE_RMISS: usize = 8;
const DEFAULT_CACHE_WMISS: usize = 8;

View file

@ -203,7 +203,7 @@ pub trait BackendTransaction {
// and terms.
let (f_andnot, f_rem): (Vec<_>, Vec<_>) = l.iter().partition(|f| f.is_andnot());
// We make this an iter, so everything comes off in order. Using pop means we
// We make this an iter, so everything comes off in order. if we used pop it means we
// pull from the tail, which is the WORST item to start with!
let mut f_rem_iter = f_rem.iter();
@ -216,6 +216,11 @@ pub trait BackendTransaction {
}
};
// Setup the counter of terms we have left to evaluate.
// This is used so that we shortcut return ONLY when we really do have
// more terms remaining.
let mut f_rem_count = f_rem.len() + f_andnot.len() - 1;
// Setup the query plan tracker
let mut plan = Vec::new();
plan.push(fp);
@ -225,20 +230,12 @@ pub trait BackendTransaction {
// When below thres, we have to return partials to trigger the entry_no_match_filter check.
// But we only do this when there are actually multiple elements in the and,
// because an and with 1 element now is FULLY resolved.
if idl.len() < thres && f_rem.len() > 0 {
lfilter_warning!(
au,
"NOTICE: Cand set shorter than threshold, early return"
);
if idl.len() < thres && f_rem_count > 0 {
let setplan = FilterPlan::AndPartialThreshold(plan);
return Ok((IDL::PartialThreshold(idl.clone()), setplan));
} else if idl.len() == 0 {
// Regardless of the input state, if it's empty, this can never
// be satisfied, so return we are indexed and complete.
lfilter_warning!(
au,
"NOTICE: empty candidate set, shortcutting return."
);
let setplan = FilterPlan::AndEmptyCand(plan);
return Ok((IDL::Indexed(IDLBitRange::new()), setplan));
}
@ -248,26 +245,19 @@ pub trait BackendTransaction {
// Now, for all remaining,
for f in f_rem_iter {
f_rem_count -= 1;
let (inter, fp) = self.filter2idl(au, f, thres)?;
plan.push(fp);
cand_idl = match (cand_idl, inter) {
(IDL::Indexed(ia), IDL::Indexed(ib)) => {
let r = ia & ib;
if r.len() < thres {
if r.len() < thres && f_rem_count > 0 {
// When below thres, we have to return partials to trigger the entry_no_match_filter check.
lfilter_warning!(
au,
"NOTICE: Cand set shorter than threshold, early return"
);
let setplan = FilterPlan::AndPartialThreshold(plan);
return Ok((IDL::PartialThreshold(r), setplan));
} else if r.len() == 0 {
// Regardless of the input state, if it's empty, this can never
// be satisfied, so return we are indexed and complete.
lfilter_warning!(
au,
"NOTICE: empty candidate set, shortcutting return."
);
let setplan = FilterPlan::AndEmptyCand(plan);
return Ok((IDL::Indexed(IDLBitRange::new()), setplan));
} else {
@ -278,12 +268,8 @@ pub trait BackendTransaction {
| (IDL::Partial(ia), IDL::Indexed(ib))
| (IDL::Partial(ia), IDL::Partial(ib)) => {
let r = ia & ib;
if r.len() < thres {
if r.len() < thres && f_rem_count > 0 {
// When below thres, we have to return partials to trigger the entry_no_match_filter check.
lfilter_warning!(
au,
"NOTICE: Cand set shorter than threshold, early return"
);
let setplan = FilterPlan::AndPartialThreshold(plan);
return Ok((IDL::PartialThreshold(r), setplan));
} else {
@ -296,12 +282,8 @@ pub trait BackendTransaction {
| (IDL::PartialThreshold(ia), IDL::Partial(ib))
| (IDL::Partial(ia), IDL::PartialThreshold(ib)) => {
let r = ia & ib;
if r.len() < thres {
if r.len() < thres && f_rem_count > 0 {
// When below thres, we have to return partials to trigger the entry_no_match_filter check.
lfilter_warning!(
au,
"NOTICE: Cand set shorter than threshold, early return"
);
let setplan = FilterPlan::AndPartialThreshold(plan);
return Ok((IDL::PartialThreshold(r), setplan));
} else {
@ -321,6 +303,7 @@ pub trait BackendTransaction {
// debug!("partial cand set ==> {:?}", cand_idl);
for f in f_andnot.iter() {
f_rem_count -= 1;
let f_in = match f {
FilterResolved::AndNot(f_in) => f_in,
_ => {
@ -341,7 +324,6 @@ pub trait BackendTransaction {
// Don't trigger threshold on and nots if fully indexed.
if r.len() < thres {
// When below thres, we have to return partials to trigger the entry_no_match_filter check.
lfilter_warning!(au, "NOTICE: Cand set shorter than threshold, early return");
return Ok(IDL::PartialThreshold(r));
} else {
IDL::Indexed(r)
@ -355,11 +337,7 @@ pub trait BackendTransaction {
let r = ia.andnot(ib);
// DO trigger threshold on partials, because we have to apply the filter
// test anyway, so we may as well shortcut at this point.
if r.len() < thres {
lfilter_warning!(
au,
"NOTICE: Cand set shorter than threshold, early return"
);
if r.len() < thres && f_rem_count > 0 {
let setplan = FilterPlan::AndPartialThreshold(plan);
return Ok((IDL::PartialThreshold(r), setplan));
} else {
@ -374,11 +352,7 @@ pub trait BackendTransaction {
let r = ia.andnot(ib);
// DO trigger threshold on partials, because we have to apply the filter
// test anyway, so we may as well shortcut at this point.
if r.len() < thres {
lfilter_warning!(
au,
"NOTICE: Cand set shorter than threshold, early return"
);
if r.len() < thres && f_rem_count > 0 {
let setplan = FilterPlan::AndPartialThreshold(plan);
return Ok((IDL::PartialThreshold(r), setplan));
} else {
@ -436,13 +410,12 @@ pub trait BackendTransaction {
au: &mut AuditScope,
filt: &Filter<FilterValidResolved>,
) -> Result<Vec<Entry<EntrySealed, EntryCommitted>>, OperationError> {
//
// Unlike DS, even if we don't get the index back, we can just pass
// to the in-memory filter test and be done.
lperf_segment!(au, "be::search", || {
// Do a final optimise of the filter
lfilter!(au, "filter unoptimised form --> {:?}", filt);
let filt = filt.optimise();
let filt = lperf_segment!(au, "be::search<filt::optimise>", || { filt.optimise() });
lfilter!(au, "filter optimised to --> {:?}", filt);
// Using the indexes, resolve the IDL here, or ALLIDS.
@ -466,21 +439,20 @@ pub trait BackendTransaction {
"filter (search) was partially or fully unindexed. {:?}",
filt
);
lperf_segment!(au, "be::search<entry::ftest::allids>", || {
entries
.into_iter()
.filter(|e| e.entry_match_no_index(&filt))
.collect()
})
}
IDL::PartialThreshold(_) => {
lfilter_warning!(
au,
"filter (search) was partial unindexed due to test threshold {:?}",
filt
);
lperf_segment!(au, "be::search<entry::ftest::thresh>", || {
entries
.into_iter()
.filter(|e| e.entry_match_no_index(&filt))
.collect()
})
}
// Since the index fully resolved, we can shortcut the filter test step here!
IDL::Indexed(_) => {
@ -537,16 +509,8 @@ pub trait BackendTransaction {
// Now, check the idl -- if it's fully resolved, we can skip this because the query
// was fully indexed.
match &idl {
IDL::Indexed(idl) => {
lfilter!(au, "filter (exists) was fully indexed 👏");
Ok(idl.len() > 0)
}
IDL::Indexed(idl) => Ok(idl.len() > 0),
IDL::PartialThreshold(_) => {
lfilter_warning!(
au,
"filter (exists) was partial unindexed due to test threshold {:?}",
filt
);
let entries = try_audit!(au, self.get_idlayer().get_identry(au, &idl));
// if not 100% resolved query, apply the filter test.
@ -894,10 +858,14 @@ impl<'a> BackendWriteTransaction<'a> {
audit: &mut AuditScope,
v: i64,
) -> Result<(), OperationError> {
if self.get_db_index_version() < v {
let dbv = self.get_db_index_version();
ladmin_info!(audit, "upgrade_reindex -> dbv: {} v: {}", dbv, v);
if dbv < v {
self.reindex(audit)?;
}
self.set_db_index_version(v)
} else {
Ok(())
}
}
pub fn reindex(&mut self, audit: &mut AuditScope) -> Result<(), OperationError> {
@ -1125,16 +1093,17 @@ impl Backend {
}
}
pub fn write(&self, idxmeta: BTreeSet<(String, IndexType)>) -> BackendWriteTransaction {
pub fn write(&self, idxmeta: &BTreeSet<(String, IndexType)>) -> BackendWriteTransaction {
BackendWriteTransaction {
idlayer: self.idlayer.write(),
idxmeta,
// TODO: Performance improvement here by NOT cloning the idxmeta.
idxmeta: (*idxmeta).clone(),
}
}
// Should this actually call the idlayer directly?
pub fn reset_db_s_uuid(&self, audit: &mut AuditScope) -> Uuid {
let wr = self.write(BTreeSet::new());
let wr = self.write(&BTreeSet::new());
let sid = wr.reset_db_s_uuid().unwrap();
wr.commit(audit).unwrap();
sid
@ -1186,7 +1155,7 @@ mod tests {
idxmeta.insert(("uuid".to_string(), IndexType::PRESENCE));
idxmeta.insert(("ta".to_string(), IndexType::EQUALITY));
idxmeta.insert(("tb".to_string(), IndexType::EQUALITY));
let mut be_txn = be.write(idxmeta);
let mut be_txn = be.write(&idxmeta);
// Could wrap another future here for the future::ok bit...
let r = $test_fn(&mut audit, &mut be_txn);

View file

@ -17,6 +17,7 @@ pub struct TlsConfiguration {
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Configuration {
pub address: String,
pub ldapaddress: Option<String>,
pub threads: usize,
// db type later
pub db_path: String,
@ -30,6 +31,10 @@ pub struct Configuration {
impl fmt::Display for Configuration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "address: {}, ", self.address)
.and_then(|_| match &self.ldapaddress {
Some(la) => write!(f, "ldap address: {}, ", la),
None => write!(f, "ldap address: disabled, "),
})
.and_then(|_| write!(f, "thread count: {}, ", self.threads))
.and_then(|_| write!(f, "dbpath: {}, ", self.db_path))
.and_then(|_| write!(f, "max request size: {}b, ", self.maximum_request))
@ -49,6 +54,7 @@ impl Configuration {
pub fn new() -> Self {
let mut c = Configuration {
address: String::from("127.0.0.1:8080"),
ldapaddress: None,
threads: num_cpus::get(),
db_path: String::from(""),
maximum_request: 262_144, // 256k
@ -82,6 +88,10 @@ impl Configuration {
.unwrap_or_else(|| String::from("127.0.0.1:8080"));
}
pub fn update_ldapbind(&mut self, l: &Option<String>) {
self.ldapaddress = l.clone();
}
pub fn update_tls(
&mut self,
ca: &Option<PathBuf>,

View file

@ -105,7 +105,7 @@ pub const UUID_SCHEMA_ATTR_PASSWORD_IMPORT: &str = "00000000-0000-0000-0000-ffff
// System and domain infos
// I'd like to strongly criticise william of the past for fucking up these allocations.
pub const STR_UUID_SYSTEM_INFO: &str = "00000000-0000-0000-0000-ffffff000001";
pub const UUID_DOMAIN_INFO: &str = "00000000-0000-0000-0000-ffffff000025";
pub const STR_UUID_DOMAIN_INFO: &str = "00000000-0000-0000-0000-ffffff000025";
// DO NOT allocate here, allocate below.
// Access controls
@ -152,4 +152,5 @@ lazy_static! {
pub static ref UUID_ANONYMOUS: Uuid = Uuid::parse_str(STR_UUID_ANONYMOUS).unwrap();
pub static ref UUID_SYSTEM_CONFIG: Uuid = Uuid::parse_str(STR_UUID_SYSTEM_CONFIG).unwrap();
pub static ref UUID_SYSTEM_INFO: Uuid = Uuid::parse_str(STR_UUID_SYSTEM_INFO).unwrap();
pub static ref UUID_DOMAIN_INFO: Uuid = Uuid::parse_str(STR_UUID_DOMAIN_INFO).unwrap();
}

View file

@ -0,0 +1,236 @@
use crate::actors::v1_read::{LdapRequestMessage, QueryServerReadV1};
use crate::ldap::{LdapBoundToken, LdapResponseState};
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder};
use actix::prelude::*;
use futures_util::stream::StreamExt;
use ldap3_server::simple::*;
use ldap3_server::LdapCodec;
// use std::convert::TryFrom;
use std::io;
use std::marker::Unpin;
use std::net;
use std::str::FromStr;
use tokio::io::{AsyncWrite, WriteHalf};
use tokio::net::{TcpListener, TcpStream};
use tokio_util::codec::FramedRead;
use uuid::Uuid;
struct LdapReq(pub LdapMsg);
impl Message for LdapReq {
type Result = Result<(), ()>;
}
pub struct LdapServer {
qe_r: Addr<QueryServerReadV1>,
}
pub struct LdapSession<T>
where
T: AsyncWrite + Unpin,
{
qe_r: Addr<QueryServerReadV1>,
framed: actix::io::FramedWrite<WriteHalf<T>, LdapCodec>,
uat: Option<LdapBoundToken>,
}
impl<T> Actor for LdapSession<T>
where
T: 'static + AsyncWrite + Unpin,
{
type Context = actix::Context<Self>;
}
impl<T> actix::io::WriteHandler<io::Error> for LdapSession<T> where T: 'static + AsyncWrite + Unpin {}
impl<T> Handler<LdapReq> for LdapSession<T>
where
T: 'static + AsyncWrite + Unpin,
{
type Result = ResponseActFuture<Self, Result<(), ()>>;
fn handle(&mut self, msg: LdapReq, _ctx: &mut Self::Context) -> Self::Result {
let protomsg = msg.0;
// Transform the LdapMsg to something the query server can work with.
// Because of the way these futures works, it's up to the qe_r to manage
// a lot of this, so we just palm off the processing to the thead pool.
let eventid = Uuid::new_v4();
let uat = self.uat.clone();
let qsf = self.qe_r.send(LdapRequestMessage {
eventid,
protomsg,
uat,
});
let qsf = actix::fut::wrap_future::<_, Self>(qsf);
let f = qsf.map(|result, actor, ctx| {
match result {
Ok(Some(LdapResponseState::Unbind)) => ctx.stop(),
Ok(Some(LdapResponseState::Disconnect(r))) => {
actor.framed.write(r);
ctx.stop()
}
Ok(Some(LdapResponseState::Bind(uat, r))) => {
actor.uat = Some(uat);
actor.framed.write(r);
}
Ok(Some(LdapResponseState::Respond(r))) => {
actor.framed.write(r);
}
Ok(Some(LdapResponseState::MultiPartResponse(v))) => {
v.into_iter().for_each(|r| actor.framed.write(r));
}
Ok(Some(LdapResponseState::BindMultiPartResponse(uat, v))) => {
actor.uat = Some(uat);
v.into_iter().for_each(|r| actor.framed.write(r));
}
Ok(None) | Err(_) => {
error!("Internal server error");
ctx.stop();
}
};
Ok(())
});
Box::new(f)
}
}
impl<T> StreamHandler<Result<LdapMsg, io::Error>> for LdapSession<T>
where
T: 'static + AsyncWrite + Unpin,
{
fn handle(&mut self, msg: Result<LdapMsg, io::Error>, ctx: &mut Self::Context) {
match msg {
Ok(lm) => match ctx.address().try_send(LdapReq(lm)) {
// It's queued, we are done.
Ok(_) => {}
Err(_) => {
error!("Too many queue msgs for connection");
ctx.stop()
}
},
Err(_) => {
error!("Io error");
ctx.stop()
}
}
}
}
impl<T> LdapSession<T>
where
T: 'static + AsyncWrite + Unpin,
{
pub fn new(
framed: actix::io::FramedWrite<WriteHalf<T>, LdapCodec>,
qe_r: Addr<QueryServerReadV1>,
) -> Self {
LdapSession {
qe_r,
framed,
uat: None,
}
}
}
impl Actor for LdapServer {
type Context = Context<Self>;
}
#[derive(Message)]
#[rtype(result = "()")]
struct TcpConnect(pub TcpStream, pub net::SocketAddr);
impl Handler<TcpConnect> for LdapServer {
type Result = ();
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) {
LdapSession::create(move |ctx| {
let (r, w) = tokio::io::split(msg.0);
LdapSession::add_stream(FramedRead::new(r, LdapCodec), ctx);
LdapSession::new(
actix::io::FramedWrite::new(w, LdapCodec, ctx),
self.qe_r.clone(),
)
});
}
}
#[derive(Message)]
#[rtype(result = "Result<(), ()>")]
struct TlsConnect(pub &'static SslAcceptor, pub TcpStream, pub net::SocketAddr);
impl Handler<TlsConnect> for LdapServer {
type Result = ResponseActFuture<Self, Result<(), ()>>;
fn handle(&mut self, msg: TlsConnect, _: &mut Context<Self>) -> Self::Result {
let qsf = tokio_openssl::accept(msg.0, msg.1);
let qsf = actix::fut::wrap_future::<_, Self>(qsf);
let f = qsf.map(|result, actor, _ctx| {
result
.map(|tlsstream| {
LdapSession::create(move |ctx| {
let (r, w) = tokio::io::split(tlsstream);
LdapSession::add_stream(FramedRead::new(r, LdapCodec), ctx);
LdapSession::new(
actix::io::FramedWrite::new(w, LdapCodec, ctx),
actor.qe_r.clone(),
)
});
()
})
.map_err(|_| {
error!("invalid tls handshake");
()
})
});
Box::new(f)
}
}
pub(crate) async fn create_ldap_server(
address: &str,
opt_tls_params: Option<SslAcceptorBuilder>,
qe_r: Addr<QueryServerReadV1>,
) -> Result<(), ()> {
let addr = net::SocketAddr::from_str(address).map_err(|e| {
error!("Could not parse ldap server address {} -> {:?}", address, e);
()
})?;
let listener = Box::new(TcpListener::bind(&addr).await.unwrap());
match opt_tls_params {
Some(tls_params) => {
info!("Starting LDAPS interface ldaps://{} ...", address);
LdapServer::create(move |ctx| {
let acceptor = Box::new(tls_params.build());
let lacceptor = Box::leak(acceptor) as &'static _;
ctx.add_message_stream(Box::leak(listener).incoming().map(move |st| {
let st = st.unwrap();
let addr = st.peer_addr().unwrap();
TlsConnect(lacceptor, st, addr)
}));
LdapServer { qe_r }
});
}
None => {
info!("Starting LDAP interface ldap://{} ...", address);
LdapServer::create(move |ctx| {
ctx.add_message_stream(Box::leak(listener).incoming().map(|st| {
let st = st.unwrap();
let addr = st.peer_addr().unwrap();
TcpConnect(st, addr)
}));
LdapServer { qe_r }
});
}
}
info!("Created LDAP interface");
Ok(())
}

View file

@ -1,4 +1,5 @@
mod ctx;
mod ldaps;
// use actix_files as fs;
use actix::prelude::*;
use actix_session::{CookieSession, Session};
@ -36,6 +37,7 @@ use crate::crypto::setup_tls;
use crate::filter::{Filter, FilterInvalid};
use crate::idm::server::IdmServer;
use crate::interval::IntervalActor;
use crate::ldap::LdapServer;
use crate::schema::Schema;
use crate::schema::SchemaTransaction;
use crate::server::QueryServer;
@ -1319,9 +1321,12 @@ pub fn restore_server_core(config: Configuration, dst_path: &str) {
};
// Limit the scope of the schema txn.
let idxmeta = { schema.write().get_idxmeta_set() };
let mut be_wr_txn = be.write(idxmeta);
let mut be_wr_txn = {
let schema_txn = schema.write();
let idxmeta = schema_txn.get_idxmeta_set();
be.write(idxmeta)
};
let r = be_wr_txn
.restore(&mut audit, dst_path)
.and_then(|_| be_wr_txn.commit(&mut audit));
@ -1384,11 +1389,13 @@ pub fn reindex_server_core(config: Configuration) {
};
info!("Start Index Phase 1 ...");
// Limit the scope of the schema txn.
let idxmeta = { schema.write().get_idxmeta_set() };
// Reindex only the core schema attributes to bootstrap the process.
let mut be_wr_txn = be.write(idxmeta);
let mut be_wr_txn = {
// Limit the scope of the schema txn.
let schema_txn = schema.write();
let idxmeta = schema_txn.get_idxmeta_set();
be.write(idxmeta)
};
let r = be_wr_txn
.reindex(&mut audit)
.and_then(|_| be_wr_txn.commit(&mut audit));
@ -1562,7 +1569,7 @@ pub fn recover_account_core(config: Configuration, name: String, password: Strin
};
}
pub fn create_server_core(config: Configuration) -> Result<ServerCtx, ()> {
pub async fn create_server_core(config: Configuration) -> Result<ServerCtx, ()> {
// Until this point, we probably want to write to the log macro fns.
if config.integration_test_config.is_some() {
@ -1611,6 +1618,7 @@ pub fn create_server_core(config: Configuration) -> Result<ServerCtx, ()> {
return Err(());
}
};
// Any pre-start tasks here.
match &config.integration_test_config {
Some(itc) => {
@ -1641,23 +1649,57 @@ pub fn create_server_core(config: Configuration) -> Result<ServerCtx, ()> {
}
None => {}
}
let ldap = match LdapServer::new(&mut audit, &idms) {
Ok(l) => l,
Err(e) => {
audit.write_log();
error!("Unable to start LdapServer -> {:?}", e);
return Err(());
}
};
log_tx.send(Some(audit)).unwrap_or_else(|_| {
error!("CRITICAL: UNABLE TO COMMIT LOGS");
});
// Arc the idms.
// Arc the idms and ldap
let idms_arc = Arc::new(idms);
let ldap_arc = Arc::new(ldap);
// Pass it to the actor for threading.
// Start the read query server with the given be path: future config
let server_read_addr =
QueryServerReadV1::start(log_tx.clone(), qs.clone(), idms_arc.clone(), config.threads);
let server_read_addr = QueryServerReadV1::start(
log_tx.clone(),
qs.clone(),
idms_arc.clone(),
ldap_arc.clone(),
config.threads,
);
// Start the write thread
let server_write_addr = QueryServerWriteV1::start(log_tx.clone(), qs, idms_arc);
// Setup timed events associated to the write thread
let _int_addr = IntervalActor::new(server_write_addr.clone()).start();
// If we have been requested to init LDAP, configure it now.
match &config.ldapaddress {
Some(la) => {
let opt_ldap_tls_params = match setup_tls(&config) {
Ok(t) => t,
Err(e) => {
error!("Failed to configure LDAP TLS parameters -> {:?}", e);
return Err(());
}
};
ldaps::create_ldap_server(la.as_str(), opt_ldap_tls_params, server_read_addr.clone())
.await?;
}
None => {
debug!("LDAP not requested, skipping");
}
}
// Copy the max size
let secure_cookies = config.secure_cookies;
// domain will come from the qs now!
@ -1695,7 +1737,8 @@ pub fn create_server_core(config: Configuration) -> Result<ServerCtx, ()> {
// .app_data(web::Json::<CreateRequest>::configure(|cfg| { cfg
.app_data(
web::JsonConfig::default()
.limit(4096)
// Currently 4MB
.limit(4194304)
.error_handler(|err, _req| {
let s = format!("{}", err);
error::InternalError::from_response(err, HttpResponse::BadRequest().json(s))
@ -1872,8 +1915,8 @@ pub fn create_server_core(config: Configuration) -> Result<ServerCtx, ()> {
server.bind(config.address)
}
};
server.expect("Failed to initialise server!").run();
info!("ready to rock! 🤘");
Ok(ServerCtx::new(System::current(), log_tx, log_thread))

View file

@ -44,6 +44,7 @@ use kanidm_proto::v1::{OperationError, SchemaError};
use crate::be::dbentry::{DbEntry, DbEntryV1, DbEntryVers};
use ldap3_server::simple::{LdapPartialAttribute, LdapSearchResultEntry};
use std::collections::btree_map::Iter as BTreeIter;
use std::collections::btree_set::Iter as BTreeSetIter;
use std::collections::BTreeMap;
@ -1332,6 +1333,47 @@ impl Entry<EntryReduced, EntryCommitted> {
.collect();
Ok(ProtoEntry { attrs: attrs? })
}
pub fn to_ldap(
&self,
audit: &mut AuditScope,
qs: &mut QueryServerReadTransaction,
basedn: &str,
) -> Result<LdapSearchResultEntry, OperationError> {
let (attr, rdn) = self
.get_ava_single("spn")
.map(|v| ("spn", v.to_proto_string_clone()))
.or_else(|| {
self.get_ava_single("name")
.map(|v| ("name", v.to_proto_string_clone()))
})
.unwrap_or_else(|| ("uuid", self.get_uuid().to_hyphenated_ref().to_string()));
let dn = format!("{}={},{}", attr, rdn, basedn);
let attributes: Result<Vec<_>, _> = self
.attrs
.iter()
.map(|(k, vs)| {
let pvs: Result<Vec<String>, _> =
vs.iter().map(|v| qs.resolve_value(audit, v)).collect();
let pvs = pvs?;
let pvs = if k == "memberof" || k == "member" {
pvs.into_iter()
.map(|s| format!("spn={},{}", s, basedn))
.collect()
} else {
pvs
};
Ok(LdapPartialAttribute {
atype: k.clone(),
vals: pvs,
})
})
.collect();
let attributes = attributes?;
Ok(LdapSearchResultEntry { dn, attributes })
}
}
// impl<STATE> Entry<EntryValid, STATE> {

View file

@ -105,18 +105,9 @@ impl Event {
pub fn from_ro_request(
audit: &mut AuditScope,
qs: &mut QueryServerReadTransaction,
user_uuid: &str,
user_uuid: &Uuid,
) -> Result<Self, OperationError> {
// Do we need to check or load the entry from the user_uuid?
// In the future, probably yes.
//
// For now, no.
let u = try_audit!(
audit,
Uuid::parse_str(user_uuid).map_err(|_| OperationError::InvalidUuid)
);
let e = try_audit!(audit, qs.internal_search_uuid(audit, &u));
let e = try_audit!(audit, qs.internal_search_uuid(audit, &user_uuid));
Ok(Event {
origin: EventOrigin::User(e),
@ -438,6 +429,27 @@ impl SearchEvent {
}
}
pub(crate) fn new_ext_impersonate_uuid(
audit: &mut AuditScope,
qs: &mut QueryServerReadTransaction,
euuid: &Uuid,
filter: Filter<FilterInvalid>,
attrs: Option<BTreeSet<String>>,
) -> Result<Self, OperationError> {
Ok(SearchEvent {
event: Event::from_ro_request(audit, qs, euuid)?,
filter: filter
.clone()
.into_ignore_hidden()
.validate(qs.get_schema())
.map_err(OperationError::SchemaViolation)?,
filter_orig: filter
.validate(qs.get_schema())
.map_err(OperationError::SchemaViolation)?,
attrs,
})
}
#[cfg(test)]
pub unsafe fn new_internal_invalid(filter: Filter<FilterInvalid>) -> Self {
SearchEvent {

View file

@ -17,6 +17,7 @@ use crate::server::{
use crate::value::{IndexType, PartialValue};
use kanidm_proto::v1::Filter as ProtoFilter;
use kanidm_proto::v1::{OperationError, SchemaError};
use ldap3_server::simple::LdapFilter;
use std::cmp::{Ordering, PartialOrd};
use std::collections::BTreeSet;
use std::iter;
@ -419,11 +420,13 @@ impl Filter<FilterInvalid> {
f: &ProtoFilter,
qs: &mut QueryServerReadTransaction,
) -> Result<Self, OperationError> {
lperf_segment!(audit, "filter::from_ro", || {
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_ro(audit, f, qs)?,
},
})
})
}
pub fn from_rw(
@ -431,11 +434,27 @@ impl Filter<FilterInvalid> {
f: &ProtoFilter,
qs: &mut QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
lperf_segment!(audit, "filter::from_rw", || {
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_rw(audit, f, qs)?,
},
})
})
}
pub fn from_ldap_ro(
audit: &mut AuditScope,
f: &LdapFilter,
qs: &mut QueryServerReadTransaction,
) -> Result<Self, OperationError> {
lperf_segment!(audit, "filter::from_ldap_ro", || {
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_ldap_ro(audit, f, qs)?,
},
})
})
}
}
@ -654,6 +673,30 @@ impl FilterComp {
ProtoFilter::SelfUUID => FilterComp::SelfUUID,
})
}
fn from_ldap_ro(
audit: &mut AuditScope,
f: &LdapFilter,
qs: &mut QueryServerReadTransaction,
) -> Result<Self, OperationError> {
Ok(match f {
LdapFilter::And(l) => FilterComp::And(
l.iter()
.map(|f| Self::from_ldap_ro(audit, f, qs))
.collect::<Result<Vec<_>, _>>()?,
),
LdapFilter::Or(l) => FilterComp::Or(
l.iter()
.map(|f| Self::from_ldap_ro(audit, f, qs))
.collect::<Result<Vec<_>, _>>()?,
),
LdapFilter::Not(l) => FilterComp::AndNot(Box::new(Self::from_ldap_ro(audit, l, qs)?)),
LdapFilter::Equality(a, v) => {
FilterComp::Eq(a.clone(), qs.clone_partialvalue(audit, a, v)?)
}
LdapFilter::Present(a) => FilterComp::Pres(a.clone()),
})
}
}
/* We only configure partial eq if cfg test on the invalid/valid types */

View file

@ -130,6 +130,7 @@ impl Account {
Some(UserAuthToken {
name: self.name.clone(),
spn: self.spn.clone(),
displayname: self.name.clone(),
uuid: self.uuid.to_hyphenated_ref().to_string(),
application: None,

View file

@ -332,3 +332,39 @@ impl VerifyTOTPEvent {
}
}
}
#[derive(Debug)]
pub struct LdapAuthEvent {
// pub event: Event,
pub target: Uuid,
pub cleartext: String,
}
impl LdapAuthEvent {
/*
#[cfg(test)]
pub fn new_internal(target: &Uuid, cleartext: &str) -> Self {
LdapAuthEvent {
// event: Event::from_internal(),
target: *target,
cleartext: cleartext.to_string(),
}
}
*/
pub fn from_parts(
_audit: &mut AuditScope,
// qs: &mut QueryServerReadTransaction,
// uat: Option<UserAuthToken>,
target: Uuid,
cleartext: String,
) -> Result<Self, OperationError> {
// let e = Event::from_ro_uat(audit, qs, uat)?;
Ok(LdapAuthEvent {
// event: e,
target,
cleartext,
})
}
}

View file

@ -1,17 +1,18 @@
use crate::audit::AuditScope;
use crate::constants::UUID_SYSTEM_CONFIG;
use crate::constants::{AUTH_SESSION_TIMEOUT, MFAREG_SESSION_TIMEOUT, PW_MIN_LENGTH};
use crate::constants::{UUID_ANONYMOUS, UUID_SYSTEM_CONFIG};
use crate::event::{AuthEvent, AuthEventStep, AuthResult};
use crate::idm::account::Account;
use crate::idm::authsession::AuthSession;
use crate::idm::event::{
GeneratePasswordEvent, GenerateTOTPEvent, PasswordChangeEvent, RadiusAuthTokenEvent,
RegenerateRadiusSecretEvent, UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent,
UnixUserTokenEvent, VerifyTOTPEvent,
GeneratePasswordEvent, GenerateTOTPEvent, LdapAuthEvent, PasswordChangeEvent,
RadiusAuthTokenEvent, RegenerateRadiusSecretEvent, UnixGroupTokenEvent,
UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent, VerifyTOTPEvent,
};
use crate::idm::mfareg::{MfaRegCred, MfaRegNext, MfaRegSession, MfaReqInit, MfaReqStep};
use crate::idm::radius::RadiusAccount;
use crate::idm::unix::{UnixGroup, UnixUserAccount};
use crate::ldap::LdapBoundToken;
use crate::server::QueryServerReadTransaction;
use crate::server::{QueryServer, QueryServerTransaction, QueryServerWriteTransaction};
use crate::utils::{password_from_random, readable_password_from_random, uuid_from_duration, SID};
@ -246,6 +247,43 @@ impl<'a> IdmServerWriteTransaction<'a> {
account.verify_unix_credential(au, uae.cleartext.as_str())
}
pub fn auth_ldap(
&mut self,
au: &mut AuditScope,
lae: LdapAuthEvent,
_ct: Duration,
) -> Result<Option<LdapBoundToken>, OperationError> {
// TODO #59: Implement soft lock checking for unix creds here!
let account_entry = try_audit!(au, self.qs_read.internal_search_uuid(au, &lae.target));
/* !!! This would probably be better if we DIDN'T use the Unix/Account types ... ? */
// if anonymous
if lae.target == *UUID_ANONYMOUS {
// TODO: #59 We should have checked if anonymous was locked by now!
let account = Account::try_from_entry_ro(au, account_entry, &mut self.qs_read)?;
Ok(Some(LdapBoundToken {
spn: account.spn.clone(),
uuid: UUID_ANONYMOUS.clone(),
effective_uuid: UUID_ANONYMOUS.clone(),
}))
} else {
let account = UnixUserAccount::try_from_entry_ro(au, account_entry, &mut self.qs_read)?;
if account
.verify_unix_credential(au, lae.cleartext.as_str())?
.is_some()
{
Ok(Some(LdapBoundToken {
spn: account.spn.clone(),
uuid: account.uuid.clone(),
effective_uuid: UUID_ANONYMOUS.clone(),
}))
} else {
Ok(None)
}
}
}
pub fn commit(self, au: &mut AuditScope) -> Result<(), OperationError> {
lperf_segment!(au, "idm::server::IdmServerWriteTransaction::commit", || {
self.sessions.commit();

394
kanidmd/src/lib/ldap.rs Normal file
View file

@ -0,0 +1,394 @@
use crate::audit::AuditScope;
use crate::constants::{STR_UUID_DOMAIN_INFO, UUID_ANONYMOUS, UUID_DOMAIN_INFO};
use crate::event::SearchEvent;
use crate::filter::Filter;
use crate::idm::event::LdapAuthEvent;
use crate::idm::server::IdmServer;
use crate::server::QueryServerTransaction;
use kanidm_proto::v1::OperationError;
use ldap3_server::simple::*;
use std::collections::BTreeSet;
use std::iter;
use std::time::SystemTime;
use uuid::Uuid;
use regex::Regex;
pub enum LdapResponseState {
Unbind,
Disconnect(LdapMsg),
Bind(LdapBoundToken, LdapMsg),
Respond(LdapMsg),
MultiPartResponse(Vec<LdapMsg>),
BindMultiPartResponse(LdapBoundToken, Vec<LdapMsg>),
}
#[derive(Debug, Clone)]
pub struct LdapBoundToken {
pub spn: String,
pub uuid: Uuid,
// For now, always anonymous
pub effective_uuid: Uuid,
}
pub struct LdapServer {
rootdse: LdapSearchResultEntry,
basedn: String,
dnre: Regex,
}
impl LdapServer {
pub fn new(au: &mut AuditScope, idms: &IdmServer) -> Result<Self, OperationError> {
let mut idms_prox_read = idms.proxy_read();
// This is the rootdse path.
// get the domain_info item
let domain_entry = idms_prox_read
.qs_read
.internal_search_uuid(au, &UUID_DOMAIN_INFO)?;
let domain_name = domain_entry
.get_ava_single_string("domain_name")
.ok_or(OperationError::InvalidEntryState)?;
let basedn = ldap_domain_to_dc(domain_name.as_str());
let dnre = Regex::new(format!("^((?P<attr>[^=]+)=(?P<val>[^=]+),)?{}$", basedn).as_str())
.map_err(|_| OperationError::InvalidEntryState)?;
let rootdse = LdapSearchResultEntry {
dn: "".to_string(),
attributes: vec![
LdapPartialAttribute {
atype: "objectClass".to_string(),
vals: vec!["top".to_string()],
},
LdapPartialAttribute {
atype: "vendorName".to_string(),
vals: vec!["Kanidm Project".to_string()],
},
LdapPartialAttribute {
atype: "vendorVersion".to_string(),
vals: vec!["kanidm_ldap_1.0.0".to_string()],
},
LdapPartialAttribute {
atype: "supportedLDAPVersion".to_string(),
vals: vec!["3".to_string()],
},
LdapPartialAttribute {
atype: "supportedExtension".to_string(),
vals: vec!["1.3.6.1.4.1.4203.1.11.3".to_string()],
},
LdapPartialAttribute {
atype: "defaultnamingcontext".to_string(),
vals: vec![basedn.clone()],
},
],
};
Ok(LdapServer {
basedn,
rootdse,
dnre,
})
}
fn do_search(
&self,
au: &mut AuditScope,
idms: &IdmServer,
sr: &SearchRequest,
uat: &LdapBoundToken,
// eventid: &Uuid,
) -> Result<Vec<LdapMsg>, OperationError> {
// If the request is "", Base, Present("objectclass"), [], then we want the rootdse.
if sr.base == "" && sr.scope == LdapSearchScope::Base {
Ok(vec![
sr.gen_result_entry(self.rootdse.clone()),
sr.gen_success(),
])
} else {
// We want something else apparently. Need to do some more work ...
// Parse the operation and make sure it's sane before we start the txn.
// This scoping returns an extra filter component.
let (opt_attr, opt_value) = match self.dnre.captures(sr.base.as_str()) {
Some(caps) => (
caps.name("attr").map(|v| v.as_str().to_string()),
caps.name("val").map(|v| v.as_str().to_string()),
),
None => {
return Err(OperationError::InvalidRequestState);
}
};
let req_dn = match (opt_attr, opt_value) {
(Some(a), Some(v)) => Some((a, v)),
(None, None) => None,
_ => {
return Err(OperationError::InvalidRequestState);
}
};
ltrace!(au, "RDN -> {:?}", req_dn);
// Map the Some(a,v) to ...?
let ext_filter = match (&sr.scope, req_dn) {
(LdapSearchScope::OneLevel, Some(_r)) => return Ok(vec![sr.gen_success()]),
(LdapSearchScope::OneLevel, None) => {
// exclude domain_info
Some(LdapFilter::Not(Box::new(LdapFilter::Equality(
"uuid".to_string(),
STR_UUID_DOMAIN_INFO.to_string(),
))))
}
(LdapSearchScope::Base, Some((a, v))) => Some(LdapFilter::Equality(a, v)),
(LdapSearchScope::Base, None) => {
// domain_info
Some(LdapFilter::Equality(
"uuid".to_string(),
STR_UUID_DOMAIN_INFO.to_string(),
))
}
(LdapSearchScope::Subtree, Some((a, v))) => Some(LdapFilter::Equality(a, v)),
(LdapSearchScope::Subtree, None) => {
// No filter changes needed.
None
}
};
// TODO #67: limit the number of attributes here!
let attrs = if sr.attrs.len() == 0 {
// If [], then "all" attrs
None
} else {
let mut all_attrs = false;
let attrs: BTreeSet<_> = sr
.attrs
.iter()
.filter_map(|a| {
if a == "*" {
// if *, then all
all_attrs = true;
None
} else if a == "+" {
// if +, then ignore (kanidm doesn't have operational) this part.
None
} else {
// if list, add to the search
Some(a.clone())
}
})
.collect();
if all_attrs {
None
} else {
Some(attrs)
}
};
ltrace!(au, "Attrs -> {:?}", attrs);
lperf_segment!(au, "ldap::do_search<core>", || {
// Now start the txn - we need it for resolving filter components.
let mut idm_read = idms.proxy_read();
// join the filter, with ext_filter
let lfilter = match ext_filter {
Some(ext) => LdapFilter::And(vec![
sr.filter.clone(),
ext,
LdapFilter::Not(Box::new(LdapFilter::Or(vec![
LdapFilter::Equality("class".to_string(), "classtype".to_string()),
LdapFilter::Equality("class".to_string(), "attributetype".to_string()),
LdapFilter::Equality(
"class".to_string(),
"access_control_profile".to_string(),
),
]))),
]),
None => LdapFilter::And(vec![
sr.filter.clone(),
LdapFilter::Not(Box::new(LdapFilter::Or(vec![
LdapFilter::Equality("class".to_string(), "classtype".to_string()),
LdapFilter::Equality("class".to_string(), "attributetype".to_string()),
LdapFilter::Equality(
"class".to_string(),
"access_control_profile".to_string(),
),
]))),
]),
};
ltrace!(au, "ldapfilter -> {:?}", lfilter);
// Kanidm Filter from LdapFilter
let filter =
Filter::from_ldap_ro(au, &lfilter, &mut idm_read.qs_read).map_err(|e| {
lrequest_error!(au, "invalid ldap filter {:?}", e);
e
})?;
// Build the event, with the permissions from effective_uuid
// (should always be anonymous at the moment)
// ! Remember, searchEvent wraps to ignore hidden for us.
let se = lperf_segment!(au, "ldap::do_search<core><prepare_se>", || {
SearchEvent::new_ext_impersonate_uuid(
au,
&mut idm_read.qs_read,
&uat.effective_uuid,
filter,
attrs,
)
})?;
let res = idm_read.qs_read.search_ext(au, &se).map_err(|e| {
ladmin_error!(au, "search failure {:?}", e);
e
})?;
// These have already been fully reduced, so we can just slap it into the result.
let lres = lperf_segment!(au, "ldap::do_search<core><prepare results>", || {
let lres: Result<Vec<_>, _> = res
.into_iter()
.map(|e| {
e.to_ldap(au, &mut idm_read.qs_read, self.basedn.as_str())
// if okay, wrap in a ldap msg.
.map(|r| sr.gen_result_entry(r))
})
.chain(iter::once(Ok(sr.gen_success())))
.collect();
lres
});
let lres = lres.map_err(|e| {
ladmin_error!(au, "entry resolve failure {:?}", e);
e
})?;
Ok(lres)
})
}
}
fn do_bind(
&self,
au: &mut AuditScope,
idms: &IdmServer,
dn: &str,
pw: &str,
) -> Result<Option<LdapBoundToken>, OperationError> {
let mut idm_write = idms.write();
let target_uuid: Uuid = if dn == "" && pw == "" {
UUID_ANONYMOUS.clone()
} else {
idm_write.qs_read.name_to_uuid(au, dn).map_err(|e| {
ladmin_info!(au, "Error resolving id to target {:?}", e);
e
})?
};
let ct = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Clock failure!");
let lae = LdapAuthEvent::from_parts(au, target_uuid, pw.to_string())?;
idm_write
.auth_ldap(au, lae, ct)
.and_then(|r| idm_write.commit(au).map(|_| r))
}
pub fn do_op(
&self,
au: &mut AuditScope,
idms: &IdmServer,
server_op: ServerOps,
uat: Option<LdapBoundToken>,
eventid: &Uuid,
) -> Result<LdapResponseState, OperationError> {
match server_op {
ServerOps::SimpleBind(sbr) => self
.do_bind(au, idms, sbr.dn.as_str(), sbr.pw.as_str())
.map(|r| match r {
Some(lbt) => LdapResponseState::Bind(lbt, sbr.gen_success()),
None => LdapResponseState::Respond(sbr.gen_invalid_cred()),
})
.or_else(|e| {
let (rc, msg) = operationerr_to_ldapresultcode(e);
Ok(LdapResponseState::Respond(sbr.gen_error(rc, msg)))
}),
ServerOps::Search(sr) => match uat {
Some(u) => self
.do_search(au, idms, &sr, &u)
.map(LdapResponseState::MultiPartResponse)
.or_else(|e| {
let (rc, msg) = operationerr_to_ldapresultcode(e);
Ok(LdapResponseState::Respond(sr.gen_error(rc, msg)))
}),
None => {
// Search can occur without a bind, so bind first.
let lbt = match self.do_bind(au, idms, "", "") {
Ok(Some(lbt)) => lbt,
Ok(None) => {
return Ok(LdapResponseState::Respond(
sr.gen_error(LdapResultCode::InvalidCredentials, "".to_string()),
))
}
Err(e) => {
let (rc, msg) = operationerr_to_ldapresultcode(e);
return Ok(LdapResponseState::Respond(sr.gen_error(rc, msg)));
}
};
// If okay, do the search.
self.do_search(au, idms, &sr, &lbt)
.map(|r| LdapResponseState::BindMultiPartResponse(lbt, r))
.or_else(|e| {
let (rc, msg) = operationerr_to_ldapresultcode(e);
Ok(LdapResponseState::Respond(sr.gen_error(rc, msg)))
})
}
},
ServerOps::Unbind(_) => {
// No need to notify on unbind (per rfc4511)
Ok(LdapResponseState::Unbind)
}
ServerOps::Whoami(wr) => match uat {
Some(u) => Ok(LdapResponseState::Respond(
wr.gen_success(format!("u: {}", u.spn).as_str()),
)),
None => Ok(LdapResponseState::Respond(wr.gen_operror(
format!("Unbound Connection {:?}", &eventid).as_str(),
))),
},
} // end match server op
}
}
fn ldap_domain_to_dc(input: &str) -> String {
let mut output: String = String::new();
input.split('.').for_each(|dc| {
output.push_str("dc=");
output.push_str(dc);
output.push_str(",");
});
// Remove the last ','
output.pop();
output
}
fn operationerr_to_ldapresultcode(e: OperationError) -> (LdapResultCode, String) {
match e {
OperationError::InvalidRequestState => {
(LdapResultCode::ConstraintViolation, "".to_string())
}
OperationError::InvalidAttributeName(s) | OperationError::InvalidAttribute(s) => {
(LdapResultCode::InvalidAttributeSyntax, s)
}
OperationError::SchemaViolation(se) => {
(LdapResultCode::UnwillingToPerform, format!("{:?}", se))
}
e => (LdapResultCode::Other, format!("{:?}", e)),
}
}

View file

@ -25,6 +25,7 @@ mod entry;
mod event;
mod filter;
mod interval;
pub(crate) mod ldap;
mod modify;
mod value;
#[macro_use]

View file

@ -5,7 +5,6 @@
// which is importart for management of the replication topo and trust
// relationships.
use crate::plugins::Plugin;
use uuid::Uuid;
use crate::audit::AuditScope;
use crate::constants::UUID_DOMAIN_INFO;
@ -17,9 +16,7 @@ use kanidm_proto::v1::OperationError;
lazy_static! {
static ref PVCLASS_DOMAIN_INFO: PartialValue = PartialValue::new_class("domain_info");
static ref PVUUID_DOMAIN_INFO: PartialValue = PartialValue::new_uuid(
Uuid::parse_str(UUID_DOMAIN_INFO).expect("Unable to parse constant UUID_DOMAIN_INFO")
);
static ref PVUUID_DOMAIN_INFO: PartialValue = PartialValue::new_uuidr(&UUID_DOMAIN_INFO);
}
pub struct Domain {}
@ -63,16 +60,14 @@ mod tests {
use crate::constants::UUID_DOMAIN_INFO;
use crate::server::QueryServerTransaction;
use crate::value::PartialValue;
use uuid::Uuid;
// use uuid::Uuid;
// test we can create and generate the id
#[test]
fn test_domain_generate_uuid() {
run_test!(|server: &QueryServer, au: &mut AuditScope| {
let mut server_txn = server.write(duration_from_epoch_now());
let uuid_domain = Uuid::parse_str(UUID_DOMAIN_INFO)
.expect("Unable to parse constant UUID_DOMAIN_INFO");
let e_dom = server_txn
.internal_search_uuid(au, &uuid_domain)
.internal_search_uuid(au, &UUID_DOMAIN_INFO)
.expect("must not fail");
let u_dom = server_txn.get_domain_uuid();

View file

@ -12,17 +12,13 @@ use crate::server::{
use crate::value::PartialValue;
// use crate::value::{PartialValue, Value};
use kanidm_proto::v1::{ConsistencyError, OperationError};
use uuid::Uuid;
pub struct Spn {}
lazy_static! {
static ref UUID_DOMAIN_INFO_T: Uuid =
Uuid::parse_str(UUID_DOMAIN_INFO).expect("Unable to parse constant UUID_DOMAIN_INFO");
static ref CLASS_GROUP: PartialValue = PartialValue::new_class("group");
static ref CLASS_ACCOUNT: PartialValue = PartialValue::new_class("account");
static ref PV_UUID_DOMAIN_INFO: PartialValue = PartialValue::new_uuids(UUID_DOMAIN_INFO)
.expect("Unable to parse constant UUID_DOMAIN_INFO");
static ref PV_UUID_DOMAIN_INFO: PartialValue = PartialValue::new_uuidr(&UUID_DOMAIN_INFO);
}
impl Spn {
@ -30,7 +26,7 @@ impl Spn {
au: &mut AuditScope,
qs: &mut QueryServerWriteTransaction,
) -> Result<String, OperationError> {
qs.internal_search_uuid(au, &UUID_DOMAIN_INFO_T)
qs.internal_search_uuid(au, &UUID_DOMAIN_INFO)
.and_then(|e| {
e.get_ava_single_string("domain_name")
.ok_or(OperationError::InvalidEntryState)
@ -45,7 +41,7 @@ impl Spn {
au: &mut AuditScope,
qs: &mut QueryServerReadTransaction,
) -> Result<String, OperationError> {
qs.internal_search_uuid(au, &UUID_DOMAIN_INFO_T)
qs.internal_search_uuid(au, &UUID_DOMAIN_INFO)
.and_then(|e| {
e.get_ava_single_string("domain_name")
.ok_or(OperationError::InvalidEntryState)

View file

@ -458,7 +458,7 @@ impl SchemaClass {
pub trait SchemaTransaction {
fn get_classes(&self) -> BptreeMapReadSnapshot<String, SchemaClass>;
fn get_attributes(&self) -> BptreeMapReadSnapshot<String, SchemaAttribute>;
fn get_idxmeta(&self) -> BTreeSet<(String, IndexType)>;
fn get_idxmeta(&self) -> &BTreeSet<(String, IndexType)>;
fn validate(&self, _audit: &mut AuditScope) -> Vec<Result<(), ConsistencyError>> {
let mut res = Vec::new();
@ -546,7 +546,7 @@ pub trait SchemaTransaction {
.collect()
}
fn get_idxmeta_set(&self) -> BTreeSet<(String, IndexType)> {
fn get_idxmeta_set(&self) -> &BTreeSet<(String, IndexType)> {
self.get_idxmeta()
}
}
@ -1370,8 +1370,8 @@ impl<'a> SchemaTransaction for SchemaWriteTransaction<'a> {
self.attributes.to_snapshot()
}
fn get_idxmeta(&self) -> BTreeSet<(String, IndexType)> {
self.idxmeta.clone()
fn get_idxmeta(&self) -> &BTreeSet<(String, IndexType)> {
&self.idxmeta
}
}
@ -1384,8 +1384,8 @@ impl SchemaTransaction for SchemaReadTransaction {
self.attributes.to_snapshot()
}
fn get_idxmeta(&self) -> BTreeSet<(String, IndexType)> {
(*self.idxmeta).clone()
fn get_idxmeta(&self) -> &BTreeSet<(String, IndexType)> {
&(*self.idxmeta)
}
}

View file

@ -124,7 +124,12 @@ pub trait QueryServerTransaction {
let schema = self.get_schema();
let idxmeta = schema.get_idxmeta_set();
// Now resolve all references and indexes.
let vfr = try_audit!(au, se.filter.resolve(&se.event, Some(&idxmeta)));
let vfr = try_audit!(
au,
lperf_segment!(au, "server::search<filter_resove>", || {
se.filter.resolve(&se.event, Some(idxmeta))
})
);
// NOTE: We currently can't build search plugins due to the inability to hand
// the QS wr/ro to the plugin trait. However, there shouldn't be a need for search
@ -155,7 +160,7 @@ pub trait QueryServerTransaction {
lperf_segment!(au, "server::exists", || {
let schema = self.get_schema();
let idxmeta = schema.get_idxmeta_set();
let vfr = try_audit!(au, ee.filter.resolve(&ee.event, Some(&idxmeta)));
let vfr = try_audit!(au, ee.filter.resolve(&ee.event, Some(idxmeta)));
self.get_be_txn()
.exists(au, &vfr)
@ -779,7 +784,7 @@ pub struct QueryServer {
impl QueryServer {
pub fn new(be: Backend, schema: Schema) -> Self {
let (s_uuid, d_uuid) = {
let mut wr = be.write(BTreeSet::new());
let mut wr = be.write(&BTreeSet::new());
(wr.get_db_s_uuid(), wr.get_db_d_uuid())
};
@ -2108,7 +2113,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
) -> Result<(), OperationError> {
let modl =
ModifyList::new_purge_and_set("domain_name", Value::new_iname_s(new_domain_name));
let udi = PartialValue::new_uuids(UUID_DOMAIN_INFO).ok_or(OperationError::InvalidUuid)?;
let udi = PartialValue::new_uuidr(&UUID_DOMAIN_INFO);
let filt = filter_all!(f_eq("uuid", udi));
self.internal_modify(audit, filt, modl)
}
@ -3544,10 +3549,7 @@ mod tests {
// ++ Mod domain name and name to be the old type.
let me_dn = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq(
"uuid",
PartialValue::new_uuids(UUID_DOMAIN_INFO).expect("invalid uuid")
)),
filter!(f_eq("uuid", PartialValue::new_uuidr(&UUID_DOMAIN_INFO))),
ModifyList::new_list(vec![
Modify::Purged("name".to_string()),
Modify::Purged("domain_name".to_string()),
@ -3590,7 +3592,7 @@ mod tests {
// Assert that it migrated and worked as expected.
let mut server_txn = server.write(duration_from_epoch_now());
let domain = server_txn
.internal_search_uuid(audit, &Uuid::parse_str(UUID_DOMAIN_INFO).unwrap())
.internal_search_uuid(audit, &UUID_DOMAIN_INFO)
.expect("failed");
// ++ assert all names are iname
domain

View file

@ -18,7 +18,12 @@ lazy_static! {
static ref SPN_RE: Regex =
Regex::new("(?P<name>[^@]+)@(?P<realm>[^@]+)").expect("Invalid SPN regex found");
static ref INAME_RE: Regex =
Regex::new("^(_.*|.*@.*|\\d+|.*\\s.*)$").expect("Invalid Iname regex found");
Regex::new("^(_.*|.*(\\s|@|,|=).*|\\d+)$").expect("Invalid Iname regex found");
// ^ ^ ^
// | | \- must not be only integers
// | \- must not contain whitespace, @, ',', =
// \- must not start with _
// Them's be the rules.
}
#[allow(non_camel_case_types)]
@ -1503,12 +1508,15 @@ mod tests {
* - contain an @ (confuses SPN)
* - can not start with _ (... I forgot but it's important I swear >.>)
* - can not have spaces (confuses too many systems :()
* - can not have = or , (confuses ldap)
*/
let inv1 = Value::new_iname_s("1234");
let inv2 = Value::new_iname_s("bc23f637-4439-4c07-b95d-eaed0d9e4b8b");
let inv3 = Value::new_iname_s("hello@test.com");
let inv4 = Value::new_iname_s("_bad");
let inv5 = Value::new_iname_s("no spaces I'm sorry :(");
let inv6 = Value::new_iname_s("bad=equals");
let inv7 = Value::new_iname_s("bad,comma");
let val1 = Value::new_iname_s("William");
let val2 = Value::new_iname_s("this_is_okay");
@ -1520,6 +1528,8 @@ mod tests {
assert!(!inv3.validate());
assert!(!inv4.validate());
assert!(!inv5.validate());
assert!(!inv6.validate());
assert!(!inv7.validate());
assert!(val1.validate());
assert!(val2.validate());

View file

@ -29,6 +29,8 @@ struct ServerOpt {
key_path: Option<PathBuf>,
#[structopt(short = "b", long = "bindaddr")]
bind: Option<String>,
#[structopt(short = "l", long = "ldapbindaddr")]
ldapbind: Option<String>,
#[structopt(flatten)]
commonopts: CommonOpt,
}
@ -112,7 +114,7 @@ async fn main() {
if opt.debug() {
::std::env::set_var("RUST_LOG", "actix_web=debug,kanidm=debug");
} else {
::std::env::set_var("RUST_LOG", "actix_web=info,kanidm=info");
::std::env::set_var("RUST_LOG", "actix_web=info,kanidm=warn");
}
env_logger::builder()
@ -127,8 +129,9 @@ async fn main() {
config.update_db_path(&sopt.commonopts.db_path);
config.update_tls(&sopt.ca_path, &sopt.cert_path, &sopt.key_path);
config.update_bind(&sopt.bind);
config.update_ldapbind(&sopt.ldapbind);
let sctx = create_server_core(config);
let sctx = create_server_core(config).await;
match sctx {
Ok(sctx) => {
tokio::signal::ctrl_c().await.unwrap();