mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
2023 orca improve (#1342)
* Improve orca * Add improved data generator, add some basic ipa support * (least) humble brag (ever) on readme
This commit is contained in:
parent
8255c937e5
commit
ec5e7abe8d
2
Makefile
2
Makefile
|
@ -4,8 +4,6 @@ CONTAINER_TOOL_ARGS ?=
|
|||
IMAGE_ARCH ?= "linux/amd64,linux/arm64"
|
||||
CONTAINER_BUILD_ARGS ?=
|
||||
MARKDOWN_FORMAT_ARGS ?= --options-line-width=100
|
||||
# Example of using redis with sccache
|
||||
# --build-arg "SCCACHE_REDIS=redis://redis.dev.blackhats.net.au:6379"
|
||||
CONTAINER_TOOL ?= docker
|
||||
|
||||
BOOK_VERSION ?= master
|
||||
|
|
|
@ -99,7 +99,9 @@ of resource overhead and difficulty for administration.
|
|||
|
||||
Kanidm aims to have the features richness of FreeIPA, but without the resource and administration
|
||||
overheads. If you want a complete IDM package, but in a lighter footprint and easier to manage, then
|
||||
Kanidm is probably for you.
|
||||
Kanidm is probably for you. In testing with 3000 users + 1500 groups, Kanidm is 3 times faster
|
||||
for search operations and 5 times faster for modification and addition of entries (your results may
|
||||
differ however, but generally Kanidm is much faster than FreeIPA).
|
||||
|
||||
## Developer Getting Started
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ RUN zypper install -y \
|
|||
pam-devel \
|
||||
libudev-devel \
|
||||
sqlite3-devel \
|
||||
sccache \
|
||||
rsync
|
||||
RUN zypper clean -a
|
||||
RUN rustup default stable
|
||||
|
@ -34,17 +33,6 @@ RUN echo Features $KANIDM_FEATURES
|
|||
ENV CARGO_HOME=/scratch/.cargo
|
||||
ENV RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=/usr/bin/ld.lld"
|
||||
|
||||
# set up sccache if you've done the thing
|
||||
RUN if [ "${SCCACHE_REDIS}" != "" ]; \
|
||||
then \
|
||||
export CARGO_INCREMENTAL=false && \
|
||||
export CC="/usr/bin/sccache /usr/bin/clang" && \
|
||||
export RUSTC_WRAPPER=sccache && \
|
||||
sccache --start-server; \
|
||||
else \
|
||||
export CC="/usr/bin/clang"; \
|
||||
fi
|
||||
|
||||
WORKDIR /usr/src/kanidm/
|
||||
# build the CLI
|
||||
RUN if [ -z "${KANIDM_FEATURES}" ]; then \
|
||||
|
@ -65,8 +53,6 @@ else \
|
|||
--release; \
|
||||
fi
|
||||
|
||||
RUN if [ "${SCCACHE_REDIS}" != "" ]; then sccache -s; fi
|
||||
|
||||
RUN ls -al /usr/src/kanidm/target/release
|
||||
|
||||
# == Construct the tools container
|
||||
|
|
|
@ -13,7 +13,6 @@ RUN zypper install -y \
|
|||
make automake autoconf \
|
||||
libopenssl-devel pam-devel \
|
||||
sqlite3-devel \
|
||||
sccache \
|
||||
gcc \
|
||||
rsync \
|
||||
findutils \
|
||||
|
@ -23,7 +22,6 @@ RUN rustup default stable
|
|||
|
||||
COPY . /usr/src/kanidm
|
||||
|
||||
ARG SCCACHE_REDIS=""
|
||||
ARG KANIDM_FEATURES
|
||||
ARG KANIDM_BUILD_PROFILE="container_generic"
|
||||
ARG KANIDM_BUILD_OPTIONS=""
|
||||
|
@ -32,21 +30,14 @@ RUN mkdir /scratch
|
|||
RUN echo $KANIDM_BUILD_PROFILE
|
||||
RUN echo $KANIDM_FEATURES
|
||||
|
||||
|
||||
ENV CARGO_HOME=/scratch/.cargo
|
||||
|
||||
# ======================
|
||||
|
||||
# WORKDIR /usr/src/kanidm/kanidmd_web_ui
|
||||
WORKDIR /usr/src/kanidm/kanidmd_web_ui
|
||||
# # This can't be used in the wasm build for now.
|
||||
# # ENV RUSTFLAGS="-Clinker=clang"
|
||||
# RUN if [ "${SCCACHE_REDIS}" != "" ]; \
|
||||
# then \
|
||||
# export CARGO_INCREMENTAL=false && \
|
||||
# export RUSTC_WRAPPER=sccache && \
|
||||
# sccache --start-server; \
|
||||
# fi && \
|
||||
# ./build_wasm.sh
|
||||
RUN ./build_wasm.sh
|
||||
|
||||
# ======================
|
||||
|
||||
|
@ -57,15 +48,7 @@ ENV KANIDM_BUILD_PROFILE="${KANIDM_BUILD_PROFILE}"
|
|||
ENV RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=/usr/bin/ld.lld"
|
||||
|
||||
# Exports don't persist through RUN statements.
|
||||
RUN if [ "${SCCACHE_REDIS}" != "" ]; \
|
||||
then \
|
||||
export CARGO_INCREMENTAL=false && \
|
||||
export CC="/usr/bin/sccache /usr/bin/clang" && \
|
||||
export RUSTC_WRAPPER=sccache && \
|
||||
sccache --start-server; \
|
||||
else \
|
||||
export CC="/usr/bin/clang"; \
|
||||
fi && \
|
||||
RUN export CC="/usr/bin/clang"; \
|
||||
if [ -z "${KANIDM_FEATURES}" ]; then \
|
||||
cargo build -p daemon ${KANIDM_BUILD_OPTIONS} \
|
||||
--target-dir="/usr/src/kanidm/target/" \
|
||||
|
@ -75,8 +58,7 @@ else \
|
|||
--target-dir="/usr/src/kanidm/target/" \
|
||||
--features="${KANIDM_FEATURES}" \
|
||||
--release; \
|
||||
fi && \
|
||||
if [ "${SCCACHE_REDIS}" != "" ]; then sccache -s; fi
|
||||
fi
|
||||
|
||||
RUN ls -al /usr/src/kanidm/target/release
|
||||
|
||||
|
|
|
@ -46,6 +46,10 @@ impl Account {
|
|||
format!("uid={},ou=people,{}", self.name.as_str(), basedn)
|
||||
}
|
||||
|
||||
pub fn get_ipa_ldap_dn(&self, basedn: &str) -> String {
|
||||
format!("uid={},cn=users,cn=accounts,{}", self.name.as_str(), basedn)
|
||||
}
|
||||
|
||||
pub fn generate(uuid: Uuid) -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let id: u64 = rng.gen();
|
||||
|
@ -73,6 +77,10 @@ impl Group {
|
|||
format!("cn={},ou=groups,{}", self.name.as_str(), basedn)
|
||||
}
|
||||
|
||||
pub fn get_ipa_ldap_dn(&self, basedn: &str) -> String {
|
||||
format!("cn={},cn=groups,cn=accounts,{}", self.name.as_str(), basedn)
|
||||
}
|
||||
|
||||
pub fn generate(uuid: Uuid, members: Vec<Uuid>) -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
|
@ -115,6 +123,13 @@ impl Entity {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_ipa_ldap_dn(&self, basedn: &str) -> String {
|
||||
match self {
|
||||
Entity::Account(a) => a.get_ipa_ldap_dn(basedn),
|
||||
Entity::Group(g) => g.get_ipa_ldap_dn(basedn),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_entity_type(&self) -> EntityType {
|
||||
match self {
|
||||
Entity::Account(a) => EntityType::Account(a.uuid),
|
||||
|
|
163
orca/src/generate.rs
Normal file
163
orca/src/generate.rs
Normal file
|
@ -0,0 +1,163 @@
|
|||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::data::*;
|
||||
|
||||
const N_USERS: usize = 3000;
|
||||
const N_GROUPS: usize = 1500;
|
||||
const N_MEMBERSHIPS: usize = 10;
|
||||
const N_NEST: usize = 4;
|
||||
|
||||
pub(crate) fn doit(output: &Path) -> () {
|
||||
info!(
|
||||
"Performing data generation into {}",
|
||||
output.to_str().unwrap(),
|
||||
);
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
if N_MEMBERSHIPS >= N_GROUPS {
|
||||
error!("Too many memberships per group. Memberships must be less that n-groups");
|
||||
return;
|
||||
}
|
||||
|
||||
// Open before we start so we have it ready to go.
|
||||
let out_file = match File::create(output) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
error!("Failed to open {} - {:?}", output.to_str().unwrap(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Number of users
|
||||
let accounts: Vec<_> = (0..N_USERS)
|
||||
.map(|i| Account {
|
||||
name: format!("testuser{}", i),
|
||||
display_name: format!("Test User {}", i),
|
||||
password: readable_password_from_random(),
|
||||
uuid: Uuid::new_v4(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Number of groups.
|
||||
let mut groups: Vec<_> = (0..N_GROUPS)
|
||||
.map(|i| Group {
|
||||
name: format!("testgroup{}", i),
|
||||
uuid: Uuid::new_v4(),
|
||||
members: Vec::new(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Should groups be randomly nested?
|
||||
// The way this is done is we split the array based on nest level. If it's 1, we split
|
||||
// in 2, 2 we split in 3 and so on.
|
||||
if N_NEST > 0 {
|
||||
debug!("Nesting Groups");
|
||||
|
||||
let chunk_size = N_GROUPS / (N_NEST + 1);
|
||||
if chunk_size == 0 {
|
||||
error!("Unable to chunk groups, need (N_GROUPS / (N_NEST + 1)) > 0");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut chunk_iter = groups.chunks_mut(chunk_size);
|
||||
// Can't fail due to above checks.
|
||||
let mut p_chunk = chunk_iter.next().unwrap();
|
||||
while let Some(w_chunk) = chunk_iter.next() {
|
||||
// add items from work chunk to parent chunk
|
||||
p_chunk
|
||||
.iter_mut()
|
||||
.zip(w_chunk.iter())
|
||||
.for_each(|(p, w): (&mut _, &_)| p.members.push(w.uuid));
|
||||
|
||||
// swap w_chunk to p_chunk
|
||||
p_chunk = w_chunk;
|
||||
}
|
||||
}
|
||||
|
||||
// Number of memberships per user.
|
||||
// We use rand for this to sample random numbers of
|
||||
for acc in accounts.iter() {
|
||||
// Sample randomly.
|
||||
for idx in rand::seq::index::sample(&mut rng, N_GROUPS, N_MEMBERSHIPS).iter() {
|
||||
groups[idx].members.push(acc.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
// Build from the generated data above.
|
||||
let all_entities: HashMap<Uuid, Entity> = accounts
|
||||
.into_iter()
|
||||
.map(|acc| (acc.uuid, Entity::Account(acc)))
|
||||
.chain(groups.into_iter().map(|grp| (grp.uuid, Entity::Group(grp))))
|
||||
.collect();
|
||||
|
||||
// Define the entries that should exist "at the start of the test". For now, we just
|
||||
// create everything. Maybe when we start to add mod tests we need to retain a pool
|
||||
// of things to retain here for those ops.
|
||||
let precreate: HashSet<_> = all_entities.keys().copied().collect();
|
||||
|
||||
// The set of accounts in all_entities.
|
||||
let accounts: HashSet<Uuid> = all_entities
|
||||
.iter()
|
||||
.filter_map(|(uuid, ent)| match ent {
|
||||
Entity::Account(_) => Some(*uuid),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// This defines a map of "entity" to "what can it manipulate". This
|
||||
// is used to create access controls in some cases for mod tests.
|
||||
//
|
||||
// For example, if we have user with uuid X and it changes Group with
|
||||
// uuid Y, then we need to ensure that X has group-mod permissions over
|
||||
// Y in some capacity.
|
||||
let access: HashMap<Uuid, Vec<EntityType>> = HashMap::new();
|
||||
|
||||
// The set of operations to simulate. We pre-calc these so tests can randomly
|
||||
// sample and perform the searches as needed.
|
||||
|
||||
// We don't have original times, so we can fudge these.
|
||||
let orig_etime = Duration::from_secs(1);
|
||||
let rtime = Duration::from_secs(1);
|
||||
// Needed for random sampling.
|
||||
let all_ids: Vec<_> = all_entities.keys().copied().collect();
|
||||
let all_ids_len = all_ids.len();
|
||||
|
||||
let connections: Vec<_> = (0..all_ids_len)
|
||||
.map(|id| {
|
||||
// Could be rand?
|
||||
let n_search = 1;
|
||||
|
||||
let mut search_ids = Vec::new();
|
||||
for idx in rand::seq::index::sample(&mut rng, all_ids_len, n_search).iter() {
|
||||
search_ids.push(all_ids[idx]);
|
||||
}
|
||||
//
|
||||
Conn {
|
||||
id: id as i32,
|
||||
ops: vec![Op {
|
||||
orig_etime,
|
||||
rtime,
|
||||
op_type: OpType::Search(search_ids),
|
||||
}],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let td = TestData {
|
||||
all_entities,
|
||||
access,
|
||||
accounts,
|
||||
precreate,
|
||||
connections,
|
||||
};
|
||||
|
||||
if let Err(e) = serde_json::to_writer_pretty(out_file, &td) {
|
||||
error!("Writing to file -> {:?}", e);
|
||||
};
|
||||
}
|
295
orca/src/ipa.rs
Normal file
295
orca/src/ipa.rs
Normal file
|
@ -0,0 +1,295 @@
|
|||
use ldap3_proto::proto::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::{Duration, Instant};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::data::*;
|
||||
use crate::ldap::{LdapClient, LdapSchema};
|
||||
use crate::profile::IpaConfig;
|
||||
use crate::{TargetServer, TargetServerBuilder};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IpaServer {
|
||||
ldap: LdapClient,
|
||||
realm: String,
|
||||
admin_pw: String,
|
||||
}
|
||||
|
||||
impl IpaServer {
|
||||
fn construct(uri: String, realm: String, admin_pw: String) -> Result<Self, ()> {
|
||||
// explode the realm to basedn.
|
||||
// dev.kanidm.com
|
||||
// dc=dev,dc=kanidm,dc=com
|
||||
let basedn = format!("dc={}", realm.replace('.', ",dc="));
|
||||
|
||||
let ldap = LdapClient::new(uri, basedn, LdapSchema::Rfc2307bis)?;
|
||||
|
||||
Ok(IpaServer {
|
||||
ldap,
|
||||
realm,
|
||||
admin_pw,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build(uri: String, realm: String, admin_pw: String) -> Result<TargetServer, ()> {
|
||||
Self::construct(uri, realm, admin_pw).map(TargetServer::Ipa)
|
||||
}
|
||||
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(lconfig: &IpaConfig) -> Result<TargetServer, ()> {
|
||||
Self::construct(
|
||||
lconfig.uri.clone(),
|
||||
lconfig.realm.clone(),
|
||||
lconfig.admin_pw.clone(),
|
||||
)
|
||||
.map(TargetServer::Ipa)
|
||||
}
|
||||
|
||||
pub fn info(&self) -> String {
|
||||
format!("Ipa Server Connection: {} @ {}", self.realm, self.ldap.uri)
|
||||
}
|
||||
|
||||
pub fn builder(&self) -> TargetServerBuilder {
|
||||
TargetServerBuilder::Ipa(
|
||||
self.ldap.uri.clone(),
|
||||
self.realm.clone(),
|
||||
self.admin_pw.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn open_admin_connection(&self) -> Result<(), ()> {
|
||||
self.ldap.open_ipa_admin_connection(&self.admin_pw).await
|
||||
}
|
||||
|
||||
pub async fn setup_admin_delete_uuids(&self, _targets: &[Uuid]) -> Result<(), ()> {
|
||||
// todo!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn setup_admin_precreate_entities(
|
||||
&self,
|
||||
targets: &HashSet<Uuid>,
|
||||
all_entities: &HashMap<Uuid, Entity>,
|
||||
) -> Result<(), ()> {
|
||||
for u in targets {
|
||||
let e = all_entities.get(u).unwrap();
|
||||
// does it already exist?
|
||||
let res = self
|
||||
.ldap
|
||||
.search(LdapFilter::Equality(
|
||||
"cn".to_string(),
|
||||
e.get_name().to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
if !res.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dn = e.get_ipa_ldap_dn(&self.ldap.basedn);
|
||||
match e {
|
||||
Entity::Account(a) => {
|
||||
let account = LdapAddRequest {
|
||||
dn,
|
||||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec![
|
||||
"ipaobject".as_bytes().into(),
|
||||
"person".as_bytes().into(),
|
||||
"top".as_bytes().into(),
|
||||
"ipasshuser".as_bytes().into(),
|
||||
"inetorgperson".as_bytes().into(),
|
||||
"organizationalperson".as_bytes().into(),
|
||||
"krbticketpolicyaux".as_bytes().into(),
|
||||
"krbprincipalaux".as_bytes().into(),
|
||||
"inetuser".as_bytes().into(),
|
||||
"posixaccount".as_bytes().into(),
|
||||
"meporiginentry".as_bytes().into(),
|
||||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "ipauniqueid".to_string(),
|
||||
vals: vec!["autogenerate".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "uid".to_string(),
|
||||
vals: vec![a.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec![a.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "givenName".to_string(),
|
||||
vals: vec![a.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "sn".to_string(),
|
||||
vals: vec![a.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "displayName".to_string(),
|
||||
vals: vec![a.display_name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "gecos".to_string(),
|
||||
vals: vec![a.display_name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "userPassword".to_string(),
|
||||
vals: vec![a.password.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "initials".to_string(),
|
||||
vals: vec!["tu".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "homeDirectory".to_string(),
|
||||
vals: vec![format!("/home/{}", a.name).as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "mail".to_string(),
|
||||
vals: vec![format!("{}@{}", a.name, self.realm).as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "loginshell".to_string(),
|
||||
vals: vec!["/bin/zsh".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "uidNumber".to_string(),
|
||||
vals: vec!["-1".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "gidNumber".to_string(),
|
||||
vals: vec!["-1".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "krbextradata".to_string(),
|
||||
vals: vec!["placeholder".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "krblastpwdchange".to_string(),
|
||||
vals: vec!["20230119053224Z".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "krbPasswordExpiration".to_string(),
|
||||
vals: vec!["20380119053224Z".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "krbPrincipalName".to_string(),
|
||||
vals: vec![format!("{}@{}", a.name, self.realm.to_uppercase())
|
||||
.as_bytes()
|
||||
.into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "krbCanonicalName".to_string(),
|
||||
vals: vec![format!("{}@{}", a.name, self.realm.to_uppercase())
|
||||
.as_bytes()
|
||||
.into()],
|
||||
},
|
||||
],
|
||||
};
|
||||
self.ldap.add(account).await?;
|
||||
}
|
||||
Entity::Group(g) => {
|
||||
let group = LdapAddRequest {
|
||||
dn,
|
||||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec![
|
||||
"top".as_bytes().into(),
|
||||
"groupofnames".as_bytes().into(),
|
||||
"nestedgroup".as_bytes().into(),
|
||||
"ipausergroup".as_bytes().into(),
|
||||
"ipaobject".as_bytes().into(),
|
||||
"posixgroup".as_bytes().into(),
|
||||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec![g.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "ipauniqueid".to_string(),
|
||||
vals: vec!["autogenerate".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "gidNumber".to_string(),
|
||||
vals: vec!["-1".as_bytes().into()],
|
||||
},
|
||||
],
|
||||
};
|
||||
self.ldap.add(group).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the members.
|
||||
for g in targets.iter().filter_map(|u| {
|
||||
let e = all_entities.get(u).unwrap();
|
||||
match e {
|
||||
Entity::Group(g) => Some(g),
|
||||
_ => None,
|
||||
}
|
||||
}) {
|
||||
// List of dns
|
||||
let vals: Vec<Vec<u8>> = g
|
||||
.members
|
||||
.iter()
|
||||
.map(|id| {
|
||||
all_entities
|
||||
.get(id)
|
||||
.unwrap()
|
||||
.get_ipa_ldap_dn(&self.ldap.basedn)
|
||||
.as_bytes()
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let req = LdapModifyRequest {
|
||||
dn: g.get_ipa_ldap_dn(&self.ldap.basedn),
|
||||
changes: vec![LdapModify {
|
||||
operation: LdapModifyType::Replace,
|
||||
modification: LdapPartialAttribute {
|
||||
atype: "member".to_string(),
|
||||
vals,
|
||||
},
|
||||
}],
|
||||
};
|
||||
self.ldap.modify(req).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn setup_access_controls(
|
||||
&self,
|
||||
_access: &HashMap<Uuid, Vec<EntityType>>,
|
||||
_all_entities: &HashMap<Uuid, Entity>,
|
||||
) -> Result<(), ()> {
|
||||
// todo!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn open_user_connection(
|
||||
&self,
|
||||
test_start: Instant,
|
||||
name: &str,
|
||||
pw: &str,
|
||||
) -> Result<(Duration, Duration), ()> {
|
||||
self.ldap.open_user_connection(test_start, name, pw).await
|
||||
}
|
||||
|
||||
pub async fn close_connection(&self) {
|
||||
self.ldap.close_connection().await;
|
||||
}
|
||||
|
||||
pub async fn search(
|
||||
&self,
|
||||
test_start: Instant,
|
||||
ids: &[String],
|
||||
) -> Result<(Duration, Duration, usize), ()> {
|
||||
self.ldap.search_name(test_start, ids).await
|
||||
}
|
||||
}
|
|
@ -291,7 +291,12 @@ impl KaniHttpServer {
|
|||
}
|
||||
|
||||
pub async fn close_connection(&self) {
|
||||
assert!(self.client.logout().await.is_ok());
|
||||
assert!(self
|
||||
.client
|
||||
.logout()
|
||||
.await
|
||||
.map_err(|e| error!("close_connection {:?}", e))
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
pub async fn search(
|
||||
|
|
|
@ -135,6 +135,11 @@ impl LdapClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn open_ipa_admin_connection(&self, pw: &str) -> Result<(), ()> {
|
||||
let admin_dn = format!("uid=admin,cn=users,cn=accounts,{}", self.basedn);
|
||||
self.bind(admin_dn, pw.to_string()).await
|
||||
}
|
||||
|
||||
pub async fn open_user_connection(
|
||||
&self,
|
||||
test_start: Instant,
|
||||
|
|
|
@ -16,17 +16,21 @@ static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
|||
extern crate tracing;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ds::DirectoryServer;
|
||||
use crate::ipa::IpaServer;
|
||||
use crate::kani::{KaniHttpServer, KaniLdapServer};
|
||||
use crate::setup::config;
|
||||
|
||||
mod data;
|
||||
mod ds;
|
||||
mod generate;
|
||||
mod ipa;
|
||||
mod kani;
|
||||
mod ldap;
|
||||
mod preprocess;
|
||||
|
@ -39,6 +43,8 @@ include!("./opt.rs");
|
|||
impl OrcaOpt {
|
||||
pub fn debug(&self) -> bool {
|
||||
match self {
|
||||
OrcaOpt::TestConnection(opt) => opt.copt.debug,
|
||||
OrcaOpt::Generate(opt) => opt.copt.debug,
|
||||
OrcaOpt::PreProc(opt) => opt.copt.debug,
|
||||
OrcaOpt::Setup(opt) => opt.copt.debug,
|
||||
OrcaOpt::Run(opt) => opt.copt.debug,
|
||||
|
@ -50,6 +56,7 @@ pub enum TargetServerBuilder {
|
|||
Kanidm(String, String),
|
||||
KanidmLdap(String, String, String, String),
|
||||
DirSrv(String, String, String),
|
||||
Ipa(String, String, String),
|
||||
}
|
||||
|
||||
impl TargetServerBuilder {
|
||||
|
@ -59,6 +66,7 @@ impl TargetServerBuilder {
|
|||
TargetServerBuilder::Kanidm(a, b) => KaniHttpServer::build(a, b),
|
||||
TargetServerBuilder::KanidmLdap(a, b, c, d) => KaniLdapServer::build(a, b, c, d),
|
||||
TargetServerBuilder::DirSrv(a, b, c) => DirectoryServer::build(a, b, c),
|
||||
TargetServerBuilder::Ipa(a, b, c) => IpaServer::build(a, b, c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +76,7 @@ pub enum TargetServer {
|
|||
Kanidm(KaniHttpServer),
|
||||
KanidmLdap(Box<KaniLdapServer>),
|
||||
DirSrv(DirectoryServer),
|
||||
Ipa(IpaServer),
|
||||
}
|
||||
|
||||
impl TargetServer {
|
||||
|
@ -76,6 +85,7 @@ impl TargetServer {
|
|||
TargetServer::Kanidm(k) => k.info(),
|
||||
TargetServer::KanidmLdap(k) => k.info(),
|
||||
TargetServer::DirSrv(k) => k.info(),
|
||||
TargetServer::Ipa(k) => k.info(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,6 +94,7 @@ impl TargetServer {
|
|||
TargetServer::Kanidm(_) => "kanidm_http",
|
||||
TargetServer::KanidmLdap(_) => "kanidm_ldap",
|
||||
TargetServer::DirSrv(_) => "directory_server",
|
||||
TargetServer::Ipa(_) => "ipa",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,6 +103,7 @@ impl TargetServer {
|
|||
TargetServer::Kanidm(k) => k.builder(),
|
||||
TargetServer::KanidmLdap(k) => k.builder(),
|
||||
TargetServer::DirSrv(k) => k.builder(),
|
||||
TargetServer::Ipa(k) => k.builder(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +112,7 @@ impl TargetServer {
|
|||
TargetServer::Kanidm(k) => k.open_admin_connection().await,
|
||||
TargetServer::KanidmLdap(k) => k.open_admin_connection().await,
|
||||
TargetServer::DirSrv(k) => k.open_admin_connection().await,
|
||||
TargetServer::Ipa(k) => k.open_admin_connection().await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,6 +121,7 @@ impl TargetServer {
|
|||
TargetServer::Kanidm(k) => k.setup_admin_delete_uuids(targets).await,
|
||||
TargetServer::KanidmLdap(k) => k.setup_admin_delete_uuids(targets).await,
|
||||
TargetServer::DirSrv(k) => k.setup_admin_delete_uuids(targets).await,
|
||||
TargetServer::Ipa(k) => k.setup_admin_delete_uuids(targets).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,6 +143,10 @@ impl TargetServer {
|
|||
k.setup_admin_precreate_entities(targets, all_entities)
|
||||
.await
|
||||
}
|
||||
TargetServer::Ipa(k) => {
|
||||
k.setup_admin_precreate_entities(targets, all_entities)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,6 +159,7 @@ impl TargetServer {
|
|||
TargetServer::Kanidm(k) => k.setup_access_controls(access, all_entities).await,
|
||||
TargetServer::KanidmLdap(k) => k.setup_access_controls(access, all_entities).await,
|
||||
TargetServer::DirSrv(k) => k.setup_access_controls(access, all_entities).await,
|
||||
TargetServer::Ipa(k) => k.setup_access_controls(access, all_entities).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,6 +173,7 @@ impl TargetServer {
|
|||
TargetServer::Kanidm(k) => k.open_user_connection(test_start, name, pw).await,
|
||||
TargetServer::KanidmLdap(k) => k.open_user_connection(test_start, name, pw).await,
|
||||
TargetServer::DirSrv(k) => k.open_user_connection(test_start, name, pw).await,
|
||||
TargetServer::Ipa(k) => k.open_user_connection(test_start, name, pw).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,6 +182,7 @@ impl TargetServer {
|
|||
TargetServer::Kanidm(k) => k.close_connection().await,
|
||||
TargetServer::KanidmLdap(k) => k.close_connection().await,
|
||||
TargetServer::DirSrv(k) => k.close_connection().await,
|
||||
TargetServer::Ipa(k) => k.close_connection().await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,10 +195,27 @@ impl TargetServer {
|
|||
TargetServer::Kanidm(k) => k.search(test_start, ids).await,
|
||||
TargetServer::KanidmLdap(k) => k.search(test_start, ids).await,
|
||||
TargetServer::DirSrv(k) => k.search(test_start, ids).await,
|
||||
TargetServer::Ipa(k) => k.search(test_start, ids).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn conntest(target: &TargetOpt, profile_path: &Path) -> Result<(), ()> {
|
||||
info!(
|
||||
"Performing conntest of {:?} from {}",
|
||||
target,
|
||||
profile_path.to_str().unwrap(),
|
||||
);
|
||||
|
||||
let (_data, _profile, server) = config(target, profile_path)?;
|
||||
|
||||
server
|
||||
.open_admin_connection()
|
||||
.await
|
||||
.map(|_| info!("success"))
|
||||
.map_err(|_| error!("connection test failed"))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let opt = OrcaOpt::parse();
|
||||
|
@ -193,6 +231,10 @@ async fn main() {
|
|||
info!("Orca - the Kanidm Load Testing Utility.");
|
||||
debug!("cli -> {:?}", opt);
|
||||
match opt {
|
||||
OrcaOpt::TestConnection(opt) => {
|
||||
let _ = conntest(&opt.target, &opt.profile_path).await;
|
||||
}
|
||||
OrcaOpt::Generate(opt) => generate::doit(&opt.output_path),
|
||||
OrcaOpt::PreProc(opt) => preprocess::doit(&opt.input_path, &opt.output_path),
|
||||
OrcaOpt::Setup(opt) => {
|
||||
let _ = setup::doit(&opt.target, &opt.profile_path).await;
|
||||
|
@ -204,5 +246,5 @@ async fn main() {
|
|||
// run the test!
|
||||
}
|
||||
};
|
||||
info!("Success");
|
||||
debug!("Exit");
|
||||
}
|
||||
|
|
|
@ -19,13 +19,20 @@ struct PreProcOpt {
|
|||
pub output_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct GenerateOpt {
|
||||
#[clap(flatten)]
|
||||
pub copt: CommonOpt,
|
||||
#[clap(parse(from_os_str), short, long = "output")]
|
||||
/// Path to write the generated output.
|
||||
pub output_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct SetupOpt {
|
||||
#[clap(flatten)]
|
||||
pub copt: CommonOpt,
|
||||
#[clap(name = "target")]
|
||||
/// Which service to target during this operation.
|
||||
/// Valid values are "ds" or "kanidm"
|
||||
pub target: TargetOpt,
|
||||
#[clap(parse(from_os_str), short, long = "profile")]
|
||||
/// Path to the test profile.
|
||||
|
@ -37,8 +44,6 @@ struct RunOpt {
|
|||
#[clap(flatten)]
|
||||
pub copt: CommonOpt,
|
||||
#[clap(name = "target")]
|
||||
/// Which service to target during this operation.
|
||||
/// Valid values are "ds" or "kanidm"
|
||||
pub target: TargetOpt,
|
||||
#[clap(name = "test_type")]
|
||||
/// Which type of test to run against this system
|
||||
|
@ -49,10 +54,14 @@ struct RunOpt {
|
|||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// The target to run against
|
||||
pub(crate) enum TargetOpt {
|
||||
#[clap(name = "ds")]
|
||||
/// Run against the ldap/ds profile
|
||||
Ds,
|
||||
#[clap(name = "ipa")]
|
||||
/// Run against the ipa profile
|
||||
Ipa,
|
||||
#[clap(name = "kanidm")]
|
||||
/// Run against the kanidm http profile
|
||||
Kanidm,
|
||||
|
@ -67,9 +76,10 @@ impl FromStr for TargetOpt {
|
|||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"ds" => Ok(TargetOpt::Ds),
|
||||
"ipa" => Ok(TargetOpt::Ipa),
|
||||
"kanidm" => Ok(TargetOpt::Kanidm),
|
||||
"kanidm_ldap" => Ok(TargetOpt::KanidmLdap),
|
||||
_ => Err("Invalid target type. Must be ds, kanidm, or kanidm_ldap"),
|
||||
_ => Err("Invalid target type. Must be ds, ipa, kanidm, or kanidm_ldap"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +132,13 @@ Orca works in a few steps.
|
|||
"
|
||||
)]
|
||||
enum OrcaOpt {
|
||||
#[clap(name = "conntest")]
|
||||
/// Perform a connection test against the specified target
|
||||
TestConnection(SetupOpt),
|
||||
#[clap(name = "generate")]
|
||||
/// Generate a new dataset that can be used for testing. Parameters can be provided
|
||||
/// to affect the type and quantity of data created.
|
||||
Generate(GenerateOpt),
|
||||
#[clap(name = "preprocess")]
|
||||
/// Preprocess a dataset that can be used for testing
|
||||
PreProc(PreProcOpt),
|
||||
|
|
|
@ -7,6 +7,13 @@ pub struct DsConfig {
|
|||
pub base_dn: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct IpaConfig {
|
||||
pub uri: String,
|
||||
pub realm: String,
|
||||
pub admin_pw: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct KaniHttpConfig {
|
||||
pub uri: String,
|
||||
|
@ -43,6 +50,7 @@ pub struct Profile {
|
|||
pub data: String,
|
||||
pub results: String,
|
||||
pub ds_config: Option<DsConfig>,
|
||||
pub ipa_config: Option<IpaConfig>,
|
||||
pub kani_http_config: Option<KaniHttpConfig>,
|
||||
pub kani_ldap_config: Option<KaniLdapConfig>,
|
||||
#[serde(default)]
|
||||
|
|
|
@ -76,7 +76,6 @@ async fn basic_arbiter(
|
|||
|
||||
loop {
|
||||
match raw_results_rx.recv_deadline(end_of_test) {
|
||||
// We are currently discarding results.
|
||||
Ok(datum) => results.push(datum),
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
break;
|
||||
|
|
|
@ -6,6 +6,7 @@ use uuid::Uuid;
|
|||
|
||||
use crate::data::TestData;
|
||||
use crate::ds::DirectoryServer;
|
||||
use crate::ipa::IpaServer;
|
||||
use crate::kani::{KaniHttpServer, KaniLdapServer};
|
||||
use crate::profile::Profile;
|
||||
use crate::{TargetOpt, TargetServer};
|
||||
|
@ -54,6 +55,14 @@ pub(crate) fn config(
|
|||
return Err(());
|
||||
}
|
||||
}
|
||||
TargetOpt::Ipa => {
|
||||
if let Some(ipaconfig) = profile.ipa_config.as_ref() {
|
||||
IpaServer::new(ipaconfig)?
|
||||
} else {
|
||||
error!("To use ipa, you must have the ipa_config section in your profile");
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
TargetOpt::KanidmLdap => {
|
||||
if let Some(klconfig) = profile.kani_ldap_config.as_ref() {
|
||||
KaniLdapServer::new(klconfig)?
|
||||
|
|
Loading…
Reference in a new issue