Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(linux-client): remove FIREZONE_ID from example systemd file #4714

Merged
merged 18 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions rust/connlib/tunnel/src/device_channel/tun_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,15 @@ async fn set_iface_config(

res_v4.or(res_v6)?;

match dns_control_method {
None => {}
if let Err(error) = match dns_control_method {
None => Ok(()),
Some(DnsControlMethod::EtcResolvConf) => etc_resolv_conf::configure(&dns_config)
.await
.map_err(Error::ResolvConf)?,
Some(DnsControlMethod::NetworkManager) => configure_network_manager(&dns_config)?,
Some(DnsControlMethod::Systemd) => configure_systemd_resolved(&dns_config).await?,
.map_err(Error::ResolvConf),
Some(DnsControlMethod::NetworkManager) => configure_network_manager(&dns_config),
Some(DnsControlMethod::Systemd) => configure_systemd_resolved(&dns_config).await,
} {
panic!("Failed to control DNS: {error}");
Comment on lines +275 to +283
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into #4461 while working on this PR and this diff closes it

}

// TODO: Having this inside the library is definitely wrong. I think `set_iface_config`
Expand Down
32 changes: 20 additions & 12 deletions rust/headless-client/src/imp_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@ use tokio_util::codec::LengthDelimitedCodec;
const ROOT_GROUP: u32 = 0;
const ROOT_USER: u32 = 0;

/// The path for our Unix Domain Socket
///
/// Docker keeps theirs in `/run` and also appears to use filesystem permissions
/// for security, so we're following their lead. `/run` and `/var/run` are symlinked
/// on some systems, `/run` should be the newer version.
const SOCK_PATH: &str = "/run/firezone-client.sock";

pub fn default_token_path() -> PathBuf {
PathBuf::from("/etc")
.join(connlib_shared::BUNDLE_ID)
Expand Down Expand Up @@ -299,13 +292,27 @@ fn parse_resolvectl_output(s: &str) -> Vec<IpAddr> {
.collect()
}

/// The path for our Unix Domain Socket
///
/// Docker keeps theirs in `/run` and also appears to use filesystem permissions
/// for security, so we're following their lead. `/run` and `/var/run` are symlinked
/// on some systems, `/run` should be the newer version.
///
/// Also systemd can create this dir with the `RuntimeDir=` directive which is nice.
fn sock_path() -> PathBuf {
PathBuf::from("/run")
.join(connlib_shared::BUNDLE_ID)
.join("ipc.sock")
}

fn run_debug_ipc_client(_cli: Cli) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
tracing::info!(pid = std::process::id(), "run_debug_ipc_client");
let stream = UnixStream::connect(SOCK_PATH)
let sock_path = sock_path();
let stream = UnixStream::connect(&sock_path)
.await
.with_context(|| format!("couldn't connect to UDS at {SOCK_PATH}"))?;
.with_context(|| format!("couldn't connect to UDS at {}", sock_path.display()))?;
let mut stream = IpcStream::new(stream, LengthDelimitedCodec::new());

stream.send(serde_json::to_string("Hello")?.into()).await?;
Expand All @@ -328,9 +335,10 @@ async fn ipc_listen() -> Result<()> {
.gid;

// Remove the socket if a previous run left it there
tokio::fs::remove_file(SOCK_PATH).await.ok();
let listener = UnixListener::bind(SOCK_PATH).context("Couldn't bind UDS")?;
std::os::unix::fs::chown(SOCK_PATH, Some(ROOT_USER), Some(fz_gid.into()))
let sock_path = sock_path();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know some services use this as a guard against accidentally launching the service twice. Should we just bail out if we find an existing sock file here and ask the user to remove it instead?

Copy link
Collaborator Author

@ReactorScram ReactorScram Apr 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. If we can have systemd create it for us that would be cool, then it can also clean it up. I made an issue to look into it soon

tokio::fs::remove_file(&sock_path).await.ok();
let listener = UnixListener::bind(&sock_path).context("Couldn't bind UDS")?;
std::os::unix::fs::chown(&sock_path, Some(ROOT_USER), Some(fz_gid.into()))
.context("can't set firezone as the group for the UDS")?;
sd_notify::notify(true, &[sd_notify::NotifyState::Ready])?;

Expand Down
5 changes: 2 additions & 3 deletions scripts/tests/linux-group.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@ source "./scripts/tests/lib.sh"

BINARY_NAME=firezone-linux-client
FZ_GROUP="firezone"
SERVICE_NAME=firezone-client
SERVICE_NAME=firezone-client-ipc
export RUST_LOG=info

# Copy the Linux Client out of the build dir
ls . ./rust ./rust/target ./rust/target/debug
sudo cp "rust/target/debug/firezone-headless-client" "/usr/bin/$BINARY_NAME"

sudo cp "scripts/tests/systemd/$SERVICE_NAME.service" /usr/lib/systemd/system/

# The firezone group must exist before the daemon starts
sudo groupadd "$FZ_GROUP"
sudo systemctl start "$SERVICE_NAME"
sudo systemctl start "$SERVICE_NAME" || systemctl status "$SERVICE_NAME"

# Add ourselves to the firezone group
sudo gpasswd --add "$USER" "$FZ_GROUP"
Expand Down
6 changes: 3 additions & 3 deletions scripts/tests/systemd/dns-systemd-resolved.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
source "./scripts/tests/lib.sh"

BINARY_NAME=firezone-linux-client
SERVICE_NAME=firezone-client
SERVICE_NAME=firezone-client-headless

# Copy the Linux Client out of its container
docker compose exec client cat firezone-linux-client > "$BINARY_NAME"
Expand All @@ -22,12 +22,12 @@ HTTPBIN=dns.httpbin
DOCKER_IFACE="docker0"
FZ_IFACE="tun-firezone"

# Accessing a resource should fail before the client is up
echo "# Accessing a resource should fail before the client is up"
# Force curl to try the Firezone interface. I can't block off the Docker interface yet
# because it may be needed for the client to reach the portal.
curl --interface "$FZ_IFACE" $HTTPBIN/get && exit 1

# Start Firezone
echo "# Start Firezone"
resolvectl dns tun-firezone && exit 1
stat /usr/bin/firezone-linux-client
sudo systemctl start "$SERVICE_NAME" || systemctl status "$SERVICE_NAME"
Expand Down
47 changes: 47 additions & 0 deletions scripts/tests/systemd/firezone-client-headless.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[Unit]
Description=Firezone Client

[Service]
AmbientCapabilities=CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_ADMIN
DeviceAllow=/dev/net/tun
LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateMounts=true
PrivateTmp=true
# We need to be real root, not just root in our cgroup
PrivateUsers=false
ProcSubset=pid
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
# Docs say it's useless when running as root, but defense-in-depth
ProtectProc=invisible
ProtectSystem=strict
# Netlink needed for the tunnel interface, Unix needed for `systemd-resolved`
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
StateDirectory=dev.firezone.client
SystemCallArchitectures=native
# TODO: Minimize
SystemCallFilter=@aio @basic-io @file-system @io-event @network-io @signal @system-service
UMask=077

Environment="FIREZONE_API_URL=ws://localhost:8081"
Environment="FIREZONE_DNS_CONTROL=systemd-resolved"
Environment="RUST_LOG=info"

ExecStart=firezone-linux-client standalone
Type=notify
# Unfortunately we may need root to control DNS
User=root

[Install]
WantedBy=default.target
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ Description=Firezone Client

[Service]
AmbientCapabilities=CAP_NET_ADMIN
# TODO: Get rid of `CAP_CHOWN` here by asking systemd to make our runtime dir on our behalf
CapabilityBoundingSet=CAP_CHOWN CAP_NET_ADMIN
DeviceAllow=/dev/net/tun
LockPersonality=true
Expand All @@ -23,23 +22,23 @@ ProtectKernelModules=true
ProtectKernelTunables=true
# Docs say it's useless when running as root, but defense-in-depth
ProtectProc=invisible
ProtectSystem=full
ProtectSystem=strict
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
RuntimeDirectory=dev.firezone.client
StateDirectory=dev.firezone.client
SystemCallArchitectures=native
# TODO: Minimize
SystemCallFilter=@aio @basic-io @file-system @io-event @ipc @network-io @signal @system-service
UMask=177
UMask=077

Environment="FIREZONE_API_URL=ws://localhost:8081"
Environment="FIREZONE_DNS_CONTROL=systemd-resolved"
Environment="FIREZONE_ID=D0455FDE-8F65-4960-A778-B934E4E85A5F"
Environment="RUST_LOG=info"

# TODO: Make subcommands explicit once PR #4628 merges
ExecStart=firezone-linux-client
ExecStart=firezone-linux-client ipc-service
Type=notify
# Unfortunately we may need root to control DNS
User=root
Expand Down
Loading