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

Conversation

ReactorScram
Copy link
Collaborator

This is my proposal for the CLI and GUI to get along and for the GUI to do whatever customers need.

@ReactorScram ReactorScram added kind/docs Improvements or updates to documentation area/linux_client Linux client area/tauri_client The Windows and Linux Tauri GUI clients labels Mar 12, 2024
@ReactorScram ReactorScram added this to the 1.0 GA milestone Mar 12, 2024
@ReactorScram ReactorScram self-assigned this Mar 12, 2024
Copy link

vercel bot commented Mar 12, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Ignored Deployment
Name Status Preview Updated (UTC)
firezone ⬜️ Ignored (Inspect) Mar 12, 2024 7:16pm

@ReactorScram ReactorScram marked this pull request as ready for review March 12, 2024 19:16
Copy link

Terraform Cloud Plan Output

Plan: 8 to add, 7 to change, 8 to destroy.

Terraform Cloud Plan

Copy link

Performance Test Results

TCP

Test Name Received/s Sent/s Retransmits
direct-tcp-client2server 190.9 MiB (-2%) 191.4 MiB (-2%) 189 (-31%)
direct-tcp-server2client 189.7 MiB (+2%) 191.3 MiB (+2%) 476 (-38%)
relayed-tcp-client2server 132.9 MiB (-3%) 133.8 MiB (-3%) 176 (+21%)
relayed-tcp-server2client 140.2 MiB (-3%) 140.6 MiB (-3%) 192 (-3%)

UDP

Test Name Total/s Jitter Lost
direct-udp-client2server 50.0 MiB (-0%) 0.05ms (-21%) 0.00% (NaN%)
direct-udp-server2client 50.0 MiB (+0%) 0.03ms (+41%) 0.00% (NaN%)
relayed-udp-client2server 50.0 MiB (-0%) 0.13ms (+28%) 0.00% (NaN%)
relayed-udp-server2client 50.0 MiB (-0%) 0.08ms (+17%) 0.00% (NaN%)

Copy link
Member

@jamilbk jamilbk left a comment

Choose a reason for hiding this comment

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

I think maybe we should backup here a bit and rethink the design to simplify things, otherwise this is going to be a nightmare to maintain going forward.

What do you think of this approach? I tried to take the best parts of your initial design and simplify them to the common use cases I expect users to encounter:

  1. Have a separate, standalone binary, firezone-client with the current CLI flags, DNS behavior, etc. This can run on its own as a plain binary, via systemd, or Docker like it does now. This always runs as a privileged user.
  2. Have a separate, standalone GUI binary, firezone-gui that handles auth essentially and UI notifications, and that's it. This always runs unprivileged.
  3. They speak to each over over an IPC interface just like the FFI between the other clients. They could both belong to the same firezone group and speak JSON over a Unix socket for example. To test firezone-gui, we can use a mock firezone-client that responds to the calls, and vice-versa.
  4. firezone-gui can "call" Session.connect with similar parameters as the other clients to launch firezone-client.
  5. Accordingly, firezone-client checks for a token in ENV upon launch and calls its Session.connect if it finds it, otherwise it waits idle for the GUI to call connect over the IPC interface with a token provided.
  6. onUpdateRoutes, happens inside firezone-client only. onUpdateResources issues a "callback" over the IPC interface to the GUI to update the list of Resources, etc.
  7. The token is stored in the user's keychain firezone-gui (or similar).

If firezone-client is running from a GUI launch, it should open the IPC interface. Otherwise, if it's running from a standalone install, don't start the IPC interface. Then, if firezone-gui is launched with already-running firezone-client that it started previously, it can connect to it and populate the Resources list, and the menu items will be functional -- Sign out, Disconnect and Quit, etc. Otherwise, if a firezone-gui is trying to control firezone-client that's managed via systemd with a Service Account token, "Sign out" doesn't make any sense.

The above model should work similarly for Windows as well -- the firezone-client.exe tunnel service operated by the firezone-gui.exe frontend, communicating over a bidirectional local socket.

When building firezone-gui with Tauri, we can just bundle firezone-client as a sidecar binary. This should make signing a similar process for both Linux (gpg) and Windows as well.

(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.


1. No GUI allowed
2. GUI spawns its own tunnel subprocess
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.


(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.

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.


# 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.

@jamilbk
Copy link
Member

jamilbk commented Mar 13, 2024

Windows even supports domain sockets since 2018: https://stackoverflow.com/questions/3872558/af-unix-in-windows

@jamilbk
Copy link
Member

jamilbk commented Mar 13, 2024

Just some more thoughts...

Starting firezone-client as a binary from the CLI wouldn't start the IPC socket, but starting it from the GUI with --socket /var/run/firezone/socket or something would

@ReactorScram
Copy link
Collaborator Author

Yeah there's a lot to think about

  • "they would both belong to the same firezone group" - Binaries don't belong to groups, users do, right? The security for the tunnel to be sure it's talking to a valid Client and vice versa is definitely an open issue
  • Windows does support Unix domain sockets. There was a reason some dep (crash handler maybe?) said they didn't work well, so I used named pipes on Windows. But I could definitely re-consider that.

@ReactorScram ReactorScram marked this pull request as draft March 13, 2024 18:12
@ReactorScram ReactorScram removed the request for review from conectado March 13, 2024 18:12
@ReactorScram
Copy link
Collaborator Author

Will re-draft this

github-merge-queue bot pushed a commit that referenced this pull request Mar 13, 2024
Right now it only works on my dev VM, not on my test VMs, due to #4053
and #4103, but it passes tests and should be safe to merge.

There's one doc fix and one script fix which are unrelated and could be
their own PRs, but they'd be tiny, so I left them in here.

Ref #4106 and #3713 for the plan to fix all this by splitting the tunnel
process off so that the GUI runs as a normal user.
@ReactorScram
Copy link
Collaborator Author

Closing in favor of #4153

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/linux_client Linux client area/tauri_client The Windows and Linux Tauri GUI clients kind/docs Improvements or updates to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants