Skip to content

About

Cross-platform FortiGate SSL-VPN menu-bar client with SAML/SSO, auto-connect, and a native settings UI (macOS/Linux/Windows)

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

OpenFortiTray

A small cross-platform tray client for FortiGate SSL-VPN gateways that authenticate with SAML/SSO. It keeps the tunnel up, reconnects on its own, connects at login, and has a native Settings window — the things the free FortiClient tier does not do.

CI License: MIT

OpenFortiTray is the convenience layer around a FortiGate SSL-VPN, not a reimplementation: openconnect --protocol=fortinet moves the packets, your IdP handles credentials, and OpenFortiTray drives the SAML handshake, supervises the process, and gives it a UI.

  • tray icon with tunnel state + Connect / Disconnect
  • Status window with live state, the assigned IP, a session clock and a history of the last dozen state changes — the tray menu is a native OS menu and cannot repaint while it is open, so this is where a state change is visible as it happens
  • auto-connect at login and auto-reconnect (backoff 15 s → 2 min)
  • SAML login in your system browser — silent while your IdP session is alive
  • native Settings window: multiple profiles, Basic/Advanced fields, no JSON editing
  • light and dark themes that follow your OS setting
  • one binary per platform; the only runtime dependency is openconnect (≥ 8.10, 9.x recommended), which every installer pulls in for you

Authentication: SAML/SSO via external browser is the only method wired in today, and the default for new profiles. Username/password and client-cert appear in Settings but are gated ("not yet supported — use SAML/SSO").

Install

macOS — Homebrew (recommended)

brew install --cask savvaskoualis/tap/openfortitray

Then launch OpenFortiTray, open Settings…, set your gateway host and port, and Connect. On the first Connect the app asks for your Mac admin password once to install a small root-owned helper that brings the tunnel up — no separate step. Homebrew also installs openconnect and clears the download quarantine, so there is no Gatekeeper prompt.

macOS — .dmg (alternative)

Download OpenFortiTray-<version>.dmg from Releases, mount it, drag OpenFortiTray.app to /Applications, then clear the quarantine (the app is ad-hoc signed but not notarized):

xattr -dr com.apple.quarantine /Applications/OpenFortiTray.app
brew install openconnect   # the .dmg does not bundle it

First Connect installs the helper via the same one-time admin prompt.

Linux

From a checkout of this repo:

OPENFORTITRAY_GATEWAY=vpn.example.com:10443 bash scripts/install.sh

Run as your normal user (it calls sudo itself). It installs openconnect (apt/dnf/ pacman) if missing, the binary to /usr/local/bin/openfortitray, the root-owned helper + a scoped sudoers rule, and a starter config.json. Then openfortitray &. Needs a StatusNotifierItem-capable tray and webkit2gtk (4.1 on current distros, 4.0 on older ones) + GTK3, which render the app's window.

Windows

Download OpenFortiTray-<version>-Setup.exe from Releases and run it. SmartScreen (unsigned) → More info → Run anyway; approve the UAC prompt. The wizard installs to %ProgramFiles%\openfortitray, adds a Start-menu entry and an elevated logon task, and bundles openconnect (with its DLLs and wintun.dll) in %ProgramFiles%\openfortitray\openconnect, so nothing else is needed — Windows has no reliable way to install openconnect otherwise.

Every launch shows a UAC prompt — the app must be elevated to create the wintun adapter. The login task launches it elevated without a prompt. Open Settings… to set your gateway. The app uses the bundled openconnect automatically; to point it at a different build, set "openconnect_path" to its full path in %APPDATA%\openfortitray\config.json.

The app's window renders inside the Microsoft Edge WebView2 Runtime, which ships by default on Windows 11 and most updated Windows 10 machines. The Setup.exe installs it for you if it is missing (via Microsoft's own WebView2 bootstrapper); if you run the bare openfortitray-windows-amd64.exe on a machine without WebView2, install it yourself from Microsoft's WebView2 page.

Before you connect (all platforms): quit FortiClient — or any other client for this gateway — and disable its login item. FortiGate allows only one SSL-VPN session per user; two clients fight over the routes and it looks like a flaky network.

Usage

The tray menu has the current status, Connect / Disconnect, Auto-connect at login, Settings…, View logs, Check for Updates…, and Quit. Icon colour tracks state: grey disconnected, yellow connecting, green connected, red error.

The first connect opens your browser for SAML; later connects reuse the IdP session and are usually silent. If the active profile is not ready to dial (no gateway, bad port, an unsupported auth method), Connect opens Settings on the offending field instead of throwing a cryptic error.

Updates. OpenFortiTray checks GitHub for newer releases in the background. When one exists the tray shows Update to vX.Y.Z & Restart; clicking it upgrades in place (brew upgrade on macOS, the verified Setup.exe on Windows) and relaunches.

Configuration

The Settings window is the primary way to configure; you do not need to edit JSON. For reference, config.json (schema v2, multi-profile) and openfortitray.log live in:

OS directory
macOS ~/Library/Application Support/openfortitray/
Linux ~/.config/openfortitray/
Windows %APPDATA%\openfortitray\

Key profile fields: gateway (host, no scheme/port — no default), port (10443), saml_port (8020), auth.method (saml), realm, dtls (true; off → --no-dtls), dual_stack (false; off → --disable-ipv6), server_cert.mode (warn/trust/pin, with a sha256 pin), split_dns, remember_session (true). A minimal config:

{
  "schemaVersion": 2,
  "activeProfile": "Default",
  "profiles": [
    { "name": "Default", "gateway": "vpn.example.com", "port": 10443,
      "auth": { "method": "saml" }, "dtls": true, "server_cert": { "mode": "warn" } }
  ],
  "autostart": true
}

Session reuse

With remember_session on (the default, "Reuse session to avoid re-login" under Settings → Advanced), the SSL-VPN session cookie is kept in platform-native, user-scoped secret storage — the macOS login keychain, a DPAPI-encrypted file on Windows, a 0600 file on Linux — and reused on reconnect and after a restart, so the SAML browser flow runs only once per session lifetime instead of on every connect. The cookie never goes into config.json or the log. Turning the toggle off, or changing a profile's gateway or auth method, deletes the stored cookie. On Linux the cookie is protected by file permissions alone (there is no libsecret dependency).

Split-DNS domains are captured and validated but not yet installed as a scoped resolver — running alongside Tailscale/another VPN may need a manual /etc/resolver entry until that automation ships.

Privilege

openconnect must run as root to create the tunnel and edit routes. On macOS/Linux OpenFortiTray installs a root-owned helper (/usr/local/libexec/openfortitray-tunnel) reached through a sudoers rule scoped to that one path. The helper takes only start <host:port> [flag…] and stop; the gateway is pattern-validated and extra flags are checked against an exact allowlist (--no-dtls, --disable-ipv6, --servercert <fingerprint>), so the passwordless rule cannot be turned into arbitrary root. Full threat model in the header comments of scripts/openfortitray-tunnel and scripts/install.sh. On Windows there is no helper — the app runs elevated and calls openconnect directly.

Troubleshooting

Start with View logs. Common cases:

  • Connect keeps opening Settings — the active profile is not ready; the flagged field names the fix.
  • Helper not effective (macOS/Linux) — sudo -n /usr/local/libexec/openfortitray-tunnel stop must print nothing and exit 0. If it prompts, re-run scripts/install.sh as your normal user (not under sudo).
  • Routing left broken after a disconnect — sudo /usr/local/libexec/openfortitray-tunnel stop (idempotent) clears it. On Windows a reconnect/reboot restores it.
  • Port 8020 in use — free it or change the profile's SAML port (your gateway must allow the new redirect port).

Uninstall

Quit the app first.

macOS

launchctl bootout gui/$(id -u)/io.github.savvaskoualis.openfortitray 2>/dev/null
brew uninstall --cask openfortitray 2>/dev/null || sudo rm -rf /Applications/OpenFortiTray.app
sudo rm -f /usr/local/libexec/openfortitray-tunnel /etc/sudoers.d/openfortitray
rm -rf ~/Library/Application\ Support/openfortitray
# The reused session cookie lives in the login keychain, not in that directory:
security delete-generic-password -s openfortitray 2>/dev/null

(Repeat the security delete-generic-password line once per gateway you used — each gateway stores its own item under the openfortitray service. On Linux and Windows the stored session lives inside the config directory removed above.)

Linux

sudo rm -f /usr/local/bin/openfortitray /usr/local/libexec/openfortitray-tunnel /etc/sudoers.d/openfortitray
rm -rf ~/.config/openfortitray ~/.config/autostart/openfortitray.desktop

Windows (elevated PowerShell)

schtasks /Delete /TN "OpenFortiTray" /F
Remove-Item -Force "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\OpenFortiTray.lnk"
Remove-Item -Recurse -Force "$env:ProgramFiles\openfortitray","$env:APPDATA\openfortitray"

Building from source

The UI (Wails v2) renders through cgo + a native webview on macOS and Linux, so those two build on their own native toolchain (macOS: Xcode CLT; Linux: gcc + libgtk-3-dev + libwebkit2gtk-4.0-dev, Wails' default). Windows needs no cgo at all (its webview binding and the tray backend are pure Go) and builds with CGO_ENABLED=0. CI builds the full three-OS release matrix; a v* tag publishes signed-per-runner SHA256SUMS, the .dmg, and the Setup.exe.

make build    # go build -o openfortitray ./cmd/openfortitray
make test     # go vet ./... && go test -race ./...
make app      # macOS: assemble dist/OpenFortiTray.app
make dmg      # macOS: build the drag-to-Applications .dmg

On distros that no longer ship libwebkit2gtk-4.0-dev (Ubuntu 24.04+ and others), install libwebkit2gtk-4.1-dev instead and pass -tags webkit2_41 yourself — make build/make test do not add it automatically: go build -tags webkit2_41 ./cmd/openfortitray, or GOFLAGS='-tags=webkit2_41' make build / GOFLAGS='-tags=webkit2_41' make test.

License

MIT. See LICENSE.

About

Cross-platform FortiGate SSL-VPN menu-bar client with SAML/SSO, auto-connect, and a native settings UI (macOS/Linux/Windows)

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages