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:
Firstyear 2023-01-23 20:04:03 +10:00 committed by GitHub
parent 8255c937e5
commit ec5e7abe8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 574 additions and 48 deletions

View file

@ -4,8 +4,6 @@ CONTAINER_TOOL_ARGS ?=
IMAGE_ARCH ?= "linux/amd64,linux/arm64" IMAGE_ARCH ?= "linux/amd64,linux/arm64"
CONTAINER_BUILD_ARGS ?= CONTAINER_BUILD_ARGS ?=
MARKDOWN_FORMAT_ARGS ?= --options-line-width=100 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 CONTAINER_TOOL ?= docker
BOOK_VERSION ?= master BOOK_VERSION ?= master

View file

@ -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 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 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 ## Developer Getting Started

View file

@ -19,7 +19,6 @@ RUN zypper install -y \
pam-devel \ pam-devel \
libudev-devel \ libudev-devel \
sqlite3-devel \ sqlite3-devel \
sccache \
rsync rsync
RUN zypper clean -a RUN zypper clean -a
RUN rustup default stable RUN rustup default stable
@ -34,17 +33,6 @@ RUN echo Features $KANIDM_FEATURES
ENV CARGO_HOME=/scratch/.cargo ENV CARGO_HOME=/scratch/.cargo
ENV RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=/usr/bin/ld.lld" 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/ WORKDIR /usr/src/kanidm/
# build the CLI # build the CLI
RUN if [ -z "${KANIDM_FEATURES}" ]; then \ RUN if [ -z "${KANIDM_FEATURES}" ]; then \
@ -65,8 +53,6 @@ else \
--release; \ --release; \
fi fi
RUN if [ "${SCCACHE_REDIS}" != "" ]; then sccache -s; fi
RUN ls -al /usr/src/kanidm/target/release RUN ls -al /usr/src/kanidm/target/release
# == Construct the tools container # == Construct the tools container

View file

@ -13,7 +13,6 @@ RUN zypper install -y \
make automake autoconf \ make automake autoconf \
libopenssl-devel pam-devel \ libopenssl-devel pam-devel \
sqlite3-devel \ sqlite3-devel \
sccache \
gcc \ gcc \
rsync \ rsync \
findutils \ findutils \
@ -23,7 +22,6 @@ RUN rustup default stable
COPY . /usr/src/kanidm COPY . /usr/src/kanidm
ARG SCCACHE_REDIS=""
ARG KANIDM_FEATURES ARG KANIDM_FEATURES
ARG KANIDM_BUILD_PROFILE="container_generic" ARG KANIDM_BUILD_PROFILE="container_generic"
ARG KANIDM_BUILD_OPTIONS="" ARG KANIDM_BUILD_OPTIONS=""
@ -32,21 +30,14 @@ RUN mkdir /scratch
RUN echo $KANIDM_BUILD_PROFILE RUN echo $KANIDM_BUILD_PROFILE
RUN echo $KANIDM_FEATURES RUN echo $KANIDM_FEATURES
ENV CARGO_HOME=/scratch/.cargo 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. # # This can't be used in the wasm build for now.
# # ENV RUSTFLAGS="-Clinker=clang" # # ENV RUSTFLAGS="-Clinker=clang"
# RUN if [ "${SCCACHE_REDIS}" != "" ]; \ RUN ./build_wasm.sh
# then \
# export CARGO_INCREMENTAL=false && \
# export RUSTC_WRAPPER=sccache && \
# sccache --start-server; \
# fi && \
# ./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" ENV RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=/usr/bin/ld.lld"
# Exports don't persist through RUN statements. # Exports don't persist through RUN statements.
RUN if [ "${SCCACHE_REDIS}" != "" ]; \ RUN export CC="/usr/bin/clang"; \
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 && \
if [ -z "${KANIDM_FEATURES}" ]; then \ if [ -z "${KANIDM_FEATURES}" ]; then \
cargo build -p daemon ${KANIDM_BUILD_OPTIONS} \ cargo build -p daemon ${KANIDM_BUILD_OPTIONS} \
--target-dir="/usr/src/kanidm/target/" \ --target-dir="/usr/src/kanidm/target/" \
@ -75,8 +58,7 @@ else \
--target-dir="/usr/src/kanidm/target/" \ --target-dir="/usr/src/kanidm/target/" \
--features="${KANIDM_FEATURES}" \ --features="${KANIDM_FEATURES}" \
--release; \ --release; \
fi && \ fi
if [ "${SCCACHE_REDIS}" != "" ]; then sccache -s; fi
RUN ls -al /usr/src/kanidm/target/release RUN ls -al /usr/src/kanidm/target/release

View file

@ -46,6 +46,10 @@ impl Account {
format!("uid={},ou=people,{}", self.name.as_str(), basedn) 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 { pub fn generate(uuid: Uuid) -> Self {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let id: u64 = rng.gen(); let id: u64 = rng.gen();
@ -73,6 +77,10 @@ impl Group {
format!("cn={},ou=groups,{}", self.name.as_str(), basedn) 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 { pub fn generate(uuid: Uuid, members: Vec<Uuid>) -> Self {
let mut rng = rand::thread_rng(); 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 { pub fn get_entity_type(&self) -> EntityType {
match self { match self {
Entity::Account(a) => EntityType::Account(a.uuid), Entity::Account(a) => EntityType::Account(a.uuid),

163
orca/src/generate.rs Normal file
View 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
View 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
}
}

View file

@ -291,7 +291,12 @@ impl KaniHttpServer {
} }
pub async fn close_connection(&self) { 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( pub async fn search(

View file

@ -135,6 +135,11 @@ impl LdapClient {
.await .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( pub async fn open_user_connection(
&self, &self,
test_start: Instant, test_start: Instant,

View file

@ -16,17 +16,21 @@ static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
extern crate tracing; extern crate tracing;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use uuid::Uuid; use uuid::Uuid;
use crate::ds::DirectoryServer; use crate::ds::DirectoryServer;
use crate::ipa::IpaServer;
use crate::kani::{KaniHttpServer, KaniLdapServer}; use crate::kani::{KaniHttpServer, KaniLdapServer};
use crate::setup::config;
mod data; mod data;
mod ds; mod ds;
mod generate;
mod ipa;
mod kani; mod kani;
mod ldap; mod ldap;
mod preprocess; mod preprocess;
@ -39,6 +43,8 @@ include!("./opt.rs");
impl OrcaOpt { impl OrcaOpt {
pub fn debug(&self) -> bool { pub fn debug(&self) -> bool {
match self { match self {
OrcaOpt::TestConnection(opt) => opt.copt.debug,
OrcaOpt::Generate(opt) => opt.copt.debug,
OrcaOpt::PreProc(opt) => opt.copt.debug, OrcaOpt::PreProc(opt) => opt.copt.debug,
OrcaOpt::Setup(opt) => opt.copt.debug, OrcaOpt::Setup(opt) => opt.copt.debug,
OrcaOpt::Run(opt) => opt.copt.debug, OrcaOpt::Run(opt) => opt.copt.debug,
@ -50,6 +56,7 @@ pub enum TargetServerBuilder {
Kanidm(String, String), Kanidm(String, String),
KanidmLdap(String, String, String, String), KanidmLdap(String, String, String, String),
DirSrv(String, String, String), DirSrv(String, String, String),
Ipa(String, String, String),
} }
impl TargetServerBuilder { impl TargetServerBuilder {
@ -59,6 +66,7 @@ impl TargetServerBuilder {
TargetServerBuilder::Kanidm(a, b) => KaniHttpServer::build(a, b), TargetServerBuilder::Kanidm(a, b) => KaniHttpServer::build(a, b),
TargetServerBuilder::KanidmLdap(a, b, c, d) => KaniLdapServer::build(a, b, c, d), TargetServerBuilder::KanidmLdap(a, b, c, d) => KaniLdapServer::build(a, b, c, d),
TargetServerBuilder::DirSrv(a, b, c) => DirectoryServer::build(a, b, c), 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), Kanidm(KaniHttpServer),
KanidmLdap(Box<KaniLdapServer>), KanidmLdap(Box<KaniLdapServer>),
DirSrv(DirectoryServer), DirSrv(DirectoryServer),
Ipa(IpaServer),
} }
impl TargetServer { impl TargetServer {
@ -76,6 +85,7 @@ impl TargetServer {
TargetServer::Kanidm(k) => k.info(), TargetServer::Kanidm(k) => k.info(),
TargetServer::KanidmLdap(k) => k.info(), TargetServer::KanidmLdap(k) => k.info(),
TargetServer::DirSrv(k) => k.info(), TargetServer::DirSrv(k) => k.info(),
TargetServer::Ipa(k) => k.info(),
} }
} }
@ -84,6 +94,7 @@ impl TargetServer {
TargetServer::Kanidm(_) => "kanidm_http", TargetServer::Kanidm(_) => "kanidm_http",
TargetServer::KanidmLdap(_) => "kanidm_ldap", TargetServer::KanidmLdap(_) => "kanidm_ldap",
TargetServer::DirSrv(_) => "directory_server", TargetServer::DirSrv(_) => "directory_server",
TargetServer::Ipa(_) => "ipa",
} }
} }
@ -92,6 +103,7 @@ impl TargetServer {
TargetServer::Kanidm(k) => k.builder(), TargetServer::Kanidm(k) => k.builder(),
TargetServer::KanidmLdap(k) => k.builder(), TargetServer::KanidmLdap(k) => k.builder(),
TargetServer::DirSrv(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::Kanidm(k) => k.open_admin_connection().await,
TargetServer::KanidmLdap(k) => k.open_admin_connection().await, TargetServer::KanidmLdap(k) => k.open_admin_connection().await,
TargetServer::DirSrv(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::Kanidm(k) => k.setup_admin_delete_uuids(targets).await,
TargetServer::KanidmLdap(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::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) k.setup_admin_precreate_entities(targets, all_entities)
.await .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::Kanidm(k) => k.setup_access_controls(access, all_entities).await,
TargetServer::KanidmLdap(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::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::Kanidm(k) => k.open_user_connection(test_start, name, pw).await,
TargetServer::KanidmLdap(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::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::Kanidm(k) => k.close_connection().await,
TargetServer::KanidmLdap(k) => k.close_connection().await, TargetServer::KanidmLdap(k) => k.close_connection().await,
TargetServer::DirSrv(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::Kanidm(k) => k.search(test_start, ids).await,
TargetServer::KanidmLdap(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::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] #[tokio::main]
async fn main() { async fn main() {
let opt = OrcaOpt::parse(); let opt = OrcaOpt::parse();
@ -193,6 +231,10 @@ async fn main() {
info!("Orca - the Kanidm Load Testing Utility."); info!("Orca - the Kanidm Load Testing Utility.");
debug!("cli -> {:?}", opt); debug!("cli -> {:?}", opt);
match 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::PreProc(opt) => preprocess::doit(&opt.input_path, &opt.output_path),
OrcaOpt::Setup(opt) => { OrcaOpt::Setup(opt) => {
let _ = setup::doit(&opt.target, &opt.profile_path).await; let _ = setup::doit(&opt.target, &opt.profile_path).await;
@ -204,5 +246,5 @@ async fn main() {
// run the test! // run the test!
} }
}; };
info!("Success"); debug!("Exit");
} }

View file

@ -19,13 +19,20 @@ struct PreProcOpt {
pub output_path: PathBuf, 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)] #[derive(Debug, Parser)]
struct SetupOpt { struct SetupOpt {
#[clap(flatten)] #[clap(flatten)]
pub copt: CommonOpt, pub copt: CommonOpt,
#[clap(name = "target")] #[clap(name = "target")]
/// Which service to target during this operation.
/// Valid values are "ds" or "kanidm"
pub target: TargetOpt, pub target: TargetOpt,
#[clap(parse(from_os_str), short, long = "profile")] #[clap(parse(from_os_str), short, long = "profile")]
/// Path to the test profile. /// Path to the test profile.
@ -37,8 +44,6 @@ struct RunOpt {
#[clap(flatten)] #[clap(flatten)]
pub copt: CommonOpt, pub copt: CommonOpt,
#[clap(name = "target")] #[clap(name = "target")]
/// Which service to target during this operation.
/// Valid values are "ds" or "kanidm"
pub target: TargetOpt, pub target: TargetOpt,
#[clap(name = "test_type")] #[clap(name = "test_type")]
/// Which type of test to run against this system /// Which type of test to run against this system
@ -49,10 +54,14 @@ struct RunOpt {
} }
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// The target to run against
pub(crate) enum TargetOpt { pub(crate) enum TargetOpt {
#[clap(name = "ds")] #[clap(name = "ds")]
/// Run against the ldap/ds profile /// Run against the ldap/ds profile
Ds, Ds,
#[clap(name = "ipa")]
/// Run against the ipa profile
Ipa,
#[clap(name = "kanidm")] #[clap(name = "kanidm")]
/// Run against the kanidm http profile /// Run against the kanidm http profile
Kanidm, Kanidm,
@ -67,9 +76,10 @@ impl FromStr for TargetOpt {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
"ds" => Ok(TargetOpt::Ds), "ds" => Ok(TargetOpt::Ds),
"ipa" => Ok(TargetOpt::Ipa),
"kanidm" => Ok(TargetOpt::Kanidm), "kanidm" => Ok(TargetOpt::Kanidm),
"kanidm_ldap" => Ok(TargetOpt::KanidmLdap), "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 { 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")] #[clap(name = "preprocess")]
/// Preprocess a dataset that can be used for testing /// Preprocess a dataset that can be used for testing
PreProc(PreProcOpt), PreProc(PreProcOpt),

View file

@ -7,6 +7,13 @@ pub struct DsConfig {
pub base_dn: String, pub base_dn: String,
} }
#[derive(Debug, Deserialize)]
pub struct IpaConfig {
pub uri: String,
pub realm: String,
pub admin_pw: String,
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct KaniHttpConfig { pub struct KaniHttpConfig {
pub uri: String, pub uri: String,
@ -43,6 +50,7 @@ pub struct Profile {
pub data: String, pub data: String,
pub results: String, pub results: String,
pub ds_config: Option<DsConfig>, pub ds_config: Option<DsConfig>,
pub ipa_config: Option<IpaConfig>,
pub kani_http_config: Option<KaniHttpConfig>, pub kani_http_config: Option<KaniHttpConfig>,
pub kani_ldap_config: Option<KaniLdapConfig>, pub kani_ldap_config: Option<KaniLdapConfig>,
#[serde(default)] #[serde(default)]

View file

@ -76,7 +76,6 @@ async fn basic_arbiter(
loop { loop {
match raw_results_rx.recv_deadline(end_of_test) { match raw_results_rx.recv_deadline(end_of_test) {
// We are currently discarding results.
Ok(datum) => results.push(datum), Ok(datum) => results.push(datum),
Err(RecvTimeoutError::Timeout) => { Err(RecvTimeoutError::Timeout) => {
break; break;

View file

@ -6,6 +6,7 @@ use uuid::Uuid;
use crate::data::TestData; use crate::data::TestData;
use crate::ds::DirectoryServer; use crate::ds::DirectoryServer;
use crate::ipa::IpaServer;
use crate::kani::{KaniHttpServer, KaniLdapServer}; use crate::kani::{KaniHttpServer, KaniLdapServer};
use crate::profile::Profile; use crate::profile::Profile;
use crate::{TargetOpt, TargetServer}; use crate::{TargetOpt, TargetServer};
@ -54,6 +55,14 @@ pub(crate) fn config(
return Err(()); 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 => { TargetOpt::KanidmLdap => {
if let Some(klconfig) = profile.kani_ldap_config.as_ref() { if let Some(klconfig) = profile.kani_ldap_config.as_ref() {
KaniLdapServer::new(klconfig)? KaniLdapServer::new(klconfig)?