mirror of https://github.com/h44z/wg-portal.git
356 lines
15 KiB
Markdown
356 lines
15 KiB
Markdown
# wg-portal-2
|
|
|
|
[](https://github.com/fedor-git/wg-portal-2/actions/workflows/docker-publish.yml)
|
|
[](https://opensource.org/licenses/MIT)
|
|

|
|
[](https://goreportcard.com/report/github.com/fedor-git/wg-portal-2)
|
|

|
|

|
|
[](https://github.com/h44z/wg-portal)
|
|
|
|
## Recent Enhancements
|
|
|
|
This fork includes several important improvements:
|
|
|
|
### 🚀 Cluster Mode Support
|
|
Run multiple instances with a shared database. Synchronization is handled by the **fanout module** for seamless peer and interface updates across all nodes.
|
|
|
|
### 🔧 Configuration Enhancements
|
|
New core configuration options for fine-grained control over DNS, routing, and peer lifecycle management.
|
|
|
|
## Introduction
|
|
<!-- Text from this line # is included in docs/documentation/overview.md -->
|
|
**WireGuard Portal** is a simple, web-based configuration portal for [WireGuard](https://wireguard.com) server management.
|
|
The portal uses the WireGuard [wgctrl](https://github.com/WireGuard/wgctrl-go) library to manage existing VPN
|
|
interfaces. This allows for the seamless activation or deactivation of new users without disturbing existing VPN
|
|
connections.
|
|
|
|
The configuration portal supports using a database (SQLite, MySQL, MsSQL, or Postgres), OAuth or LDAP
|
|
(Active Directory or OpenLDAP) as a user source for authentication and profile data.
|
|
|
|
## Features
|
|
|
|
* Self-hosted - the whole application is a single binary
|
|
* Responsive multi-language web UI written in Vue.js
|
|
* Automatically selects IP from the network pool assigned to the client
|
|
* QR-Code for convenient mobile client configuration
|
|
* Sends email to the client with QR-code and client config
|
|
* Enable / Disable clients seamlessly
|
|
* Generation of wg-quick configuration file (`wgX.conf`) if required
|
|
* User authentication (database, OAuth, or LDAP), Passkey support
|
|
* IPv6 ready
|
|
* Docker ready
|
|
* Can be used with existing WireGuard setups
|
|
* Support for multiple WireGuard interfaces
|
|
* Supports multiple WireGuard backends (wgctrl or MikroTik [BETA])
|
|
* Peer Expiry Feature
|
|
* Handles route and DNS settings like wg-quick does
|
|
* Exposes Prometheus metrics for monitoring and alerting
|
|
* REST API for management and client deployment
|
|
* Webhook for custom actions on peer, interface, or user updates
|
|
|
|
<!-- Text to this line # is included in docs/documentation/overview.md -->
|
|

|
|
|
|
## Configuration Guide
|
|
|
|
### Core Options
|
|
|
|
The latest version introduces several new core configuration options for production deployments:
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `admin_user` | string | - | Administrator username/email for initial setup |
|
|
| `admin_password` | string | - | Administrator password (hashed in database) |
|
|
| `admin_api_token` | string | - | API token for admin operations and cluster auth |
|
|
| `restore_state` | boolean | `false` | Restore WireGuard interface state from database on startup |
|
|
| `import_existing` | boolean | `false` | Import existing WireGuard configuration |
|
|
| `manage_dns` | boolean | `true` | Enable/disable automatic DNS management (resolv.conf). Set to `false` if managing DNS externally. |
|
|
| `ignore_main_default_route` | boolean | `false` | When `true`, the main route table won't be modified. Useful in container or complex networking setups. |
|
|
| `delete_expired_peers` | boolean | `false` | Automatically remove peers after their TTL expires. |
|
|
| `default_user_ttl` | string/integer | `0` | Default Time-To-Live for peers (e.g., `30d`, `1` for 1 day). `0` means no expiration. |
|
|
| `sync_on_startup` | boolean | `false` | Synchronize interface state with database on startup |
|
|
| `force_client_ip_as_allowed_ip` | boolean | `false` | Use client IP as allowed IP instead of calculating from pool |
|
|
| `master` | boolean | `false` | Mark this instance as master (for failover scenarios) |
|
|
|
|
### Cluster Mode with Fanout Module
|
|
|
|
The **Fanout module** enables synchronization across multiple WireGuard Portal instances sharing a single database. This is essential for high-availability deployments.
|
|
|
|
#### How It Works
|
|
|
|
1. Each instance publishes events (peer updates, deletions, interface changes) to other instances
|
|
2. All instances listen for updates and apply changes locally
|
|
3. Debouncing prevents thundering herd problems
|
|
4. Automatic peer kicking on startup ensures consistency
|
|
|
|
#### Fanout Configuration Options
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `enabled` | boolean | `false` | Enable cluster synchronization |
|
|
| `self_url` | string | - | This instance's URL (supports `{{ external_url }}` template) |
|
|
| `peers` | array | - | List of peer node URLs for cluster communication |
|
|
| `auth_header` | string | `Authorization` | HTTP header name for authentication |
|
|
| `auth_value` | string | - | Auth value (e.g., `Basic base64{user:token}`) |
|
|
| `timeout` | duration | `2s` | Request timeout for peer communication |
|
|
| `debounce` | duration | `300ms` | Time to wait before batching events |
|
|
| `origin` | string | `wg-portal` | Optional identifier for event origin |
|
|
| `kick_on_start` | boolean | `true` | Remove stale entries on startup |
|
|
| `tls_skip_verify` | boolean | `false` | Skip TLS verification for peer URLs (NOT for production!) |
|
|
| `topics` | array | all | Event topics to synchronize |
|
|
|
|
#### Configuration Example
|
|
|
|
```yaml
|
|
core:
|
|
admin_user: username@example.com
|
|
admin_password: ...
|
|
restore_state: false
|
|
import_existing: false
|
|
admin_api_token: ...
|
|
manage_dns: false
|
|
ignore_main_default_route: true
|
|
delete_expired_peers: true
|
|
default_user_ttl: 1d # 1 days default expiration
|
|
sync_on_startup: true
|
|
force_client_ip_as_allowed_ip: true
|
|
master: true
|
|
|
|
fanout:
|
|
enabled: true
|
|
self_url: "https://wg-portal-1.example.com" # Use template variable: {{ external_url }}
|
|
# List of other cluster nodes
|
|
peers:
|
|
- "https://wg-portal-2.example.com"
|
|
- "https://wg-portal-3.example.com"
|
|
|
|
# Communication settings
|
|
auth_header: "Authorization"
|
|
auth_value: "Basic base64{admin_user:admin_api_token}"
|
|
timeout: 2s
|
|
debounce: 300ms # Wait 300ms before processing event batch
|
|
origin: "wg-portal" # Optional origin identifier
|
|
kick_on_start: true # Remove stale peer entries on startup
|
|
|
|
# Event topics to synchronize (all shown by default)
|
|
topics:
|
|
- "peers.updated" # When peer list is updated
|
|
- "peer.save" # Individual peer saved
|
|
- "peer.delete" # Peer deleted
|
|
- "interface.save" # Interface configuration changed
|
|
- "interface.updated"# Interface state changed
|
|
tls_skip_verify: true
|
|
|
|
advanced:
|
|
config_storage_path: /etc/wireguard/
|
|
rule_prio_offset: 32000
|
|
log_level: info
|
|
log_pretty: true
|
|
log_json: false
|
|
expiry_check_interval: 5h
|
|
|
|
web:
|
|
listening_address: :8888
|
|
external_url: https://wg-portal-1.example.com:4848
|
|
request_logging: true
|
|
csrf_secret: ...
|
|
session_secret: ...
|
|
|
|
statistics:
|
|
use_ping_checks: false
|
|
ping_check_workers: 3
|
|
ping_unprivileged: false
|
|
ping_check_interval: 5m
|
|
data_collection_interval: 60s
|
|
collect_interface_data: true
|
|
collect_peer_data: true
|
|
collect_audit_data: true
|
|
store_audit_data: false
|
|
listening_address: ":8787"
|
|
export_detailed_peer_metrics: false
|
|
only_export_connected_peers: true
|
|
```
|
|
|
|
#### Deployment Architecture
|
|
|
|
```
|
|
┌───────────────────────────┐
|
|
│ Shared Database │
|
|
│ MySQL/PostgreSQL │
|
|
└─────────────┬─────────────┘
|
|
│
|
|
┌─────────────────┼──────────────────┐
|
|
│ │ │
|
|
┌───▼────┐ ┌───▼────┐ ┌────▼───┐
|
|
│ Node 1 │◄──────►│ Node 2 │◄──────►│ Node 3 │
|
|
│ Portal │ │ Portal │ │ Portal │
|
|
└───┬────┘ └───┬────┘ └────┬───┘
|
|
│ │ │
|
|
[wg0] [wg0] [wg0]
|
|
│ │ │
|
|
┌───┴─────────────────┼──────────────────┴───┐
|
|
│ Network / Load Balancer (HAProxy/nginx) │
|
|
└────────────────────────────────────────────┘
|
|
```
|
|
|
|
### DNS Management
|
|
|
|
When `manage_dns: false`, the portal won't modify `/etc/resolv.conf`. This is useful when:
|
|
- Running in containers with custom DNS configuration
|
|
- Using external DNS management tools
|
|
- DNS is configured via cloud orchestration (Kubernetes, etc.)
|
|
|
|
### Route Management
|
|
|
|
Setting `ignore_main_default_route: true` prevents modification of the main route table. This is necessary for:
|
|
- Complex multi-interface hosts
|
|
- Containers with restricted capabilities
|
|
- Environments where routing is managed externally
|
|
|
|
### Peer Expiration
|
|
|
|
Configure automatic peer cleanup:
|
|
|
|
```yaml
|
|
core:
|
|
delete_expired_peers: true
|
|
default_user_ttl: 30d # 30 days expiration
|
|
```
|
|
|
|
Individual peers can also have custom TTL values. After expiration:
|
|
1. Peer status changes to "expired"
|
|
2. Peer is automatically removed if `delete_expired_peers: true`
|
|
3. WireGuard configuration is updated automatically
|
|
|
|
### Advanced Options
|
|
|
|
Fine-tune WireGuard configuration and logging:
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `config_storage_path` | string | `/etc/wireguard/` | Directory where WireGuard config files are stored |
|
|
| `rule_prio_offset` | integer | `32000` | Base priority offset for iptables rules |
|
|
| `log_level` | string | `info` | Logging level: `debug`, `info`, `warn`, `error` |
|
|
| `log_pretty` | boolean | `false` | Pretty-print logs (human-readable format) |
|
|
| `log_json` | boolean | `false` | Output logs as JSON for log aggregation |
|
|
| `expiry_check_interval` | duration | `5h` | How often to check for expired peers |
|
|
|
|
### Web Interface Options
|
|
|
|
Configure the web portal:
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `listening_address` | string | `:8080` | HTTP listening address |
|
|
| `external_url` | string | - | External URL for QR codes and API responses |
|
|
| `request_logging` | boolean | `true` | Log HTTP requests |
|
|
| `csrf_secret` | string | - | Secret key for CSRF protection (auto-generated if not set) |
|
|
| `session_secret` | string | - | Secret key for session encryption (auto-generated if not set) |
|
|
|
|
### Statistics & Monitoring Options
|
|
|
|
Configure metrics collection and reporting:
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `use_ping_checks` | boolean | `false` | Enable ping-based peer availability checks |
|
|
| `ping_check_workers` | integer | `3` | Number of concurrent ping workers |
|
|
| `ping_unprivileged` | boolean | `false` | Use unprivileged ping (requires ICMP capabilities) |
|
|
| `ping_check_interval` | duration | `5m` | Interval between ping checks |
|
|
| `data_collection_interval` | duration | `60s` | How often to collect statistics |
|
|
| `collect_interface_data` | boolean | `true` | Collect interface metrics (traffic, handshakes) |
|
|
| `collect_peer_data` | boolean | `true` | Collect peer-level metrics |
|
|
| `collect_audit_data` | boolean | `false` | Collect audit trail data |
|
|
| `store_audit_data` | boolean | `false` | Persist audit data to database |
|
|
| `listening_address` | string | `:8787` | Prometheus metrics listening address |
|
|
| `export_detailed_peer_metrics` | boolean | `false` | Export per-peer metrics (can be expensive) |
|
|
| `only_export_connected_peers` | boolean | `true` | Only export metrics for connected peers |
|
|
|
|
## Documentation
|
|
|
|
For the complete documentation visit [wgportal.org](https://wgportal.org).
|
|
|
|
## What is out of scope
|
|
|
|
* Automatic generation or application of any `iptables` or `nftables` rules.
|
|
* Support for operating systems other than linux.
|
|
* Automatic import of private keys of an existing WireGuard setup.
|
|
|
|
## Quick Start with Cluster Mode
|
|
|
|
### Prerequisites
|
|
|
|
- MySQL 5.7+ or PostgreSQL 10+ (shared database)
|
|
- Linux kernel with WireGuard support
|
|
- 3+ instances for HA setup (optional, 1 instance works fine for single-node)
|
|
|
|
### Docker Cluster Deployment
|
|
|
|
```bash
|
|
# Create shared network
|
|
docker network create wg-portal
|
|
|
|
# Start database
|
|
docker run -d \
|
|
--name mysql-wg \
|
|
--network wg-portal \
|
|
-e MYSQL_ROOT_PASSWORD=secret \
|
|
mysql:8.0
|
|
|
|
# Wait for DB to be ready
|
|
sleep 10
|
|
|
|
# Start first portal node
|
|
docker run -d \
|
|
--name wg-portal-1 \
|
|
--network wg-portal \
|
|
--cap-add=NET_ADMIN \
|
|
-e PORTAL_LISTEN_ADDRESS=0.0.0.0:8080 \
|
|
-e PORTAL_EXTERNAL_URL=https://wg-portal-1.example.com \
|
|
wgportal/wg-portal
|
|
|
|
# Start second portal node
|
|
docker run -d \
|
|
--name wg-portal-2 \
|
|
--network wg-portal \
|
|
--cap-add=NET_ADMIN \
|
|
-e PORTAL_LISTEN_ADDRESS=0.0.0.0:8080 \
|
|
-e PORTAL_EXTERNAL_URL=https://wg-portal-2.example.com \
|
|
wgportal/wg-portal
|
|
```
|
|
|
|
### Verify Cluster Communication
|
|
|
|
```bash
|
|
# Check logs for fanout initialization
|
|
docker logs wg-portal-1 | grep -i fanout
|
|
|
|
# Test peer synchronization
|
|
# Create/modify peer on node 1, should appear on node 2 within debounce time
|
|
```
|
|
|
|
## Testing & Validation
|
|
|
|
The cluster mode has been tested with:
|
|
- ✅ MySQL 5.7, 8.0+
|
|
- ✅ PostgreSQL 12+
|
|
- ✅ Up to 5 concurrent nodes
|
|
- ✅ Load balancer (HAProxy, nginx)
|
|
- ✅ Automatic failover scenarios
|
|
|
|
## Application stack
|
|
|
|
* [wgctrl-go](https://github.com/WireGuard/wgctrl-go) and [netlink](https://github.com/vishvananda/netlink) for interface handling
|
|
* [Bootstrap](https://getbootstrap.com/), for the HTML templates
|
|
* [Vue.js](https://vuejs.org/), for the frontend
|
|
|
|
## License
|
|
|
|
* MIT License. [MIT](LICENSE.txt) or <https://opensource.org/licenses/MIT>
|
|
|
|
|
|
> [!IMPORTANT]
|
|
> Since the project was accepted by the Docker-Sponsored Open Source Program, the Docker image location has moved to [wgportal/wg-portal](https://hub.docker.com/r/wgportal/wg-portal).
|
|
> Please update the Docker image from **fedor-git/wg-portal-2** to **wgportal/wg-portal**.
|