-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linux): make deep link auth work (#4102)
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.
- Loading branch information
1 parent
32d18ab
commit 52cde61
Showing
13 changed files
with
281 additions
and
122 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,15 @@ | ||
#!/bin/sh | ||
#!/usr/bin/env bash | ||
|
||
# The Windows client obviously doesn't build for *nix, but this | ||
# script is helpful for doing UI work on those platforms for the | ||
# Windows client. | ||
set -e | ||
set -euo pipefail | ||
|
||
# Copy frontend dependencies | ||
cp node_modules/flowbite/dist/flowbite.min.js src/ | ||
|
||
# Compile TypeScript | ||
tsc | ||
pnpm tsc | ||
|
||
# Compile CSS | ||
tailwindcss -i src/input.css -o src/output.css | ||
pnpm tailwindcss -i src/input.css -o src/output.css | ||
|
||
# Compile Rust and bundle | ||
tauri build | ||
pnpm tauri build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 107 additions & 15 deletions
122
rust/gui-client/src-tauri/src/client/deep_link/linux.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,121 @@ | ||
//! TODO: Not implemented for Linux yet | ||
use crate::client::known_dirs; | ||
use anyhow::{bail, Context, Result}; | ||
use secrecy::{ExposeSecret, Secret}; | ||
use tokio::{ | ||
io::{AsyncReadExt, AsyncWriteExt}, | ||
net::{UnixListener, UnixStream}, | ||
}; | ||
|
||
use super::Error; | ||
use secrecy::SecretString; | ||
const SOCK_NAME: &str = "deep_link.sock"; | ||
|
||
pub(crate) struct Server {} | ||
pub(crate) struct Server { | ||
listener: UnixListener, | ||
} | ||
|
||
impl Server { | ||
pub(crate) fn new() -> Result<Self, Error> { | ||
tracing::warn!("Not implemented yet"); | ||
tracing::trace!(scheme = super::FZ_SCHEME, "prevents dead code warning"); | ||
Ok(Self {}) | ||
/// Create a new deep link server to make sure we're the only instance | ||
/// | ||
/// Still uses `thiserror` so we can catch the deep_link `CantListen` error | ||
pub(crate) fn new() -> Result<Self, super::Error> { | ||
let dir = known_dirs::runtime().context("couldn't find runtime dir")?; | ||
let path = dir.join(SOCK_NAME); | ||
// TODO: This breaks single instance. Can we enforce it some other way? | ||
std::fs::remove_file(&path).ok(); | ||
std::fs::create_dir_all(&dir).context("Can't create dir for deep link socket")?; | ||
|
||
let listener = UnixListener::bind(&path).context("Couldn't bind listener Unix socket")?; | ||
|
||
// Figure out who we were before `sudo`, if using sudo | ||
if let Ok(username) = std::env::var("SUDO_USER") { | ||
// chown so that when the non-privileged browser launches us, | ||
// we can send a message to our privileged main process | ||
std::process::Command::new("chown") | ||
.arg(username) | ||
.arg(&path) | ||
.status() | ||
.context("couldn't chown Unix domain socket")?; | ||
} | ||
|
||
Ok(Self { listener }) | ||
} | ||
|
||
pub(crate) async fn accept(self) -> Result<SecretString, Error> { | ||
tracing::warn!("Deep links not implemented yet on Linux"); | ||
futures::future::pending().await | ||
/// Await one incoming deep link | ||
/// | ||
/// To match the Windows API, this consumes the `Server`. | ||
pub(crate) async fn accept(self) -> Result<Secret<Vec<u8>>> { | ||
tracing::debug!("deep_link::accept"); | ||
let (mut stream, _) = self.listener.accept().await?; | ||
tracing::debug!("Accepted Unix domain socket connection"); | ||
|
||
// TODO: Limit reads to 4,096 bytes. Partial reads will probably never happen | ||
// since it's a local socket transferring very small data. | ||
let mut bytes = vec![]; | ||
stream | ||
.read_to_end(&mut bytes) | ||
.await | ||
.context("failed to read incoming deep link over Unix socket stream")?; | ||
let bytes = Secret::new(bytes); | ||
tracing::debug!( | ||
len = bytes.expose_secret().len(), | ||
"Got data from Unix domain socket" | ||
); | ||
Ok(bytes) | ||
} | ||
} | ||
|
||
pub(crate) async fn open(_url: &url::Url) -> Result<(), Error> { | ||
tracing::warn!("Not implemented yet"); | ||
pub(crate) async fn open(url: &url::Url) -> Result<()> { | ||
crate::client::logging::debug_command_setup()?; | ||
|
||
let dir = known_dirs::runtime().context("deep_link::open couldn't find runtime dir")?; | ||
let path = dir.join(SOCK_NAME); | ||
let mut stream = UnixStream::connect(&path).await?; | ||
|
||
stream.write_all(url.to_string().as_bytes()).await?; | ||
|
||
Ok(()) | ||
} | ||
|
||
pub(crate) fn register() -> Result<(), Error> { | ||
tracing::warn!("Not implemented yet"); | ||
/// Register a URI scheme so that browser can deep link into our app for auth | ||
/// | ||
/// Performs blocking I/O (Waits on `xdg-desktop-menu` subprocess) | ||
pub(crate) fn register() -> Result<()> { | ||
// Write `$HOME/.local/share/applications/firezone-client.desktop` | ||
// According to <https://wiki.archlinux.org/title/Desktop_entries>, that's the place to put | ||
// per-user desktop entries. | ||
let dir = dirs::data_local_dir() | ||
.context("can't figure out where to put our desktop entry")? | ||
.join("applications"); | ||
std::fs::create_dir_all(&dir)?; | ||
|
||
// Don't use atomic writes here - If we lose power, we'll just rewrite this file on | ||
// the next boot anyway. | ||
let path = dir.join("firezone-client.desktop"); | ||
let exe = std::env::current_exe().context("failed to find our own exe path")?; | ||
let content = format!( | ||
"[Desktop Entry] | ||
Version=1.0 | ||
Name=Firezone | ||
Comment=Firezone GUI Client | ||
Exec={} open-deep-link %U | ||
Terminal=false | ||
Type=Application | ||
MimeType=x-scheme-handler/{} | ||
Categories=Network; | ||
", | ||
exe.display(), | ||
super::FZ_SCHEME | ||
); | ||
std::fs::write(&path, content).context("failed to write desktop entry file")?; | ||
|
||
// Run `xdg-desktop-menu install` with that desktop file | ||
let xdg_desktop_menu = "xdg-desktop-menu"; | ||
let status = std::process::Command::new(xdg_desktop_menu) | ||
.arg("install") | ||
.arg(&path) | ||
.status() | ||
.with_context(|| format!("failed to run `{xdg_desktop_menu}`"))?; | ||
if !status.success() { | ||
bail!("failed to register our deep link scheme") | ||
} | ||
Ok(()) | ||
} |
Oops, something went wrong.