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"
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

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
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

View file

@ -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

View file

@ -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

View file

@ -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
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) {
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(

View file

@ -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,

View file

@ -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");
}

View file

@ -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),

View file

@ -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)]

View file

@ -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;

View file

@ -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)?