A CLI for managing VPN connections with automatic TOTP (Time-based One-Time Password) authentication using high performance code and security best practices.
- Secure Credential Management: Stores PIN and TOTP secret securely in GNOME Keyring
- Automatic OTP Generation: Generates TOTP tokens automatically during connection
- OpenConnect Integration: Uses OpenConnect CLI for robust VPN connectivity (F5 protocol support)
- Automatic Reconnection: Detects network interruptions and reconnects with exponential backoff
- Health Monitoring: Periodic health checks detect silent VPN failures
- Fast & Lightweight: written in Rust and with minimal dependencies
-
Operating System: Linux (tested on Ubuntu/Debian, RHEL/Fedora)
-
OpenConnect: Version 9.x or later
# Ubuntu/Debian sudo apt install openconnect # RHEL/Fedora sudo dnf install openconnect # Verify installation which openconnect
-
GNOME Keyring: For secure credential storage
sudo apt install gnome-keyring libsecret-1-dev
-
Root Privileges: Required for TUN device creation (run with
sudo)
Download pre-built packages for your distribution from the GitHub Releases page.
# Install the package
sudo dpkg -i akon_latest_amd64.deb
# If there are dependency issues, run:
sudo apt-get install -f# Install the package
sudo dnf install ./akon-latest-1.x86_64.rpm# Clone the repository
git clone https://github.com/vcwild/akon.git
cd akon
# Build and install (sets up passwordless sudo for openconnect)
make install
# Verify installation
akon --helpWhat make install does:
- Builds the release binary
- Installs to
/usr/local/bin/akon - Configures passwordless sudo for openconnect
- No password prompts when connecting to VPN!
Store your VPN credentials securely:
akon setupYou'll be prompted for:
- Server: VPN server hostname (e.g.,
vpn.example.com) - Username: Your VPN username
- PIN: Your numeric PIN
- TOTP Secret: Your TOTP secret key (Base32 encoded)
These credentials are stored in:
- Config file:
~/.config/akon/config.toml(server, username, protocol) - Keyring: GNOME Keyring (PIN and TOTP secret - encrypted)
akon vpn onWhat happens:
- Loads config from
~/.config/akon/config.toml - Retrieves PIN and TOTP secret from keyring
- Generates current TOTP token
- Spawns OpenConnect with credentials
- Monitors connection progress
- Reports IP address when connected
akon vpn statusOutputs:
- Connected (exit code 0): Shows IP, device, duration, PID
- Not connected (exit code 1): No active connection
- Stale state (exit code 2): Process died, cleanup needed
akon vpn offDisconnect flow:
- Sends SIGTERM for graceful shutdown (5s timeout)
- Falls back to SIGKILL if process doesn't respond
- Cleans up state file
Generate OTP token for manual use:
akon get-passwordOutputs PIN+TOTP combined password (does not initiate connection).
~/.config/akon/config.toml
[vpn]
server = "vpn.example.com"
username = "your.username"
protocol = "f5" # F5 SSL VPN protocol
# Optional settings
timeout = 60
no_dtls = false
lazy_mode = true # Connect VPN when running 'akon' without argumentsWhen lazy_mode = true is set in your configuration, running akon without any arguments will automatically connect to the VPN:
# With lazy mode enabled, these are equivalent:
akon
akon vpn on
# With lazy mode disabled, akon without args shows help
akon # Shows usage informationThis feature is perfect for quick VPN connections - just type akon and go!
akon automatically detects network interruptions and reconnects your VPN with intelligent retry logic.
Add a [reconnection] section to your config to enable automatic reconnection:
[vpn]
server = "vpn.example.com"
username = "your.username"
protocol = "f5"
[reconnection]
# Required: HTTP/HTTPS endpoint to check connectivity
health_check_endpoint = "https://your-internal-server.example.com/"
# Optional: Customize retry behavior (defaults shown)
max_attempts = 3 # Maximum reconnection attempts (default)
base_interval_secs = 5 # Initial retry delay
backoff_multiplier = 2 # Exponential backoff multiplier
max_interval_secs = 60 # Maximum delay between attempts
consecutive_failures_threshold = 1 # Health check failures before reconnection (default)
health_check_interval_secs = 10 # How often to check health (default)The name "akon" is a playful triple entendre:
- Memorable Command: A short, 4-letter command that's easy to type and remember
- Project Evolution: An acronym to auto-openconnect, the predecessor project
- Cultural Reference: A nod to the famous singer Akon, because connecting to VPN should be as smooth as his music
akon uses a CLI process delegation architecture:
- Spawns OpenConnect as a child process
- Manages process lifecycle (spawn β monitor β terminate)
- Parses output in real-time for connection events
- Provides clean async API using Tokio
This design eliminates FFI complexity while maintaining full OpenConnect functionality.
flowchart TB
User([π€ User]) -->|$ akon vpn on| CLI[CLI Entry Point]
CLI --> Config[Load Config<br/>~/.config/akon/config.toml]
Config --> Keyring[π Retrieve Credentials<br/>GNOME Keyring]
Keyring -->|PIN + TOTP Secret| TOTP[Generate TOTP Token<br/>Time-based OTP]
TOTP -->|PIN+OTP| Connector[CLI Connector<br/>Process Manager]
Connector -->|spawn sudo openconnect| OC[π OpenConnect Process<br/>VPN Tunnel]
OC -->|stdout/stderr| Parser[Output Parser<br/>Regex Matching]
Parser -->|Connection Events| Monitor[Connection Monitor<br/>State Machine]
Monitor -->|Connected Event| State[Update State<br/>/tmp/akon_vpn_state.json]
State --> Success[β VPN Connected<br/>IP Assigned]
Success -.->|periodic checks| Health[π₯ Health Check<br/>HTTP Probe]
Health -->|HTTP GET| Endpoint[Internal Endpoint<br/>Connectivity Test]
Endpoint -->|Success| Continue[Continue Monitoring]
Endpoint -->|Failure| Threshold{Consecutive<br/>Failures β₯ threshold?}
Threshold -->|No| Continue
Threshold -->|Yes| Reconnect[π Reconnection<br/>Exponential Backoff]
Monitor -.->|NetworkManager D-Bus| NM[πΆ Network Events<br/>WiFi/Ethernet Changes]
NM -.->|suspend/resume<br/>WiFi change| Reconnect
Reconnect -->|backoff: 5sβ10sβ20sβ40sβ60s| Connector
style User fill:#34495e,stroke:#2c3e50,stroke-width:3px,color:#fff
style CLI fill:#3498db,stroke:#2980b9,stroke-width:3px,color:#fff
style Config fill:#95a5a6,stroke:#7f8c8d,stroke-width:2px,color:#fff
style Keyring fill:#f39c12,stroke:#e67e22,stroke-width:3px,color:#fff
style TOTP fill:#16a085,stroke:#138d75,stroke-width:2px,color:#fff
style Connector fill:#2980b9,stroke:#1f618d,stroke-width:3px,color:#fff
style OC fill:#27ae60,stroke:#229954,stroke-width:4px,color:#fff
style Parser fill:#8e44ad,stroke:#7d3c98,stroke-width:2px,color:#fff
style Monitor fill:#2c3e50,stroke:#1c2833,stroke-width:3px,color:#fff
style State fill:#34495e,stroke:#2c3e50,stroke-width:2px,color:#fff
style Success fill:#27ae60,stroke:#229954,stroke-width:4px,color:#fff
style Health fill:#9b59b6,stroke:#8e44ad,stroke-width:3px,color:#fff
style Endpoint fill:#3498db,stroke:#2980b9,stroke-width:2px,color:#fff
style Continue fill:#16a085,stroke:#138d75,stroke-width:2px,color:#fff
style Threshold fill:#e67e22,stroke:#d35400,stroke-width:3px,color:#fff
style Reconnect fill:#e74c3c,stroke:#c0392b,stroke-width:4px,color:#fff
style NM fill:#9b59b6,stroke:#8e44ad,stroke-width:2px,color:#fff
linkStyle 0 stroke:#3498db,stroke-width:3px
linkStyle 1 stroke:#95a5a6,stroke-width:2px
linkStyle 2 stroke:#f39c12,stroke-width:3px
linkStyle 3 stroke:#16a085,stroke-width:2px
linkStyle 4 stroke:#2980b9,stroke-width:3px
linkStyle 5 stroke:#27ae60,stroke-width:4px
linkStyle 6 stroke:#8e44ad,stroke-width:2px
linkStyle 7 stroke:#2c3e50,stroke-width:3px
linkStyle 8 stroke:#34495e,stroke-width:2px
linkStyle 9 stroke:#27ae60,stroke-width:3px
linkStyle 10 stroke:#9b59b6,stroke-width:2px,stroke-dasharray: 5 5
linkStyle 11 stroke:#3498db,stroke-width:2px
linkStyle 12 stroke:#16a085,stroke-width:2px
linkStyle 13 stroke:#e67e22,stroke-width:2px
linkStyle 14 stroke:#16a085,stroke-width:2px
linkStyle 15 stroke:#e74c3c,stroke-width:3px
linkStyle 16 stroke:#9b59b6,stroke-width:2px,stroke-dasharray: 5 5
linkStyle 17 stroke:#9b59b6,stroke-width:2px,stroke-dasharray: 5 5
linkStyle 18 stroke:#e74c3c,stroke-width:3px
Key Components:
- CLI Layer: Command handlers for
setup,vpn on/off/status,get-password - Config Management: TOML configuration with secure credential storage
- Authentication: TOTP generation, keyring integration, password assembly
- VPN Connector: OpenConnect process lifecycle management
- Output Parser: Real-time parsing of OpenConnect output
- Health Monitoring: Periodic endpoint checks for silent failures
- Reconnection Manager: Exponential backoff retry logic
- State Management: Persistent connection state tracking
Automatically detects systemd and logs to journal:
# View logs
journalctl -f -u akon
# View with priority filter
journalctl -f -u akon -p infoakon/
βββ akon-core/ # Core library
β βββ src/
β β βββ auth/ # OTP, keyring, password generation
β β βββ config/ # TOML configuration
β β βββ vpn/ # VPN connection management
β β β βββ cli_connector.rs # OpenConnect process manager
β β β βββ output_parser.rs # Output parsing with regex
β β β βββ connection_event.rs # Event types
β β βββ error.rs # Error types
β βββ tests/ # Unit tests
βββ src/ # CLI application
β βββ cli/ # Command implementations
β β βββ setup.rs # Setup command
β β βββ vpn.rs # VPN commands (on/off/status)
β βββ main.rs # Entry point
βββ tests/ # Integration tests# Run all tests
cargo test
# Run with coverage (requires cargo-tarpaulin)
cargo install cargo-tarpaulin
cargo tarpaulin --out HtmlView coverage report
open tarpaulin-report.htmlTesting and mock keyring
For tests that need a keyring implementation (CI or local), akon-core provides a lightweight "mock keyring" implementation which stores credentials in-memory. This is useful for unit and integration tests that must not interact with the system keyring.
The mock keyring and its test-only dependency (lazy_static) are behind a feature flag
so they are opt-in for consumers of akon-core:
- Feature name:
mock-keyring - Optional dependency:
lazy_static(enabled only whenmock-keyringis enabled)
Run tests that require the mock keyring with
# Run a single integration test using the mock keyring
cargo test -p akon-core --test integration_keyring_tests --features mock-keyring -- --nocaptureNotes:
lazy_staticis declared as an optional dependency enabled bymock-keyringand also present as adev-dependencyso developers can run tests locally without enabling the feature.- This means the
lazy_staticcrate is not linked into production binaries unless a consumer enablesmock-keyringexplicitly. - The mock keyring mirrors production retrieval behavior for PINs (the runtime truncates retrieved PINs to 30 characters). Tests validate truncation and password assembly.
Contributions are welcome! Please:
- Follow existing code style
- Add tests for new features
- Update documentation
- Run
cargo clippybefore submitting - Ensure all tests pass:
cargo test
This project is licensed under the MIT license.
Note: This tool is designed for F5 SSL VPN protocol. Other protocols may work but are not officially supported.