README.md (5987B)
1 Privilege separation library for Unix-likes OSes 2 ================================================ 3 4 [<img alt="git" src="https://git.philomathiclife.com/badges/priv_sep.svg" height="20">](https://git.philomathiclife.com/priv_sep/log.html) 5 [<img alt="crates.io" src="https://img.shields.io/crates/v/priv_sep.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/priv_sep) 6 [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-priv_sep-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/priv_sep/latest/priv_sep/) 7 8 `priv_sep` is a library that uses the system's libc to perform privilege separation and privilege reduction. 9 10 Note the only platforms that are currently supported are platforms that correspond to the following 11 `target_os` values: 12 13 * `dragonfly` 14 * `freebsd` 15 * `linux` 16 * `macos` 17 * `netbsd` 18 * `openbsd` 19 20 ## `priv_sep` in action for OpenBSD 21 22 ```rust 23 use core::{convert::Infallible, ffi::CStr}; 24 use priv_sep::{Permissions, PrivDropErr, Promise, Promises}; 25 use std::{ 26 fs, 27 io::Error, 28 net::{Ipv6Addr, SocketAddrV6}, 29 }; 30 use tokio::net::TcpListener; 31 #[tokio::main(flavor = "current_thread")] 32 async fn main() -> Result<Infallible, PrivDropErr<Error>> { 33 /// Config file. 34 const CONFIG: &CStr = c"config"; 35 /// Config file. 36 const CONFIG_STR: &str = match CONFIG.to_str() { 37 Ok(val) => val, 38 Err(_) => panic!("config is not a valid str"), 39 }; 40 // Get the user ID and group ID for nobody from `passwd(5)`. 41 // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`. 42 // `pledge(2)` `id`, `inet`, `rpath`, `stdio`, and `unveil`. 43 // Bind to TCP `[::1]:443` as root. 44 // `setgroups(2)` to drop all supplementary groups. 45 // `setresgid(2)` to the group ID associated with nobody. 46 // `setresuid(2)` to the user ID associated with nobody. 47 // Remove `id` from our `pledge(2)`d promises. 48 let (listener, mut promises) = Promises::new_chroot_then_priv_drop_async( 49 c"nobody", 50 c"/path/chroot/", 51 [Promise::Inet, Promise::Rpath, Promise::Unveil], 52 false, 53 async || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await, 54 ) 55 .await?; 56 // At this point, the process is running under nobody. 57 // Only allow file system access to `config` and only allow read access to it. 58 Permissions::READ.unveil(CONFIG)?; 59 // Read `config`. 60 // This will of course fail if the file does not exist or nobody does not 61 // have read permissions. 62 let config = fs::read(CONFIG_STR).map_err(PrivDropErr::Other)?; 63 // Remove file system access. 64 Permissions::NONE.unveil(CONFIG)?; 65 // Remove `rpath` and `unveil` from our `pledge(2)`d promises 66 // (i.e., only have `inet` and `stdio` abilities when we begin accepting TCP connections). 67 promises.remove_promises_then_pledge([Promise::Rpath, Promise::Unveil])?; 68 loop { 69 // Handle TCP connections. 70 if let Ok((_, ip)) = listener.accept().await { 71 assert!(ip.is_ipv6()); 72 } 73 } 74 } 75 ``` 76 77 ## `priv_sep` in action for Unix-like OSes 78 79 ```rust 80 use core::convert::Infallible; 81 use priv_sep::{PrivDropErr, UserInfo}; 82 use std::{ 83 io::Error, 84 net::{Ipv6Addr, SocketAddrV6}, 85 }; 86 use tokio::net::TcpListener; 87 #[tokio::main(flavor = "current_thread")] 88 async fn main() -> Result<Infallible, PrivDropErr<Error>> { 89 // Get the user ID and group ID for nobody from `passwd(5)`. 90 // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`. 91 // Bind to TCP `[::1]:443` as root. 92 // `setgroups(2)` to drop all supplementary groups. 93 // `setresgid(2)` to the group ID associated with nobody. 94 // `setresuid(2)` to the user ID associated with nobody. 95 let listener = 96 UserInfo::chroot_then_priv_drop_async(c"nobody", c"/path/chroot/", false, async || { 97 TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await 98 }) 99 .await?; 100 // At this point, the process is running under nobody. 101 loop { 102 // Handle TCP connections. 103 if let Ok((_, ip)) = listener.accept().await { 104 assert!(ip.is_ipv6()); 105 } 106 } 107 } 108 ``` 109 110 ## Minimum Supported Rust Version (MSRV) 111 112 This will frequently be updated to be the same as stable. Specifically, any time stable is updated and that 113 update has "useful" features or compilation no longer succeeds (e.g., due to new compiler lints), then MSRV 114 will be updated. 115 116 MSRV changes will correspond to a SemVer minor version bump. 117 118 ## SemVer Policy 119 120 * All on-by-default features of this library are covered by SemVer 121 * MSRV is considered exempt from SemVer as noted above 122 123 ## License 124 125 Licensed under either of 126 127 * Apache License, Version 2.0 ([LICENSE-APACHE](https://www.apache.org/licenses/LICENSE-2.0)) 128 * MIT license ([LICENSE-MIT](https://opensource.org/licenses/MIT)) 129 130 at your option. 131 132 ## Contribution 133 134 Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, 135 as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 136 137 Before any PR is sent, `cargo clippy --all-targets` and `cargo t` should be run _for each possible combination of 138 "features"_ using the stable and MSRV toolchains. One easy way to achieve this is by invoking 139 [`ci-cargo`](https://crates.io/crates/ci-cargo) with the `--all-targets` option in the `priv_sep` directory. 140 Additionally, one should test all `ignore` tests as both root and non-root. When run as root or on OpenBSD, 141 `ignore` tests should be run separately. Last, `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` 142 should be run to ensure documentation can be built on non-OpenBSD platforms; otherwise `cargo doc --all-features` 143 should be run. 144 145 ### Status 146 147 The crate is only tested on the `x86_64-unknown-linux-gnu`, `x86_64-unknown-openbsd`, and `aarch64-apple-darwin` 148 targets; but it should work on most of the supported platforms.