SELinux support for kanidm-unixd-tasks daemon (#1661)

* selinux is an optional feature
* unix_integration: add selinux config option

On SELinux systems, this setting controls whether SELinux relabeling of
newly created home directories should be performed. The default value of
this is on (even on non-SELinux systems), but the tasks daemon will
perform an additional runtime check for SELinux support and will disable
this feature automatically if this check fails.

* unix_integration: wire up home dir selinux labeling
* unix_integration: create equivalence rules in SELinux policy for aliases
* book: document selinux setting
* Add myself to CONTRIBUTORS.md

Signed-off-by: Kenton Groombridge <concord@gentoo.org>
This commit is contained in:
Kenton Groombridge 2023-05-30 05:51:12 -04:00 committed by GitHub
parent 10fa229cf1
commit e3d5f3c8ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 289 additions and 15 deletions

View file

@ -27,6 +27,7 @@
- Pi-Cla
- Sebastiano Tocci(Seba-T)
- Minh Phan (MinhPhan8803)
- Kenton Groombridge (0xC0ncord)
## Acknowledgements

171
Cargo.lock generated
View file

@ -426,7 +426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06c690e5e2800f70c0cf8773a9fe7680d66e719dae9b4cabedd13ef4885d056"
dependencies = [
"base64 0.13.1",
"bitflags",
"bitflags 1.3.2",
"cfg-if 1.0.0",
"core-foundation",
"devd-rs",
@ -503,6 +503,29 @@ dependencies = [
"serde",
]
[[package]]
name = "bindgen"
version = "0.65.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5"
dependencies = [
"bitflags 1.3.2",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"prettyplease 0.2.6",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.16",
"which",
]
[[package]]
name = "bit-set"
version = "0.5.3"
@ -524,6 +547,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84"
[[package]]
name = "blake3"
version = "0.3.8"
@ -644,6 +673,15 @@ dependencies = [
"jobserver",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
@ -714,6 +752,17 @@ dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "clang-sys"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "3.2.25"
@ -721,7 +770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"atty",
"bitflags",
"bitflags 1.3.2",
"clap_derive",
"clap_lex",
"indexmap",
@ -1308,6 +1357,12 @@ dependencies = [
"syn 2.0.17",
]
[[package]]
name = "dunce"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "dyn-clone"
version = "1.0.11"
@ -1673,7 +1728,7 @@ version = "0.13.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"libc",
"libgit2-sys",
"log",
@ -1682,6 +1737,12 @@ dependencies = [
"url",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "gloo"
version = "0.8.0"
@ -2366,6 +2427,7 @@ dependencies = [
"reqwest",
"rpassword 7.2.0",
"rusqlite",
"selinux",
"serde",
"serde_json",
"sketching",
@ -2550,6 +2612,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lber"
version = "0.4.0"
@ -2615,6 +2683,16 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if 1.0.0",
"winapi",
]
[[package]]
name = "libnss"
version = "0.4.0"
@ -3017,7 +3095,7 @@ version = "0.10.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cfg-if 1.0.0",
"foreign-types",
"libc",
@ -3158,6 +3236,12 @@ dependencies = [
"proc-macro-hack",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "peg"
version = "0.8.1"
@ -3281,7 +3365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
"bitflags",
"bitflags 1.3.2",
"cfg-if 1.0.0",
"concurrent-queue",
"libc",
@ -3317,6 +3401,16 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "prettyplease"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b69d39aab54d069e7f2fe8cb970493e7834601ca2d8c65fd7bbd183578080d1"
dependencies = [
"proc-macro2",
"syn 2.0.16",
]
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
@ -3553,7 +3647,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -3562,7 +3656,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -3576,6 +3670,12 @@ dependencies = [
"thiserror",
]
[[package]]
name = "reference-counted-singleton"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f"
[[package]]
name = "regex"
version = "1.8.3"
@ -3704,7 +3804,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@ -3712,6 +3812,12 @@ dependencies = [
"smallvec",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.2.3"
@ -3736,7 +3842,7 @@ version = "0.37.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
@ -3818,7 +3924,7 @@ version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
@ -3835,6 +3941,32 @@ dependencies = [
"libc",
]
[[package]]
name = "selinux"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b5ff8f655381fc80f470384da0a461b19b7bf18d685639ccf6df0abd3d2363d"
dependencies = [
"bitflags 2.3.1",
"libc",
"once_cell",
"reference-counted-singleton",
"selinux-sys",
"thiserror",
]
[[package]]
name = "selinux-sys"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f37ccfd0557caec11f117e6d6103aaa217d1aad80cfb6ee3d0e1af5b568a1d9"
dependencies = [
"bindgen",
"cc",
"dunce",
"walkdir",
]
[[package]]
name = "semver"
version = "0.9.0"
@ -4037,6 +4169,12 @@ dependencies = [
"dirs",
]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "signal-hook"
version = "0.3.15"
@ -5042,6 +5180,17 @@ dependencies = [
"web-sys",
]
[[package]]
name = "which"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [
"either",
"libc",
"once_cell",
]
[[package]]
name = "whoami"
version = "1.4.0"
@ -5365,7 +5514,7 @@ checksum = "b64c253c1d401f1ea868ca9988db63958cfa15a69f739101f338d6f05eea8301"
dependencies = [
"boolinator",
"once_cell",
"prettyplease",
"prettyplease 0.1.25",
"proc-macro-error",
"proc-macro2",
"quote",

View file

@ -119,6 +119,7 @@ scim_proto = "^0.2.0"
# scim_proto = { path = "../scim/proto", version = "^0.2.0" }
# scim_proto = { git = "https://github.com/kanidm/scim.git", version = "0.1.1" }
selinux = "^0.4.1"
serde = "^1.0.163"
serde_cbor = { version = "0.12.0-dev", package = "serde_cbor_2" }
serde_json = "^1.0.96"

View file

@ -55,6 +55,7 @@ home_alias = "spn"
use_etc_skel = false
uid_attr_map = "spn"
gid_attr_map = "spn"
selinux = true
```
`pam_allowed_login_groups` defines a set of POSIX groups where membership of any of these groups
@ -89,6 +90,12 @@ when first created. Defaults to false.
`gid_attr_map` chooses which attribute is used for domain local groups in presentation. Defaults to
`spn`. Groups from a trust will always use spn.
`selinux` controls whether the `kanidm_unixd_tasks` daemon should detect and enable SELinux runtime
compatibility features to ensure that newly created home directories are labeled correctly. This
setting as no bearing on systems without SELinux, as these features will automatically be disabled
if SELinux is not detected when the daemon starts. Note that `kanidm_unixd_tasks` must also be built
with the SELinux feature flag for this functionality. Defaults to true.
You can then check the communication status of the daemon:
```bash

View file

@ -14,6 +14,7 @@ repository.workspace = true
[features]
default = ["unix"]
unix = []
selinux = ["dep:selinux"]
[[bin]]
name = "kanidm_unixd"
@ -55,6 +56,7 @@ r2d2.workspace = true
r2d2_sqlite.workspace = true
rpassword.workspace = true
rusqlite.workspace = true
selinux = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
sketching.workspace = true

View file

@ -13,3 +13,4 @@ pub const DEFAULT_HOME_ALIAS: Option<HomeAttr> = 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;
pub const DEFAULT_SELINUX: bool = true;

View file

@ -27,6 +27,8 @@ pub mod client_sync;
pub mod constants;
#[cfg(target_family = "unix")]
pub(crate) mod db;
#[cfg(all(target_family = "unix", feature = "selinux"))]
pub mod selinux_util;
#[cfg(target_family = "unix")]
pub mod unix_config;
#[cfg(target_family = "unix")]

View file

@ -0,0 +1,32 @@
use std::ffi::CString;
use selinux::{kernel_support, label::back_end::File, label::Labeler, KernelSupport};
pub fn supported() -> bool {
return !matches!(kernel_support(), KernelSupport::Unsupported);
}
pub fn get_labeler() -> Result<Labeler<File>, String> {
if let Ok(v) = Labeler::new(&[], true) {
Ok(v)
} else {
Err("Failed getting handle for SELinux labeling".to_string())
}
}
pub fn do_setfscreatecon_for_path(
path_raw: &String,
labeler: &Labeler<File>,
) -> Result<(), String> {
match labeler.look_up(&CString::new(path_raw.to_owned()).unwrap(), 0) {
Ok(context) => {
if let Err(_) = context.set_for_new_file_system_objects(true) {
return Err("Failed setting creation context home directory path".to_string());
}
Ok(())
}
Err(_) => {
return Err("Failed looking up default context for home directory path".to_string());
}
}
}

View file

@ -34,6 +34,15 @@ use tokio_util::codec::{Decoder, Encoder, Framed};
use users::{get_effective_gid, get_effective_uid};
use walkdir::WalkDir;
#[cfg(all(target_family = "unix", feature = "selinux"))]
use kanidm_unix_common::selinux_util;
#[cfg(all(target_family = "unix", feature = "selinux"))]
use selinux::SecurityContext;
#[cfg(all(target_family = "unix", feature = "selinux"))]
use std::process::Command;
#[cfg(all(target_family = "unix", feature = "selinux"))]
use users::get_user_by_uid;
struct TaskCodec;
impl Decoder for TaskCodec {
@ -111,10 +120,35 @@ fn create_home_directory(
return Err("Invalid/Corrupt home directory path - no prefix found".to_string());
}
// Get a handle to the SELinux labeling interface
#[cfg(all(target_family = "unix", feature = "selinux"))]
let labeler = selinux_util::get_labeler()?;
// Construct a path for SELinux context lookups.
// We do this because the policy only associates a home directory to its owning
// user by the name of the directory. Since the real user's home directory is (by
// default) their uuid or spn, its context will always be the policy default
// (usually user_u or unconfined_u). This lookup path is used to ask the policy
// what the context SHOULD be, and we will create policy equivalence rules below
// so that relabels in the future do not break it.
#[cfg(all(target_family = "unix", feature = "selinux"))]
// Yes, gid, because we use the GID number for both the user's UID and primary GID
let sel_lookup_path_raw = match get_user_by_uid(info.gid) {
Some(v) => format!("{}{}", home_prefix, v.name().to_str().unwrap()),
None => {
return Err("Failed looking up username by uid for SELinux relabeling".to_string());
}
};
// Does the home directory exist?
if !hd_path.exists() {
// Set a umask
let before = unsafe { umask(0o0027) };
// Set the SELinux security context for file creation
#[cfg(all(target_family = "unix", feature = "selinux"))]
selinux_util::do_setfscreatecon_for_path(&sel_lookup_path_raw, &labeler)?;
// Create the dir
if let Err(e) = fs::create_dir_all(hd_path) {
let _ = unsafe { umask(before) };
@ -135,15 +169,47 @@ fn create_home_directory(
.strip_prefix(skel_dir)
.map_err(|e| e.to_string())?,
);
#[cfg(all(target_family = "unix", feature = "selinux"))]
{
// Look up the correct SELinux context of this object
let sel_lookup_path = Path::new(&sel_lookup_path_raw).join(
entry
.path()
.strip_prefix(skel_dir)
.map_err(|e| e.to_string())?,
);
selinux_util::do_setfscreatecon_for_path(
&sel_lookup_path.to_str().unwrap().to_string(),
&labeler,
)?;
}
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)?;
// Create equivalence rule in the SELinux policy
#[cfg(all(target_family = "unix", feature = "selinux"))]
if Command::new("semanage")
.args(["fcontext", "-ae", &sel_lookup_path_raw, &hd_path_raw])
.spawn()
.is_err()
{
return Err("Failed creating SELinux policy equivalence rule".to_string());
}
}
}
}
// Reset object creation SELinux context to default
#[cfg(all(target_family = "unix", feature = "selinux"))]
if SecurityContext::set_default_context_for_new_file_system_objects().is_err() {
return Err("Failed resetting SELinux file creation contexts".to_string());
}
let name_rel_path = Path::new(&name);
// Does the aliases exist

View file

@ -4,12 +4,15 @@ use std::fs::File;
use std::io::{ErrorKind, Read};
use std::path::Path;
#[cfg(all(target_family = "unix", feature = "selinux"))]
use crate::selinux_util;
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_USE_ETC_SKEL,
DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX, DEFAULT_SELINUX, DEFAULT_SHELL,
DEFAULT_SOCK_PATH, DEFAULT_TASK_SOCK_PATH, DEFAULT_UID_ATTR_MAP, DEFAULT_USE_ETC_SKEL,
};
#[derive(Debug, Deserialize)]
@ -27,6 +30,7 @@ struct ConfigInt {
use_etc_skel: Option<bool>,
uid_attr_map: Option<String>,
gid_attr_map: Option<String>,
selinux: Option<bool>,
}
#[derive(Debug, Copy, Clone)]
@ -85,6 +89,7 @@ pub struct KanidmUnixdConfig {
pub use_etc_skel: bool,
pub uid_attr_map: UidAttr,
pub gid_attr_map: UidAttr,
pub selinux: bool,
}
impl Default for KanidmUnixdConfig {
@ -115,7 +120,9 @@ impl Display for KanidmUnixdConfig {
}
writeln!(f, "uid_attr_map: {}", self.uid_attr_map)?;
writeln!(f, "gid_attr_map: {}", self.gid_attr_map)
writeln!(f, "gid_attr_map: {}", self.gid_attr_map)?;
writeln!(f, "selinux: {}", self.selinux)
}
}
@ -140,6 +147,7 @@ impl KanidmUnixdConfig {
use_etc_skel: DEFAULT_USE_ETC_SKEL,
uid_attr_map: DEFAULT_UID_ATTR_MAP,
gid_attr_map: DEFAULT_GID_ATTR_MAP,
selinux: DEFAULT_SELINUX,
}
}
@ -246,6 +254,11 @@ impl KanidmUnixdConfig {
}
})
.unwrap_or(self.gid_attr_map),
selinux: match config.selinux.unwrap_or(self.selinux) {
#[cfg(all(target_family = "unix", feature = "selinux"))]
true => selinux_util::supported(),
_ => false,
},
})
}
}