Skip to content
Open
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
13 changes: 10 additions & 3 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ jobs:
# ubuntu:25.10 is minimal — git for Swatinem/rust-cache key
# generation, curl + ca-certificates for rust-toolchain's
# rustup install, plus the GTK / libadwaita / sourceview /
# OpenSSL / libsecret -dev packages the workspace links.
# OpenSSL / libsecret -dev packages the workspace links, plus
# libkrb5-dev + clang for the MSSQL driver's integrated
# (Kerberos/GSSAPI) auth (libgssapi-sys links gssapi_krb5 and
# runs bindgen).
# No sudo (container runs as root by default).
run: |
apt-get update
Expand All @@ -52,7 +55,9 @@ jobs:
libadwaita-1-dev \
libgtksourceview-5-dev \
libssl-dev \
libsecret-1-dev
libsecret-1-dev \
libkrb5-dev \
clang
- uses: dtolnay/rust-toolchain@1.93
with:
components: rustfmt, clippy
Expand All @@ -65,8 +70,10 @@ jobs:
run: cargo clippy --all-targets -- -D warnings
- name: Build
run: cargo build --workspace
# --bins matters: tablepro-app has no lib target, so --lib alone
# skips every test in the app crate.
- name: Unit tests
run: cargo test --workspace --lib
run: cargo test --workspace --lib --bins

integration:
name: Driver integration tests (docker)
Expand Down
111 changes: 107 additions & 4 deletions linux/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion linux/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ secrecy = { version = "0.10", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "tls-rustls", "chrono", "rust_decimal", "uuid", "json"] }
tiberius = { version = "0.12", default-features = false, features = ["tds73", "rustls", "chrono", "rust_decimal"] }
# `integrated-auth-gssapi` (Windows integrated auth) links MIT Kerberos
# (libkrb5) and runs bindgen (libclang) at build time.
tiberius = { version = "0.12", default-features = false, features = ["tds73", "rustls", "chrono", "rust_decimal", "integrated-auth-gssapi"] }
chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] }
rust_decimal = { version = "1", default-features = false, features = ["serde", "std"] }
futures = "0.3"
Expand All @@ -49,3 +51,14 @@ relm4 = { version = "0.11", default-features = false, features = ["macros", "lib
lto = "fat"
codegen-units = 1
strip = "symbols"

# tiberius 0.12.3 (its latest release) pins libgssapi ^0.4.5. libgssapi 0.4.6's
# `Buf::deref` calls `slice::from_raw_parts(NULL, 0)` on the empty token a
# successful Kerberos handshake returns -- undefined behavior that aborts every
# debug build (`cargo run`). tiberius already fixed this on `main` (merged PR
# prisma/tiberius#372: bump libgssapi 0.4.5 -> 0.8.x, whose deref is guarded)
# but has not cut a release; the bug is tracked open as prisma/tiberius#343.
# Pin the fixed commit until a release lands, then drop this and bump the
# `tiberius` version above to the release.
[patch.crates-io]
tiberius = { git = "https://github.com/prisma/tiberius", rev = "a6b4fcdae0de5702427290b89f8d05bc51f3bcfa" }
31 changes: 28 additions & 3 deletions linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ System dependencies:

```bash
# Ubuntu / Debian
sudo apt install -y build-essential pkg-config libgtk-4-dev libadwaita-1-dev libssl-dev libsecret-1-dev
sudo apt install -y build-essential pkg-config libgtk-4-dev libadwaita-1-dev libssl-dev libsecret-1-dev libkrb5-dev clang

# Fedora
sudo dnf install -y gcc pkg-config gtk4-devel libadwaita-devel openssl-devel libsecret-devel
sudo dnf install -y gcc pkg-config gtk4-devel libadwaita-devel openssl-devel libsecret-devel krb5-devel clang

# Arch
sudo pacman -S --needed base-devel pkg-config gtk4 libadwaita openssl libsecret
sudo pacman -S --needed base-devel pkg-config gtk4 libadwaita openssl libsecret krb5 clang
```

Verify the right versions are present:
Expand All @@ -56,6 +56,31 @@ cd linux
cargo run -p tablepro-app
```

`libkrb5-dev` and `clang` are there for the SQL Server driver's Windows
integrated auth, which links MIT Kerberos and runs bindgen at build
time.

## SQL Server with Windows integrated auth

Pick **Method → Windows (Kerberos)** in the connect dialog. There is no
username or password to enter: the driver uses whatever ticket `klist`
shows, so get one first.

```bash
kinit you@EXAMPLE.COM
```

The driver asks for `MSSQLSvc/<host>:<port>`, built from the host and
port you typed, not from an SSH tunnel's local forward. Two things are
worth knowing:

- tiberius imports that SPN as a raw Kerberos principal, so it resolves
in your *default* realm. When the service lives in another realm, set
`default_realm` in `/etc/krb5.conf` and map the host with
`[domain_realm]` (plus `[capaths]` for a cross-realm trust).
- The host has to match the SPN registered on the server. An IP address
or a CNAME usually does not.

## Documentation index

| Topic | File |
Expand Down
25 changes: 18 additions & 7 deletions linux/crates/app/src/services/connection_service.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::sync::Arc;

use secrecy::SecretString;
use tablepro_core::{ConnectOptions, Connection, DriverRegistry, ReadOnlyConnection, TableInfo};
use tablepro_core::{AuthMode, ConnectOptions, Connection, DriverRegistry, ReadOnlyConnection, TableInfo};
use tablepro_ssh::{SshConfig, SshTunnel};
use tablepro_storage::{SavedConnection, SavedSshAuth, load_password, load_ssh_passphrase, load_ssh_password};

Expand All @@ -11,11 +11,16 @@ pub async fn open_saved(registry: Arc<DriverRegistry>, saved: SavedConnection) -
let driver = registry
.get(&saved.driver_id)
.ok_or_else(|| format!("driver {} not registered", saved.driver_id))?;
let password = load_password(saved.id)
.await
.ok()
.flatten()
.unwrap_or_else(|| SecretString::new(String::new().into()));
// Kerberos authenticates from the ambient ticket cache, so there is
// no stored secret to read back from the keyring.
let password = match saved.auth_mode {
AuthMode::Kerberos => SecretString::new(String::new().into()),
AuthMode::Password => load_password(saved.id)
.await
.ok()
.flatten()
.unwrap_or_else(|| SecretString::new(String::new().into())),
};
let id = saved.id;

let ssh_cfg = match &saved.ssh {
Expand All @@ -30,6 +35,8 @@ pub async fn open_saved(registry: Arc<DriverRegistry>, saved: SavedConnection) -
username: saved.username,
password,
use_tls: saved.use_tls,
auth_mode: saved.auth_mode,
service_endpoint: None,
};

let (conn, tunnel) = establish(&*driver, opts.clone(), ssh_cfg.clone(), saved.read_only).await?;
Expand Down Expand Up @@ -58,9 +65,13 @@ pub async fn establish(
let tunnel = if let Some(cfg) = ssh {
let remote_host = std::mem::take(&mut opts.host);
let remote_port = opts.port;
let tun = SshTunnel::open(cfg, remote_host, remote_port)
let tun = SshTunnel::open(cfg, remote_host.clone(), remote_port)
.await
.map_err(|e| format!("ssh: {e}"))?;
// The socket now points at the local forward, so remember what
// the service is actually called; without this Kerberos would
// ask the KDC for MSSQLSvc/127.0.0.1:<ephemeral port>.
opts.service_endpoint = Some((remote_host, remote_port));
opts.host = tun.local_host().to_string();
opts.port = tun.local_port();
Some(tun)
Expand Down
Loading