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

docs(linux-client): launch modes, flags, etc, for Linux CLI and GUI #4106

Closed
wants to merge 1 commit into from
Closed
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
50 changes: 50 additions & 0 deletions rust/gui-client/docs/launch-modes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Linux launch modes

(Without thinking of systemd yet)

1. No GUI allowed
2. GUI spawns its own tunnel subprocess
Copy link
Member

@jamilbk jamilbk Mar 12, 2024

Choose a reason for hiding this comment

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

Can a userspace gui spawn a privileged process?

If we can spawn the process from the GUI binary after launch then package management becomes much easier, but I suspect we'll hit issues with this approach. For example -- if I add a shortcut to my GNOME desktop to launch firezone, does that launch using sudo too? What if sudo requires a password?

If we can't reliably spawn a privileged process, then we might be stuck with the two process model. In this case then we can ship two binaries in the GUI package and configure setuid / setcap in the postinst or equivalent packaging script.

This should already be a solved problem in Linux -- aren't there userspace applications that need access to privileged devices, and access is controlled via group membership? E.g. udev or docker or network groups. The "privileged daemon / userspace client" model seems well-used in Linux so maybe that's why I keep intuitively going back to it.

Copy link
Collaborator Author

@ReactorScram ReactorScram Mar 13, 2024

Choose a reason for hiding this comment

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

I don't understand. If we're spawning another process then we are using two processes, right? Running the GUI as root is breaking a lot of stuff, so if the GUI spawns a tunnel subprocess, that means the GUI sticks around as the non-privileged parent process.

Sudo will require a password on most systems. There is a graphical sudo for desktop environments that will kick in, sometimes I think it happens automagically even if we just call sudo in the app.

Yeah we should see what Docker does. "Privileged daemon / userspace client" would be Mode 3 in this doc.

Due to DNS I think we are required to run the tunnel as root. I didn't find any more fine-grained security for setting DNS on an interface. (Other than hooking into NetworkManager) If we run as root, there's no need to do setcap.

I think we're really still talking past each other on 2 processes vs 2 binaries. We can run a privileged process and an unprivileged process from the same binary. Even if unprivileged code has a vulnerability and jumps into privileged code, it couldn't do anything since it won't have root powers.

So there is no need to package 2 binaries using Tauri sidecar bundling or anything - It's already easy to do Busybox-style multi-call binaries where two "binaries" are packed into one binary at the source code level, and I'm already doing that for the crash handling.

Like I would not be surprised if it turns out that Docker's privileged daemon and non-privileged userspace client both run from the same binary on disk. That's how web browsers do it.

3. GUI and tunnel start up separately, GUI connects to the tunnel
Copy link
Member

Choose a reason for hiding this comment

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

I was thinking either (2) or (3), but not both just to keep things as simple as possible for the two use cases.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure maybe we should focus on (3) first. (2) requires sudo so it's like, if you're a power user with sudo powers, you could just use the CLI, or you would have the expertise to configure Mode 3 anyway.

The only reason I was thinking of starting with Mode 2 is that it might be simpler to test, and the security is simpler (The processes trust each other based on a parent-child relationship, same as web browsers do) But customers probably won't want Mode 2, they'll probably need Mode 3.


# How many binaries?

2 downloads, each containing 1 binary on disk.

1. `firezone-linux-client` only supports Mode 1.
2. `firezone-gui-client` supports all modes.

# Flags

(These might actually be subcommands, but I kept the leading hyphens for now.)

`firezone-linux-client` only accepts `--headless`, for the purpose of flag compatibility with `firezone-gui-client`.
Copy link
Member

Choose a reason for hiding this comment

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

It's ok if they don't have flag compatibility I think

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right now the Tauri bundles have the binary as just firezone, honestly I'd prefer it be more descriptive, e.g. firezone-cli-client and firezone-gui-client. We can allow the arch and OS to be implicit, but firezone could be anything, even a gateway if we ever allow Clients and Gateways on the same system.

It bails out if it sees any other flag, since all other flags relate to GUI behavior.

The flags must be compatible between both binaries, since they may be installed as `firezone-client`.

1. `--headless` means Mode 1. `firezone-gui-client` pretends to be `firezone-linux-client`, refusing any GUI connections.
2. `--standalone-gui` means Mode 2. `firezone-gui-client` becomes a GUI process, then calls `sudo` to launch its own tunnel subprocess. Closing the GUI closes the tunnel.
Copy link
Member

@jamilbk jamilbk Mar 12, 2024

Choose a reason for hiding this comment

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

sudo is a complex system -- I'm not sure we can rely on the assumption it will be callable in the manner you're expecting.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

True, they may want it to run on a system where normal users don't have sudo powers. But in that case they wouldn't be using Mode 2, they'd be using Mode 3, which doesn't need sudo.

3. `--gui-only` means Mode 3. `firezone-gui-client` becomes a GUI process, and tries to connect to a tunnel process elsewhere in the system.
4. `--tunnel-only` means Mode 3. `firezone-gui-client` becomes a tunnel process, raises the tunnel if it has a token, and waits for a connection from a GUI process elsewhere in the system.
5. `--auto-gui` means auto-detect Mode 2 or Mode 3. If the systemd service for the tunnel appears to be installed, or if a tunnel is running, `firezone-gui-client` enters Mode 3. (Connect to existing tunnel) Otherwise, it enters Mode 2. (Spawn a tunnel)
6. No flags means `firezone-linux-client` enters Mode 1, its only possible mode, and `firezone-gui-client` acts as if it got `--auto-gui`. This means `--auto-gui` is redundant, but it could be useful if the default changes later or something.

For Mode 2, `firezone-gui-client` internally launches itself as a subprocess with `--tunnel-of-standalone-gui` which is meant for machine use. This is similar to `--tunnel-only` but it waits for the GUI to connect, refuses connections from any other process, and tries to automatically close if it detects that the GUI stopped suddenly without asking the tunnel to stop gracefully first.

If `firezone-gui-client` observes its exe name is `firezone-linux-client`, it pretends to be `firezone-linux-client` and rejects any GUI-related flags, in the style of Busybox multi-call binaries.

# What about systemd and desktop entries?

Desktop entries (i.e. desktop shortcuts and "Start Menu" entries) will call `firezone-gui-client --auto-gui`. Desktop entries do not launch `firezone-linux-client`.

Systemd launches `firezone-linux-client --headless` or `firezone-gui-client --tunnel-only`.

# CLI / GUI conflict

What if both packages are installed on the system?
Copy link
Member

Choose a reason for hiding this comment

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

In many cases the headless client won't use a package manager (e.g. Docker)

Copy link
Member

Choose a reason for hiding this comment

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

It's ok if both packages are installed, but you wouldn't want to allow both to run at the same time due to route conflicts.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay well even without a package manager, if we say "Put this systemd unit here" then either they have the same name to make the conflict obvious, or they have separate names and remind customers not to enable / run both at once.


We could put `Conflicts` directives in the packages and just say we don't support it. That might be best for now.

Then the GUI client can have a symlink `firezone-linux-client` that acts the same as the CLI-only client would, so having the GUI installed is equivalent to also having the CLI installed and having the conflict resolved already.

Both packages would enable their own systemd service by default. We can name this `firezone-client.service`, since there should never be two Clients, this makes sure there can't be two Client service units.
Loading