priv_sep

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

README.md (6683B)


      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 for Unix-like platforms. The following `target_os` values are supported:
     10 
     11 * `dragonfly`
     12 * `freebsd`
     13 * `linux`
     14 * `macos`
     15 * `netbsd`
     16 * `openbsd`
     17 
     18 ## `priv_sep` in action
     19 
     20 ```rust
     21 use core::convert::Infallible;
     22 use priv_sep::{PrivDropErr, UserInfo};
     23 use std::{
     24     io::Error,
     25     net::{Ipv6Addr, SocketAddrV6},
     26 };
     27 use tokio::net::TcpListener;
     28 #[tokio::main(flavor = "current_thread")]
     29 async fn main() -> Result<Infallible, PrivDropErr<Error>> {
     30     // Get the user ID and group ID for nobody from `passwd(5)`.
     31     // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
     32     // Bind to TCP `[::1]:443` as root.
     33     // `setgroups(2)` to drop all supplementary groups.
     34     // `setresgid(2)` to the group ID associated with nobody.
     35     // `setresuid(2)` to the user ID associated with nobody.
     36     let listener =
     37         UserInfo::chroot_then_priv_drop_async(c"nobody", c"/path/chroot/", false, async || {
     38             TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
     39         })
     40         .await?;
     41     // At this point, the process is running under nobody.
     42     loop {
     43         // Handle TCP connections.
     44         if let Ok((_, ip)) = listener.accept().await {
     45             assert!(ip.is_ipv6());
     46         }
     47     }
     48 }
     49 ```
     50 
     51 <details>
     52 <summary>Incorporating <a href="https://man.openbsd.org/pledge.2"><code>pledge(2)</code></a> and <a href="https://man.openbsd.org/unveil.2"><code>unveil(2)</code></a> on OpenBSD</summary>
     53 
     54 ```rust
     55 use core::convert::Infallible;
     56 use priv_sep::{Permissions, PrivDropErr, Promise, Promises};
     57 use std::{
     58     fs,
     59     io::Error,
     60     net::{Ipv6Addr, SocketAddrV6},
     61 };
     62 use tokio::net::TcpListener;
     63 #[tokio::main(flavor = "current_thread")]
     64 async fn main() -> Result<Infallible, PrivDropErr<Error>> {
     65     /// Config file.
     66     const CONFIG: &str = "config";
     67     // Get the user ID and group ID for nobody from `passwd(5)`.
     68     // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
     69     // `pledge(2)` `id`, `inet`, `rpath`, `stdio`, and `unveil`.
     70     // Bind to TCP `[::1]:443` as root.
     71     // `setgroups(2)` to drop all supplementary groups.
     72     // `setresgid(2)` to the group ID associated with nobody.
     73     // `setresuid(2)` to the user ID associated with nobody.
     74     // Remove `id` from our `pledge(2)`d promises.
     75     let (listener, mut promises) = Promises::new_chroot_then_priv_drop_async(
     76         c"nobody",
     77         c"/path/chroot/",
     78         [Promise::Inet, Promise::Rpath, Promise::Unveil],
     79         false,
     80         async || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await,
     81     )
     82     .await?;
     83     // At this point, the process is running under nobody.
     84     // Only allow file system access to `config` and only allow read access to it.
     85     Permissions::READ.unveil(CONFIG)?;
     86     // Read `config`.
     87     // This will of course fail if the file does not exist or nobody does not
     88     // have read permissions.
     89     let config = fs::read(CONFIG).map_err(PrivDropErr::Other)?;
     90     // Remove file system access.
     91     Permissions::NONE.unveil(CONFIG)?;
     92     // Remove `rpath` and `unveil` from our `pledge(2)`d promises
     93     // (i.e., only have `inet` and `stdio` abilities when we begin accepting TCP connections).
     94     promises.remove_promises_then_pledge([Promise::Rpath, Promise::Unveil])?;
     95     loop {
     96         // Handle TCP connections.
     97         if let Ok((_, ip)) = listener.accept().await {
     98             assert!(ip.is_ipv6());
     99         }
    100     }
    101 }
    102 ```
    103 </details>
    104 
    105 ## Cargo "features"
    106 
    107 ### `alloc`
    108 
    109 Enables [`alloc`](https://doc.rust-lang.org/stable/alloc/) support. While "typical" use of `priv_sep` should work
    110 without `alloc`, there are cases where one may desire heap allocation. For example if a database entry associated
    111 with a user requires more than 1 KiB of space, `UserInfo::new` will error with `Errno::ERANGE` when `alloc` is not
    112 enabled.
    113 
    114 Additional `CStrHelper` `impl`s are exposed as well (e.g., `String`).
    115 
    116 ### `std`
    117 
    118 Enables [`std`](https://doc.rust-lang.org/stable/std/) support. This is useful for additional `CStrHelper` `impl`s
    119 (e.g., `OsStr`) as well as `TryFrom<Error>` and `From<Errno>`.
    120 
    121 This feature implies [`alloc`](#alloc) and is enabled by default via the `default` feature.
    122 
    123 ## Minimum Supported Rust Version (MSRV)
    124 
    125 This will frequently be updated to be the same as stable. Specifically, any time stable is updated and that
    126 update has "useful" features or compilation no longer succeeds (e.g., due to new compiler lints), then MSRV
    127 will be updated.
    128 
    129 MSRV changes will correspond to a SemVer minor version bump.
    130 
    131 ## SemVer Policy
    132 
    133 * All on-by-default features of this library are covered by SemVer
    134 * MSRV is considered exempt from SemVer as noted above
    135 
    136 ## License
    137 
    138 Licensed under either of
    139 
    140 * Apache License, Version 2.0 ([LICENSE-APACHE](https://www.apache.org/licenses/LICENSE-2.0))
    141 * MIT license ([LICENSE-MIT](https://opensource.org/licenses/MIT))
    142 
    143 at your option.
    144 
    145 ## Contribution
    146 
    147 Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you,
    148 as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
    149 
    150 Before any PR is sent, `cargo clippy --all-targets`, `cargo test --all-targets`, and `cargo test --doc` should be
    151 run _for each possible combination of "features"_ using the stable and MSRV toolchains. One easy way to achieve this
    152 is by invoking [`ci-cargo`](https://crates.io/crates/ci-cargo) as `ci-cargo clippy --all-targets test --all-targets`
    153 in the `priv_sep` directory.
    154 
    155 Additionally, one should test all `ignore` tests as both root and non-root for both toolchains. These tests should
    156 be run individually since they may interfere with each other.
    157 
    158 Last, `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features -Zbuild-std=std` should be run to ensure
    159 documentation can be built.
    160 
    161 ### Status
    162 
    163 The crate is only tested on the `x86_64-unknown-linux-gnu`, `x86_64-unknown-openbsd`, and `aarch64-apple-darwin`
    164 targets; but it should work on most of the supported platforms.