Sentry integration (#13)
* Sentry integration * Introduce a more generic CIRRUS_SENTRY_TAGS * Revert switching to nightly toolchain
This commit is contained in:
parent
22c92688e5
commit
11910d8540
|
|
@ -23,5 +23,5 @@ task:
|
||||||
install_goreleaser_script:
|
install_goreleaser_script:
|
||||||
- brew install go goreleaser/tap/goreleaser-pro
|
- brew install go goreleaser/tap/goreleaser-pro
|
||||||
build_script:
|
build_script:
|
||||||
- cargo build --release
|
- cargo build --profile release-with-debug
|
||||||
release_script: goreleaser
|
release_script: goreleaser
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ builds:
|
||||||
goarch:
|
goarch:
|
||||||
- arm64
|
- arm64
|
||||||
prebuilt:
|
prebuilt:
|
||||||
path: target/release/softnet
|
path: "target/release-with-debug/softnet"
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- id: binary
|
- id: binary
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,6 +7,10 @@ edition = "2021"
|
||||||
[lib]
|
[lib]
|
||||||
path = "lib/mod.rs"
|
path = "lib/mod.rs"
|
||||||
|
|
||||||
|
[profile.release-with-debug]
|
||||||
|
inherits = "release"
|
||||||
|
debug = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
smoltcp = "0.8.1"
|
smoltcp = "0.8.1"
|
||||||
libc = "0.2.126"
|
libc = "0.2.126"
|
||||||
|
|
@ -16,8 +20,10 @@ vmnet = "0.1.1"
|
||||||
clap = { version = "3.1.18", features = ["derive"] }
|
clap = { version = "3.1.18", features = ["derive"] }
|
||||||
mac_address = "1.1.3"
|
mac_address = "1.1.3"
|
||||||
privdrop = "0.5.2"
|
privdrop = "0.5.2"
|
||||||
thiserror = "1.0.31"
|
anyhow = { version = "1.0.66", features = ["backtrace"] }
|
||||||
ip_network = "0.4.1"
|
ip_network = "0.4.1"
|
||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
system-configuration = "0.5.0"
|
system-configuration = "0.5.0"
|
||||||
num_enum = "0.5.7"
|
num_enum = "0.5.7"
|
||||||
|
sentry = "0.29.1"
|
||||||
|
sentry-anyhow = { version = "0.29.1", features = ["backtrace"] }
|
||||||
|
|
|
||||||
30
lib/host.rs
30
lib/host.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Error, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use std::os::unix::net::UnixDatagram;
|
use std::os::unix::net::UnixDatagram;
|
||||||
|
|
@ -27,27 +27,23 @@ impl Host {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.map_err(|err| Error::VmnetFailed { source: err })?;
|
.context("failed to initialize vmnet interface")?;
|
||||||
|
|
||||||
// Retrieve first IP (gateway) used for this interface
|
// Retrieve first IP (gateway) used for this interface
|
||||||
let gateway_ip = match interface.parameters().get(ParameterKind::StartAddress) {
|
let Some(Parameter::StartAddress(gateway_ip)) = interface.parameters().get(ParameterKind::StartAddress) else {
|
||||||
Some(Parameter::StartAddress(gateway_ip)) => gateway_ip,
|
return Err(anyhow!("failed to retrieve vmnet's interface start address"));
|
||||||
_ => return Err(Error::VmnetUnexpected),
|
|
||||||
};
|
};
|
||||||
let gateway_ip = Ipv4Addr::from_str(&gateway_ip).map_err(|_| Error::VmnetUnexpected)?;
|
let gateway_ip = Ipv4Addr::from_str(&gateway_ip)
|
||||||
|
.context("failed to parse vmnet's interface start address")?;
|
||||||
|
|
||||||
// Retrieve max packet size for this interface
|
// Retrieve max packet size for this interface
|
||||||
let max_packet_size = match interface.parameters().get(ParameterKind::MaxPacketSize) {
|
let Some(Parameter::MaxPacketSize(max_packet_size)) = interface.parameters().get(ParameterKind::MaxPacketSize) else {
|
||||||
Some(Parameter::MaxPacketSize(max_packet_size)) => max_packet_size,
|
return Err(anyhow!("failed to retrieve vmnet's interface max packet size"));
|
||||||
_ => return Err(Error::VmnetUnexpected),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up a socketpair() to emulate polling of the vmnet interface
|
// Set up a socketpair() to emulate polling of the vmnet interface
|
||||||
let (new_packets_tx, new_packets_rx) =
|
let (new_packets_tx, new_packets_rx) = UnixDatagram::pair()?;
|
||||||
UnixDatagram::pair().map_err(|err| Error::InitFailed { source: err.into() })?;
|
new_packets_rx.set_nonblocking(true)?;
|
||||||
new_packets_rx
|
|
||||||
.set_nonblocking(true)
|
|
||||||
.map_err(|err| Error::InitFailed { source: err.into() })?;
|
|
||||||
|
|
||||||
let (callback_can_continue_tx, callback_can_continue_rx) = sync_channel(0);
|
let (callback_can_continue_tx, callback_can_continue_rx) = sync_channel(0);
|
||||||
|
|
||||||
|
|
@ -64,7 +60,7 @@ impl Host {
|
||||||
// [1]: https://en.wikipedia.org/wiki/Blocks_(C_language_extension)
|
// [1]: https://en.wikipedia.org/wiki/Blocks_(C_language_extension)
|
||||||
callback_can_continue_rx.recv().unwrap();
|
callback_can_continue_rx.recv().unwrap();
|
||||||
})
|
})
|
||||||
.map_err(|err| Error::VmnetFailed { source: err })?;
|
.context("failed to set vmnet interface's event callback")?;
|
||||||
|
|
||||||
Ok(Host {
|
Ok(Host {
|
||||||
interface,
|
interface,
|
||||||
|
|
@ -104,14 +100,14 @@ impl Host {
|
||||||
// First make sure our callback won't be scheduled again after it finishes
|
// First make sure our callback won't be scheduled again after it finishes
|
||||||
self.interface
|
self.interface
|
||||||
.clear_event_callback()
|
.clear_event_callback()
|
||||||
.map_err(|err| Error::VmnetFailed { source: err })?;
|
.context("failed to clear vmnet interface's event callback")?;
|
||||||
|
|
||||||
// Now let the callback finish
|
// Now let the callback finish
|
||||||
let _ = self.callback_can_continue_tx.send(());
|
let _ = self.callback_can_continue_tx.send(());
|
||||||
|
|
||||||
self.interface
|
self.interface
|
||||||
.finalize()
|
.finalize()
|
||||||
.map_err(|err| Error::VmnetFailed { source: err })?;
|
.context("failed to finalize vmnet's interface")?;
|
||||||
|
|
||||||
self.finalized = true;
|
self.finalized = true;
|
||||||
|
|
||||||
|
|
|
||||||
25
lib/mod.rs
25
lib/mod.rs
|
|
@ -3,28 +3,3 @@ mod host;
|
||||||
mod poller;
|
mod poller;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
mod vm;
|
mod vm;
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("initialization failed")]
|
|
||||||
InitFailed { source: Box<dyn std::error::Error> },
|
|
||||||
|
|
||||||
#[error("failed to poll")]
|
|
||||||
PollFailed { source: std::io::Error },
|
|
||||||
|
|
||||||
#[error("vmnet failed")]
|
|
||||||
VmnetFailed { source: vmnet::Error },
|
|
||||||
|
|
||||||
#[error("vmnet returned unexpected data")]
|
|
||||||
VmnetUnexpected,
|
|
||||||
|
|
||||||
#[error("failed to do I/O on VM socket")]
|
|
||||||
VMIOFailed { source: std::io::Error },
|
|
||||||
|
|
||||||
#[error("failed to do I/O on host socket")]
|
|
||||||
HostIOFailed { source: vmnet::Error },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Error, Result};
|
use anyhow::Result;
|
||||||
use num_enum::IntoPrimitive;
|
use num_enum::IntoPrimitive;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -19,8 +19,7 @@ enum EventKey {
|
||||||
|
|
||||||
impl Poller {
|
impl Poller {
|
||||||
pub fn new(vm_fd: RawFd, host_fd: RawFd) -> Result<Poller> {
|
pub fn new(vm_fd: RawFd, host_fd: RawFd) -> Result<Poller> {
|
||||||
let poller =
|
let poller = polling::Poller::new()?;
|
||||||
polling::Poller::new().map_err(|err| Error::InitFailed { source: err.into() })?;
|
|
||||||
|
|
||||||
Ok(Poller {
|
Ok(Poller {
|
||||||
poller,
|
poller,
|
||||||
|
|
@ -31,13 +30,9 @@ impl Poller {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arm(&self) -> Result<()> {
|
pub fn arm(&self) -> Result<()> {
|
||||||
|
self.poller.add(self.vm_fd as RawFd, self.vm_interest())?;
|
||||||
self.poller
|
self.poller
|
||||||
.add(self.vm_fd as RawFd, self.vm_interest())
|
.add(self.host_fd as RawFd, self.host_interest())?;
|
||||||
.map_err(|err| Error::PollFailed { source: err })?;
|
|
||||||
|
|
||||||
self.poller
|
|
||||||
.add(self.host_fd as RawFd, self.host_interest())
|
|
||||||
.map_err(|err| Error::PollFailed { source: err })?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -46,19 +41,16 @@ impl Poller {
|
||||||
self.events.clear();
|
self.events.clear();
|
||||||
|
|
||||||
self.poller
|
self.poller
|
||||||
.modify(self.vm_fd as RawFd, self.vm_interest())
|
.modify(self.vm_fd as RawFd, self.vm_interest())?;
|
||||||
.map_err(|err| Error::PollFailed { source: err })?;
|
|
||||||
self.poller
|
self.poller
|
||||||
.modify(self.host_fd as RawFd, self.host_interest())
|
.modify(self.host_fd as RawFd, self.host_interest())?;
|
||||||
.map_err(|err| Error::PollFailed { source: err })?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(&mut self) -> Result<(bool, bool)> {
|
pub fn wait(&mut self) -> Result<(bool, bool)> {
|
||||||
self.poller
|
self.poller
|
||||||
.wait(&mut self.events, Some(Duration::from_millis(100)))
|
.wait(&mut self.events, Some(Duration::from_millis(100)))?;
|
||||||
.map_err(|err| Error::PollFailed { source: err })?;
|
|
||||||
|
|
||||||
let vm_readable = self.events.iter().any(|ev| ev.key == EventKey::VM.into());
|
let vm_readable = self.events.iter().any(|ev| ev.key == EventKey::VM.into());
|
||||||
let host_readable = self.events.iter().any(|ev| ev.key == EventKey::Host.into());
|
let host_readable = self.events.iter().any(|ev| ev.key == EventKey::Host.into());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::proxy::udp_packet_helper::UdpPacketHelper;
|
use crate::proxy::udp_packet_helper::UdpPacketHelper;
|
||||||
use crate::proxy::Proxy;
|
use crate::proxy::Proxy;
|
||||||
use crate::{Error, Result};
|
use anyhow::{Context, Result};
|
||||||
use smoltcp::wire::{EthernetFrame, EthernetProtocol, Ipv4Packet, UdpPacket};
|
use smoltcp::wire::{EthernetFrame, EthernetProtocol, Ipv4Packet, UdpPacket};
|
||||||
|
|
||||||
impl Proxy {
|
impl Proxy {
|
||||||
|
|
@ -19,7 +19,7 @@ impl Proxy {
|
||||||
self.vm
|
self.vm
|
||||||
.write(frame.as_ref())
|
.write(frame.as_ref())
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|err| Error::VMIOFailed { source: err })
|
.context("failed to write to the VM")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allowed_from_host(&mut self, frame: &EthernetFrame<&[u8]>) -> Option<()> {
|
fn allowed_from_host(&mut self, frame: &EthernetFrame<&[u8]>) -> Option<()> {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ use crate::dhcp_snooper::DhcpSnooper;
|
||||||
use crate::host::Host;
|
use crate::host::Host;
|
||||||
use crate::poller::Poller;
|
use crate::poller::Poller;
|
||||||
use crate::vm::VM;
|
use crate::vm::VM;
|
||||||
use crate::Error;
|
use anyhow::Result;
|
||||||
use crate::Result;
|
|
||||||
use mac_address::MacAddress;
|
use mac_address::MacAddress;
|
||||||
use smoltcp::wire::EthernetFrame;
|
use smoltcp::wire::EthernetFrame;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
|
@ -69,7 +68,7 @@ impl Proxy {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(Error::VMIOFailed { source: err });
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +87,7 @@ impl Proxy {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(Error::HostIOFailed { source: err });
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::proxy::udp_packet_helper::UdpPacketHelper;
|
use crate::proxy::udp_packet_helper::UdpPacketHelper;
|
||||||
use crate::proxy::Proxy;
|
use crate::proxy::Proxy;
|
||||||
use crate::{Error, Result};
|
use anyhow::Context;
|
||||||
|
use anyhow::Result;
|
||||||
use smoltcp::wire::{
|
use smoltcp::wire::{
|
||||||
ArpPacket, EthernetFrame, EthernetProtocol, IpProtocol, Ipv4Packet, UdpPacket,
|
ArpPacket, EthernetFrame, EthernetProtocol, IpProtocol, Ipv4Packet, UdpPacket,
|
||||||
};
|
};
|
||||||
|
|
@ -16,7 +17,7 @@ impl Proxy {
|
||||||
self.host
|
self.host
|
||||||
.write(frame.as_ref())
|
.write(frame.as_ref())
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|err| Error::HostIOFailed { source: err })
|
.context("failed to write to the host")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allowed_from_vm(&self, frame: &EthernetFrame<&[u8]>) -> Option<()> {
|
fn allowed_from_vm(&self, frame: &EthernetFrame<&[u8]>) -> Option<()> {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Error, Result};
|
use anyhow::Result;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||||
use std::os::unix::net::UnixDatagram;
|
use std::os::unix::net::UnixDatagram;
|
||||||
|
|
||||||
|
|
@ -9,8 +9,7 @@ pub struct VM {
|
||||||
impl VM {
|
impl VM {
|
||||||
pub fn new(vm_fd: RawFd) -> Result<VM> {
|
pub fn new(vm_fd: RawFd) -> Result<VM> {
|
||||||
let sock = unsafe { UnixDatagram::from_raw_fd(vm_fd) };
|
let sock = unsafe { UnixDatagram::from_raw_fd(vm_fd) };
|
||||||
sock.set_nonblocking(true)
|
sock.set_nonblocking(true)?;
|
||||||
.map_err(|err| Error::InitFailed { source: err.into() })?;
|
|
||||||
|
|
||||||
Ok(VM { sock })
|
Ok(VM { sock })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
61
src/main.rs
61
src/main.rs
|
|
@ -1,10 +1,12 @@
|
||||||
|
use anyhow::{anyhow, Context};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use privdrop::PrivDrop;
|
use privdrop::PrivDrop;
|
||||||
use softnet::proxy::Proxy;
|
use softnet::proxy::Proxy;
|
||||||
|
use std::env;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::CommandExt;
|
||||||
use std::process::Command;
|
use std::process::{Command, ExitCode};
|
||||||
use system_configuration::core_foundation::base::TCFType;
|
use system_configuration::core_foundation::base::TCFType;
|
||||||
use system_configuration::core_foundation::dictionary::CFDictionary;
|
use system_configuration::core_foundation::dictionary::CFDictionary;
|
||||||
use system_configuration::core_foundation::number::CFNumber;
|
use system_configuration::core_foundation::number::CFNumber;
|
||||||
|
|
@ -44,17 +46,42 @@ struct Args {
|
||||||
sudo_escalation_done: bool,
|
sudo_escalation_done: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> ExitCode {
|
||||||
if let Err(err) = try_main() {
|
// Enable backtraces by default
|
||||||
match err.source() {
|
if env::var("RUST_BACKTRACE").is_err() {
|
||||||
Some(source) => eprintln!("{}: {}", err, source),
|
env::set_var("RUST_BACKTRACE", "1");
|
||||||
None => eprintln!("{}", err),
|
}
|
||||||
|
|
||||||
|
// Initialize Sentry
|
||||||
|
let _sentry = sentry::init(sentry::ClientOptions {
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enrich future events with Cirrus CI-specific tags
|
||||||
|
if let Ok(tags) = env::var("CIRRUS_SENTRY_TAGS") {
|
||||||
|
sentry::configure_scope(|scope| {
|
||||||
|
for (key, value) in tags.split(",").map(|tag| tag.split_once("=")).flatten() {
|
||||||
|
scope.set_tag(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match try_main() {
|
||||||
|
Ok(_) => ExitCode::SUCCESS,
|
||||||
|
Err(err) => {
|
||||||
|
// Print the error into stderr
|
||||||
|
let causes: Vec<String> = err.chain().map(|x| x.to_string()).collect();
|
||||||
|
eprintln!("{}", causes.join(": "));
|
||||||
|
|
||||||
|
// Capture the error into Sentry
|
||||||
|
sentry_anyhow::capture_anyhow(&err);
|
||||||
|
|
||||||
|
ExitCode::FAILURE
|
||||||
}
|
}
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_main() -> Result<(), Box<dyn std::error::Error>> {
|
fn try_main() -> anyhow::Result<()> {
|
||||||
let args: Args = Args::parse();
|
let args: Args = Args::parse();
|
||||||
|
|
||||||
// No need to run anything, just return
|
// No need to run anything, just return
|
||||||
|
|
@ -66,11 +93,11 @@ fn try_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
// Retrieve real (not effective) user and group names
|
// Retrieve real (not effective) user and group names
|
||||||
let current_user_name = get_current_username()
|
let current_user_name = get_current_username()
|
||||||
.ok_or("failed to resolve real user name")?
|
.ok_or(anyhow!("failed to resolve real user name"))?
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
let current_group_name = get_current_groupname()
|
let current_group_name = get_current_groupname()
|
||||||
.ok_or("failed to resolve real group name")?
|
.ok_or(anyhow!("failed to resolve real group name"))?
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
|
@ -81,7 +108,8 @@ fn try_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args = std::env::args().skip(1);
|
let args = std::env::args().skip(1);
|
||||||
|
|
||||||
let _ = Command::new("sudo")
|
let _ = Command::new("sudo")
|
||||||
.arg("-n")
|
.arg("--non-interactive")
|
||||||
|
.arg("--preserve-env=SENTRY_DSN,CIRRUS_SENTRY_TAGS")
|
||||||
.arg(&exe)
|
.arg(&exe)
|
||||||
.args(args)
|
.args(args)
|
||||||
.arg("--sudo-escalation-done")
|
.arg("--sudo-escalation-done")
|
||||||
|
|
@ -92,14 +120,17 @@ fn try_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.exec();
|
.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err("root privileges are required to run and passwordless sudo was not available".into());
|
return Err(anyhow!(
|
||||||
|
"root privileges are required to run and passwordless sudo was not available"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set bootpd(8) min/max lease time while still having the root privileges
|
// Set bootpd(8) min/max lease time while still having the root privileges
|
||||||
set_bootpd_lease_time(args.bootpd_lease_time);
|
set_bootpd_lease_time(args.bootpd_lease_time);
|
||||||
|
|
||||||
// Initialize the proxy while still having the root privileges
|
// Initialize the proxy while still having the root privileges
|
||||||
let mut proxy = Proxy::new(args.vm_fd as RawFd, args.vm_mac_address)?;
|
let mut proxy = Proxy::new(args.vm_fd as RawFd, args.vm_mac_address)
|
||||||
|
.context("failed to initialize proxy")?;
|
||||||
|
|
||||||
// Drop effective privileges to the user
|
// Drop effective privileges to the user
|
||||||
// and group which have had invoked us
|
// and group which have had invoked us
|
||||||
|
|
@ -107,10 +138,10 @@ fn try_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.user(args.user.unwrap_or(current_user_name))
|
.user(args.user.unwrap_or(current_user_name))
|
||||||
.group(args.group.unwrap_or(current_group_name))
|
.group(args.group.unwrap_or(current_group_name))
|
||||||
.apply()
|
.apply()
|
||||||
.map_err(|err| format!("failed to drop privileges: {}", err))?;
|
.context("failed to drop privileges")?;
|
||||||
|
|
||||||
// Run proxy
|
// Run proxy
|
||||||
proxy.run().map_err(|err| err.into())
|
proxy.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sudo_escalation_works() -> bool {
|
fn sudo_escalation_works() -> bool {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue