From e3d5f3c8ae84ae6627c3171f015db68bd12b3d6b Mon Sep 17 00:00:00 2001 From: Kenton Groombridge Date: Tue, 30 May 2023 05:51:12 -0400 Subject: [PATCH] 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 --- CONTRIBUTORS.md | 1 + Cargo.lock | 171 ++++++++++++++++++++-- Cargo.toml | 1 + book/src/integrations/pam_and_nsswitch.md | 7 + examples/unixd | 2 +- unix_integration/Cargo.toml | 2 + unix_integration/src/constants.rs | 1 + unix_integration/src/lib.rs | 2 + unix_integration/src/selinux_util.rs | 32 ++++ unix_integration/src/tasks_daemon.rs | 66 +++++++++ unix_integration/src/unix_config.rs | 19 ++- 11 files changed, 289 insertions(+), 15 deletions(-) create mode 100644 unix_integration/src/selinux_util.rs diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2ebd6c4fa..411af3c00 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -27,6 +27,7 @@ - Pi-Cla - Sebastiano Tocci(Seba-T) - Minh Phan (MinhPhan8803) +- Kenton Groombridge (0xC0ncord) ## Acknowledgements diff --git a/Cargo.lock b/Cargo.lock index edb5bfb3a..1c2f851e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index a565df4e1..5913f8de8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/book/src/integrations/pam_and_nsswitch.md b/book/src/integrations/pam_and_nsswitch.md index 409191f37..4aee89760 100644 --- a/book/src/integrations/pam_and_nsswitch.md +++ b/book/src/integrations/pam_and_nsswitch.md @@ -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 diff --git a/examples/unixd b/examples/unixd index f60b7e82d..31cd66e2d 100644 --- a/examples/unixd +++ b/examples/unixd @@ -7,4 +7,4 @@ # home_alias = "spn" # use_etc_skel = false # uid_attr_map = "spn" -# gid_attr_map = "spn" \ No newline at end of file +# gid_attr_map = "spn" diff --git a/unix_integration/Cargo.toml b/unix_integration/Cargo.toml index 0ea623a1e..3a5e7e283 100644 --- a/unix_integration/Cargo.toml +++ b/unix_integration/Cargo.toml @@ -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 diff --git a/unix_integration/src/constants.rs b/unix_integration/src/constants.rs index a888f646d..ab6b8e644 100644 --- a/unix_integration/src/constants.rs +++ b/unix_integration/src/constants.rs @@ -13,3 +13,4 @@ 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; +pub const DEFAULT_SELINUX: bool = true; diff --git a/unix_integration/src/lib.rs b/unix_integration/src/lib.rs index e1a34bd5c..bbefec968 100644 --- a/unix_integration/src/lib.rs +++ b/unix_integration/src/lib.rs @@ -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")] diff --git a/unix_integration/src/selinux_util.rs b/unix_integration/src/selinux_util.rs new file mode 100644 index 000000000..04b415af4 --- /dev/null +++ b/unix_integration/src/selinux_util.rs @@ -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, 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, +) -> 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()); + } + } +} diff --git a/unix_integration/src/tasks_daemon.rs b/unix_integration/src/tasks_daemon.rs index 08485bca8..5bc2338d7 100644 --- a/unix_integration/src/tasks_daemon.rs +++ b/unix_integration/src/tasks_daemon.rs @@ -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,16 +169,48 @@ 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 for alias in info.aliases.iter() { diff --git a/unix_integration/src/unix_config.rs b/unix_integration/src/unix_config.rs index fed00c64e..3fff33366 100644 --- a/unix_integration/src/unix_config.rs +++ b/unix_integration/src/unix_config.rs @@ -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, uid_attr_map: Option, gid_attr_map: Option, + selinux: Option, } #[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, + }, }) } }