priv_sep

Privilege separation library.
git clone https://git.philomathiclife.com/repos/priv_sep
Log | Files | Refs | README

README.md (6100B)


      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
     21 
     22 ```rust
     23 use core::convert::Infallible;
     24 use priv_sep::{PrivDropErr, UserInfo};
     25 use std::{
     26     io::Error,
     27     net::{Ipv6Addr, SocketAddrV6},
     28 };
     29 use tokio::net::TcpListener;
     30 #[tokio::main(flavor = "current_thread")]
     31 async fn main() -> Result<Infallible, PrivDropErr<Error>> {
     32     // Get the user ID and group ID for nobody from `passwd(5)`.
     33     // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
     34     // Bind to TCP `[::1]:443` as root.
     35     // `setgroups(2)` to drop all supplementary groups.
     36     // `setresgid(2)` to the group ID associated with nobody.
     37     // `setresuid(2)` to the user ID associated with nobody.
     38     let listener =
     39         UserInfo::chroot_then_priv_drop_async(c"nobody", c"/path/chroot/", false, async || {
     40             TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
     41         })
     42         .await?;
     43     // At this point, the process is running under nobody.
     44     loop {
     45         // Handle TCP connections.
     46         if let Ok((_, ip)) = listener.accept().await {
     47             assert!(ip.is_ipv6());
     48         }
     49     }
     50 }
     51 ```
     52 
     53 ## `priv_sep` in action for OpenBSD incorporating `pledge(2)` and `unveil(2)`
     54 
     55 ```rust
     56 use core::{convert::Infallible, ffi::CStr};
     57 use priv_sep::{Permissions, PrivDropErr, Promise, Promises};
     58 use std::{
     59     fs,
     60     io::Error,
     61     net::{Ipv6Addr, SocketAddrV6},
     62 };
     63 use tokio::net::TcpListener;
     64 #[tokio::main(flavor = "current_thread")]
     65 async fn main() -> Result<Infallible, PrivDropErr<Error>> {
     66     /// Config file.
     67     const CONFIG: &CStr = c"config";
     68     /// Config file.
     69     const CONFIG_STR: &str = match CONFIG.to_str() {
     70         Ok(val) => val,
     71         Err(_) => panic!("config is not a valid str"),
     72     };
     73     // Get the user ID and group ID for nobody from `passwd(5)`.
     74     // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
     75     // `pledge(2)` `id`, `inet`, `rpath`, `stdio`, and `unveil`.
     76     // Bind to TCP `[::1]:443` as root.
     77     // `setgroups(2)` to drop all supplementary groups.
     78     // `setresgid(2)` to the group ID associated with nobody.
     79     // `setresuid(2)` to the user ID associated with nobody.
     80     // Remove `id` from our `pledge(2)`d promises.
     81     let (listener, mut promises) = Promises::new_chroot_then_priv_drop_async(
     82         c"nobody",
     83         c"/path/chroot/",
     84         [Promise::Inet, Promise::Rpath, Promise::Unveil],
     85         false,
     86         async || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await,
     87     )
     88     .await?;
     89     // At this point, the process is running under nobody.
     90     // Only allow file system access to `config` and only allow read access to it.
     91     Permissions::READ.unveil(CONFIG)?;
     92     // Read `config`.
     93     // This will of course fail if the file does not exist or nobody does not
     94     // have read permissions.
     95     let config = fs::read(CONFIG_STR).map_err(PrivDropErr::Other)?;
     96     // Remove file system access.
     97     Permissions::NONE.unveil(CONFIG)?;
     98     // Remove `rpath` and `unveil` from our `pledge(2)`d promises
     99     // (i.e., only have `inet` and `stdio` abilities when we begin accepting TCP connections).
    100     promises.remove_promises_then_pledge([Promise::Rpath, Promise::Unveil])?;
    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`, `cargo test --all-targets`, and `cargo test --doc` should be
    138 run _for each possible combination of "features"_ using the stable and MSRV toolchains. One easy way to achieve this
    139 is by invoking [`ci-cargo`](https://crates.io/crates/ci-cargo) as `ci-cargo clippy --all-targets test --all-targets`
    140 in the `priv_sep` directory.
    141 
    142 Additionally, one should test all `ignore` tests as both root and non-root for both toolchains. These tests should
    143 be run individually since they may interfere with each other.
    144 
    145 Last, `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` should be run to ensure documentation can be
    146 built on non-OpenBSD platforms; otherwise `cargo doc --all-features` should be run.
    147 
    148 ### Status
    149 
    150 The crate is only tested on the `x86_64-unknown-linux-gnu`, `x86_64-unknown-openbsd`, and `aarch64-apple-darwin`
    151 targets; but it should work on most of the supported platforms.