Add the unixd tasks daemon (#349)

Fixes #180 - this adds an oddjobd style tasks daemon to the unix tools. This supports creation of home directories and the maintenance of alias symlinks to these allowing user renames. The tasks daemon is written to require root, but is seperate from the unixd daemon. Communication is via a root-only unix socket that the task daemon connects into to reduce the possibility of exploit.

Fixes #369 due to the changes to call_daemon_blocking
This commit is contained in:
Firstyear 2021-03-13 12:33:15 +10:00 committed by GitHub
parent d2ca2c5bc9
commit adb3f819ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 891 additions and 88 deletions

27
Cargo.lock generated
View file

@ -1443,9 +1443,9 @@ dependencies = [
[[package]]
name = "http-client"
version = "6.3.4"
version = "6.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98c12a6a451357392f3307325e9a15cbd27451abdaad96e74c30ea8786f615c4"
checksum = "5566ecc26bc6b04e773e680d66141fced78e091ad818e420d726c152b05a64ff"
dependencies = [
"async-trait",
"cfg-if 1.0.0",
@ -2232,7 +2232,6 @@ dependencies = [
name = "pam_kanidm"
version = "1.1.0-alpha.3"
dependencies = [
"async-std",
"kanidm_unix_int",
"libc",
]
@ -2634,14 +2633,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.4.3"
version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
checksum = "54fd1046a3107eb58f42de31d656fee6853e5d276c455fd943742dce89fc3dd3"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
@ -2655,9 +2653,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.22"
version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "remove_dir_all"
@ -3257,15 +3255,6 @@ dependencies = [
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"once_cell",
]
[[package]]
name = "tide"
version = "0.15.1"
@ -3487,9 +3476,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "typenum"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "unicode-bidi"

View file

@ -19,3 +19,15 @@ exclude = [
"kanidm_unix_int/pam_tester"
]
[patch.crates-io]
# tokio = { path = "../../tokio/tokio" }
# tokio-util = { path = "../../tokio/tokio-util" }
# tokio = { git = "https://github.com/Firstyear/tokio.git", rev = "aa6fb48d9a1f3652ee79e3b018a2b9d0c9f89c1e" }
# tokio-util = { git = "https://github.com/Firstyear/tokio.git", rev = "aa6fb48d9a1f3652ee79e3b018a2b9d0c9f89c1e" }
# concread = { path = "../concread" }
# idlset = { path = "../idlset" }
# ldap3_server = { path = "../ldap3_server" }
# webauthn-rs = { path = "../webauthn-rs" }
# webauthn-authenticator-rs = { path = "../webauthn-authenticator-rs" }

View file

@ -0,0 +1,132 @@
unixd homes task
----------------
Kanidm attempts to promote uuid's as the primary foreign key that should be
used by applications. A classic feature of pam and nsswitch tools is to create
the home directory of the account on first login.
Because of these two things, we previously would have to ask deployments to choose between
the following attributes for home directory names.
* uuid - the preferred foreign key, but not user-friendly.
* name/spn - user friendly, but will break on account rename.
The unixd tasks daemon is inspired by oddjobd, and allows us to create home directories
with awareness of this problem.
home directory design
---------------------
On login, the tasks daemon uses the value of "home_attr" as the name of the
home directory/folder. If present, the value of "home_alias" is used in getent
responses, and a symlink from "home_alias" is made to home attr. For example:
::
home_attr = uuid
getent passwd <id>
home = /home/6a159739-93f0-4bff-bdfb-6044c1bab55c
/home/6a159739-93f0-4bff-bdfb-6044c1bab55c
::
home_attr = uuid
home_alias = spn
getent passwd <id>
home = /home/<id>@<domain>
/home/6a159739-93f0-4bff-bdfb-6044c1bab55c
/home/<id>@<domain> -> /home/6a159739-93f0-4bff-bdfb-6044c1bab55c
This allows us to flip the symlink on logins if id/domain is ever changed, without
losing or breaking the content of the home directory.
tasks daemon design
-------------------
The current unixd daemon runs as an isolated and unprivileged user. This is because pam/nss
contact the unixd daemon which performs network access on their behalf. Due to this
design, this limits damage in a compromise as the unixd daemon user is not root, and has limited
channels to other processes.
However, to create home directories, root permissions are required.
An extra daemon is created that carries this out. This is the unixd-tasks daemon. As it runs
as root, the tasks daemon is an attractive target for "bad people" ™ so careful design around
the security of this is required.
::
┌───────────────┐
│ Pam │ /var/run/kanidm-unixd/sock
│ │───────────┐(mode 777)
└───────────────┘ │
│ ┌──────────────────────────────┐
│ │ Unixd │
├──────────▶│ (isolated user) │
┌──────────────┐ │ └──────────────────────────────┘
│ Nsswitch │ │ ▲
│ │───────────┘ │
└──────────────┘ │
/var/run/kanidm-unixd/tasks-sock │
(mode 600, isolated user) │
┌─────────────────────┐
│ Unixd Tasks │
│ (root) │
└─────────────────────┘
The tasks daemon runs as root and has no network facing elements. It connects to the
unixd daemon via a protected unix socket. The unixd daemon establishes a listening
unix domain socket that only root or itself can access at /var/run/kanidm-unixd/tasks-sock which
the unixd tasks daemon connects to. This is because with systemd dynamic users
the tasks daemon may not know what user account it has to chown sockets to, so it is
not viable for the tasks daemon to create the listening socket with the correct permissions.
This is especially true if the unixd daemon restarts and acquires a new uid, while the tasks
daemon persists.
The tasks daemon only receives a single datagram, which informs it of the details of
the path and symlinks to create. The daemon filters for a number of path injection attacks
that may be present in the names of the accounts. The Kanidm server also filters for path injections in
usernames.
The unixd daemon maintains a work queue that it ships to the tasks daemon. This queue is
bounded and if the queue is not being serviced, it proceeds with the login/process
as we must assume the user has *not* configured the tasks daemon on the system. This queue
also prevents memory growth/ddos if we are overloaded by login requests.
In packaging the tasks daemon will use systemds isolation features to further harden this. For
example:
::
CapabilityBoundingSet=CAP_CHOWN,CAP_FOWNER
SystemCallFilter=@aio @basic-io @chown @file-system @io-event @network-io @sync
ProtectSystem=strict
ReadWritePaths=/home /var/run/kanidm-unixd
RestrictAddressFamilies=AF_UNIX
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
PrivateNetwork=true
ProtectHostname=true
ProtectClock=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
MemoryDenyWriteExecute=true
// todo, should be added to unixd
# ProtectHome=¶

View file

@ -13,6 +13,9 @@ cache is also able to cache missing-entry responses to reduce network traffic
and main server load.
Additionally, the daemon means that the pam and nsswitch integration libraries
can be small, helping to reduce the attack surface of the machine.
Similar, a tasks daemon is also provided that can create home directories on first
login, and supports a number of features related to aliases and links to these
home directories.
We recommend you install the client daemon from your system package manager.
@ -25,13 +28,22 @@ You can check the daemon is running on your Linux system with
systemctl status kanidm_unixd
This daemon uses connection configuration from /etc/kanidm/config. This is the covered in
client_tools. You can also configure some details of the unixd daemon in /etc/kanidm/unixd.
You can check the privileged tasks daemon is running with
systemctl status kanidm_unixd_tasks
> **NOTE** The `kanidm_unixd_tasks` daemon is not required for pam and nsswitch functionality.
> If disabled, your system will function as usual. It is however recommended due to the features
> it provides supporting kanidm's capabilities.
Both unixd daemons use the connection configuration from /etc/kanidm/config. This is the covered in
client_tools. You can also configure some details of the unixd daemons in /etc/kanidm/unixd.
pam_allowed_login_groups = ["posix_group"]
default_shell = "/bin/bash"
home_prefix = "/home/"
home_attr = "uuid"
home_alias = "spn"
uid_attr_map = "spn"
gid_attr_map = "spn"
@ -47,12 +59,17 @@ a trailing `/`. Defaults to `/home/`.
`home_attr` is the default token attribute used for the home directory path. Valid
choices are `uuid`, `name`, `spn`. Defaults to `uuid`.
`home_alias` is the default token attribute used for generating symlinks pointing to the users
home directory. If set, this will become the value of the home path
to nss calls. It is recommended you choose a "human friendly" attribute here.
Valid choices are `none`, `uuid`, `name`, `spn`. Defaults to `spn`.
> **NOTICE:**
> All users in kanidm can change their name (and their spn) at any time. If you change
> `home_attr` from `uuid` you *must* have a plan on how to manage these directory renames
> in your system. We recommend that you have a stable id (like the uuid) and symlinks
> from the name to the uuid folder. The project plans to add automatic support for this
> with https://github.com/kanidm/kanidm/issues/180
> from the name to the uuid folder. Automatic support is provided for this via the unixd
> tasks daemon, as documented here.
`uid_attr_map` chooses which attribute is used for domain local users in presentation. Defaults
to `spn`. Users from a trust will always use spn.
@ -140,17 +157,19 @@ Each of these controls one of the four stages of pam. The content should look li
password requisite pam_cracklib.so
password [default=1 ignore=ignore success=ok] pam_localuser.so
password required pam_unix.so use_authtok nullok shadow try_first_pass
password required pam_kanidm.so
password required pam_kanidm.so
# /etc/pam.d/common-session-pc
session optional pam_systemd.so
session required pam_limits.so
session required pam_mkhomedir.so skel=/etc/skel/ umask=0022
session optional pam_unix.so try_first_pass
session optional pam_kanidm.so
session optional pam_umask.so
session optional pam_env.so
> **WARNING:** Ensure that `pam_mkhomedir` or `pam_oddjobd` are *not* present in your pam configuration
> these interfer with the correct operation of the kanidm tasks daemon.
### Fedora
TBD
@ -170,6 +189,8 @@ And add the lines:
Then restart the kanidm-unixd.service.
The same pattern is true for the kanidm-unixd-tasks.service daemon.
To debug the pam module interactions add `debug` to the module arguments such as:
auth sufficient pam_kanidm.so debug
@ -179,6 +200,9 @@ To debug the pam module interactions add `debug` to the module arguments such as
Check that the /var/run/kanidm-unixd/sock is 777, and that non-root readers can see it with
ls or other tools.
Ensure that /var/run/kanidm-unixd/task_sock is 700, and that it is owned by the kanidm unixd
process user.
### Check you can access the kanidm server
You can check this with the client tools:

View file

@ -20,19 +20,12 @@ serde_derive = "1.0"
toml = "0.5"
uuid = { version = "0.8", features = ["serde", "v4"] }
url = "2.1.1"
# users = "0.10"
# webauthn-authenticator-rs = "^0.1.2"
# webauthn-authenticator-rs = { path = "../../webauthn-authenticator-rs" }
webauthn-rs = "0.3.0-alpha.1"
# webauthn-rs = { path = "../../webauthn-rs" }
[dev-dependencies]
# tokio = { version = "0.2", features = ["full"] }
tokio = { version = "1", features = ["rt", "net", "time", "macros", "sync", "signal"] }
kanidm = { path = "../kanidmd" }
futures = "0.3"
async-std = "1.6"
webauthn-authenticator-rs = "0.3.0-alpha.1"
# webauthn-authenticator-rs = { path = "../../webauthn-authenticator-rs" }

View file

@ -17,7 +17,6 @@ zxcvbn = { version = "2.0", features = ["ser"] }
base32 = "0.4"
thiserror = "1.0"
webauthn-rs = "0.3.0-alpha.5"
# webauthn-rs = { path = "../../webauthn-rs" }
[dev-dependencies]
serde_json = "1.0"

View file

@ -44,7 +44,6 @@ qrcode = { version = "0.12", default-features = false }
zxcvbn = "2.0"
webauthn-authenticator-rs = "^0.3.0-alpha.6"
# webauthn-authenticator-rs = { path = "../../webauthn-authenticator-rs" }
[build-dependencies]
structopt = { version = "0.3", default-features = false }

View file

@ -19,6 +19,10 @@ path = "src/lib.rs"
name = "kanidm_unixd"
path = "src/daemon.rs"
[[bin]]
name = "kanidm_unixd_tasks"
path = "src/tasks_daemon.rs"
[[bin]]
name = "kanidm_ssh_authorizedkeys"
path = "src/ssh_authorizedkeys.rs"
@ -45,8 +49,10 @@ kanidm_proto = { path = "../kanidm_proto", version = "1.1.0-alpha" }
kanidm = { path = "../kanidmd", version = "1.1.0-alpha" }
toml = "0.5"
rpassword = "5.0"
tokio = { version = "1", features = ["rt", "macros", "sync", "time", "net", "io-util", "signal"] }
## Removed "signal"
tokio = { version = "1", features = ["rt", "macros", "sync", "time", "net", "io-util"] }
tokio-util = { version = "0.6", features = ["codec"] }
futures = "0.3"
bytes = "1.0"

View file

@ -13,7 +13,7 @@ extern crate libnss;
#[macro_use]
extern crate lazy_static;
use kanidm_unix_common::client::call_daemon_blocking;
use kanidm_unix_common::client_sync::call_daemon_blocking;
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse, NssGroup, NssUser};

View file

@ -11,5 +11,4 @@ path = "src/lib.rs"
[dependencies]
kanidm_unix_int = { path = "../", version = "1.1.0-alpha" }
async-std = "1.6"
libc = "0.2"

View file

@ -20,8 +20,7 @@ use std::collections::BTreeSet;
use std::convert::TryFrom;
use std::ffi::CStr;
// use std::os::raw::c_char;
use async_std::task;
use kanidm_unix_common::client::call_daemon;
use kanidm_unix_common::client_sync::call_daemon_blocking;
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse};
@ -92,7 +91,7 @@ impl PamHooks for PamKanidm {
let req = ClientRequest::PamAccountAllowed(account_id);
// PamResultCode::PAM_IGNORE
match task::block_on(call_daemon(cfg.sock_path.as_str(), req)) {
match call_daemon_blocking(cfg.sock_path.as_str(), req) {
Ok(r) => match r {
ClientResponse::PamStatus(Some(true)) => {
// println!("PAM_SUCCESS");
@ -201,7 +200,7 @@ impl PamHooks for PamKanidm {
};
let req = ClientRequest::PamAuthenticate(account_id, authtok);
match task::block_on(call_daemon(cfg.sock_path.as_str(), req)) {
match call_daemon_blocking(cfg.sock_path.as_str(), req) {
Ok(r) => match r {
ClientResponse::PamStatus(Some(true)) => {
// println!("PAM_SUCCESS");
@ -264,7 +263,7 @@ impl PamHooks for PamKanidm {
PamResultCode::PAM_SUCCESS
}
fn sm_open_session(_pamh: &PamHandle, args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode {
fn sm_open_session(pamh: &PamHandle, args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode {
let opts = match Options::try_from(&args) {
Ok(o) => o,
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
@ -275,7 +274,35 @@ impl PamHooks for PamKanidm {
println!("args -> {:?}", args);
println!("opts -> {:?}", opts);
}
PamResultCode::PAM_SUCCESS
let account_id = match pamh.get_user(None) {
Ok(aid) => aid,
Err(e) => {
if opts.debug {
println!("Error get_user -> {:?}", e);
}
return e;
}
};
let cfg = match get_cfg() {
Ok(cfg) => cfg,
Err(e) => return e,
};
let req = ClientRequest::PamAccountBeginSession(account_id);
match call_daemon_blocking(cfg.sock_path.as_str(), req) {
Ok(ClientResponse::Ok) => {
// println!("PAM_SUCCESS");
PamResultCode::PAM_SUCCESS
}
other => {
if opts.debug {
println!("PAM_IGNORE -> {:?}", other);
}
PamResultCode::PAM_IGNORE
}
}
}
fn sm_setcred(_pamh: &PamHandle, args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode {

View file

@ -1,6 +1,6 @@
use crate::db::Db;
use crate::unix_config::{HomeAttr, UidAttr};
use crate::unix_proto::{NssGroup, NssUser};
use crate::unix_proto::{HomeDirectoryInfo, NssGroup, NssUser};
use kanidm_client::asynchronous::KanidmAsyncClient;
use kanidm_client::ClientError;
use kanidm_proto::v1::{OperationError, UnixGroupToken, UnixUserToken};
@ -37,6 +37,7 @@ pub struct CacheLayer {
default_shell: String,
home_prefix: String,
home_attr: HomeAttr,
home_alias: Option<HomeAttr>,
uid_attr_map: UidAttr,
gid_attr_map: UidAttr,
nxcache: Mutex<LruCache<Id, SystemTime>>,
@ -65,6 +66,7 @@ impl CacheLayer {
default_shell: String,
home_prefix: String,
home_attr: HomeAttr,
home_alias: Option<HomeAttr>,
uid_attr_map: UidAttr,
gid_attr_map: UidAttr,
) -> Result<Self, ()> {
@ -88,6 +90,7 @@ impl CacheLayer {
default_shell,
home_prefix,
home_attr,
home_alias,
uid_attr_map,
gid_attr_map,
nxcache: Mutex::new(LruCache::new(NXCACHE_SIZE)),
@ -593,17 +596,34 @@ impl CacheLayer {
.unwrap_or_else(|| Vec::with_capacity(0)))
}
#[inline(always)]
fn token_homedirectory_alias(&self, token: &UnixUserToken) -> Option<String> {
self.home_alias.map(|t| match t {
// If we have an alias. use it.
HomeAttr::Uuid => token.uuid.as_str().to_string(),
HomeAttr::Spn => token.spn.as_str().to_string(),
HomeAttr::Name => token.name.as_str().to_string(),
})
}
#[inline(always)]
fn token_homedirectory_attr(&self, token: &UnixUserToken) -> String {
match self.home_attr {
HomeAttr::Uuid => token.uuid.as_str().to_string(),
HomeAttr::Spn => token.spn.as_str().to_string(),
HomeAttr::Name => token.name.as_str().to_string(),
}
}
#[inline(always)]
fn token_homedirectory(&self, token: &UnixUserToken) -> String {
format!(
"{}{}",
self.home_prefix,
match self.home_attr {
HomeAttr::Uuid => token.uuid.as_str(),
HomeAttr::Spn => token.spn.as_str(),
HomeAttr::Name => token.name.as_str(),
}
)
self.token_homedirectory_alias(token)
.unwrap_or_else(|| self.token_homedirectory_attr(token))
}
#[inline(always)]
fn token_abs_homedirectory(&self, token: &UnixUserToken) -> String {
format!("{}{}", self.home_prefix, self.token_homedirectory(token))
}
#[inline(always)]
@ -619,7 +639,7 @@ impl CacheLayer {
self.get_cached_usertokens().await.map(|l| {
l.into_iter()
.map(|tok| NssUser {
homedir: self.token_homedirectory(&tok),
homedir: self.token_abs_homedirectory(&tok),
name: self.token_uidattr(&tok),
gid: tok.gidnumber,
gecos: tok.displayname,
@ -632,7 +652,7 @@ impl CacheLayer {
async fn get_nssaccount(&self, account_id: Id) -> Result<Option<NssUser>, ()> {
let token = self.get_usertoken(account_id).await?;
Ok(token.map(|tok| NssUser {
homedir: self.token_homedirectory(&tok),
homedir: self.token_abs_homedirectory(&tok),
name: self.token_uidattr(&tok),
gid: tok.gidnumber,
gecos: tok.displayname,
@ -858,6 +878,21 @@ impl CacheLayer {
}
}
pub async fn pam_account_beginsession(
&self,
account_id: &str,
) -> Result<Option<HomeDirectoryInfo>, ()> {
let token = self.get_usertoken(Id::Name(account_id.to_string())).await?;
Ok(token.as_ref().map(|tok| HomeDirectoryInfo {
gid: tok.gidnumber,
name: self.token_homedirectory_attr(tok),
aliases: self
.token_homedirectory_alias(tok)
.map(|s| vec![s])
.unwrap_or_else(Vec::new),
}))
}
pub async fn test_connection(&self) -> bool {
let state = self.get_cachestate().await;
match state {

View file

@ -1,4 +1,3 @@
use async_std::task;
use bytes::{BufMut, BytesMut};
use futures::SinkExt;
use futures::StreamExt;
@ -6,6 +5,7 @@ use std::error::Error;
use std::io::Error as IoError;
use std::io::ErrorKind;
use tokio::net::UnixStream;
// use tokio::runtime::Builder;
use tokio_util::codec::Framed;
use tokio_util::codec::{Decoder, Encoder};
@ -68,10 +68,3 @@ pub async fn call_daemon(path: &str, req: ClientRequest) -> Result<ClientRespons
}
}
}
pub fn call_daemon_blocking(
path: &str,
req: ClientRequest,
) -> Result<ClientResponse, Box<dyn Error>> {
task::block_on(call_daemon(path, req))
}

View file

@ -0,0 +1,110 @@
use std::error::Error;
use std::io::Error as IoError;
use std::io::ErrorKind;
use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
use std::time::{Duration, SystemTime};
use crate::unix_proto::{ClientRequest, ClientResponse};
const TIMEOUT: u64 = 2000;
pub fn call_daemon_blocking(
path: &str,
req: ClientRequest,
) -> Result<ClientResponse, Box<dyn Error>> {
let mut stream = UnixStream::connect(path)
.and_then(|socket| {
socket
.set_read_timeout(Some(Duration::from_millis(TIMEOUT)))
.map(|_| socket)
})
.and_then(|socket| {
socket
.set_write_timeout(Some(Duration::from_millis(TIMEOUT)))
.map(|_| socket)
})
.map_err(|e| {
error!("stream setup error -> {:?}", e);
e
})
.map_err(Box::new)?;
let data = serde_cbor::to_vec(&req).map_err(|e| {
error!("socket encoding error -> {:?}", e);
Box::new(IoError::new(ErrorKind::Other, "CBOR encode error"))
})?;
// .map_err(Box::new)?;
stream
.write_all(data.as_slice())
.and_then(|_| stream.flush())
.map_err(|e| {
error!("stream write error -> {:?}", e);
e
})
.map_err(Box::new)?;
// Now wait on the response.
let start = SystemTime::now();
let timeout = Duration::from_millis(TIMEOUT);
let mut read_started = false;
let mut data = Vec::with_capacity(1024);
let mut counter = 0;
loop {
let mut buffer = [0; 1024];
let durr = SystemTime::now().duration_since(start).map_err(Box::new)?;
if durr > timeout {
error!("Socket timeout");
// timed out, not enough activity.
break;
}
// Would be a lot easier if we had peek ...
// https://github.com/rust-lang/rust/issues/76923
match stream.read(&mut buffer) {
Ok(0) => {
if read_started {
debug!("read_started true, we have completed");
// We're done, no more bytes.
break;
} else {
debug!("Waiting ...");
// Still can wait ...
continue;
}
}
Ok(count) => {
data.extend_from_slice(&buffer);
counter += count;
if count == 1024 {
debug!("Filled 1024 bytes, looping ...");
// We have filled the buffer, we need to copy and loop again.
read_started = true;
continue;
} else {
debug!("Filled {} bytes, complete", count);
// We have a partial read, so we are complete.
break;
}
}
Err(e) => {
error!("Steam read failure -> {:?}", e);
// Failure!
return Err(Box::new(e));
}
}
}
// Extend from slice fills with 0's, so we need to truncate now.
data.truncate(counter);
// Now attempt to decode.
let cr = serde_cbor::from_slice::<ClientResponse>(data.as_slice()).map_err(|e| {
error!("socket encoding error -> {:?}", e);
Box::new(IoError::new(ErrorKind::Other, "CBOR decode error"))
})?;
Ok(cr)
}

View file

@ -1,11 +1,13 @@
use crate::unix_config::{HomeAttr, UidAttr};
pub const DEFAULT_SOCK_PATH: &str = "/var/run/kanidm-unixd/sock";
pub const DEFAULT_TASK_SOCK_PATH: &str = "/var/run/kanidm-unixd/task_sock";
pub const DEFAULT_DB_PATH: &str = "/var/cache/kanidm-unixd/kanidm.cache.db";
pub const DEFAULT_CONN_TIMEOUT: u64 = 2;
pub const DEFAULT_CACHE_TIMEOUT: u64 = 15;
pub const DEFAULT_SHELL: &str = "/bin/bash";
pub const DEFAULT_HOME_PREFIX: &str = "/home/";
pub const DEFAULT_HOME_ATTR: HomeAttr = HomeAttr::Uuid;
pub const DEFAULT_HOME_ALIAS: Option<HomeAttr> = Some(HomeAttr::Spn);
pub const DEFAULT_UID_ATTR_MAP: UidAttr = UidAttr::Spn;
pub const DEFAULT_GID_ATTR_MAP: UidAttr = UidAttr::Spn;

View file

@ -14,6 +14,8 @@ extern crate log;
use users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
use std::fs::metadata;
use std::io::Error as IoError;
use std::io::ErrorKind;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
@ -24,7 +26,11 @@ use libc::umask;
use std::error::Error;
use std::io;
use std::sync::Arc;
use std::time::Duration;
use tokio::net::{UnixListener, UnixStream};
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::sync::oneshot;
use tokio::time;
use tokio_util::codec::Framed;
use tokio_util::codec::{Decoder, Encoder};
@ -32,10 +38,12 @@ use kanidm_client::KanidmClientBuilder;
use kanidm_unix_common::cache::CacheLayer;
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse};
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse, TaskRequest, TaskResponse};
//=== the codec
type AsyncTaskRequest = (TaskRequest, oneshot::Sender<()>);
struct ClientCodec;
impl Decoder for ClientCodec {
@ -74,15 +82,100 @@ impl ClientCodec {
}
}
struct TaskCodec;
impl Decoder for TaskCodec {
type Item = TaskResponse;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
match serde_cbor::from_slice::<TaskResponse>(&src) {
Ok(msg) => {
// Clear the buffer for the next message.
src.clear();
Ok(Some(msg))
}
_ => Ok(None),
}
}
}
impl Encoder<TaskRequest> for TaskCodec {
type Error = io::Error;
fn encode(&mut self, msg: TaskRequest, dst: &mut BytesMut) -> Result<(), Self::Error> {
debug!("Attempting to send request -> {:?} ...", msg);
let data = serde_cbor::to_vec(&msg).map_err(|e| {
error!("socket encoding error -> {:?}", e);
io::Error::new(io::ErrorKind::Other, "CBOR encode error")
})?;
dst.put(data.as_slice());
Ok(())
}
}
impl TaskCodec {
fn new() -> Self {
TaskCodec
}
}
fn rm_if_exist(p: &str) {
let _ = std::fs::remove_file(p).map_err(|e| {
warn!("attempting to remove {:?} -> {:?}", p, e);
});
}
async fn handle_task_client(
stream: UnixStream,
task_channel_tx: &Sender<AsyncTaskRequest>,
task_channel_rx: &mut Receiver<AsyncTaskRequest>,
) -> Result<(), Box<dyn Error>> {
// setup the codec
let mut reqs = Framed::new(stream, TaskCodec::new());
loop {
// TODO wait on the channel OR the task handler, so we know
// when it closes.
let v = match task_channel_rx.recv().await {
Some(v) => v,
None => return Ok(()),
};
debug!("Sending Task -> {:?}", v.0);
// Write the req to the socket.
if let Err(_e) = reqs.send(v.0.clone()).await {
// re-queue the event if not timed out.
// This is indicated by the one shot being dropped.
if !v.1.is_closed() {
let _ = task_channel_tx
.send_timeout(v, Duration::from_millis(100))
.await;
}
// now return the error.
return Err(Box::new(IoError::new(ErrorKind::Other, "oh no!")));
}
match reqs.next().await {
Some(Ok(TaskResponse::Success)) => {
debug!("Task was acknowledged and completed.");
// Send a result back via the one-shot
// Ignore if it fails.
let _ = v.1.send(());
}
other => {
error!("Error -> {:?}", other);
return Err(Box::new(IoError::new(ErrorKind::Other, "oh no!")));
}
}
}
}
async fn handle_client(
sock: UnixStream,
cachelayer: Arc<CacheLayer>,
task_channel_tx: &Sender<AsyncTaskRequest>,
) -> Result<(), Box<dyn Error>> {
debug!("Accepted connection");
@ -183,6 +276,50 @@ async fn handle_client(
.map(ClientResponse::PamStatus)
.unwrap_or(ClientResponse::Error)
}
ClientRequest::PamAccountBeginSession(account_id) => {
debug!("pam account begin session");
match cachelayer
.pam_account_beginsession(account_id.as_str())
.await
{
Ok(Some(info)) => {
let (tx, rx) = oneshot::channel();
match task_channel_tx
.send_timeout(
(TaskRequest::HomeDirectory(info), tx),
Duration::from_millis(100),
)
.await
{
Ok(()) => {
// Now wait for the other end OR
// timeout.
match time::timeout_at(
time::Instant::now() + Duration::from_millis(1000),
rx,
)
.await
{
Ok(Ok(_)) => {
debug!("Task completed, returning to pam ...");
ClientResponse::Ok
}
_ => {
// Timeout or other error.
ClientResponse::Error
}
}
}
Err(_) => {
// We could not submit the req. Move on!
ClientResponse::Error
}
}
}
_ => ClientResponse::Error,
}
}
ClientRequest::InvalidateCache => {
debug!("invalidate cache");
cachelayer
@ -308,6 +445,7 @@ async fn main() {
};
rm_if_exist(cfg.sock_path.as_str());
rm_if_exist(cfg.task_sock_path.as_str());
let cb = cb.connect_timeout(cfg.conn_timeout);
@ -382,6 +520,7 @@ async fn main() {
cfg.default_shell.clone(),
cfg.home_prefix.clone(),
cfg.home_attr,
cfg.home_alias,
cfg.uid_attr_map,
cfg.gid_attr_map,
)
@ -396,7 +535,7 @@ async fn main() {
let cachelayer = Arc::new(cl_inner);
// Set the umask while we open the path
// Set the umask while we open the path for most clients.
let before = unsafe { umask(0) };
let listener = match UnixListener::bind(cfg.sock_path.as_str()) {
Ok(l) => l,
@ -405,18 +544,71 @@ async fn main() {
std::process::exit(1);
}
};
// Setup the root-only socket. Take away all others.
let _ = unsafe { umask(0o0077) };
let task_listener = match UnixListener::bind(cfg.task_sock_path.as_str()) {
Ok(l) => l,
Err(_e) => {
error!("Failed to bind unix socket.");
std::process::exit(1);
}
};
// Undo it.
let _ = unsafe { umask(before) };
let (task_channel_tx, mut task_channel_rx) = channel(16);
let task_channel_tx = Arc::new(task_channel_tx);
let task_channel_tx_cln = task_channel_tx.clone();
tokio::spawn(async move {
loop {
match task_listener.accept().await {
Ok((socket, _addr)) => {
// Did it come from root?
if let Ok(ucred) = socket.peer_cred() {
if ucred.uid() == 0 {
// all good!
} else {
// move along.
debug!("Task handler not running as root, ignoring ...");
continue;
}
} else {
// move along.
debug!("Task handler not running as root, ignoring ...");
continue;
};
debug!("A task handler has connected.");
// It did? Great, now we can wait and spin on that one
// client.
if let Err(e) =
handle_task_client(socket, &task_channel_tx, &mut task_channel_rx).await
{
error!("Task client error occured; error = {:?}", e);
}
// If they DC we go back to accept.
}
Err(err) => {
error!("Task Accept error -> {:?}", err);
}
}
// done
}
});
// TODO: Setup a task that handles pre-fetching here.
let server = async move {
loop {
let tc_tx = task_channel_tx_cln.clone();
match listener.accept().await {
Ok((socket, _addr)) => {
let cachelayer_ref = cachelayer.clone();
tokio::spawn(async move {
if let Err(e) = handle_client(socket, cachelayer_ref.clone()).await {
if let Err(e) = handle_client(socket, cachelayer_ref.clone(), &tc_tx).await
{
error!("an error occured; error = {:?}", e);
}
});

View file

@ -14,16 +14,15 @@ extern crate log;
use log::debug;
use structopt::StructOpt;
use futures::executor::block_on;
// use futures::executor::block_on;
use kanidm_unix_common::client::call_daemon;
use kanidm_unix_common::client_sync::call_daemon_blocking;
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse};
include!("./opt/unixd_status.rs");
#[tokio::main]
async fn main() {
fn main() {
let opt = UnixdStatusOpt::from_args();
if opt.debug {
::std::env::set_var("RUST_LOG", "kanidm=debug,kanidm_client=debug");
@ -32,7 +31,7 @@ async fn main() {
}
env_logger::init();
debug!("Starting cache invalidate tool ...");
debug!("Starting cache status tool ...");
let cfg = match KanidmUnixdConfig::new().read_options_from_optional_config("/etc/kanidm/unixd")
{
@ -45,7 +44,7 @@ async fn main() {
let req = ClientRequest::Status;
match block_on(call_daemon(cfg.sock_path.as_str(), req)) {
match call_daemon_blocking(cfg.sock_path.as_str(), req) {
Ok(r) => match r {
ClientResponse::Ok => info!("working!"),
_ => {

View file

@ -15,6 +15,7 @@ extern crate log;
pub mod cache;
pub mod client;
pub mod client_sync;
pub mod constants;
pub(crate) mod db;
pub mod unix_config;

View file

@ -0,0 +1,254 @@
#![deny(warnings)]
#![warn(unused_extern_crates)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_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)]
#[macro_use]
extern crate log;
use users::{get_effective_gid, get_effective_uid};
use std::os::unix::fs::symlink;
use libc::{lchown, umask};
use std::ffi::CString;
use bytes::{BufMut, BytesMut};
use futures::SinkExt;
use futures::StreamExt;
use std::fs;
use std::io;
use std::path::Path;
use std::time::Duration;
use tokio::net::UnixStream;
use tokio::time;
use tokio_util::codec::Framed;
use tokio_util::codec::{Decoder, Encoder};
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{HomeDirectoryInfo, TaskRequest, TaskResponse};
struct TaskCodec;
impl Decoder for TaskCodec {
type Item = TaskRequest;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
match serde_cbor::from_slice::<TaskRequest>(&src) {
Ok(msg) => {
// Clear the buffer for the next message.
src.clear();
Ok(Some(msg))
}
_ => Ok(None),
}
}
}
impl Encoder<TaskResponse> for TaskCodec {
type Error = io::Error;
fn encode(&mut self, msg: TaskResponse, dst: &mut BytesMut) -> Result<(), Self::Error> {
debug!("Attempting to send request -> {:?} ...", msg);
let data = serde_cbor::to_vec(&msg).map_err(|e| {
error!("socket encoding error -> {:?}", e);
io::Error::new(io::ErrorKind::Other, "CBOR encode error")
})?;
dst.put(data.as_slice());
Ok(())
}
}
impl TaskCodec {
fn new() -> Self {
TaskCodec
}
}
fn create_home_directory(info: &HomeDirectoryInfo, home_prefix: &str) -> Result<(), String> {
// Final sanity check to prevent certain classes of attacks.
let name = info
.name
.trim_start_matches('.')
.replace("/", "")
.replace("\\", "");
let home_prefix_path = Path::new(home_prefix);
// Does our home_prefix actually exist?
if !home_prefix_path.exists() || !home_prefix_path.is_dir() {
return Err("Invalid home_prefix from configuration".to_string());
}
// Actually process the request here.
let hd_path_raw = format!("{}{}", home_prefix, name);
let hd_path = Path::new(&hd_path_raw);
// Assert the resulting named home path is consistent and correct.
if let Some(pp) = hd_path.parent() {
if pp != home_prefix_path {
return Err("Invalid home directory name - not within home_prefix".to_string());
}
} else {
return Err("Invalid/Corrupt home directory path - no prefix found".to_string());
}
let hd_path_os =
CString::new(hd_path_raw.clone()).map_err(|_| "Unable to create c-string".to_string())?;
// Does the home directory exist?
if !hd_path.exists() {
// Set a umask
let before = unsafe { umask(0o0027) };
// TODO: Should we copy content from /etc/skel?
// Create the dir
if let Err(e) = fs::create_dir_all(hd_path) {
let _ = unsafe { umask(before) };
return Err(format!("{:?}", e));
}
let _ = unsafe { umask(before) };
// Change the owner to the gid - remember, kanidm ONLY has gid's, the uid is implied.
if unsafe { lchown(hd_path_os.as_ptr(), info.gid, info.gid) } != 0 {
return Err("Unable to set ownership".to_string());
}
}
let name_rel_path = Path::new(&name);
// Does the aliases exist
for alias in info.aliases.iter() {
// Sanity check the alias.
let alias = alias.replace(".", "").replace("/", "").replace("\\", "");
let alias_path_raw = format!("{}{}", home_prefix, alias);
let alias_path = Path::new(&alias_path_raw);
// Assert the resulting alias path is consistent and correct.
if let Some(pp) = alias_path.parent() {
if pp != home_prefix_path {
return Err("Invalid home directory alias - not within home_prefix".to_string());
}
} else {
return Err("Invalid/Corrupt alias directory path - no prefix found".to_string());
}
if alias_path.exists() {
let attr = match fs::symlink_metadata(alias_path) {
Ok(a) => a,
Err(e) => {
return Err(format!("{:?}", e));
}
};
if attr.file_type().is_symlink() {
// Probably need to update it.
if let Err(e) = fs::remove_file(alias_path) {
return Err(format!("{:?}", e));
}
if let Err(e) = symlink(name_rel_path, alias_path) {
return Err(format!("{:?}", e));
}
}
} else {
// Does not exist. Create.
if let Err(e) = symlink(name_rel_path, alias_path) {
return Err(format!("{:?}", e));
}
}
}
Ok(())
}
async fn handle_tasks(stream: UnixStream, home_prefix: &str) {
let mut reqs = Framed::new(stream, TaskCodec::new());
loop {
match reqs.next().await {
Some(Ok(TaskRequest::HomeDirectory(info))) => {
debug!("Received task -> HomeDirectory({:?})", info);
let resp = match create_home_directory(&info, home_prefix) {
Ok(()) => TaskResponse::Success,
Err(msg) => TaskResponse::Error(msg),
};
// Now send a result.
if let Err(e) = reqs.send(resp).await {
error!("Error -> {:?}", e);
return;
}
// All good, loop.
}
other => {
error!("Error -> {:?}", other);
return;
}
}
}
}
#[tokio::main]
async fn main() {
// let cuid = get_current_uid();
// let cgid = get_current_gid();
// We only need to check effective id
let ceuid = get_effective_uid();
let cegid = get_effective_gid();
if ceuid != 0 || cegid != 0 {
eprintln!("Refusing to run - this process *MUST* operate as root.");
std::process::exit(1);
}
env_logger::init();
let unixd_path = Path::new("/etc/kanidm/unixd");
let unixd_path_str = match unixd_path.to_str() {
Some(cps) => cps,
None => {
error!("Unable to turn unixd_path to str");
std::process::exit(1);
}
};
let cfg = match KanidmUnixdConfig::new().read_options_from_optional_config(unixd_path) {
Ok(v) => v,
Err(_) => {
error!("Failed to parse {}", unixd_path_str);
std::process::exit(1);
}
};
let task_sock_path = cfg.task_sock_path.clone();
debug!("Attempting to use {} ...", task_sock_path);
let server = async move {
loop {
info!("Attempting to connect to kanidm_unixd ...");
// Try to connect to the daemon.
match UnixStream::connect(&task_sock_path).await {
// Did we connect?
Ok(stream) => {
info!("Found kanidm_unixd, waiting for tasks ...");
// Yep! Now let the main handler do it's job.
// If it returns (dc, etc, then we loop and try again).
handle_tasks(stream, &cfg.home_prefix).await;
}
Err(e) => {
error!("Unable to find kanidm_unixd, sleeping ...");
debug!("\\---> {:?}", e);
// Back off.
time::sleep(Duration::from_millis(5000)).await;
}
}
}
};
server.await;
}

View file

@ -1,6 +1,7 @@
use crate::constants::{
DEFAULT_CACHE_TIMEOUT, DEFAULT_CONN_TIMEOUT, DEFAULT_DB_PATH, DEFAULT_GID_ATTR_MAP,
DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX, DEFAULT_SHELL, DEFAULT_SOCK_PATH, DEFAULT_UID_ATTR_MAP,
DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX, DEFAULT_SHELL, DEFAULT_SOCK_PATH,
DEFAULT_TASK_SOCK_PATH, DEFAULT_UID_ATTR_MAP,
};
use serde_derive::Deserialize;
use std::fs::File;
@ -11,12 +12,14 @@ use std::path::Path;
struct ConfigInt {
db_path: Option<String>,
sock_path: Option<String>,
task_sock_path: Option<String>,
conn_timeout: Option<u64>,
cache_timeout: Option<u64>,
pam_allowed_login_groups: Option<Vec<String>>,
default_shell: Option<String>,
home_prefix: Option<String>,
home_attr: Option<String>,
home_alias: Option<String>,
uid_attr_map: Option<String>,
gid_attr_map: Option<String>,
}
@ -38,12 +41,14 @@ pub enum UidAttr {
pub struct KanidmUnixdConfig {
pub db_path: String,
pub sock_path: String,
pub task_sock_path: String,
pub conn_timeout: u64,
pub cache_timeout: u64,
pub pam_allowed_login_groups: Vec<String>,
pub default_shell: String,
pub home_prefix: String,
pub home_attr: HomeAttr,
pub home_alias: Option<HomeAttr>,
pub uid_attr_map: UidAttr,
pub gid_attr_map: UidAttr,
}
@ -59,12 +64,14 @@ impl KanidmUnixdConfig {
KanidmUnixdConfig {
db_path: DEFAULT_DB_PATH.to_string(),
sock_path: DEFAULT_SOCK_PATH.to_string(),
task_sock_path: DEFAULT_TASK_SOCK_PATH.to_string(),
conn_timeout: DEFAULT_CONN_TIMEOUT,
cache_timeout: DEFAULT_CACHE_TIMEOUT,
pam_allowed_login_groups: Vec::new(),
default_shell: DEFAULT_SHELL.to_string(),
home_prefix: DEFAULT_HOME_PREFIX.to_string(),
home_attr: DEFAULT_HOME_ATTR,
home_alias: DEFAULT_HOME_ALIAS,
uid_attr_map: DEFAULT_UID_ATTR_MAP,
gid_attr_map: DEFAULT_GID_ATTR_MAP,
}
@ -93,6 +100,7 @@ impl KanidmUnixdConfig {
Ok(KanidmUnixdConfig {
db_path: config.db_path.unwrap_or(self.db_path),
sock_path: config.sock_path.unwrap_or(self.sock_path),
task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
conn_timeout: config.conn_timeout.unwrap_or(self.conn_timeout),
cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
pam_allowed_login_groups: config
@ -112,6 +120,19 @@ impl KanidmUnixdConfig {
}
})
.unwrap_or(self.home_attr),
home_alias: config
.home_alias
.and_then(|v| match v.as_str() {
"none" => Some(None),
"uuid" => Some(Some(HomeAttr::Uuid)),
"spn" => Some(Some(HomeAttr::Spn)),
"name" => Some(Some(HomeAttr::Name)),
_ => {
warn!("Invalid home_alias configured, using default ...");
None
}
})
.unwrap_or(self.home_alias),
uid_attr_map: config
.uid_attr_map
.and_then(|v| match v.as_str() {

View file

@ -25,6 +25,7 @@ pub enum ClientRequest {
NssGroupByName(String),
PamAuthenticate(String, String),
PamAccountAllowed(String),
PamAccountBeginSession(String),
InvalidateCache,
ClearCache,
Status,
@ -41,3 +42,21 @@ pub enum ClientResponse {
Ok,
Error,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HomeDirectoryInfo {
pub gid: u32,
pub name: String,
pub aliases: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum TaskRequest {
HomeDirectory(HomeDirectoryInfo),
}
#[derive(Serialize, Deserialize, Debug)]
pub enum TaskResponse {
Success,
Error(String),
}

View file

@ -9,8 +9,8 @@ use kanidm::core::create_server_core;
use kanidm_unix_common::cache::{CacheLayer, Id};
use kanidm_unix_common::constants::{
DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX, DEFAULT_SHELL,
DEFAULT_UID_ATTR_MAP,
DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX,
DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP,
};
use tokio::runtime::Runtime;
@ -117,6 +117,7 @@ fn run_test(fix_fn: fn(&mut KanidmClient) -> (), test_fn: fn(CacheLayer, KanidmA
DEFAULT_SHELL.to_string(),
DEFAULT_HOME_PREFIX.to_string(),
DEFAULT_HOME_ATTR,
DEFAULT_HOME_ALIAS,
DEFAULT_UID_ATTR_MAP,
DEFAULT_GID_ATTR_MAP,
))

View file

@ -73,17 +73,12 @@ sshkeys = "0.3"
rpassword = "5.0"
num_cpus = "1.10"
idlset = { version = "^0.1.12" , features = ["use_smallvec"] }
# idlset = { path = "../../idlset", features = ["use_smallvec"] }
zxcvbn = "2.0"
base64 = "0.13"
idlset = { version = "^0.1.12" , features = ["use_smallvec"] }
ldap3_server = "0.1"
# ldap3_server = { path = "../../ldap3_server" }
webauthn-rs = "0.3.0-alpha.5"
# webauthn-rs = { path = "../../webauthn-rs" }
libc = "0.2"
users = "0.11"
@ -98,7 +93,6 @@ simd_support = [ "concread/simd_support" ]
criterion = { version = "0.3", features = ["html_reports"] }
# For testing webauthn
webauthn-authenticator-rs = "0.3.0-alpha.5"
# webauthn-authenticator-rs = { path = "../../webauthn-authenticator-rs" }
[dev-dependencies.cargo-husky]
version = "1"

View file

@ -23,11 +23,12 @@ lazy_static! {
};
static ref INAME_RE: Regex = {
#[allow(clippy::expect_used)]
Regex::new("^(_.*|.*(\\s|@|,|=).*|\\d+|root|nobody|nogroup|wheel|sshd|shadow|systemd.*)$").expect("Invalid Iname regex found")
// ^ ^ ^
// | | \- must not be only integers
// | \- must not contain whitespace, @, ',', =
// \- must not start with _
Regex::new("^((\\.|_).*|.*(\\s|@|,|/|\\\\|=).*|\\d+|root|nobody|nogroup|wheel|sshd|shadow|systemd.*)$").expect("Invalid Iname regex found")
// ^ ^ ^ ^
// | | | \- must not be a reserved name.
// | | \- must not be only integers
// | \- must not contain whitespace, @, ',', /, \, =
// \- must not start with _ or .
// Them's be the rules.
};
static ref NSUNIQUEID_RE: Regex = {
@ -1609,9 +1610,10 @@ mod tests {
* - be a pure int (confusion to gid/uid/linux)
* - a uuid (confuses our name mapper)
* - contain an @ (confuses SPN)
* - can not start with _ (... I forgot but it's important I swear >.>)
* - can not start with _ (... api end points have _ as a magic char)
* - can not have spaces (confuses too many systems :()
* - can not have = or , (confuses ldap)
* - can not have ., /, \ (path injection attacks)
*/
let inv1 = Value::new_iname("1234");
let inv2 = Value::new_iname("bc23f637-4439-4c07-b95d-eaed0d9e4b8b");