mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
20221103 ipa import driver (#1180)
This commit is contained in:
parent
2e864be37f
commit
1ed4d7c1bd
122
Cargo.lock
generated
122
Cargo.lock
generated
|
@ -134,7 +134,7 @@ dependencies = [
|
|||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.16",
|
||||
"time 0.3.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -596,9 +596,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
|||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.12.2"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aec14f5d4e6e3f927cd0c81f72e5710d95ee9019fbeb4b3021193867491bfd8"
|
||||
checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
|
@ -769,9 +769,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
|||
|
||||
[[package]]
|
||||
name = "compact_jwt"
|
||||
version = "0.2.8"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5656b98b1584764a52906e67caec20dfb9b0179ac2052d0d5937b083bc39a120"
|
||||
checksum = "51f9032b96a89dd79ffc5f62523d5351ebb40680cbdfc4029393b511b9e971aa"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"base64urlsafedata",
|
||||
|
@ -869,7 +869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time 0.3.16",
|
||||
"time 0.3.17",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -885,7 +885,7 @@ dependencies = [
|
|||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time 0.3.16",
|
||||
"time 0.3.17",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
@ -2193,9 +2193,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.5.0"
|
||||
version = "2.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
|
||||
checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
|
@ -2245,6 +2245,27 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kanidm-ipa-sync"
|
||||
version = "1.1.0-alpha.11-dev"
|
||||
dependencies = [
|
||||
"base64urlsafedata",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"kanidm_client",
|
||||
"kanidm_proto",
|
||||
"kanidmd_lib",
|
||||
"ldap3_client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"users",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kanidm_client"
|
||||
version = "1.1.0-alpha.11-dev"
|
||||
|
@ -2269,6 +2290,7 @@ dependencies = [
|
|||
"base32",
|
||||
"base64urlsafedata",
|
||||
"last-git-commit",
|
||||
"scim_proto",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time 0.2.27",
|
||||
|
@ -2518,16 +2540,36 @@ dependencies = [
|
|||
"nom 2.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ldap3_client"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/kanidm/ldap3.git#6b0d146d3f85a32add3bdc3639ba4146822eb861"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"base64urlsafedata",
|
||||
"futures-util",
|
||||
"ldap3_proto",
|
||||
"openssl",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tokio-openssl",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ldap3_proto"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62d7f04b6dc4d5401b817596e424ecb4a0931db1418f3987a27e0ab69320665e"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/kanidm/ldap3.git#6b0d146d3f85a32add3bdc3639ba4146822eb861"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"lber",
|
||||
"nom 7.1.1",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2768,9 +2810,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
|
||||
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
|
@ -2886,9 +2928,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.1"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
|
@ -2915,15 +2957,6 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oauth2"
|
||||
version = "4.2.3"
|
||||
|
@ -3256,9 +3289,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
|
@ -3539,9 +3572,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
|
@ -3747,6 +3780,21 @@ dependencies = [
|
|||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scim_proto"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/kanidm/scim.git#f7a9241bf413ac2e40cb974876d2b0433a866c74"
|
||||
dependencies = [
|
||||
"base64urlsafedata",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time 0.2.27",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
|
@ -4411,16 +4459,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.16"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca"
|
||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||
dependencies = [
|
||||
"itoa 1.0.4",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros 0.2.5",
|
||||
"time-macros 0.2.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4441,9 +4487,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b"
|
||||
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
@ -5250,7 +5296,7 @@ dependencies = [
|
|||
"oid-registry",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.16",
|
||||
"time 0.3.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5370,5 +5416,5 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"quick-error",
|
||||
"regex",
|
||||
"time 0.3.16",
|
||||
"time 0.3.17",
|
||||
]
|
||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -5,6 +5,7 @@ lto = "thin"
|
|||
|
||||
[workspace]
|
||||
members = [
|
||||
"iam_migrations/freeipa",
|
||||
"kanidm_client",
|
||||
"kanidm_proto",
|
||||
"kanidm_tools",
|
||||
|
@ -83,7 +84,17 @@ kanidm_unix_int = { path = "./kanidm_unix_int" }
|
|||
last-git-commit = "0.2.0"
|
||||
# REMOVE this
|
||||
lazy_static = "^1.4.0"
|
||||
ldap3_proto = "^0.2.3"
|
||||
# ldap3_client = "^0.3.0"
|
||||
# ldap3_proto = "^0.3.0"
|
||||
|
||||
# ldap3_client = { path = "../ldap3/client", version = "0.3.0" }
|
||||
# ldap3_proto = { path = "../ldap3/proto", version = "0.3.0" }
|
||||
# scim_proto = { path = "../scim/proto", version = "0.1.0" }
|
||||
|
||||
ldap3_client = { git = "https://github.com/kanidm/ldap3.git", version = "0.3.0" }
|
||||
ldap3_proto = { git = "https://github.com/kanidm/ldap3.git", version = "0.3.0" }
|
||||
scim_proto = { git = "https://github.com/kanidm/scim.git", version = "0.1.0" }
|
||||
|
||||
libc = "^0.2.135"
|
||||
libnss = "^0.4.0"
|
||||
libsqlite3-sys = "^0.25.0"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
dn: cn=Retro Changelog Plugin,cn=plugins,cn=config
|
||||
changetype: modify
|
||||
add: nsslapd-include-suffix
|
||||
nsslapd-include-suffix: cn=accounts,dc=dev,dc=kanidm,dc=com
|
||||
nsslapd-include-suffix: dc=dev,dc=kanidm,dc=com
|
||||
|
|
36
iam_migrations/freeipa/Cargo.toml
Normal file
36
iam_migrations/freeipa/Cargo.toml
Normal file
|
@ -0,0 +1,36 @@
|
|||
[package]
|
||||
name = "kanidm-ipa-sync"
|
||||
description = "Kanidm Client Tools"
|
||||
documentation = "https://kanidm.github.io/kanidm/stable/"
|
||||
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64urlsafedata.workspace = true
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
kanidm_client.workspace = true
|
||||
kanidm_proto.workspace = true
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
tracing.workspace = true
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
|
||||
|
||||
users.workspace = true
|
||||
|
||||
ldap3_client.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
toml.workspace = true
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
|
||||
# For file metadata, should this me moved out?
|
||||
kanidmd_lib.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
clap_complete.workspace = true
|
12
iam_migrations/freeipa/src/config.rs
Normal file
12
iam_migrations/freeipa/src/config.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub sync_token: String,
|
||||
pub ipa_uri: Url,
|
||||
pub ipa_ca: String,
|
||||
pub ipa_sync_dn: String,
|
||||
pub ipa_sync_pw: String,
|
||||
pub ipa_sync_base_dn: String,
|
||||
}
|
507
iam_migrations/freeipa/src/main.rs
Normal file
507
iam_migrations/freeipa/src/main.rs
Normal file
|
@ -0,0 +1,507 @@
|
|||
#![deny(warnings)]
|
||||
#![warn(unused_extern_crates)]
|
||||
#![deny(clippy::todo)]
|
||||
#![deny(clippy::unimplemented)]
|
||||
#![deny(clippy::unwrap_used)]
|
||||
#![deny(clippy::panic)]
|
||||
#![deny(clippy::unreachable)]
|
||||
#![deny(clippy::await_holding_lock)]
|
||||
#![deny(clippy::needless_pass_by_value)]
|
||||
#![deny(clippy::trivially_copy_pass_by_ref)]
|
||||
// We allow expect since it forces good error messages at the least.
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
mod config;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::config::Config;
|
||||
use clap::Parser;
|
||||
use std::fs::metadata;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::thread;
|
||||
use tokio::runtime;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
use kanidm_client::KanidmClientBuilder;
|
||||
use kanidm_proto::scim_v1::{
|
||||
ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest, ScimSyncState,
|
||||
};
|
||||
use kanidmd_lib::utils::file_permissions_readonly;
|
||||
|
||||
use users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
|
||||
|
||||
use ldap3_client::{
|
||||
proto, proto::LdapFilter, LdapClientBuilder, LdapSyncRepl, LdapSyncReplEntry,
|
||||
LdapSyncStateValue,
|
||||
};
|
||||
|
||||
include!("./opt.rs");
|
||||
|
||||
async fn driver_main(opt: Opt) {
|
||||
debug!("Starting kanidm freeipa sync driver.");
|
||||
// Parse the configs.
|
||||
|
||||
let mut f = match File::open(&opt.ipa_sync_config) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
error!("Unable to open profile file [{:?}] 🥺", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut contents = String::new();
|
||||
if let Err(e) = f.read_to_string(&mut contents) {
|
||||
error!("unable to read profile contents {:?}", e);
|
||||
return;
|
||||
};
|
||||
|
||||
let sync_config: Config = match toml::from_str(contents.as_str()) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("unable to parse config {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
debug!(?sync_config);
|
||||
|
||||
let cb = match KanidmClientBuilder::new().read_options_from_optional_config(&opt.client_config)
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
error!("Failed to parse {}", opt.client_config.to_string_lossy());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Do we need this?
|
||||
// let cb = cb.connect_timeout(cfg.conn_timeout);
|
||||
|
||||
let rsclient = match cb.build() {
|
||||
Ok(rsc) => rsc,
|
||||
Err(_e) => {
|
||||
error!("Failed to build async client");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
rsclient.set_token(sync_config.sync_token.clone()).await;
|
||||
|
||||
// Preflight check.
|
||||
// * can we connect to ipa?
|
||||
|
||||
let mut ipa_client = match LdapClientBuilder::new(&sync_config.ipa_uri)
|
||||
.add_tls_ca(&sync_config.ipa_ca)
|
||||
.build()
|
||||
.await
|
||||
{
|
||||
Ok(lc) => lc,
|
||||
Err(e) => {
|
||||
error!(?e, "Failed to connect to freeipa");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match ipa_client
|
||||
.bind(
|
||||
sync_config.ipa_sync_dn.clone(),
|
||||
sync_config.ipa_sync_pw.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
debug!(ipa_sync_dn = ?sync_config.ipa_sync_dn, ipa_uri = %sync_config.ipa_uri);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(?e, "Failed to bind (authenticate) to freeipa");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// * can we connect to kanidm?
|
||||
// - get the current sync cookie from kanidm.
|
||||
let scim_sync_status = match rsclient.scim_v1_sync_status().await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!(?e, "Failed to access scim sync status");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
debug!(state=?scim_sync_status);
|
||||
|
||||
// === Everything is connected! ===
|
||||
|
||||
// Based on the scim_sync_status, perform our sync repl
|
||||
|
||||
let mode = proto::SyncRequestMode::RefreshOnly;
|
||||
|
||||
let cookie = match &scim_sync_status {
|
||||
ScimSyncState::Refresh => None,
|
||||
ScimSyncState::Active { cookie } => Some(cookie.0.clone()),
|
||||
};
|
||||
|
||||
let filter = LdapFilter::Or(vec![
|
||||
// LdapFilter::Equality("objectclass".to_string(), "domain".to_string()),
|
||||
LdapFilter::And(vec![
|
||||
LdapFilter::Equality("objectclass".to_string(), "person".to_string()),
|
||||
LdapFilter::Equality("objectclass".to_string(), "ipantuserattrs".to_string()),
|
||||
LdapFilter::Equality("objectclass".to_string(), "posixaccount".to_string()),
|
||||
]),
|
||||
LdapFilter::And(vec![
|
||||
LdapFilter::Equality("objectclass".to_string(), "groupofnames".to_string()),
|
||||
LdapFilter::Equality("objectclass".to_string(), "ipausergroup".to_string()),
|
||||
LdapFilter::Not(Box::new(LdapFilter::Equality(
|
||||
"objectclass".to_string(),
|
||||
"mepmanagedentry".to_string(),
|
||||
))),
|
||||
// Need to exclude the admins group as it gid conflicts to admin.
|
||||
LdapFilter::Not(Box::new(LdapFilter::Equality(
|
||||
"cn".to_string(),
|
||||
"admins".to_string(),
|
||||
))),
|
||||
// Kani internally has an all persons group.
|
||||
LdapFilter::Not(Box::new(LdapFilter::Equality(
|
||||
"cn".to_string(),
|
||||
"ipausers".to_string(),
|
||||
))),
|
||||
]),
|
||||
LdapFilter::And(vec![
|
||||
LdapFilter::Equality("objectclass".to_string(), "ipatoken".to_string()),
|
||||
LdapFilter::Equality("objectclass".to_string(), "ipatokentotp".to_string()),
|
||||
]),
|
||||
]);
|
||||
|
||||
debug!(ipa_sync_base_dn = ?sync_config.ipa_sync_base_dn, ?cookie, ?mode, ?filter);
|
||||
let sync_result = match ipa_client
|
||||
.syncrepl(sync_config.ipa_sync_base_dn, filter, cookie, mode)
|
||||
.await
|
||||
{
|
||||
Ok(results) => results,
|
||||
Err(e) => {
|
||||
error!(?e, "Failed to perform syncrepl from ipa");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if opt.proto_dump {
|
||||
let stdout = std::io::stdout();
|
||||
if let Err(e) = serde_json::to_writer_pretty(stdout, &sync_result) {
|
||||
error!(?e, "Failed to serialise ldap sync response");
|
||||
}
|
||||
}
|
||||
|
||||
// pre-process the entries.
|
||||
// - > fn so we can test.
|
||||
let scim_sync_request = match process_ipa_sync_result(scim_sync_status, sync_result).await {
|
||||
Ok(ssr) => ssr,
|
||||
Err(()) => return,
|
||||
};
|
||||
|
||||
if opt.proto_dump {
|
||||
let stdout = std::io::stdout();
|
||||
// write it out.
|
||||
if let Err(e) = serde_json::to_writer_pretty(stdout, &scim_sync_request) {
|
||||
error!(?e, "Failed to serialise scim sync request");
|
||||
};
|
||||
} else {
|
||||
if let Err(e) = rsclient.scim_v1_sync_update(&scim_sync_request).await {
|
||||
error!(
|
||||
?e,
|
||||
"Failed to submit scim sync update - see the kanidmd server log for more details."
|
||||
);
|
||||
};
|
||||
}
|
||||
// done!
|
||||
}
|
||||
|
||||
async fn process_ipa_sync_result(
|
||||
from_state: ScimSyncState,
|
||||
sync_result: LdapSyncRepl,
|
||||
) -> Result<ScimSyncRequest, ()> {
|
||||
match sync_result {
|
||||
LdapSyncRepl::Success {
|
||||
cookie,
|
||||
refresh_deletes,
|
||||
entries,
|
||||
delete_uuids,
|
||||
present_uuids,
|
||||
} => {
|
||||
if refresh_deletes {
|
||||
error!("Unsure how to handle refreshDeletes=True");
|
||||
return Err(());
|
||||
}
|
||||
|
||||
if !present_uuids.is_empty() {
|
||||
error!("Unsure how to handle presentUuids > 0");
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let to_state = cookie
|
||||
.map(|cookie| {
|
||||
ScimSyncState::Active { cookie }
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
error!("Invalid state, ldap sync repl did not provide a valid state cookie in response.");
|
||||
})?;
|
||||
|
||||
// Future - make this par-map
|
||||
let entries = entries
|
||||
.into_iter()
|
||||
.filter_map(|e| match ipa_to_scim_entry(e) {
|
||||
Ok(Some(e)) => Some(Ok(e)),
|
||||
Ok(None) => None,
|
||||
Err(()) => Some(Err(())),
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
|
||||
let entries = match entries {
|
||||
Ok(e) => e,
|
||||
Err(()) => {
|
||||
error!("Failed to process IPA entries to SCIM");
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ScimSyncRequest {
|
||||
from_state,
|
||||
to_state,
|
||||
entries,
|
||||
delete_uuids,
|
||||
})
|
||||
}
|
||||
LdapSyncRepl::RefreshRequired => {
|
||||
let to_state = ScimSyncState::Refresh;
|
||||
|
||||
Ok(ScimSyncRequest {
|
||||
from_state,
|
||||
to_state,
|
||||
entries: Vec::new(),
|
||||
delete_uuids: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ipa_to_scim_entry(sync_entry: LdapSyncReplEntry) -> Result<Option<ScimEntry>, ()> {
|
||||
debug!("{:#?}", sync_entry);
|
||||
// Is this an entry we need to observe/look at?
|
||||
|
||||
// check the sync_entry state?
|
||||
if sync_entry.state != LdapSyncStateValue::Add {
|
||||
todo!();
|
||||
}
|
||||
|
||||
let dn = sync_entry.entry.dn.clone();
|
||||
|
||||
let oc = sync_entry.entry.attrs.get("objectclass").ok_or_else(|| {
|
||||
error!("Invalid entry - no object class {}", dn);
|
||||
})?;
|
||||
|
||||
if oc.contains("person") {
|
||||
let LdapSyncReplEntry {
|
||||
entry_uuid,
|
||||
state: _,
|
||||
mut entry,
|
||||
} = sync_entry;
|
||||
|
||||
let id = entry_uuid;
|
||||
|
||||
let user_name = entry.remove_ava_single("uid").ok_or_else(|| {
|
||||
error!("Missing required attribute uid");
|
||||
})?;
|
||||
|
||||
let display_name = entry.remove_ava_single("cn").ok_or_else(|| {
|
||||
error!("Missing required attribute cn");
|
||||
})?;
|
||||
|
||||
let gidnumber = entry
|
||||
.remove_ava_single("gidnumber")
|
||||
.map(|gid| {
|
||||
u32::from_str(&gid).map_err(|_| {
|
||||
error!("Invalid gidnumber");
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let homedirectory = entry.remove_ava_single("homedirectory");
|
||||
let password_import = entry.remove_ava_single("ipanthash");
|
||||
let login_shell = entry.remove_ava_single("loginshell");
|
||||
let external_id = Some(entry.dn);
|
||||
|
||||
Ok(Some(
|
||||
ScimSyncPerson {
|
||||
id,
|
||||
external_id,
|
||||
user_name,
|
||||
display_name,
|
||||
gidnumber,
|
||||
homedirectory,
|
||||
password_import,
|
||||
login_shell,
|
||||
}
|
||||
.into(),
|
||||
))
|
||||
} else if oc.contains("groupofnames") {
|
||||
let LdapSyncReplEntry {
|
||||
entry_uuid,
|
||||
state: _,
|
||||
mut entry,
|
||||
} = sync_entry;
|
||||
|
||||
let id = entry_uuid;
|
||||
|
||||
let name = entry.remove_ava_single("cn").ok_or_else(|| {
|
||||
error!("Missing required attribute cn");
|
||||
})?;
|
||||
|
||||
let description = entry.remove_ava_single("description");
|
||||
|
||||
let gidnumber = entry
|
||||
.remove_ava_single("gidnumber")
|
||||
.map(|gid| {
|
||||
u32::from_str(&gid).map_err(|_| {
|
||||
error!("Invalid gidnumber");
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let members: Vec<_> = entry
|
||||
.remove_ava("member")
|
||||
.map(|set| {
|
||||
set.into_iter()
|
||||
.map(|external_id| ScimExternalMember { external_id })
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let external_id = Some(entry.dn);
|
||||
|
||||
Ok(Some(
|
||||
ScimSyncGroup {
|
||||
id,
|
||||
external_id,
|
||||
name,
|
||||
description,
|
||||
gidnumber,
|
||||
members,
|
||||
}
|
||||
.into(),
|
||||
))
|
||||
} else if oc.contains("ipatokentotp") {
|
||||
// Skip for now, we don't supporty multiple totp yet.
|
||||
Ok(None)
|
||||
} else {
|
||||
debug!("Skipping entry {} with oc {:?}", dn, oc);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn config_security_checks(cfg_path: &Path) -> bool {
|
||||
let cfg_path_str = cfg_path.to_string_lossy();
|
||||
|
||||
if !cfg_path.exists() {
|
||||
// there's no point trying to start up if we can't read a usable config!
|
||||
error!(
|
||||
"Config missing from {} - cannot start up. Quitting.",
|
||||
cfg_path_str
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
let cfg_meta = match metadata(&cfg_path) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("Unable to read metadata for {} - {:?}", cfg_path_str, e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if !file_permissions_readonly(&cfg_meta) {
|
||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
|
||||
cfg_path_str
|
||||
);
|
||||
}
|
||||
|
||||
let cuid = get_current_uid();
|
||||
let ceuid = get_effective_uid();
|
||||
|
||||
if cfg_meta.uid() == cuid || cfg_meta.uid() == ceuid {
|
||||
warn!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...",
|
||||
cfg_path_str
|
||||
);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cuid = get_current_uid();
|
||||
let ceuid = get_effective_uid();
|
||||
let cgid = get_current_gid();
|
||||
let cegid = get_effective_gid();
|
||||
|
||||
let opt = Opt::parse();
|
||||
|
||||
let fmt_layer = fmt::layer().with_writer(std::io::stderr);
|
||||
|
||||
let filter_layer = if opt.debug {
|
||||
match EnvFilter::try_new("kanidm_client=debug,kanidm_ipa_sync=debug,ldap3_client=debug") {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
eprintln!("ERROR! Unable to start tracing {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match EnvFilter::try_from_default_env() {
|
||||
Ok(f) => f,
|
||||
Err(_) => EnvFilter::new("kanidm_client=warn,kanidm_ipa_sync=info,ldap3_client=warn"),
|
||||
}
|
||||
};
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(filter_layer)
|
||||
.with(fmt_layer)
|
||||
.init();
|
||||
|
||||
// Startup sanity checks.
|
||||
if opt.skip_root_check {
|
||||
warn!("Skipping root user check, if you're running this for testing, ensure you clean up temporary files.")
|
||||
// TODO: this wording is not great m'kay.
|
||||
} else {
|
||||
if cuid == 0 || ceuid == 0 || cgid == 0 || cegid == 0 {
|
||||
error!("Refusing to run - this process must not operate as root.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !config_security_checks(&opt.client_config) || !config_security_checks(&opt.ipa_sync_config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let par_count = thread::available_parallelism()
|
||||
.expect("Failed to determine available parallelism")
|
||||
.get();
|
||||
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
// We configure this as we use parallel workers at some points.
|
||||
.max_blocking_threads(par_count)
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to initialise tokio runtime!");
|
||||
|
||||
tracing::debug!("Using {} worker threads", par_count);
|
||||
|
||||
rt.block_on(async move { driver_main(opt).await });
|
||||
|
||||
info!("Success!");
|
||||
}
|
31
iam_migrations/freeipa/src/opt.rs
Normal file
31
iam_migrations/freeipa/src/opt.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
|
||||
|
||||
use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH;
|
||||
pub const DEFAULT_IPA_CONFIG_PATH: &str = "/etc/kanidm/ipa-sync";
|
||||
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
#[clap(about = "Kanidm FreeIPA Sync Driver")]
|
||||
pub struct Opt {
|
||||
/// Enable debbuging of the sync driver
|
||||
#[clap(short, long, env = "KANIDM_DEBUG")]
|
||||
pub debug: bool,
|
||||
/// Path to the client config file.
|
||||
#[clap(parse(from_os_str), short, long, default_value_os_t = DEFAULT_CLIENT_CONFIG_PATH.into())]
|
||||
pub client_config: PathBuf,
|
||||
|
||||
/// Path to the ipa-sync config file.
|
||||
#[clap(parse(from_os_str), short, long, default_value_os_t = DEFAULT_IPA_CONFIG_PATH.into())]
|
||||
pub ipa_sync_config: PathBuf,
|
||||
|
||||
#[clap(short, long, hide = true)]
|
||||
/// Dump the ldap protocol inputs, as well as the scim outputs. This can be used
|
||||
/// to create test cases for testing the parser.
|
||||
///
|
||||
/// No actions are taken on the kanidm instance, this is purely a dump of the
|
||||
/// state in/out.
|
||||
pub proto_dump: bool,
|
||||
|
||||
#[clap(short, long, hide = true)]
|
||||
pub skip_root_check: bool,
|
||||
}
|
423
iam_migrations/freeipa/src/tests.rs
Normal file
423
iam_migrations/freeipa/src/tests.rs
Normal file
|
@ -0,0 +1,423 @@
|
|||
use crate::process_ipa_sync_result;
|
||||
use kanidm_proto::scim_v1::ScimSyncState;
|
||||
|
||||
use ldap3_client::LdapSyncRepl;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ldap_to_scim() {
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
|
||||
let sync_request: LdapSyncRepl =
|
||||
serde_json::from_str(TEST_LDAP_SYNC_REPL_1).expect("failed to parse ldap sync");
|
||||
|
||||
let scim_sync_request = process_ipa_sync_result(ScimSyncState::Refresh, sync_request)
|
||||
.await
|
||||
.expect("failed to process ldap sync repl");
|
||||
|
||||
assert!(matches!(
|
||||
scim_sync_request.from_state,
|
||||
ScimSyncState::Refresh
|
||||
));
|
||||
|
||||
// need to setup a fake ldap sync result.
|
||||
|
||||
// What do we expect?
|
||||
}
|
||||
|
||||
const TEST_LDAP_SYNC_REPL_1: &str = r#"
|
||||
{
|
||||
"Success": {
|
||||
"cookie": "aXBhLXN5bmNyZXBsLWthbmkuZGV2LmJsYWNraGF0cy5uZXQuYXU6Mzg5I2NuPWRpcmVjdG9yeSBtYW5hZ2VyOmRjPWRldixkYz1ibGFja2hhdHMsZGM9bmV0LGRjPWF1Oih8KCYob2JqZWN0Q2xhc3M9cGVyc29uKShvYmplY3RDbGFzcz1pcGFudHVzZXJhdHRycykob2JqZWN0Q2xhc3M9cG9zaXhhY2NvdW50KSkoJihvYmplY3RDbGFzcz1ncm91cG9mbmFtZXMpKG9iamVjdENsYXNzPWlwYXVzZXJncm91cCkoIShvYmplY3RDbGFzcz1tZXBtYW5hZ2VkZW50cnkpKSkoJihvYmplY3RDbGFzcz1pcGF0b2tlbikob2JqZWN0Q2xhc3M9aXBhdG9rZW50b3RwKSkpIzEwOQ",
|
||||
"refresh_deletes": false,
|
||||
"entries": [
|
||||
{
|
||||
"entry_uuid": "ac60034b-3498-11ed-a50d-919b4b1a5ec0",
|
||||
"state": "Add",
|
||||
"entry": {
|
||||
"dn": "uid=admin,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"attrs": {
|
||||
"cn": [
|
||||
"Administrator"
|
||||
],
|
||||
"gecos": [
|
||||
"Administrator"
|
||||
],
|
||||
"gidnumber": [
|
||||
"8200000"
|
||||
],
|
||||
"homedirectory": [
|
||||
"/home/admin"
|
||||
],
|
||||
"ipanthash": [
|
||||
"CVBguEizG80swI8sftaknw"
|
||||
],
|
||||
"ipantsecurityidentifier": [
|
||||
"S-1-5-21-148961183-2750130983-218252910-500"
|
||||
],
|
||||
"ipauniqueid": [
|
||||
"ad15f644-3498-11ed-95c3-5254006b0418"
|
||||
],
|
||||
"krbextradata": [
|
||||
"AAL4hSJjcm9vdC9hZG1pbkBERVYuQkxBQ0tIQVRTLk5FVC5BVQA"
|
||||
],
|
||||
"krblastadminunlock": [
|
||||
"20220915015504Z"
|
||||
],
|
||||
"krblastfailedauth": [
|
||||
"20221108050316Z"
|
||||
],
|
||||
"krblastpwdchange": [
|
||||
"20220915015504Z"
|
||||
],
|
||||
"krbloginfailedcount": [
|
||||
"0"
|
||||
],
|
||||
"krbpasswordexpiration": [
|
||||
"20221214015504Z"
|
||||
],
|
||||
"krbprincipalkey": [
|
||||
"MIIB1KADAgEBoQMCAQGiAwIBAaMDAgEBpIIBvDCCAbgwdKAbMBmgAwIBBKESBBBgeEMvRkhoVWphRX0iKXxCoVUwU6ADAgEUoUwESiAAuyt8szEUVLiWVjSTuUgbgCf8heFMeIhSmGTgJpwL50kddprbdeKuOYvyxepdAil/MqHs4qdqj54reDDqFW0T2bg1Iv9O1cZEMGSgGzAZoAMCAQShEgQQU2xOXT16V21hPFkzPClsJKFFMEOgAwIBE6E8BDoQALfdG+243xBQDt01+bFr46DcZnlHctoSyUQKw8I8FzvRE1LK9Ttl5qkkOHADpA7XSj1lQ2RFqBsSMHSgGzAZoAMCAQShEgQQay9XSC9tPDJJVjIwUDxFRKFVMFOgAwIBEqFMBEogADJjxICRFFzpOcsxMY3xVedF3IBd7qzsQJlSvShaeKwyhTBFI/wvVDtQq6ogWKlACUcAVk2N6p91VtRHHjxXVhKQvT0kt/KS7zBkoBswGaADAgEEoRIEEE5nNTh5SmgpZic0bDAmNUWhRTBDoAMCARGhPAQ6EAClGqBf9jZWixZo/evVMVH01NkI1VpR0fNrGyvtML78p5j6TAne5Nms/wj9BtVawuv+h+Gz1fjdfw"
|
||||
],
|
||||
"krbprincipalname": [
|
||||
"admin@DEV.BLACKHATS.NET.AU",
|
||||
"root@DEV.BLACKHATS.NET.AU"
|
||||
],
|
||||
"loginshell": [
|
||||
"/bin/bash"
|
||||
],
|
||||
"memberof": [
|
||||
"cn=Add Configuration Sub-Entries,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Add Replication Agreements,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Host Enrollment,cn=privileges,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Modify DNA Range,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Modify PassSync Managers Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Modify Replication Agreements,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Read DNA Range,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Read LDBM Database Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Read PassSync Managers Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Read Replication Agreements,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Read Replication Changelog Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Remove Replication Agreements,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Replication Administrators,cn=privileges,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=System: Add krbPrincipalName to a Host,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=System: Enroll a Host,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=System: Manage Host Certificates,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=System: Manage Host Enrollment Password,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=System: Manage Host Keytab,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=System: Manage Host Principals,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=Write Replication Changelog Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=admins,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"cn=trust admins,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||
],
|
||||
"objectclass": [
|
||||
"inetuser",
|
||||
"ipantuserattrs",
|
||||
"ipaobject",
|
||||
"ipasshgroupofpubkeys",
|
||||
"ipasshuser",
|
||||
"krbprincipalaux",
|
||||
"krbticketpolicyaux",
|
||||
"person",
|
||||
"posixaccount",
|
||||
"top"
|
||||
],
|
||||
"sn": [
|
||||
"Administrator"
|
||||
],
|
||||
"uid": [
|
||||
"admin"
|
||||
],
|
||||
"uidnumber": [
|
||||
"8200000"
|
||||
],
|
||||
"userpassword": [
|
||||
"{PBKDF2_SHA256}AAAIAJ3EnyWJXp/ytIk6sqf1BbLO9fzObD3q5I4y2bRFfgAFVo6CaRAaZ7KPYzU6Y340VSUV4NGRRcBjeU8q+aoTOkuzQM91jl+xlCydiB0CjeIDZ0tGy4NmQUFzfg7+exsKhNk2MfUrHcaqfZBtT7Lkfei4Rk7810TQf3NlHIRO8K3egPQ8Ox52Upw1E5QGEKQmDOjrtLtOF5gbyFtR5wc0wUJfmMhd/g65GkqFIr5vbPan3kL3ZqMhh1rrj4ISi9Ui8P7E8GDicoJDPwPf6YD9D0dx6yk72GyiuYt6p2aGJWMY897xqgB+YMgPptiDPik22ExoBAoHeJNIzKjITc2ohLLn6RkCk4GcCwMVZmcxesl/T/OMeSkNvoOM1zy7ANsGbQeaLqpViJSV0xT5PJ6NoIKMU2pIP57Q17VAlYigtCPU"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"entry_uuid": "ac60034e-3498-11ed-a50d-919b4b1a5ec0",
|
||||
"state": "Add",
|
||||
"entry": {
|
||||
"dn": "cn=editors,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"attrs": {
|
||||
"cn": [
|
||||
"editors"
|
||||
],
|
||||
"description": [
|
||||
"Limited admins who can edit other users"
|
||||
],
|
||||
"gidnumber": [
|
||||
"8200002"
|
||||
],
|
||||
"ipantsecurityidentifier": [
|
||||
"S-1-5-21-148961183-2750130983-218252910-1002"
|
||||
],
|
||||
"ipauniqueid": [
|
||||
"ad191e00-3498-11ed-b143-5254006b0418"
|
||||
],
|
||||
"objectclass": [
|
||||
"groupofnames",
|
||||
"ipantgroupattrs",
|
||||
"ipaobject",
|
||||
"ipausergroup",
|
||||
"nestedgroup",
|
||||
"posixgroup",
|
||||
"top"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"entry_uuid": "0c56a965-3499-11ed-a50d-919b4b1a5ec0",
|
||||
"state": "Add",
|
||||
"entry": {
|
||||
"dn": "cn=trust admins,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"attrs": {
|
||||
"cn": [
|
||||
"trust admins"
|
||||
],
|
||||
"description": [
|
||||
"Trusts administrators group"
|
||||
],
|
||||
"ipauniqueid": [
|
||||
"0f233c48-3499-11ed-8e23-5254006b0418"
|
||||
],
|
||||
"member": [
|
||||
"uid=admin,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||
],
|
||||
"objectclass": [
|
||||
"groupofnames",
|
||||
"ipaobject",
|
||||
"ipausergroup",
|
||||
"nestedgroup",
|
||||
"top"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"entry_uuid": "babb8302-43a1-11ed-a50d-919b4b1a5ec0",
|
||||
"state": "Add",
|
||||
"entry": {
|
||||
"dn": "uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"attrs": {
|
||||
"cn": [
|
||||
"Test User"
|
||||
],
|
||||
"displayname": [
|
||||
"Test User"
|
||||
],
|
||||
"gecos": [
|
||||
"Test User"
|
||||
],
|
||||
"gidnumber": [
|
||||
"12345"
|
||||
],
|
||||
"givenname": [
|
||||
"Test"
|
||||
],
|
||||
"homedirectory": [
|
||||
"/home/testuser"
|
||||
],
|
||||
"initials": [
|
||||
"TU"
|
||||
],
|
||||
"ipanthash": [
|
||||
"iEb36u6PsRetBr3YMLdYbA"
|
||||
],
|
||||
"ipantsecurityidentifier": [
|
||||
"S-1-5-21-148961183-2750130983-218252910-1004"
|
||||
],
|
||||
"ipauniqueid": [
|
||||
"d939d566-43a1-11ed-85aa-5254006b0418"
|
||||
],
|
||||
"ipauserauthtype": [
|
||||
"password"
|
||||
],
|
||||
"krbcanonicalname": [
|
||||
"testuser@DEV.BLACKHATS.NET.AU"
|
||||
],
|
||||
"krbextradata": [
|
||||
"AAL732ljdGVzdHVzZXJAREVWLkJMQUNLSEFUUy5ORVQuQVUA"
|
||||
],
|
||||
"krblastadminunlock": [
|
||||
"20221108044931Z"
|
||||
],
|
||||
"krblastfailedauth": [
|
||||
"20221108045207Z"
|
||||
],
|
||||
"krblastpwdchange": [
|
||||
"20221108045003Z"
|
||||
],
|
||||
"krbloginfailedcount": [
|
||||
"0"
|
||||
],
|
||||
"krbpasswordexpiration": [
|
||||
"20230206045003Z"
|
||||
],
|
||||
"krbprincipalkey": [
|
||||
"MIIB1KADAgEBoQMCAQGiAwIBBKMDAgEBpIIBvDCCAbgwdKAbMBmgAwIBBKESBBAhIyRjVSl7LkVCXCZqISkkoVUwU6ADAgEUoUwESiAAyMSJYrMnu6mUnDV3ls7arH782SiSi1+vSFosLoLogJZQHKAxUljESwhySlEn+tAEF3yEenvuigNNDtFS/cYMn4oQ1c/vH4tnMGSgGzAZoAMCAQShEgQQOGVcI0Q7YS53OCdxcmd6WaFFMEOgAwIBE6E8BDoQAG6TZ38sFh9gXirZsZcZEiFls92uUh1+Azz7DxrCpo0B8+he39ACvuwLIaxzfswHZE8/pQUFRiHeMHSgGzAZoAMCAQShEgQQOFpvIFxvRlFEVilZVkYhRaFVMFOgAwIBEqFMBEogANBXnuehcaBtCPIXvaGcUEXXkGxiHlDIBFhXeu6l6w0Nj2Cm8Fezun8ip+si3JuxZkaK7TlxccZQOpjxSRuwekeKrzTNp+vS7jBkoBswGaADAgEEoRIEEFZDXFBrSEZDO0kuX2BORyihRTBDoAMCARGhPAQ6EAChj/DZFH3h9pW31ipzT4PrtdDcR83qla52bf+bLDV6LFV6FvFqq3fBJnpiIwuD9rPBBuDut+1ncg"
|
||||
],
|
||||
"krbprincipalname": [
|
||||
"testuser@DEV.BLACKHATS.NET.AU"
|
||||
],
|
||||
"loginshell": [
|
||||
"/bin/sh"
|
||||
],
|
||||
"mail": [
|
||||
"testuser@dev.blackhats.net.au"
|
||||
],
|
||||
"memberof": [
|
||||
"cn=ipausers,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||
],
|
||||
"mepmanagedentry": [
|
||||
"cn=testuser,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||
],
|
||||
"objectclass": [
|
||||
"inetorgperson",
|
||||
"inetuser",
|
||||
"ipantuserattrs",
|
||||
"ipaobject",
|
||||
"ipasshgroupofpubkeys",
|
||||
"ipasshuser",
|
||||
"ipauserauthtypeclass",
|
||||
"krbprincipalaux",
|
||||
"krbticketpolicyaux",
|
||||
"meporiginentry",
|
||||
"organizationalperson",
|
||||
"person",
|
||||
"posixaccount",
|
||||
"top"
|
||||
],
|
||||
"sn": [
|
||||
"User"
|
||||
],
|
||||
"uid": [
|
||||
"testuser"
|
||||
],
|
||||
"uidnumber": [
|
||||
"8200004"
|
||||
],
|
||||
"userpassword": [
|
||||
"{PBKDF2_SHA256}AAAIAOTKJTaS7zR1u0ar5vDHPzcd9FoDiQVYvpT/n19NpTQJKJfdugke9vwpYxaZk+SnR/WHi4oeKd1IyaVmAC+H5d4qUYcc74xLGoyaezCNy8HkKBz9Q/9MD/gvzUjWUTYjbnXAMjzVpAHhVtzAoPrZVoWgXWkhga+YDsqKnqG0g1UeMTgja2zYr0mrG6Y+w+VJP3nnbQ9q4vpb7MGIs8xgjse+nIWZC+mPrK4ZEjSeE9Tjj+0C6nFq1+xU6KZK8NOG8kuHyVeS87zddJApqLSb2p6X/ixobak1j8VzXFd9lxewMfY+gieoXtn47KCFsquWGlavY4ZqjHYu4+MuHDTN/s8E06O/DkLLxPPO4iSH1B6pIaVTMHxsybX7FRLTj/MOb2+oYwWZty8WJ+dRD7gDg0vdUJr/H8EzJkrdXhNyz7f+"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"entry_uuid": "f4dbef82-5f20-11ed-a50d-919b4b1a5ec0",
|
||||
"state": "Add",
|
||||
"entry": {
|
||||
"dn": "ipatokenuniqueid=380e27a4-438d-4c94-9dde-a3f6bc64ea1a,cn=otp,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"attrs": {
|
||||
"ipatokenotpalgorithm": [
|
||||
"sha1"
|
||||
],
|
||||
"ipatokenotpdigits": [
|
||||
"6"
|
||||
],
|
||||
"ipatokenotpkey": [
|
||||
"hS/2a0DXBSKMJIlIcAJbBfCCZN0b8aTpoaJcj0RAAlV7QRQ"
|
||||
],
|
||||
"ipatokenowner": [
|
||||
"uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||
],
|
||||
"ipatokentotpclockoffset": [
|
||||
"0"
|
||||
],
|
||||
"ipatokentotptimestep": [
|
||||
"30"
|
||||
],
|
||||
"ipatokenuniqueid": [
|
||||
"380e27a4-438d-4c94-9dde-a3f6bc64ea1a"
|
||||
],
|
||||
"objectclass": [
|
||||
"ipatoken",
|
||||
"ipatokentotp",
|
||||
"top"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"entry_uuid": "d547c581-5f26-11ed-a50d-919b4b1a5ec0",
|
||||
"state": "Add",
|
||||
"entry": {
|
||||
"dn": "cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"attrs": {
|
||||
"cn": [
|
||||
"testgroup"
|
||||
],
|
||||
"description": [
|
||||
"Test group"
|
||||
],
|
||||
"ipauniqueid": [
|
||||
"f1b96e6c-5f26-11ed-8cd2-5254006b0418"
|
||||
],
|
||||
"objectclass": [
|
||||
"groupofnames",
|
||||
"ipaobject",
|
||||
"ipausergroup",
|
||||
"nestedgroup",
|
||||
"top"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"entry_uuid": "d547c583-5f26-11ed-a50d-919b4b1a5ec0",
|
||||
"state": "Add",
|
||||
"entry": {
|
||||
"dn": "cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"attrs": {
|
||||
"cn": [
|
||||
"testexternal"
|
||||
],
|
||||
"ipauniqueid": [
|
||||
"f67fd292-5f26-11ed-a6d0-5254006b0418"
|
||||
],
|
||||
"objectclass": [
|
||||
"groupofnames",
|
||||
"ipaexternalgroup",
|
||||
"ipaobject",
|
||||
"ipausergroup",
|
||||
"nestedgroup",
|
||||
"top"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"entry_uuid": "f90b0b81-5f26-11ed-a50d-919b4b1a5ec0",
|
||||
"state": "Add",
|
||||
"entry": {
|
||||
"dn": "cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"attrs": {
|
||||
"cn": [
|
||||
"testposix"
|
||||
],
|
||||
"gidnumber": [
|
||||
"1234567"
|
||||
],
|
||||
"ipauniqueid": [
|
||||
"fb64973e-5f26-11ed-9cfe-5254006b0418"
|
||||
],
|
||||
"objectclass": [
|
||||
"groupofnames",
|
||||
"ipaobject",
|
||||
"ipausergroup",
|
||||
"nestedgroup",
|
||||
"posixgroup",
|
||||
"top"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"delete_uuids": [],
|
||||
"present_uuids": []
|
||||
}
|
||||
}
|
||||
"#;
|
|
@ -38,6 +38,7 @@ use webauthn_rs_proto::{
|
|||
};
|
||||
|
||||
mod person;
|
||||
mod scim;
|
||||
mod service_account;
|
||||
mod sync_account;
|
||||
mod system;
|
||||
|
|
16
kanidm_client/src/scim.rs
Normal file
16
kanidm_client/src/scim.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use crate::{ClientError, KanidmClient};
|
||||
use kanidm_proto::scim_v1::{ScimSyncRequest, ScimSyncState};
|
||||
|
||||
impl KanidmClient {
|
||||
pub async fn scim_v1_sync_status(&self) -> Result<ScimSyncState, ClientError> {
|
||||
self.perform_get_request("/scim/v1/Sync").await
|
||||
}
|
||||
|
||||
pub async fn scim_v1_sync_update(
|
||||
&self,
|
||||
scim_sync_request: &ScimSyncRequest,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_post_request("/scim/v1/Sync", scim_sync_request)
|
||||
.await
|
||||
}
|
||||
}
|
|
@ -31,4 +31,21 @@ impl KanidmClient {
|
|||
self.perform_post_request("/v1/sync_account", new_acct)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_sync_account_generate_token(
|
||||
&self,
|
||||
id: &str,
|
||||
label: &str,
|
||||
) -> Result<String, ClientError> {
|
||||
self.perform_post_request(
|
||||
format!("/v1/sync_account/{}/_sync_token", id).as_str(),
|
||||
label,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_sync_account_destroy_token(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(format!("/v1/sync_account/{}/_sync_token", id,).as_str())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ wasm = ["webauthn-rs-proto/wasm"]
|
|||
[dependencies]
|
||||
base32.workspace = true
|
||||
base64urlsafedata.workspace = true
|
||||
scim_proto.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
time = { workspace = true, features = ["serde", "std"] }
|
||||
|
|
|
@ -1,9 +1,155 @@
|
|||
use base64urlsafedata::Base64UrlSafeData;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use scim_proto::prelude::{ScimEntry, ScimError};
|
||||
use scim_proto::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum ScimSyncState {
|
||||
Initial,
|
||||
Refresh,
|
||||
Active { cookie: Base64UrlSafeData },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ScimSyncRequest {
|
||||
pub from_state: ScimSyncState,
|
||||
pub to_state: ScimSyncState,
|
||||
|
||||
// How do I want to represent different entities to kani? Split by type? All in one?
|
||||
pub entries: Vec<ScimEntry>,
|
||||
// Delete uuids?
|
||||
pub delete_uuids: Vec<Uuid>,
|
||||
}
|
||||
|
||||
pub const SCIM_SCHEMA_SYNC_PERSON: &str = "urn:ietf:params:scim:schemas:kanidm:1.0:sync:person";
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
#[serde(into = "ScimEntry")]
|
||||
pub struct ScimSyncPerson {
|
||||
pub id: Uuid,
|
||||
pub external_id: Option<String>,
|
||||
pub user_name: String,
|
||||
pub display_name: String,
|
||||
pub gidnumber: Option<u32>,
|
||||
pub homedirectory: Option<String>,
|
||||
pub password_import: Option<String>,
|
||||
pub login_shell: Option<String>,
|
||||
}
|
||||
|
||||
/*
|
||||
impl TryFrom<ScimEntry> for ScimSyncPerson {
|
||||
type Error = ScimError;
|
||||
|
||||
fn try_from(_value: ScimEntry) -> Result<Self, Self::Error> {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl Into<ScimEntry> for ScimSyncPerson {
|
||||
fn into(self) -> ScimEntry {
|
||||
let ScimSyncPerson {
|
||||
id,
|
||||
external_id,
|
||||
user_name,
|
||||
display_name,
|
||||
gidnumber,
|
||||
homedirectory,
|
||||
password_import,
|
||||
login_shell,
|
||||
} = self;
|
||||
|
||||
let schemas = vec![SCIM_SCHEMA_SYNC_PERSON.to_string()];
|
||||
|
||||
let mut attrs = BTreeMap::default();
|
||||
|
||||
set_string!(attrs, "userName", user_name);
|
||||
set_string!(attrs, "displayName", display_name);
|
||||
set_option_u32!(attrs, "gidNumber", gidnumber);
|
||||
set_option_string!(attrs, "homeDirectory", homedirectory);
|
||||
set_option_string!(attrs, "passwordImport", password_import);
|
||||
set_option_string!(attrs, "loginShell", login_shell);
|
||||
|
||||
ScimEntry {
|
||||
schemas,
|
||||
id,
|
||||
external_id,
|
||||
meta: None,
|
||||
attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const SCIM_SCHEMA_SYNC_GROUP: &str = "urn:ietf:params:scim:schemas:kanidm:1.0:sync:group";
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct ScimExternalMember {
|
||||
pub external_id: String,
|
||||
}
|
||||
|
||||
impl Into<ScimComplexAttr> for ScimExternalMember {
|
||||
fn into(self) -> ScimComplexAttr {
|
||||
let ScimExternalMember { external_id } = self;
|
||||
let mut attrs = BTreeMap::default();
|
||||
|
||||
attrs.insert(
|
||||
"external_id".to_string(),
|
||||
ScimSimpleAttr::String(external_id),
|
||||
);
|
||||
|
||||
ScimComplexAttr { attrs }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
#[serde(into = "ScimEntry")]
|
||||
pub struct ScimSyncGroup {
|
||||
pub id: Uuid,
|
||||
pub external_id: Option<String>,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub gidnumber: Option<u32>,
|
||||
pub members: Vec<ScimExternalMember>,
|
||||
}
|
||||
|
||||
/*
|
||||
impl TryFrom<ScimEntry> for ScimSyncPerson {
|
||||
type Error = ScimError;
|
||||
|
||||
fn try_from(_value: ScimEntry) -> Result<Self, Self::Error> {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl Into<ScimEntry> for ScimSyncGroup {
|
||||
fn into(self) -> ScimEntry {
|
||||
let ScimSyncGroup {
|
||||
id,
|
||||
external_id,
|
||||
name,
|
||||
description,
|
||||
gidnumber,
|
||||
members,
|
||||
} = self;
|
||||
|
||||
let schemas = vec![SCIM_SCHEMA_SYNC_GROUP.to_string()];
|
||||
|
||||
let mut attrs = BTreeMap::default();
|
||||
|
||||
set_string!(attrs, "name", name);
|
||||
set_option_u32!(attrs, "gidNumber", gidnumber);
|
||||
set_option_string!(attrs, "description", description);
|
||||
set_multi_complex!(attrs, "members", members);
|
||||
|
||||
ScimEntry {
|
||||
schemas,
|
||||
id,
|
||||
external_id,
|
||||
meta: None,
|
||||
attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "kanidm_tools"
|
||||
default-run = "kanidm"
|
||||
description = "Kanidm Client Tools"
|
||||
documentation = "https://docs.rs/kanidm_tools/latest/kanidm_tools/"
|
||||
documentation = "https://kanidm.github.io/kanidm/stable/"
|
||||
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
|
|
@ -30,6 +30,7 @@ pub mod raw;
|
|||
pub mod recycle;
|
||||
pub mod serviceaccount;
|
||||
pub mod session;
|
||||
pub mod synch;
|
||||
|
||||
impl SelfOpt {
|
||||
pub fn debug(&self) -> bool {
|
||||
|
@ -68,6 +69,7 @@ impl SystemOpt {
|
|||
SystemOpt::PwBadlist { commands } => commands.debug(),
|
||||
SystemOpt::Oauth2 { commands } => commands.debug(),
|
||||
SystemOpt::Domain { commands } => commands.debug(),
|
||||
SystemOpt::Synch { commands } => commands.debug(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,6 +78,7 @@ impl SystemOpt {
|
|||
SystemOpt::PwBadlist { commands } => commands.exec().await,
|
||||
SystemOpt::Oauth2 { commands } => commands.exec().await,
|
||||
SystemOpt::Domain { commands } => commands.exec().await,
|
||||
SystemOpt::Synch { commands } => commands.exec().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
68
kanidm_tools/src/cli/synch.rs
Normal file
68
kanidm_tools/src/cli/synch.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use crate::SynchOpt;
|
||||
|
||||
impl SynchOpt {
|
||||
pub fn debug(&self) -> bool {
|
||||
match self {
|
||||
SynchOpt::List(copt) => copt.debug,
|
||||
SynchOpt::Get(nopt) => nopt.copt.debug,
|
||||
SynchOpt::Create { copt, .. }
|
||||
| SynchOpt::GenerateToken { copt, .. }
|
||||
| SynchOpt::DestroyToken { copt, .. } => copt.debug,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn exec(&self) {
|
||||
match self {
|
||||
SynchOpt::List(copt) => {
|
||||
let client = copt.to_client().await;
|
||||
match client.idm_sync_account_list().await {
|
||||
Ok(r) => r.iter().for_each(|ent| println!("{}", ent)),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
SynchOpt::Get(nopt) => {
|
||||
let client = nopt.copt.to_client().await;
|
||||
match client.idm_sync_account_get(nopt.name.as_str()).await {
|
||||
Ok(Some(e)) => println!("{}", e),
|
||||
Ok(None) => println!("No matching entries"),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
SynchOpt::Create {
|
||||
account_id,
|
||||
copt,
|
||||
description,
|
||||
} => {
|
||||
let client = copt.to_client().await;
|
||||
match client
|
||||
.idm_sync_account_create(&account_id, description.as_deref())
|
||||
.await
|
||||
{
|
||||
Ok(()) => println!("Success"),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
SynchOpt::GenerateToken {
|
||||
account_id,
|
||||
label,
|
||||
copt,
|
||||
} => {
|
||||
let client = copt.to_client().await;
|
||||
match client
|
||||
.idm_sync_account_generate_token(&account_id, &label)
|
||||
.await
|
||||
{
|
||||
Ok(token) => println!("token: {}", token),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
SynchOpt::DestroyToken { account_id, copt } => {
|
||||
let client = copt.to_client().await;
|
||||
match client.idm_sync_account_destroy_token(&account_id).await {
|
||||
Ok(()) => println!("Success"),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -732,6 +732,42 @@ pub enum DomainOpt {
|
|||
ResetTokenKey(CommonOpt),
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum SynchOpt {
|
||||
#[clap(name = "list")]
|
||||
/// List all configured IDM sync accounts
|
||||
List(CommonOpt),
|
||||
#[clap(name = "get")]
|
||||
/// Display a selected IDM sync account
|
||||
Get(Named),
|
||||
/// Create a new IDM sync account
|
||||
#[clap(name = "create")]
|
||||
Create {
|
||||
#[clap()]
|
||||
account_id: String,
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
#[clap(name = "description")]
|
||||
description: Option<String>,
|
||||
},
|
||||
#[clap(name = "generate-token")]
|
||||
GenerateToken {
|
||||
#[clap()]
|
||||
account_id: String,
|
||||
#[clap()]
|
||||
label: String,
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
},
|
||||
#[clap(name = "destroy-token")]
|
||||
DestroyToken {
|
||||
#[clap()]
|
||||
account_id: String,
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum SystemOpt {
|
||||
#[clap(name = "pw-badlist")]
|
||||
|
@ -752,6 +788,11 @@ pub enum SystemOpt {
|
|||
#[clap(subcommand)]
|
||||
commands: DomainOpt,
|
||||
},
|
||||
#[clap(name = "sync", hide = true)]
|
||||
Synch {
|
||||
#[clap(subcommand)]
|
||||
commands: SynchOpt,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
|
|
|
@ -37,7 +37,7 @@ use kanidmd_lib::{
|
|||
// ===========================================================
|
||||
|
||||
pub struct QueryServerReadV1 {
|
||||
idms: Arc<IdmServer>,
|
||||
pub(crate) idms: Arc<IdmServer>,
|
||||
ldap: Arc<LdapServer>,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use kanidmd_lib::prelude::*;
|
||||
|
||||
use crate::QueryServerWriteV1;
|
||||
use kanidmd_lib::idm::scim::GenerateScimSyncTokenEvent;
|
||||
use crate::{QueryServerReadV1, QueryServerWriteV1};
|
||||
use kanidmd_lib::idm::scim::{GenerateScimSyncTokenEvent, ScimSyncUpdateEvent};
|
||||
use kanidmd_lib::idm::server::IdmServerTransaction;
|
||||
|
||||
use kanidm_proto::scim_v1::{ScimSyncRequest, ScimSyncState};
|
||||
|
||||
impl QueryServerWriteV1 {
|
||||
#[instrument(
|
||||
level = "info",
|
||||
|
@ -77,4 +79,48 @@ impl QueryServerWriteV1 {
|
|||
.sync_account_destroy_token(&ident, target, ct)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
skip_all,
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn handle_scim_sync_apply(
|
||||
&self,
|
||||
bearer: Option<String>,
|
||||
changes: ScimSyncRequest,
|
||||
eventid: Uuid,
|
||||
) -> Result<(), OperationError> {
|
||||
let ct = duration_from_epoch_now();
|
||||
let mut idms_prox_write = self.idms.proxy_write(ct).await;
|
||||
|
||||
let ident =
|
||||
idms_prox_write.validate_and_parse_sync_token_to_ident(bearer.as_deref(), ct)?;
|
||||
|
||||
let sse = ScimSyncUpdateEvent { ident };
|
||||
|
||||
idms_prox_write
|
||||
.scim_sync_apply(&sse, &changes, ct)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryServerReadV1 {
|
||||
#[instrument(
|
||||
level = "info",
|
||||
skip_all,
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn handle_scim_sync_status(
|
||||
&self,
|
||||
bearer: Option<String>,
|
||||
eventid: Uuid,
|
||||
) -> Result<ScimSyncState, OperationError> {
|
||||
let ct = duration_from_epoch_now();
|
||||
let idms_prox_read = self.idms.proxy_read().await;
|
||||
|
||||
let ident = idms_prox_read.validate_and_parse_sync_token_to_ident(bearer.as_deref(), ct)?;
|
||||
|
||||
idms_prox_read.scim_sync_get_state(&ident)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ pub struct AppState {
|
|||
pub trait RequestExtensions {
|
||||
fn get_current_uat(&self) -> Option<String>;
|
||||
|
||||
fn get_auth_bearer(&self) -> Option<&str>;
|
||||
fn get_auth_bearer(&self) -> Option<String>;
|
||||
|
||||
fn get_current_auth_session_id(&self) -> Option<Uuid>;
|
||||
|
||||
|
@ -84,7 +84,7 @@ pub trait RequestExtensions {
|
|||
}
|
||||
|
||||
impl RequestExtensions for tide::Request<AppState> {
|
||||
fn get_auth_bearer(&self) -> Option<&str> {
|
||||
fn get_auth_bearer(&self) -> Option<String> {
|
||||
// Contact the QS to get it to validate wtf is up.
|
||||
// let kref = &self.state().bundy_handle;
|
||||
// self.session().get::<UserAuthToken>("uat")
|
||||
|
@ -97,6 +97,7 @@ impl RequestExtensions for tide::Request<AppState> {
|
|||
// Turn it to a &str, and then check the prefix
|
||||
h.as_str().strip_prefix("Bearer ")
|
||||
})
|
||||
.map(str::to_string)
|
||||
}
|
||||
|
||||
fn get_current_uat(&self) -> Option<String> {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::routemaps::{RouteMap, RouteMaps};
|
||||
use super::{to_tide_response, AppState, RequestExtensions};
|
||||
use kanidm_proto::scim_v1::ScimSyncRequest;
|
||||
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||
use kanidmd_lib::prelude::*;
|
||||
|
||||
|
@ -93,32 +94,36 @@ pub async fn sync_account_token_delete(req: tide::Request<AppState>) -> tide::Re
|
|||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
async fn scim_sync_post(_req: tide::Request<AppState>) -> tide::Result {
|
||||
// let (eventid, hvalue) = req.new_eventid();
|
||||
/*
|
||||
let ApiTokenGenerate {
|
||||
label,
|
||||
expiry,
|
||||
read_write,
|
||||
} = req.body_json().await?;
|
||||
*/
|
||||
async fn scim_sync_post(mut req: tide::Request<AppState>) -> tide::Result {
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
|
||||
// We need to deserialise the body.
|
||||
// Given the token, and a sync update, apply the changes if any
|
||||
let bearer = req.get_auth_bearer();
|
||||
|
||||
Ok(tide::Response::new(500))
|
||||
// Change this type later.
|
||||
let changes: ScimSyncRequest = req.body_json().await?;
|
||||
|
||||
let res = req
|
||||
.state()
|
||||
.qe_w_ref
|
||||
.handle_scim_sync_apply(bearer, changes, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
async fn scim_sync_get(_req: tide::Request<AppState>) -> tide::Result {
|
||||
// let (eventid, hvalue) = req.new_eventid();
|
||||
async fn scim_sync_get(req: tide::Request<AppState>) -> tide::Result {
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
|
||||
// let bearer = req.get_auth_bearer();
|
||||
// Given the token, what is it's connected sync state?
|
||||
let bearer = req.get_auth_bearer();
|
||||
trace!(?bearer);
|
||||
|
||||
// Given the token
|
||||
// What is the connected sync session
|
||||
// Issue it's current state (version) cookie.
|
||||
|
||||
// todo!();
|
||||
Ok(tide::Response::new(500))
|
||||
let res = req
|
||||
.state()
|
||||
.qe_r_ref
|
||||
.handle_scim_sync_status(bearer, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
async fn scim_sink_get(req: tide::Request<AppState>) -> tide::Result {
|
||||
|
|
|
@ -1327,7 +1327,8 @@ pub const JSON_IDM_HP_ACP_SYNC_ACCOUNT_MANAGE_PRIV_V1: &str = r#"{
|
|||
],
|
||||
"acp_modify_presentattr": [
|
||||
"name",
|
||||
"description"
|
||||
"description",
|
||||
"sync_token_session"
|
||||
],
|
||||
"acp_modify_class": [],
|
||||
"acp_create_attr": [
|
||||
|
|
|
@ -1709,7 +1709,7 @@ impl Entry<EntryReduced, EntryCommitted> {
|
|||
// so they are all in "ldap forms" which makes our next stage a bit easier.
|
||||
|
||||
// Stage 1 - transform our results to a map of kani attr -> ldap value.
|
||||
let attr_map: Result<Map<&str, Vec<String>>, _> = self
|
||||
let attr_map: Result<Map<&str, Vec<Vec<u8>>>, _> = self
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|(k, vs)| {
|
||||
|
@ -1717,8 +1717,8 @@ impl Entry<EntryReduced, EntryCommitted> {
|
|||
.map(|pvs| (k.as_str(), pvs))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let attr_map = attr_map?;
|
||||
|
||||
// Stage 2 - transform and get all our attr - names out that we need to return.
|
||||
// ldap a, kani a
|
||||
let attr_names: Vec<(&str, &str)> = if all_attrs {
|
||||
|
@ -1748,7 +1748,7 @@ impl Entry<EntryReduced, EntryCommitted> {
|
|||
match ldap_a {
|
||||
"entrydn" => Some(LdapPartialAttribute {
|
||||
atype: "entrydn".to_string(),
|
||||
vals: vec![dn.clone()],
|
||||
vals: vec![dn.as_bytes().to_vec()],
|
||||
}),
|
||||
_ => attr_map.get(kani_a).map(|pvs| LdapPartialAttribute {
|
||||
atype: ldap_a.to_string(),
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::time::Duration;
|
|||
use base64urlsafedata::Base64UrlSafeData;
|
||||
|
||||
use compact_jwt::{Jws, JwsSigner};
|
||||
use kanidm_proto::scim_v1::ScimSyncRequest;
|
||||
use kanidm_proto::scim_v1::*;
|
||||
use kanidm_proto::v1::ApiTokenPurpose;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -225,14 +226,38 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ScimSyncUpdateEvent {}
|
||||
pub struct ScimSyncUpdateEvent {
|
||||
pub ident: Identity,
|
||||
}
|
||||
|
||||
impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||
pub fn scim_sync_update(
|
||||
pub fn scim_sync_apply(
|
||||
&mut self,
|
||||
_sse: &ScimSyncUpdateEvent,
|
||||
sse: &ScimSyncUpdateEvent,
|
||||
_changes: &ScimSyncRequest,
|
||||
_ct: Duration,
|
||||
) -> Result<(), OperationError> {
|
||||
let _sync_uuid = match &sse.ident.origin {
|
||||
IdentType::User(_) | IdentType::Internal => {
|
||||
warn!("Ident type is not synchronise");
|
||||
return Err(OperationError::AccessDenied);
|
||||
}
|
||||
IdentType::Synch(u) => {
|
||||
// Ok!
|
||||
u
|
||||
}
|
||||
};
|
||||
|
||||
match sse.ident.access_scope() {
|
||||
AccessScope::IdentityOnly | AccessScope::ReadOnly | AccessScope::ReadWrite => {
|
||||
warn!("Ident access scope is not synchronise");
|
||||
return Err(OperationError::AccessDenied);
|
||||
}
|
||||
AccessScope::Synchronise => {
|
||||
// As you were
|
||||
}
|
||||
};
|
||||
|
||||
// Only update entries related to this uuid
|
||||
// Make a sync_authority uuid to relate back to on creates.
|
||||
|
||||
|
@ -283,7 +308,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> {
|
|||
Some(b) => ScimSyncState::Active {
|
||||
cookie: Base64UrlSafeData(b.to_vec()),
|
||||
},
|
||||
None => ScimSyncState::Initial,
|
||||
None => ScimSyncState::Refresh,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -300,7 +325,7 @@ mod tests {
|
|||
use kanidm_proto::v1::ApiTokenPurpose;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::{GenerateScimSyncTokenEvent, ScimSyncToken};
|
||||
use super::{GenerateScimSyncTokenEvent, ScimSyncToken, ScimSyncUpdateEvent};
|
||||
|
||||
use async_std::task;
|
||||
|
||||
|
@ -359,7 +384,7 @@ mod tests {
|
|||
.expect("Failed to get current sync state");
|
||||
trace!(?sync_state);
|
||||
|
||||
assert!(matches!(sync_state, ScimSyncState::Initial));
|
||||
assert!(matches!(sync_state, ScimSyncState::Refresh));
|
||||
|
||||
drop(idms_prox_read);
|
||||
|
||||
|
@ -497,4 +522,141 @@ mod tests {
|
|||
}
|
||||
|
||||
// Need to delete different phases such as conflictn and end of the agreement.
|
||||
|
||||
#[test]
|
||||
fn test_idm_scim_sync_refresh_1() {
|
||||
run_idm_test!(|_qs: &QueryServer,
|
||||
idms: &IdmServer,
|
||||
_idms_delayed: &mut IdmServerDelayed| {
|
||||
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
||||
|
||||
let mut idms_prox_write = task::block_on(idms.proxy_write(ct));
|
||||
|
||||
let sync_uuid = Uuid::new_v4();
|
||||
|
||||
let e1 = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("sync_account")),
|
||||
("name", Value::new_iname("test_scim_sync")),
|
||||
("uuid", Value::new_uuid(sync_uuid)),
|
||||
("description", Value::new_utf8s("A test sync agreement"))
|
||||
);
|
||||
|
||||
let ce = CreateEvent::new_internal(vec![e1]);
|
||||
let cr = idms_prox_write.qs_write.create(&ce);
|
||||
assert!(cr.is_ok());
|
||||
|
||||
let gte = GenerateScimSyncTokenEvent::new_internal(sync_uuid, "Sync Connector");
|
||||
|
||||
let sync_token = idms_prox_write
|
||||
.scim_sync_generate_token(>e, ct)
|
||||
.expect("failed to generate new scim sync token");
|
||||
|
||||
let ident = idms_prox_write
|
||||
.validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct)
|
||||
.expect("Failed to process sync token to ident");
|
||||
|
||||
let sse = ScimSyncUpdateEvent { ident };
|
||||
|
||||
let changes =
|
||||
serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync");
|
||||
|
||||
let res = idms_prox_write.scim_sync_apply(&sse, &changes, ct);
|
||||
|
||||
// Currently in testing this is just access denied.
|
||||
assert!(matches!(res, Err(OperationError::AccessDenied)));
|
||||
|
||||
assert!(idms_prox_write.commit().is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
const TEST_SYNC_SCIM_IPA_1: &str = r#"
|
||||
{
|
||||
"from_state": "Refresh",
|
||||
"to_state": {
|
||||
"Active": {
|
||||
"cookie": "aXBhLXN5bmNyZXBsLWthbmkuZGV2LmJsYWNraGF0cy5uZXQuYXU6Mzg5I2NuPWRpcmVjdG9yeSBtYW5hZ2VyOmRjPWRldixkYz1ibGFja2hhdHMsZGM9bmV0LGRjPWF1Oih8KCYob2JqZWN0Q2xhc3M9cGVyc29uKShvYmplY3RDbGFzcz1pcGFudHVzZXJhdHRycykob2JqZWN0Q2xhc3M9cG9zaXhhY2NvdW50KSkoJihvYmplY3RDbGFzcz1ncm91cG9mbmFtZXMpKG9iamVjdENsYXNzPWlwYXVzZXJncm91cCkoIShvYmplY3RDbGFzcz1tZXBtYW5hZ2VkZW50cnkpKSghKGNuPWFkbWlucykpKCEoY249aXBhdXNlcnMpKSkoJihvYmplY3RDbGFzcz1pcGF0b2tlbikob2JqZWN0Q2xhc3M9aXBhdG9rZW50b3RwKSkpIzEwOQ"
|
||||
}
|
||||
},
|
||||
"entries": [
|
||||
{
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:person"
|
||||
],
|
||||
"id": "ac60034b-3498-11ed-a50d-919b4b1a5ec0",
|
||||
"externalId": "uid=admin,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"displayName": "Administrator",
|
||||
"gidNumber": 8200000,
|
||||
"homeDirectory": "/home/admin",
|
||||
"loginShell": "/bin/bash",
|
||||
"passwordImport": "CVBguEizG80swI8sftaknw",
|
||||
"userName": "admin"
|
||||
},
|
||||
{
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||
],
|
||||
"id": "ac60034e-3498-11ed-a50d-919b4b1a5ec0",
|
||||
"externalId": "cn=editors,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"description": "Limited admins who can edit other users",
|
||||
"gidNumber": 8200002,
|
||||
"name": "editors"
|
||||
},
|
||||
{
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||
],
|
||||
"id": "0c56a965-3499-11ed-a50d-919b4b1a5ec0",
|
||||
"externalId": "cn=trust admins,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"description": "Trusts administrators group",
|
||||
"members": [
|
||||
{
|
||||
"external_id": "uid=admin,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||
}
|
||||
],
|
||||
"name": "trust admins"
|
||||
},
|
||||
{
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:person"
|
||||
],
|
||||
"id": "babb8302-43a1-11ed-a50d-919b4b1a5ec0",
|
||||
"externalId": "uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"displayName": "Test User",
|
||||
"gidNumber": 12345,
|
||||
"homeDirectory": "/home/testuser",
|
||||
"loginShell": "/bin/sh",
|
||||
"passwordImport": "iEb36u6PsRetBr3YMLdYbA",
|
||||
"userName": "testuser"
|
||||
},
|
||||
{
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||
],
|
||||
"id": "d547c581-5f26-11ed-a50d-919b4b1a5ec0",
|
||||
"externalId": "cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"description": "Test group",
|
||||
"name": "testgroup"
|
||||
},
|
||||
{
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||
],
|
||||
"id": "d547c583-5f26-11ed-a50d-919b4b1a5ec0",
|
||||
"externalId": "cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"name": "testexternal"
|
||||
},
|
||||
{
|
||||
"schemas": [
|
||||
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||
],
|
||||
"id": "f90b0b81-5f26-11ed-a50d-919b4b1a5ec0",
|
||||
"externalId": "cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||
"gidNumber": 1234567,
|
||||
"name": "testposix"
|
||||
}
|
||||
],
|
||||
"delete_uuids": []
|
||||
}
|
||||
"#;
|
||||
}
|
||||
|
|
|
@ -86,31 +86,31 @@ impl LdapServer {
|
|||
attributes: vec![
|
||||
LdapPartialAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["top".to_string()],
|
||||
vals: vec!["top".as_bytes().to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "vendorName".to_string(),
|
||||
vals: vec!["Kanidm Project".to_string()],
|
||||
vals: vec!["Kanidm Project".as_bytes().to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "vendorVersion".to_string(),
|
||||
vals: vec!["kanidm_ldap_1.0.0".to_string()],
|
||||
vals: vec!["kanidm_ldap_1.0.0".as_bytes().to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "supportedLDAPVersion".to_string(),
|
||||
vals: vec!["3".to_string()],
|
||||
vals: vec!["3".as_bytes().to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "supportedExtension".to_string(),
|
||||
vals: vec!["1.3.6.1.4.1.4203.1.11.3".to_string()],
|
||||
vals: vec!["1.3.6.1.4.1.4203.1.11.3".as_bytes().to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "supportedFeatures".to_string(),
|
||||
vals: vec!["1.3.6.1.4.1.4203.1.5.1".to_string()],
|
||||
vals: vec!["1.3.6.1.4.1.4203.1.5.1".as_bytes().to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "defaultnamingcontext".to_string(),
|
||||
vals: vec![basedn.clone()],
|
||||
vals: vec![basedn.as_bytes().to_vec()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -717,12 +717,12 @@ mod tests {
|
|||
let mut attrs = HashSet::new();
|
||||
for a in $e.attributes.iter() {
|
||||
for v in a.vals.iter() {
|
||||
attrs.insert((a.atype.as_str(), v.as_str()));
|
||||
attrs.insert((a.atype.as_str(), v.as_slice()));
|
||||
}
|
||||
};
|
||||
$(
|
||||
assert!(attrs.contains(&(
|
||||
$item.0, $item.1
|
||||
$item.0, $item.1.as_bytes()
|
||||
)));
|
||||
)*
|
||||
|
||||
|
|
|
@ -688,22 +688,25 @@ pub trait QueryServerTransaction<'a> {
|
|||
&self,
|
||||
value: &ValueSet,
|
||||
basedn: &str,
|
||||
) -> Result<Vec<String>, OperationError> {
|
||||
) -> Result<Vec<Vec<u8>>, OperationError> {
|
||||
if let Some(r_set) = value.as_refer_set() {
|
||||
let v: Result<Vec<_>, _> = r_set
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|ur| {
|
||||
let rdn = self.uuid_to_rdn(ur)?;
|
||||
Ok(format!("{},{}", rdn, basedn))
|
||||
Ok(format!("{},{}", rdn, basedn).into_bytes())
|
||||
})
|
||||
.collect();
|
||||
v
|
||||
} else if let Some(k_set) = value.as_sshkey_map() {
|
||||
let v: Vec<_> = k_set.values().cloned().collect();
|
||||
let v: Vec<_> = k_set.values().cloned().map(|s| s.into_bytes()).collect();
|
||||
Ok(v)
|
||||
} else {
|
||||
let v: Vec<_> = value.to_proto_string_clone_iter().collect();
|
||||
let v: Vec<_> = value
|
||||
.to_proto_string_clone_iter()
|
||||
.map(|s| s.into_bytes())
|
||||
.collect();
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,11 +97,14 @@ impl DirectoryServer {
|
|||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["top".to_string(), "organizationalUnit".to_string()],
|
||||
vals: vec![
|
||||
"top".as_bytes().into(),
|
||||
"organizationalUnit".as_bytes().into(),
|
||||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "ou".to_string(),
|
||||
vals: vec!["people".to_string()],
|
||||
vals: vec!["people".as_bytes().into()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -121,11 +124,14 @@ impl DirectoryServer {
|
|||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["top".to_string(), "organizationalUnit".to_string()],
|
||||
vals: vec![
|
||||
"top".as_bytes().into(),
|
||||
"organizationalUnit".as_bytes().into(),
|
||||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "ou".to_string(),
|
||||
vals: vec!["groups".to_string()],
|
||||
vals: vec!["groups".as_bytes().into()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -155,40 +161,40 @@ impl DirectoryServer {
|
|||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec![
|
||||
"top".to_string(),
|
||||
"nsPerson".to_string(),
|
||||
"nsAccount".to_string(),
|
||||
"nsOrgPerson".to_string(),
|
||||
"posixAccount".to_string(),
|
||||
"top".as_bytes().into(),
|
||||
"nsPerson".as_bytes().into(),
|
||||
"nsAccount".as_bytes().into(),
|
||||
"nsOrgPerson".as_bytes().into(),
|
||||
"posixAccount".as_bytes().into(),
|
||||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec![a.uuid.to_string()],
|
||||
vals: vec![a.uuid.as_bytes().to_vec()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "uid".to_string(),
|
||||
vals: vec![a.name.clone()],
|
||||
vals: vec![a.name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "displayName".to_string(),
|
||||
vals: vec![a.display_name.clone()],
|
||||
vals: vec![a.display_name.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "userPassword".to_string(),
|
||||
vals: vec![a.password.clone()],
|
||||
vals: vec![a.password.as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "homeDirectory".to_string(),
|
||||
vals: vec![format!("/home/{}", a.uuid)],
|
||||
vals: vec![format!("/home/{}", a.uuid).as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "uidNumber".to_string(),
|
||||
vals: vec!["1000".to_string()],
|
||||
vals: vec!["1000".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "gidNumber".to_string(),
|
||||
vals: vec!["1000".to_string()],
|
||||
vals: vec!["1000".as_bytes().into()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -200,11 +206,14 @@ impl DirectoryServer {
|
|||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["top".to_string(), "groupOfNames".to_string()],
|
||||
vals: vec![
|
||||
"top".as_bytes().into(),
|
||||
"groupOfNames".as_bytes().into(),
|
||||
],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec![g.uuid.to_string(), g.name.clone()],
|
||||
vals: vec![g.uuid.as_bytes().to_vec(), g.name.as_bytes().into()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -222,7 +231,7 @@ impl DirectoryServer {
|
|||
}
|
||||
}) {
|
||||
// List of dns
|
||||
let vals: Vec<_> = g
|
||||
let vals: Vec<Vec<u8>> = g
|
||||
.members
|
||||
.iter()
|
||||
.map(|id| {
|
||||
|
@ -230,6 +239,8 @@ impl DirectoryServer {
|
|||
.get(id)
|
||||
.unwrap()
|
||||
.get_ds_ldap_dn(&self.ldap.basedn)
|
||||
.as_bytes()
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -270,11 +281,11 @@ impl DirectoryServer {
|
|||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["top".to_string(), "groupOfNames".to_string()],
|
||||
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["priv_account_manage".to_string()],
|
||||
vals: vec!["priv_account_manage".as_bytes().into()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -297,11 +308,11 @@ impl DirectoryServer {
|
|||
attributes: vec![
|
||||
LdapAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["top".to_string(), "groupOfNames".to_string()],
|
||||
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
||||
},
|
||||
LdapAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["priv_group_manage".to_string()],
|
||||
vals: vec!["priv_group_manage".as_bytes().into()],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -317,14 +328,14 @@ impl DirectoryServer {
|
|||
modification: LdapPartialAttribute {
|
||||
atype: "aci".to_string(),
|
||||
vals: vec![
|
||||
r#"(targetattr="dc || description || objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.to_string(),
|
||||
r#"(targetattr="dc || description || objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.as_bytes().into(),
|
||||
|
||||
r#"(targetattr="ou || objectClass")(targetfilter="(objectClass=organizationalUnit)")(version 3.0; acl "Enable anyone ou read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.to_string(),
|
||||
r#"(targetattr="cn || member || gidNumber || nsUniqueId || description || objectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable anyone group read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.to_string(),
|
||||
format!(r#"(targetattr="cn || member || gidNumber || description || objectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable group_admin to manage groups"; allow (write,add, delete)(groupdn="ldap:///cn=priv_group_manage,{}");)"#, self.ldap.basedn),
|
||||
r#"(targetattr="objectClass || description || nsUniqueId || uid || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate")(targetfilter="(objectClass=posixaccount)")(version 3.0; acl "Enable anyone user read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.to_string(),
|
||||
r#"(targetattr="displayName || legalName || userPassword || nsSshPublicKey")(version 3.0; acl "Enable self partial modify"; allow (write)(userdn="ldap:///self");)"#.to_string(),
|
||||
format!(r#"(targetattr="uid || description || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalName || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objectClass=nsAccount))")(version 3.0; acl "Enable user admin create"; allow (write, add, delete, read)(groupdn="ldap:///cn=priv_account_manage,{}");)"#, self.ldap.basedn),
|
||||
r#"(targetattr="ou || objectClass")(targetfilter="(objectClass=organizationalUnit)")(version 3.0; acl "Enable anyone ou read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.as_bytes().into(),
|
||||
r#"(targetattr="cn || member || gidNumber || nsUniqueId || description || objectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable anyone group read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.as_bytes().into(),
|
||||
format!(r#"(targetattr="cn || member || gidNumber || description || objectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable group_admin to manage groups"; allow (write,add, delete)(groupdn="ldap:///cn=priv_group_manage,{}");)"#, self.ldap.basedn).as_bytes().into(),
|
||||
r#"(targetattr="objectClass || description || nsUniqueId || uid || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate")(targetfilter="(objectClass=posixaccount)")(version 3.0; acl "Enable anyone user read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.as_bytes().into(),
|
||||
r#"(targetattr="displayName || legalName || userPassword || nsSshPublicKey")(version 3.0; acl "Enable self partial modify"; allow (write)(userdn="ldap:///self");)"#.as_bytes().into(),
|
||||
format!(r#"(targetattr="uid || description || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalName || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objectClass=nsAccount))")(version 3.0; acl "Enable user admin create"; allow (write, add, delete, read)(groupdn="ldap:///cn=priv_account_manage,{}");)"#, self.ldap.basedn).as_bytes().into(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -352,10 +363,20 @@ impl DirectoryServer {
|
|||
== 0;
|
||||
|
||||
if need_account {
|
||||
priv_account.push(account.get_ds_ldap_dn(&self.ldap.basedn))
|
||||
priv_account.push(
|
||||
account
|
||||
.get_ds_ldap_dn(&self.ldap.basedn)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)
|
||||
}
|
||||
if need_group {
|
||||
priv_group.push(account.get_ds_ldap_dn(&self.ldap.basedn))
|
||||
priv_group.push(
|
||||
account
|
||||
.get_ds_ldap_dn(&self.ldap.basedn)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue