From 55ee2410d789f4f9469d622938e6cbef04c5058d Mon Sep 17 00:00:00 2001 From: kalebo Date: Thu, 3 Nov 2022 17:49:11 -0600 Subject: [PATCH] Add /etc/skel templating and notes adjacent to kanidm-unixd and packaging (#1113) --- .gitignore | 5 ++ CONTRIBUTORS.md | 1 + Cargo.lock | 1 + Cargo.toml | 1 + examples/unixd | 1 + kanidm_book/src/SUMMARY.md | 1 + .../src/integrations/pam_and_nsswitch.md | 4 + kanidm_book/src/integrations/traefik.md | 56 +++++++++++++ kanidm_unix_int/Cargo.toml | 1 + kanidm_unix_int/src/constants.rs | 1 + kanidm_unix_int/src/tasks_daemon.rs | 47 ++++++++--- kanidm_unix_int/src/unix_config.rs | 6 +- platform/debian/pam-config-kanidm | 19 +++++ platform/debian/simple_pkg.sh | 83 +++++++++++++++++++ 14 files changed, 215 insertions(+), 12 deletions(-) create mode 100644 kanidm_book/src/integrations/traefik.md create mode 100644 platform/debian/pam-config-kanidm create mode 100755 platform/debian/simple_pkg.sh diff --git a/.gitignore b/.gitignore index 52cb3bcce..2795e0e6b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,11 @@ orca/example_profiles/small/orca-edited.toml kanidm_unix_int/pam_tester/Cargo.lock .vscode/ +# kanidm simple packaging +deployment-config/ +kanidm_simple_pkg/ +kanidm-client-tools.tar.gz + # python things **/__pycache__/** **/.venv/** diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index db7f054a2..dd6e1166d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -20,6 +20,7 @@ * Thomas Sanchez (daedric) * Dominik Süß (theSuess) * Florian Klink (flokli) +* Kaleb Olson (kalebo) ## Acknowledgements diff --git a/Cargo.lock b/Cargo.lock index 7b3abbc70..69e26302c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2334,6 +2334,7 @@ dependencies = [ "toml", "tracing", "users", + "walkdir", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 86ac12a80..68335340d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -160,6 +160,7 @@ webauthn-rs-proto = "0.4.7" # webauthn-rs-proto = { path = "../webauthn-rs/webauthn-rs-proto" } web-sys = "^0.3.60" whoami = "^1.2.3" +walkdir = "2" yew = "^0.19.3" yew-agent = "^0.1.0" diff --git a/examples/unixd b/examples/unixd index ec744e5bd..124d215af 100644 --- a/examples/unixd +++ b/examples/unixd @@ -5,5 +5,6 @@ # home_prefix = "/home/" # home_attr = "uuid" # home_alias = "spn" +# use_etc_skel = false # uid_attr_map = "spn" # gid_attr_map = "spn" \ No newline at end of file diff --git a/kanidm_book/src/SUMMARY.md b/kanidm_book/src/SUMMARY.md index 43c25f08d..38498280a 100644 --- a/kanidm_book/src/SUMMARY.md +++ b/kanidm_book/src/SUMMARY.md @@ -30,6 +30,7 @@ - [PAM and nsswitch](integrations/pam_and_nsswitch.md) - [RADIUS](integrations/radius.md) - [LDAP](integrations/ldap.md) +- [Traefik](integrations/traefik.md) # Integration Examples diff --git a/kanidm_book/src/integrations/pam_and_nsswitch.md b/kanidm_book/src/integrations/pam_and_nsswitch.md index b8e58982d..29ce1ae16 100644 --- a/kanidm_book/src/integrations/pam_and_nsswitch.md +++ b/kanidm_book/src/integrations/pam_and_nsswitch.md @@ -45,6 +45,7 @@ You can also configure some unixd-specific options with the file /etc/kanidm/uni home_prefix = "/home/" home_attr = "uuid" home_alias = "spn" + use_etc_skel = false uid_attr_map = "spn" gid_attr_map = "spn" @@ -73,6 +74,9 @@ Valid choices are `none`, `uuid`, `name`, `spn`. Defaults to `spn`. > from the name to the UUID folder. Automatic support is provided for this via the unixd > tasks daemon, as documented here. +`use_etc_skel` controls if home directories should be prepopulated with the contents of `/etc/skel` +when first created. Defaults to false. + `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. diff --git a/kanidm_book/src/integrations/traefik.md b/kanidm_book/src/integrations/traefik.md new file mode 100644 index 000000000..c4d0cafc2 --- /dev/null +++ b/kanidm_book/src/integrations/traefik.md @@ -0,0 +1,56 @@ +# Traefik + +Traefik is a flexible HTTP reverse proxy webserver that can be integrated with Docker to allow dynamic configuration +and to automatically use LetsEncrypt to provide valid TLS certificates. +We can leverage this in the setup of Kanidm by specifying the configuration of Kanidm and Traefik in the same [Docker Compose configuration](https://docs.docker.com/compose/). + +## Example setup +Create a new directory and copy the following YAML file into it as `docker-compose.yml`. +Edit the YAML to update the LetsEncrypt account email for your domain and the FQDN where Kanidm will be made available. +Ensure you adjust this file or Kanidm's configuration to have a matching HTTPS port; the line `traefik.http.services.kanidm.loadbalancer.server.port=8443` sets this on the Traefik side. +> **NOTE** You will need to generate self-signed certificates for Kanidm, and copy the configuration into the `kanidm_data` volume. Some instructions are available in the "Installing the Server" section of this book. + + +`docker-compose.yml` +```yaml +version: "3.4" + +services: + traefik: + image: traefik:v2.6 + container_name: traefik + command: + - "--certificatesresolvers.http.acme.email=admin@example.com" + - "--certificatesresolvers.http.acme.storage=/letsencrypt/acme.json" + - "--certificatesresolvers.http.acme.tlschallenge=true" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.websecure.http.tls=true" + - "--entrypoints.websecure.http.tls.certResolver=http" + - "--log.level=INFO" + - "--providers.docker=true" + - "--providers.docker.exposedByDefault=false" + - "--serverstransport.insecureskipverify=true" + restart: always + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + - "443:443" + kanidm: + container_name: kanidm + image: kanidm/server:devel + restart: unless-stopped + volumes: + - kanidm_data:/data + labels: + - traefik.enable=true + - traefik.http.routers.kanidm.entrypoints=websecure + - traefik.http.routers.kanidm.rule=Host(`idm.example.com`) + - traefik.http.routers.kanidm.service=kanidm + - traefik.http.serversTransports.kanidm.insecureSkipVerify=true + - traefik.http.services.kanidm.loadbalancer.server.port=8443 + - traefik.http.services.kanidm.loadbalancer.server.scheme=https +volumes: + kanidm_data: {} +``` + +Finally you may run `docker-compose up` to start up both Kanidm and Traefik. diff --git a/kanidm_unix_int/Cargo.toml b/kanidm_unix_int/Cargo.toml index 3153ce9fb..a8c3bfbde 100644 --- a/kanidm_unix_int/Cargo.toml +++ b/kanidm_unix_int/Cargo.toml @@ -70,6 +70,7 @@ tokio-util = { workspace = true, features = ["codec"] } tracing.workspace = true reqwest.workspace = true users.workspace = true +walkdir.workspace = true [features] # default = [ "libsqlite3-sys/bundled" ] diff --git a/kanidm_unix_int/src/constants.rs b/kanidm_unix_int/src/constants.rs index 7616ee496..a888f646d 100644 --- a/kanidm_unix_int/src/constants.rs +++ b/kanidm_unix_int/src/constants.rs @@ -10,5 +10,6 @@ pub const DEFAULT_SHELL: &str = "/bin/sh"; pub const DEFAULT_HOME_PREFIX: &str = "/home/"; pub const DEFAULT_HOME_ATTR: HomeAttr = HomeAttr::Uuid; pub const DEFAULT_HOME_ALIAS: Option = Some(HomeAttr::Spn); +pub const DEFAULT_USE_ETC_SKEL: bool = false; pub const DEFAULT_UID_ATTR_MAP: UidAttr = UidAttr::Spn; pub const DEFAULT_GID_ATTR_MAP: UidAttr = UidAttr::Spn; diff --git a/kanidm_unix_int/src/tasks_daemon.rs b/kanidm_unix_int/src/tasks_daemon.rs index ab89644ca..624139499 100644 --- a/kanidm_unix_int/src/tasks_daemon.rs +++ b/kanidm_unix_int/src/tasks_daemon.rs @@ -12,6 +12,7 @@ use std::ffi::CString; use std::os::unix::fs::symlink; +use std::os::unix::ffi::OsStrExt; use std::path::Path; use std::time::Duration; use std::{fs, io}; @@ -29,6 +30,7 @@ use tokio::net::UnixStream; use tokio::time; use tokio_util::codec::{Decoder, Encoder, Framed}; use users::{get_effective_gid, get_effective_uid}; +use walkdir::WalkDir; struct TaskCodec; @@ -68,7 +70,18 @@ impl TaskCodec { } } -fn create_home_directory(info: &HomeDirectoryInfo, home_prefix: &str) -> Result<(), String> { +fn chown(path: &Path, gid: u32) -> Result<(), String> { + let path_os = CString::new(path.as_os_str().as_bytes()) + .map_err(|_| "Unable to create c-string".to_string())?; + + // Change the owner to the gid - remember, kanidm ONLY has gid's, the uid is implied. + if unsafe { lchown(path_os.as_ptr(), gid, gid) } != 0 { + return Err("Unable to set ownership".to_string()); + } + Ok(()) +} + +fn create_home_directory(info: &HomeDirectoryInfo, home_prefix: &str, use_etc_skel: bool) -> Result<(), String> { // Final sanity check to prevent certain classes of attacks. let name = info .name @@ -96,14 +109,10 @@ fn create_home_directory(info: &HomeDirectoryInfo, home_prefix: &str) -> Result< 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) }; @@ -111,10 +120,26 @@ fn create_home_directory(info: &HomeDirectoryInfo, home_prefix: &str) -> Result< } let _ = unsafe { umask(before) }; - // Change the owner to the gid - remember, kanidm ONLY has gid's, the uid is implied. + chown(hd_path, info.gid)?; - if unsafe { lchown(hd_path_os.as_ptr(), info.gid, info.gid) } != 0 { - return Err("Unable to set ownership".to_string()); + // Copy in structure from /etc/skel/ if present + let skel_dir = Path::new("/etc/skel/"); + if use_etc_skel && skel_dir.exists() { + info!("preparing homedir using /etc/skel"); + for entry in WalkDir::new(skel_dir).into_iter().filter_map(|e| e.ok()) { + let dest = &hd_path.join( + entry + .path() + .strip_prefix(skel_dir) + .map_err(|e| e.to_string())?, + ); + if entry.path().is_dir() { + fs::create_dir_all(dest).map_err(|e| e.to_string())?; + } else { + fs::copy(entry.path(), dest).map_err(|e| e.to_string())?; + } + chown(dest, info.gid)?; + } } } @@ -166,7 +191,7 @@ fn create_home_directory(info: &HomeDirectoryInfo, home_prefix: &str) -> Result< Ok(()) } -async fn handle_tasks(stream: UnixStream, home_prefix: &str) { +async fn handle_tasks(stream: UnixStream, cfg: &KanidmUnixdConfig) { let mut reqs = Framed::new(stream, TaskCodec::new()); loop { @@ -174,7 +199,7 @@ async fn handle_tasks(stream: UnixStream, home_prefix: &str) { Some(Ok(TaskRequest::HomeDirectory(info))) => { debug!("Received task -> HomeDirectory({:?})", info); - let resp = match create_home_directory(&info, home_prefix) { + let resp = match create_home_directory(&info, &cfg.home_prefix, cfg.use_etc_skel) { Ok(()) => TaskResponse::Success, Err(msg) => TaskResponse::Error(msg), }; @@ -249,7 +274,7 @@ async fn main() { 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; + handle_tasks(stream, &cfg).await; } Err(e) => { error!("Unable to find kanidm_unixd, sleeping ..."); diff --git a/kanidm_unix_int/src/unix_config.rs b/kanidm_unix_int/src/unix_config.rs index 3a57868c2..fed00c64e 100644 --- a/kanidm_unix_int/src/unix_config.rs +++ b/kanidm_unix_int/src/unix_config.rs @@ -9,7 +9,7 @@ use serde::Deserialize; use crate::constants::{ DEFAULT_CACHE_TIMEOUT, DEFAULT_CONN_TIMEOUT, DEFAULT_DB_PATH, DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX, DEFAULT_SHELL, DEFAULT_SOCK_PATH, - DEFAULT_TASK_SOCK_PATH, DEFAULT_UID_ATTR_MAP, + DEFAULT_TASK_SOCK_PATH, DEFAULT_UID_ATTR_MAP, DEFAULT_USE_ETC_SKEL, }; #[derive(Debug, Deserialize)] @@ -24,6 +24,7 @@ struct ConfigInt { home_prefix: Option, home_attr: Option, home_alias: Option, + use_etc_skel: Option, uid_attr_map: Option, gid_attr_map: Option, } @@ -81,6 +82,7 @@ pub struct KanidmUnixdConfig { pub home_prefix: String, pub home_attr: HomeAttr, pub home_alias: Option, + pub use_etc_skel: bool, pub uid_attr_map: UidAttr, pub gid_attr_map: UidAttr, } @@ -135,6 +137,7 @@ impl KanidmUnixdConfig { home_prefix: DEFAULT_HOME_PREFIX.to_string(), home_attr: DEFAULT_HOME_ATTR, home_alias: DEFAULT_HOME_ALIAS, + use_etc_skel: DEFAULT_USE_ETC_SKEL, uid_attr_map: DEFAULT_UID_ATTR_MAP, gid_attr_map: DEFAULT_GID_ATTR_MAP, } @@ -220,6 +223,7 @@ impl KanidmUnixdConfig { } }) .unwrap_or(self.home_alias), + use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel), uid_attr_map: config .uid_attr_map .and_then(|v| match v.as_str() { diff --git a/platform/debian/pam-config-kanidm b/platform/debian/pam-config-kanidm new file mode 100644 index 000000000..3e1d17d37 --- /dev/null +++ b/platform/debian/pam-config-kanidm @@ -0,0 +1,19 @@ +Name: Kanidm Authentication +Default: yes +Priority: 300 + +Auth-Type: Primary +Auth: + [success=end new_authtok_reqd=done default=ignore] pam_kanidm.so ignore_unknown_user + +Account-Type: Primary +Account: + [success=end new_authtok_reqd=done default=ignore] pam_kanidm.so ignore_unknown_user + +Session-Type: Additional +Session: + optional pam_kanidm.so + +Password-Type: Additional +Password: + optional pam_kanidm.so diff --git a/platform/debian/simple_pkg.sh b/platform/debian/simple_pkg.sh new file mode 100755 index 000000000..27c16d648 --- /dev/null +++ b/platform/debian/simple_pkg.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + +set -xe + +## NOTE this is based on the Arch Linux PKGBUILD. It combines kanidm_tools, unixd and ssh +# as well as the systemd services. This is a simple alternative for building a tarball for +# use on debian based systems (tested on ubuntu 22.04). + +pushd "$( dirname -- "$0"; )/../../" + +pkgdir=$(realpath kanidm_simple_pkg) +rm -rf "$pkgdir" +mkdir -p "$pkgdir" + +# build the project +make release/kanidm release/kanidm-unixd + +# enable the following block to include deployment specific configuration files +if [ 1 -eq 0 ]; then + mkdir -p deployment-config + + # Customize the following heredocs according to the deployment + cat << EOF > deployment-config/config +uri = "https://idm.example.com" +verify_ca = true +verify_hostnames = true +EOF + + cat << EOF > deployment-config/unixd +pam_allowed_login_groups = [""] +EOF + + install -Dm644 deployment-config/config "${pkgdir}/etc/kanidm/config" + install -Dm644 deployment-config/unixd "${pkgdir}/etc/kanidm/unixd" + +fi + +# This is for allowing login via PAM. It needs to be enabled using `pam-auth-update` +install -Dm644 platform/debian/pam-config-kanidm "${pkgdir}/usr/share/pam-configs/kanidm" + +# Install kanidm cli +install -Dm755 target/release/kanidm "${pkgdir}/usr/local/sbin/kanidm" +install -Dm644 target/release/build/completions/_kanidm "${pkgdir}/usr/share/zsh/site-functions/_kanidm" +install -Dm644 target/release/build/completions/kanidm.bash "${pkgdir}/usr/share/bash-completion/completions/kanidm.sh" + +# Install systemd service files +install -Dm644 examples/systemd/kanidm-unixd.service "${pkgdir}/usr/lib/systemd/system/kanidm-unixd.service" +install -Dm644 examples/systemd/kanidm-unixd-tasks.service "${pkgdir}/usr/lib/systemd/system/kanidm-unixd-tasks.service" + +# NB., the debian style lib dir and security dir +install -Dm755 target/release/libnss_kanidm.so "${pkgdir}/usr/lib/x86_64-linux-gnu/libnss_kanidm.so.2" +install -Dm755 target/release/libpam_kanidm.so "${pkgdir}/usr/lib/x86_64-linux-gnu/security/pam_kanidm.so" + +# install kanidm unix utilities +install -Dm755 target/release/kanidm_cache_clear "${pkgdir}/usr/local/sbin/kanidm_cache_clear" +install -Dm755 target/release/kanidm_cache_invalidate "${pkgdir}/usr/local/sbin/kanidm_cache_invalidate" +install -Dm755 target/release/kanidm_ssh_authorizedkeys "${pkgdir}/usr/local/sbin/kanidm_ssh_authorizedkeys" +install -Dm755 target/release/kanidm_ssh_authorizedkeys_direct "${pkgdir}/usr/local/sbin/kanidm_ssh_authorizedkeys_direct" +install -Dm755 target/release/kanidm_unixd "${pkgdir}/usr/local/sbin/kanidm_unixd" +install -Dm755 target/release/kanidm_unixd_status "${pkgdir}/usr/local/sbin/kanidm_unixd_status" +install -Dm755 target/release/kanidm_unixd_tasks "${pkgdir}/usr/local/sbin/kanidm_unixd_tasks" + +# Install Bash and ZSH completions +install -Dm644 target/release/build/completions/_kanidm_ssh_authorizedkeys_direct "${pkgdir}/usr/share/zsh/site-functions/_kanidm_ssh_authorizedkeys_direct" +install -Dm644 target/release/build/completions/_kanidm_cache_clear "${pkgdir}/usr/share/zsh/site-functions/_kanidm_cache_clear" +install -Dm644 target/release/build/completions/_kanidm_cache_invalidate "${pkgdir}/usr/share/zsh/site-functions/_kanidm_cache_invalidate" +install -Dm644 target/release/build/completions/_kanidm_ssh_authorizedkeys "${pkgdir}/usr/share/zsh/site-functions/_kanidm_ssh_authorizedkeys" +install -Dm644 target/release/build/completions/_kanidm_unixd_status "${pkgdir}/usr/share/zsh/site-functions/_kanidm_unixd_status" + +install -Dm644 target/release/build/completions/kanidm_ssh_authorizedkeys_direct.bash "${pkgdir}/usr/share/bash-completion/completions/kanidm_ssh_authorizedkeys_direct.sh" +install -Dm644 target/release/build/completions/kanidm_cache_clear.bash "${pkgdir}/usr/share/bash-completion/completions/kanidm_cache_clear.sh" +install -Dm644 target/release/build/completions/kanidm_cache_invalidate.bash "${pkgdir}/usr/share/bash-completion/completions/kanidm_cache_invalidate.sh" +install -Dm644 target/release/build/completions/kanidm_ssh_authorizedkeys.bash "${pkgdir}/usr/share/bash-completion/completions/kanidm_ssh_authorizedkeys.sh" +install -Dm644 target/release/build/completions/kanidm_unixd_status.bash "${pkgdir}/usr/share/bash-completion/completions/kanidm_unixd_status.sh" + +tar cvzf "kanidm-client-tools.tar.gz" -C "$pkgdir" . + +# extract the package in root, enable and run the systemd services and then setup nsswitch according to the docs +# and run pam-auth-update. You may also want to setup the ssh config. It's wise to leave a root console open until +# you've confirmed pam-auth-update worked so you don't lock yourself out. + +popd +