https://github.com/ilia-ae/wg-yubikey-manager
ππ macOS app for managing WireGuard VPN configurations with YubiKey authentication
https://github.com/ilia-ae/wg-yubikey-manager
2026 blog hardware-token macos swift vpn wireguard yubikey
Last synced: 28 days ago
JSON representation
ππ macOS app for managing WireGuard VPN configurations with YubiKey authentication
- Host: GitHub
- URL: https://github.com/ilia-ae/wg-yubikey-manager
- Owner: ilia-ae
- License: mit
- Created: 2026-03-20T09:13:34.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-05-18T06:13:54.000Z (about 2 months ago)
- Last Synced: 2026-05-18T07:54:34.024Z (about 2 months ago)
- Topics: 2026, blog, hardware-token, macos, swift, vpn, wireguard, yubikey
- Language: Go
- Homepage: https://ilia.ae/en/blog/infosec/wireguard-yubikey-manager-hardware-backed-vpn-security/
- Size: 191 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# π WG YubiKey Manager
**Secure WireGuard VPN management with hardware-backed key storage on YubiKey**
[](https://go.dev/)
[](https://v3alpha.wails.io/)
[](https://svelte.dev/)
[](https://vitejs.dev/)
[](https://www.apple.com/macos/)
[](https://www.yubico.com/)
[](LICENSE)
[**English**](README.md) | [**Π ΡΡΡΠΊΠΈΠΉ**](README_RU.md)

*A macOS native desktop application that combines WireGuard VPN tunnel management with YubiKey hardware security β your private keys never touch the disk.*
---
## β¨ Features
### π Hardware Key Security
- **YubiKey PIV Storage** β WireGuard private keys are encrypted and stored on YubiKey PIV slots (`5f0001`β`5f00ff`)
- **PIN-Protected Decryption** β keys are decrypted in-memory only at connection time using GPG + AES-256
- **Zero Disk Exposure** β private keys are never written to disk, only held in RAM during active sessions
- **Slot Registry** β automatic metadata management in YubiKey slot `5fc10f`
### π VPN Tunnel Management
- **One-Click Connect/Disconnect** β seamless WireGuard tunnel lifecycle management
- **Live Connection Stats** β real-time handshake, transfer (RX/TX), latency, peer info, uptime
- **Multi-Tunnel Support** β manage multiple WireGuard configurations from a single interface
- **Config Import** β paste or upload `.conf` files with automatic validation
- **Auto-Discovery** β scan YubiKey for stored configurations and sync to local state
### π₯οΈ Native macOS Experience
- **Translucent Window** β native glassmorphism UI with dark/light theme support
- **Menu Bar Tray** β persistent status indicator (β connected / β― disconnected)
- **Privilege Escalation** β one-time sudoers setup via `osascript`, passwordless afterwards
- **macOS App Bundle** β distributable `.app` with proper `Info.plist` and icon
---
## ποΈ Architecture
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WG YubiKey Manager β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ€
β Frontend (Svelte) β Backend (Go) β
β β β
β ββββββββββββββββββ β ββββββββββββββββββββββββββββββββ β
β β App.svelte ββββΌβββ internal/app/app.go β β
β β StatusBar β β β βββ Connect / Disconnect β β
β β TunnelList β β β βββ Import / Delete β β
β β TunnelCard β β β βββ GetStatus / emitStatus β β
β β PinDialog β β ββββββββββββββββββββββββββββββββ€ β
β β ImportDialog β β β internal/yubikey/ β β
β β TokenInfo β β β βββ PIV slot read/write β β
β ββββββββββββββββββ β β βββ GPG encrypt/decrypt β β
β β β βββ Registry management β β
β Wails Runtime β ββββββββββββββββββββββββββββββββ€ β
β (JS bindings) β β internal/wireguard/ β β
β β β βββ Config parsing (INI) β β
β β β βββ Validation β β
β β ββββββββββββββββββββββββββββββββ€ β
β β β internal/tunnel/ β β
β β β βββ Active tunnel state β β
β β ββββββββββββββββββββββββββββββββ€ β
β β β internal/privilege/ β β
β β β βββ sudo + sudoers setup β β
β β ββββββββββββββββββββββββββββββββ€ β
β β β internal/tray/ β β
β β β βββ macOS NSStatusItem β β
β β ββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββ€
β macOS (wireguard-go, wg, ykman, gpg) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
---
## π Prerequisites
| Dependency | Version | Installation |
|---|---|---|
| **macOS** | 12.0+ (Monterey) | β |
| **Go** | 1.25+ | [go.dev/dl](https://go.dev/dl/) |
| **Node.js** | 18+ | `brew install node` |
| **YubiKey** | 5 Series (PIV) | [yubico.com](https://www.yubico.com/) |
| **ykman** | latest | `brew install ykman` |
| **GnuPG** | 2.x | `brew install gnupg` |
| **WireGuard** | latest | `brew install wireguard-go wireguard-tools` |
| **Wails CLI** | v3 alpha | `go install github.com/wailsapp/wails/v3/cmd/wails3@latest` |
### Quick Install (Homebrew)
```bash
brew install go node ykman gnupg wireguard-go wireguard-tools
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
```
---
## π Getting Started
### Clone
```bash
git clone https://github.com/razqqm/wg-yubikey-manager.git
cd wg-yubikey-manager
```
### Development Mode
```bash
wails dev
```
This starts the app with hot-reload for both frontend and backend.
### Build
```bash
make build
```
Produces a binary at `build/wg-yubikey-manager`.
### Build macOS App Bundle
```bash
./build-app.sh
```
Creates `build/WG YubiKey Manager.app` β drag to `/Applications/` to install.
### Install
```bash
cp -r "build/WG YubiKey Manager.app" /Applications/
```
---
## π Usage
### First Launch
1. **Insert your YubiKey** into a USB port
2. **Launch the app** β it will prompt once for admin password to install a sudoers rule
3. The menu bar shows β― (disconnected)
### Import a WireGuard Config
1. Click **Import** in the app
2. Paste your `.conf` file content or select a file
3. Enter a name for the tunnel
4. Enter your **YubiKey PIN** β the config is encrypted and stored on the YubiKey
5. The config is validated automatically before import
### Connect to a Tunnel
1. Click **Connect** on a tunnel card
2. Enter your **YubiKey PIN**
3. The app decrypts the key from YubiKey β sets up `wireguard-go` β configures routes and DNS
4. Menu bar changes to β (connected) with tunnel name
5. Live stats appear: handshake time, transfer, latency, peer info
### Disconnect
Click **Disconnect** β the tunnel is torn down, routes removed, DNS resolver cleaned up.
### Sync from YubiKey
Click **Sync** β the app scans all PIV slots, discovers stored configs, rebuilds the registry, and syncs to local state. Useful when moving to a new machine.
---
## π Project Structure
```
wg-yubikey-manager/
βββ cmd/
β βββ wg-yubikey-manager/
β βββ main.go # Wails app initialization (480Γ640 window)
βββ internal/
β βββ app/
β β βββ app.go # Core logic: Connect, Disconnect, Import, Stats
β βββ config/
β β βββ manager.go # JSON config persistence (~/.config/wg-yubikey-manager/)
β βββ privilege/
β β βββ escalation.go # Sudoers install + passwordless sudo execution
β βββ tray/
β β βββ tray.go # Wails v3 SystemTray (pure Go)
β β βββ dialogs.go # PIN dialog + error alerts (osascript)
β βββ tunnel/
β β βββ manager.go # Thread-safe active tunnel state (mutex-protected)
β βββ wireguard/
β β βββ wireguard.go # WireGuard INI parser + validator
β βββ yubikey/
β βββ manager.go # YubiKey PIV operations (ykman + gpg)
βββ pkg/
β βββ models/
β βββ tunnel.go # TunnelDefinition, TunnelInfo, AppStatus
β βββ wireguard.go # WireGuardConfig, Interface, Peer
β βββ yubikey.go # YubiKeyInfo, KeySlot, TokenInfo
βββ frontend/
β βββ embed.go # Go embed for production assets
β βββ index.html # HTML entry point
β βββ package.json # Svelte + Vite dependencies
β βββ vite.config.js # Vite + Svelte plugin
β βββ src/
β βββ main.js # Svelte mount point
β βββ App.svelte # Root component, state orchestration
β βββ style.css # Design system (dark/light, glassmorphism)
β βββ components/
β βββ StatusBar.svelte # YubiKey + tunnel status indicators
β βββ TokenInfo.svelte # Slot usage, Sync/Import buttons
β βββ TunnelList.svelte # Tunnel list with Add form
β βββ TunnelCard.svelte # Tunnel card with live stats
β βββ PinDialog.svelte # PIN entry modal (Enter/Esc)
β βββ ImportDialog.svelte # Config import (paste/upload + validation)
βββ resources/ # App icon (appicon.icns)
βββ build/ # Build output
βββ go.mod # Go module (github.com/razqqm/wg-yubikey-manager)
βββ wails.json # Wails framework configuration
βββ Makefile # Build automation
βββ build-app.sh # macOS .app bundle creator
```
---
## π Security Model
### Key Storage
```
ββββββββββββββ GPG AES-256 ββββββββββββββββ
β WireGuard β ββββ encrypt(PIN) βββΊ β YubiKey PIV β
β .conf β β slot 5fXXXX β
ββββββββββββββ ββββββββββββββββ
β² β
β at connect time β
βββββββ decrypt(PIN, in-memory) ββββββ
```
- **At Import**: Config is encrypted with GPG (AES-256, PIN as passphrase) and written to a YubiKey PIV data slot
- **At Connect**: Config is decrypted from YubiKey in-memory, parsed in Go, private key passed via stdin to `wg set`
- **Never on Disk**: Private keys exist only in RAM during the active session
### Privilege Escalation
1. On first launch, `EnsureSudoers()` installs `/etc/sudoers.d/wg-yubikey` via `osascript` (macOS auth dialog)
2. All subsequent privileged operations (`wireguard-go`, `wg`, `route`, DNS resolver) use `sudo -n` (no password)
3. YubiKey operations (`ykman`, `gpg`) run as the current user β no sudo needed
### Threat Model Considerations
| Threat | Mitigation |
|---|---|
| Key theft from disk | Keys never written to disk |
| Key extraction from memory | Keys cleared after tunnel setup |
| Unauthorized tunnel activation | YubiKey PIN required for every connection |
| Physical YubiKey theft | PIN protection (lockout after retries) |
| Privilege escalation abuse | Sudoers rule scoped to specific commands |
---
## βοΈ Configuration
### Application Config
Stored in `~/.config/wg-yubikey-manager/`:
| File | Purpose |
|---|---|
| `tunnels.json` | Tunnel definitions (name β YubiKey slot tag mapping) |
### YubiKey Layout
| Slot | Purpose |
|---|---|
| `5fc10f` | Registry β JSON array of `{name, tag}` pairs |
| `5f0001` β `5f00ff` | Encrypted WireGuard configs (up to 255 tunnels) |
### DNS Resolution
Per-tunnel DNS resolvers are created at `/etc/resolver/{tunnelName}` during connection and removed on disconnect.
---
## π οΈ Development
### Makefile Targets
| Target | Description |
|---|---|
| `make frontend` | Build frontend only (npm install + npm run build) |
| `make build` | Full build (frontend + Go binary) |
| `make run` | Build and run |
| `make clean` | Remove build artifacts, node_modules, dist |
| `make test` | Run Go tests |
| `make lint` | Run golangci-lint |
### Tech Stack
| Layer | Technology |
|---|---|
| Backend | Go 1.25 |
| Frontend | Svelte 4, Vite 5 |
| Bridge | Wails v3 (WebView + Go service bindings) |
| Native UI | Cocoa via CGO (NSStatusItem) |
| Crypto | GnuPG (AES-256 symmetric) |
| Hardware | YubiKey PIV via `ykman` CLI |
| VPN | `wireguard-go` + `wg` CLI |
### Frontend API
The frontend communicates with the Go backend through Wails-generated bindings:
```javascript
// Connection
await window.go.app.App.Connect(tunnelName, pin)
await window.go.app.App.Disconnect()
// Status
const status = await window.go.app.App.GetStatus()
// Tunnel management
await window.go.app.App.AddTunnel(name, tag)
await window.go.app.App.RemoveTunnel(name)
// YubiKey operations
const tokenInfo = await window.go.app.App.LoadFromToken(pin)
await window.go.app.App.ImportConfig(pin, name, configText)
await window.go.app.App.DeleteFromToken(pin, name)
const slot = await window.go.app.App.GetNextFreeSlot(pin)
// Real-time events
window.runtime.EventsOn('status:update', (status) => { ... })
```
---
## π€ Contributing
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
---
## π License
This project is licensed under the MIT License β see the [LICENSE](LICENSE) file for details.
---
## π Acknowledgments
- [Wails](https://wails.io/) β Go + Web frontend framework
- [Svelte](https://svelte.dev/) β reactive UI compiler
- [WireGuard](https://www.wireguard.com/) β modern VPN protocol
- [Yubico](https://www.yubico.com/) β hardware security keys
- [GnuPG](https://gnupg.org/) β encryption toolkit
---
**Built with β€οΈ for security-conscious VPN users**
*Your keys, your hardware, your control.*