An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          

# πŸ” WG YubiKey Manager

**Secure WireGuard VPN management with hardware-backed key storage on YubiKey**

[![Go](https://img.shields.io/badge/Go-1.25-00ADD8?style=flat-square&logo=go&logoColor=white)](https://go.dev/)
[![Wails](https://img.shields.io/badge/Wails-v3_alpha-DF0000?style=flat-square&logo=wails&logoColor=white)](https://v3alpha.wails.io/)
[![Svelte](https://img.shields.io/badge/Svelte-4.2-FF3E00?style=flat-square&logo=svelte&logoColor=white)](https://svelte.dev/)
[![Vite](https://img.shields.io/badge/Vite-5.4-646CFF?style=flat-square&logo=vite&logoColor=white)](https://vitejs.dev/)
[![Platform](https://img.shields.io/badge/platform-macOS%2012%2B-000000?style=flat-square&logo=apple&logoColor=white)](https://www.apple.com/macos/)
[![YubiKey](https://img.shields.io/badge/YubiKey-5%20Series-84BD00?style=flat-square&logo=yubico&logoColor=white)](https://www.yubico.com/)
[![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)

[**English**](README.md) | [**Русский**](README_RU.md)


WG YubiKey Manager

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