more cleanup

This commit is contained in:
Christoph Haas 2023-07-31 17:37:08 +02:00
parent 4b3225e293
commit 93f8633b2b
14 changed files with 365 additions and 182 deletions

View File

@ -42,12 +42,9 @@ COPY --from=builder /etc/group /etc/group
# Copy binaries # Copy binaries
COPY --from=builder /build/dist/wg-portal /app/wg-portal COPY --from=builder /build/dist/wg-portal /app/wg-portal
COPY --from=builder /build/dist/hc /app/hc
# Set the Current Working Directory inside the container # Set the Current Working Directory inside the container
WORKDIR /app WORKDIR /app
# Command to run the executable # Command to run the executable
CMD [ "/app/wg-portal" ] CMD [ "/app/wg-portal" ]
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 CMD [ "/app/hc", "http://localhost:11223/health" ]

222
README.md
View File

@ -1,4 +1,4 @@
# WireGuard Portal # WireGuard Portal (V2 - alpha testing)
[![Build Status](https://travis-ci.com/h44z/wg-portal.svg?token=q4pSqaqT58Jzpxdx62xk&branch=master)](https://travis-ci.com/h44z/wg-portal) [![Build Status](https://travis-ci.com/h44z/wg-portal.svg?token=q4pSqaqT58Jzpxdx62xk&branch=master)](https://travis-ci.com/h44z/wg-portal)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT)
@ -8,6 +8,9 @@
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/h44z/wg-portal) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/h44z/wg-portal)
[![Docker Pulls](https://img.shields.io/docker/pulls/h44z/wg-portal.svg)](https://hub.docker.com/r/h44z/wg-portal/) [![Docker Pulls](https://img.shields.io/docker/pulls/h44z/wg-portal.svg)](https://hub.docker.com/r/h44z/wg-portal/)
> :warning: **IMPORTANT** Version 2 is currently under development and may contain bugs. It is currently not advised to use this version
in production. Use version [1.0.17](https://github.com/h44z/wg-portal/releases) instead.
A simple, web based configuration portal for [WireGuard](https://wireguard.com). A simple, web based configuration portal for [WireGuard](https://wireguard.com).
The portal uses the WireGuard [wgctrl](https://github.com/WireGuard/wgctrl-go) library to manage existing VPN The portal uses the WireGuard [wgctrl](https://github.com/WireGuard/wgctrl-go) library to manage existing VPN
interfaces. This allows for seamless activation or deactivation of new users, without disturbing existing VPN interfaces. This allows for seamless activation or deactivation of new users, without disturbing existing VPN
@ -15,25 +18,27 @@ 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. 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 ## Features
* Self-hosted and web based * Self-hosted - the whole application is a single binary
* Responsive web UI written in Vue.JS
* Automatically select IP from the network pool assigned to client * Automatically select IP from the network pool assigned to client
* QR-Code for convenient mobile client configuration * QR-Code for convenient mobile client configuration
* Sent email to client with QR-code and client config * Sent email to client with QR-code and client config
* Enable / Disable clients seamlessly * Enable / Disable clients seamlessly
* Generation of `wgX.conf` if required * Generation of wg-quick configuration file (`wgX.conf`) if required
* User authentication (database, OAuth or LDAP)
* IPv6 ready * IPv6 ready
* User authentication (database, OAuth or LDAP) * Docker ready
* Dockerized
* Responsive web UI written in Vue.JS
* One single binary
* Can be used with existing WireGuard setups * Can be used with existing WireGuard setups
* Support for multiple WireGuard interfaces * Support for multiple WireGuard interfaces
* Peer Expiry Feature * Peer Expiry Feature
* REST API for management and client deployment (coming soon) * Handle route and DNS settings like wg-quick does
* ~~REST API for management and client deployment~~ (coming soon)
![Screenshot](screenshot.png) ![Screenshot](screenshot.png)
## Configuration ## Configuration
You can configure WireGuard Portal using a yaml configuration file. You can configure WireGuard Portal using a yaml configuration file.
The filepath of the yaml configuration file defaults to **config.yml** in the working directory of the executable. The filepath of the yaml configuration file defaults to **config.yml** in the working directory of the executable.
@ -55,94 +60,129 @@ The following configuration options are available:
| log_level | advanced | warn | The loglevel, can be one of: trace, debug, info, warn, error. | | log_level | advanced | warn | The loglevel, can be one of: trace, debug, info, warn, error. |
| log_pretty | advanced | false | Uses pretty, colorized log messages. | | log_pretty | advanced | false | Uses pretty, colorized log messages. |
| log_json | advanced | false | Logs in JSON format. | | log_json | advanced | false | Logs in JSON format. |
| ldap_sync_interval | advanced | 15m | | | ldap_sync_interval | advanced | 15m | The time interval after which users will be synchronized from LDAP. |
| start_listen_port | advanced | 51820 | | | start_listen_port | advanced | 51820 | The first port number that will be used as listening port for new interfaces. |
| start_cidr_v4 | advanced | 10.11.12.0/24 | | | start_cidr_v4 | advanced | 10.11.12.0/24 | The first IPv4 subnet that will be used for new interfaces. |
| start_cidr_v6 | advanced | fdfd:d3ad:c0de:1234::0/64 | | | start_cidr_v6 | advanced | fdfd:d3ad:c0de:1234::0/64 | The first IPv6 subnet that will be used for new interfaces. |
| use_ip_v6 | advanced | true | | | use_ip_v6 | advanced | true | Enable IPv6 support. |
| config_storage_path | advanced | | | | config_storage_path | advanced | | If a wg-quick style configuration should be stored to the filesystem, specify a storage directory. |
| expiry_check_interval | advanced | 15m | | | expiry_check_interval | advanced | 15m | The interval after which existing peers will be checked if they expired. |
| rule_prio_offset | advanced | 20000 | | | rule_prio_offset | advanced | 20000 | The default offset for ip route rule priorities. |
| route_table_offset | advanced | 20000 | | | route_table_offset | advanced | 20000 | The default offset for ip route table id's. |
| use_ping_checks | statistics | true | | | use_ping_checks | statistics | true | If enabled, peers will be pinged periodically to check if they are still connected. |
| ping_check_workers | statistics | 10 | | | ping_check_workers | statistics | 10 | Number of parallel ping checks that will be executed. |
| ping_unprivileged | statistics | false | | | ping_unprivileged | statistics | false | If set to false, the ping checks will run without root permissions (BETA). |
| ping_check_interval | statistics | 1m | | | ping_check_interval | statistics | 1m | The interval time between two ping check runs. |
| data_collection_interval | statistics | 10m | | | data_collection_interval | statistics | 10m | The interval between the data collection cycles. |
| collect_interface_data | statistics | true | | | collect_interface_data | statistics | true | A flag to enable interface data collection like bytes sent and received. |
| collect_peer_data | statistics | true | | | collect_peer_data | statistics | true | A flag to enable peer data collection like bytes sent and received, last handshake and remote endpoint address. |
| collect_audit_data | statistics | true | | | collect_audit_data | statistics | true | If enabled, some events, like portal logins, will be logged to the database. |
| host | mail | 127.0.0.1 | | | host | mail | 127.0.0.1 | The mail-server address. |
| port | mail | 25 | | | port | mail | 25 | The mail-server SMTP port. |
| encryption | mail | none | | | encryption | mail | none | SMTP encryption type, allowed values: none, tls, starttls. |
| cert_validation | mail | false | | | cert_validation | mail | false | Validate the mail server certificate (if encryption tls is used). |
| username | mail | | | | username | mail | | The SMTP user name. |
| password | mail | | | | password | mail | | The SMTP password. |
| auth_type | mail | plain | | | auth_type | mail | plain | SMTP authentication type, allowed values: plain, login, crammd5. |
| from | mail | Wireguard Portal <noreply@wireguard.local> | | | from | mail | Wireguard Portal <noreply@wireguard.local> | The address that is used to send mails. |
| link_only | mail | false | | | link_only | mail | false | Only send links to WireGuard Portal instead of the full configuration. |
| callback_url_prefix | auth | /api/v0 | | | callback_url_prefix | auth | /api/v0 | OAuth callback URL prefix. The full callback URL will look like: https://wg.portal.local/callback_url_prefix/provider_name/callback |
| oidc | auth | Empty Array - no providers configured | | | oidc | auth | Empty Array - no providers configured | A list of OpenID Connect providers. See auth/oidc properties to setup a new provider. |
| oauth | auth | Empty Array - no providers configured | | | oauth | auth | Empty Array - no providers configured | A list of plain OAuth providers. See auth/oauth properties to setup a new provider. |
| ldap | auth | Empty Array - no providers configured | | | ldap | auth | Empty Array - no providers configured | A list of LDAP providers. See auth/ldap properties to setup a new provider. |
| provider_name | auth/oidc | | | | provider_name | auth/oidc | | A unique provider name. This name must be unique throughout all authentication providers (even other types). |
| display_name | auth/oidc | | | | display_name | auth/oidc | | The display name is shown at the login page (the login button). |
| base_url | auth/oidc | | | | base_url | auth/oidc | | The base_url is the URL identifier for the service. For example: "https://accounts.google.com". |
| client_id | auth/oidc | | | | client_id | auth/oidc | | The OAuth client id. |
| client_secret | auth/oidc | | | | client_secret | auth/oidc | | The OAuth client secret. |
| extra_scopes | auth/oidc | | | | extra_scopes | auth/oidc | | Extra scopes that should be used in the OpenID Connect authentication flow. |
| field_map | auth/oidc | | | | field_map | auth/oidc | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. |
| registration_enabled | auth/oidc | | | | registration_enabled | auth/oidc | | If registration is enabled, new user accounts will created in WireGuard Portal. |
| provider_name | auth/oidc | | | | provider_name | auth/oauth | | A unique provider name. This name must be unique throughout all authentication providers (even other types). |
| display_name | auth/oauth | | | | display_name | auth/oauth | | The display name is shown at the login page (the login button). |
| base_url | auth/oauth | | | | base_url | auth/oauth | | The base_url is the URL identifier for the service. For example: "https://accounts.google.com". |
| client_id | auth/oauth | | | | client_id | auth/oauth | | The OAuth client id. |
| client_secret | auth/oauth | | | | client_secret | auth/oauth | | The OAuth client secret. |
| auth_url | auth/oauth | | | | auth_url | auth/oauth | | The URL for the authentication endpoint. |
| token_url | auth/oauth | | | | token_url | auth/oauth | | The URL for the token endpoint. |
| redirect_url | auth/oauth | | | | redirect_url | auth/oauth | | The redirect URL. |
| user_info_url | auth/oauth | | | | user_info_url | auth/oauth | | The URL for the user information endpoint. |
| scopes | auth/oauth | | | | scopes | auth/oauth | | OAuth scopes. |
| field_map | auth/oauth | | | | field_map | auth/oauth | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. |
| registration_enabled | auth/oauth | | | | registration_enabled | auth/oauth | | If registration is enabled, new user accounts will created in WireGuard Portal. |
| url | auth/ldap | | | | url | auth/ldap | | The LDAP server url. For example: ldap://srv-ad01.company.local:389 |
| start_tls | auth/ldap | | | | start_tls | auth/ldap | | Use STARTTLS to encrypt LDAP requests. |
| cert_validation | auth/ldap | | | | cert_validation | auth/ldap | | Validate the LDAP server certificate. |
| tls_certificate_path | auth/ldap | | | | tls_certificate_path | auth/ldap | | A path to the TLS certificate. |
| tls_key_path | auth/ldap | | | | tls_key_path | auth/ldap | | A path to the TLS key. |
| base_dn | auth/ldap | | | | base_dn | auth/ldap | | The base DN for searching users. For example: DC=COMPANY,DC=LOCAL |
| bind_user | auth/ldap | | | | bind_user | auth/ldap | | The bind user. For example: company\\ldap_wireguard |
| bind_pass | auth/ldap | | | | bind_pass | auth/ldap | | The bind password. |
| field_map | auth/ldap | | | | field_map | auth/ldap | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and memberof. |
| login_filter | auth/ldap | | | | login_filter | auth/ldap | | LDAP filters for users that should be allowed to log in. {{login_identifier}} will be replaced with the login username. |
| admin_group | auth/ldap | | | | admin_group | auth/ldap | | Users in this group are marked as administrators. |
| synchronize | auth/ldap | | | | synchronize | auth/ldap | | Periodically synchronize users (name, department, phone, status, ...) to the WireGuard Portal database. |
| disable_missing | auth/ldap | | | | disable_missing | auth/ldap | | If synchronization is enabled, missing LDAP users will be disabled in WireGuard Portal. |
| sync_filter | auth/ldap | | | | sync_filter | auth/ldap | | LDAP filters for users that should be synchronized to WireGuard Portal. |
| registration_enabled | auth/ldap | | | | registration_enabled | auth/ldap | | If registration is enabled, new user accounts will created in WireGuard Portal. |
| debug | database | false | | | debug | database | false | Debug database statements (log each statement). |
| slow_query_threshold | database | | | | slow_query_threshold | database | | A threshold for slow database queries. If the threshold is exceeded, a warning message will be logged. |
| type | database | sqlite | | | type | database | sqlite | The database type. Allowed values: sqlite, mssql, mysql or postgres. |
| dsn | database | sqlite.db | | | dsn | database | sqlite.db | The database DSN. For example: user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local |
| request_logging | web | false | | | request_logging | web | false | Log all HTTP requests. |
| external_url | web | http://localhost:8888 | | | external_url | web | http://localhost:8888 | The URL where a client can access WireGuard Portal. |
| listening_address | web | :8888 | | | listening_address | web | :8888 | The listening port of the web server. |
| session_identifier | web | wgPortalSession | | | session_identifier | web | wgPortalSession | The session identifier for the web frontend. |
| session_secret | web | very_secret | | | session_secret | web | very_secret | The session secret for the web frontend. |
| csrf_secret | web | extremely_secret | | | csrf_secret | web | extremely_secret | The CSRF secret. |
| site_title | web | WireGuard Portal | | | site_title | web | WireGuard Portal | The title that is shown in the web frontend. |
| site_company_name | web | WireGuard Portal | | | site_company_name | web | WireGuard Portal | The company name that is shown at the bottom of the web frontend. |
## Upgrading from V1
> :warning: Before upgrading from V1, make sure that you have a backup of your currently working configuration files and database!
To start the upgrade process, start the wg-portal binary with the **-migrateFrom** parameter.
The configuration (config.yml) for WireGuard Portal must be updated and valid before starting the upgrade.
To upgrade from a previous SQLite database, start wg-portal like:
```shell
./wg-portal-amd64 -migrateFrom=old_wg_portal.db
```
You can also specify the database type using the parameter **-migrateFromType**, supported types: mysql, mssql, postgres or sqlite.
For example:
```shell
./wg-portal-amd64 -migrateFromType=mysql -migrateFrom=user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
```
The upgrade will transform the old, existing database and store the values in the new database specified in config.yml.
Ensure that the new database does not contain any data!
## V2 TODOs
* Public REST API
* Translations
* Documentation
* Audit UI
## What is out of scope ## What is out of scope
* Generation or application of any `iptables` or `nftables` rules. * Automatic generation or application of any `iptables` or `nftables` rules.
* Setting up or changing IP-addresses of the WireGuard interface on operating systems other than linux. * Support for operating systems other than linux.
* Importing private keys of an existing WireGuard setup. * Automatic import of private keys of an existing WireGuard setup.
## Application stack ## Application stack
* [Gin, HTTP web framework written in Go](https://github.com/gin-gonic/gin) * [wgctrl-go](https://github.com/WireGuard/wgctrl-go) and [netlink](https://github.com/vishvananda/netlink) for interface handling
* [Bootstrap, for the HTML templates](https://getbootstrap.com/) * [Gin](https://github.com/gin-gonic/gin), HTTP web framework written in Go
* [Vue.JS, for the frontend](https://vuejs.org/) * [Bootstrap](https://getbootstrap.com/), for the HTML templates
* [Vue.JS](https://vuejs.org/), for the frontend
## License ## License

View File

@ -28,7 +28,8 @@ import (
func main() { func main() {
ctx := internal.SignalAwareContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) ctx := internal.SignalAwareContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
logrus.Infof("Starting WireGuard Portal...") logrus.Infof("Starting WireGuard Portal V2...")
logrus.Infof("WireGuard Portal version: %s", internal.Version)
cfg, err := config.GetConfig() cfg, err := config.GetConfig()
internal.AssertNoError(err) internal.AssertNoError(err)
@ -77,7 +78,7 @@ func main() {
statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard) statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard)
internal.AssertNoError(err) internal.AssertNoError(err)
cfgFileManager, err := configfile.NewConfigFileManager(cfg, database, database, cfgFileSystem) cfgFileManager, err := configfile.NewConfigFileManager(cfg, eventBus, database, database, cfgFileSystem)
internal.AssertNoError(err) internal.AssertNoError(err)
mailManager, err := mail.NewMailManager(cfg, mailer, cfgFileManager, database, database) mailManager, err := mail.NewMailManager(cfg, mailer, cfgFileManager, database, database)

View File

@ -2,7 +2,7 @@
version: '3.6' version: '3.6'
services: services:
wg-portal: wg-portal:
image: h44z/wg-portal:1.0.16 image: h44z/wg-portal:2.0.0-alpha1
container_name: wg-portal container_name: wg-portal
restart: unless-stopped restart: unless-stopped
logging: logging:
@ -16,4 +16,4 @@ services:
- /etc/wireguard:/etc/wireguard - /etc/wireguard:/etc/wireguard
- ./data:/app/data - ./data:/app/data
environment: environment:
- EXTERNAL_URL=http://localhost:8123 - EXTERNAL_URL=http://localhost:8888

View File

@ -33,9 +33,9 @@ const title = computed(() => {
} }
if (selectedInterface.value) { if (selectedInterface.value) {
return t("interfaces.interface.edit") + ": " + selectedInterface.value.Identifier return t("modals.interface-edit.headline-edit") + " " + selectedInterface.value.Identifier
} }
return t("interfaces.interface.new") return t("modals.interface-edit.headline-new")
}) })
const formData = ref(freshInterface()) const formData = ref(freshInterface())
@ -291,134 +291,135 @@ async function del() {
<template #default> <template #default>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#interface">Interface</a> <a class="nav-link active" data-bs-toggle="tab" href="#interface">{{ $t('modals.interface-edit.tab-interface') }}</a>
</li> </li>
<li v-if="formData.Mode==='server'" class="nav-item"> <li v-if="formData.Mode==='server'" class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#peerdefaults">Peer Defaults</a> <a class="nav-link" data-bs-toggle="tab" href="#peerdefaults">{{ $t('modals.interface-edit.tab-peerdef') }}</a>
</li> </li>
</ul> </ul>
<div id="interfaceTabs" class="tab-content"> <div id="interfaceTabs" class="tab-content">
<div id="interface" class="tab-pane fade active show"> <div id="interface" class="tab-pane fade active show">
<fieldset> <fieldset>
<legend class="mt-4">General</legend> <legend class="mt-4">{{ $t('modals.interface-edit.header-general') }}</legend>
<div v-if="props.interfaceId==='#NEW#'" class="form-group"> <div v-if="props.interfaceId==='#NEW#'" class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.identifier') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.identifier.label') }}</label>
<input v-model="formData.Identifier" class="form-control" placeholder="The device identifier" type="text"> <input v-model="formData.Identifier" class="form-control" :placeholder="$t('modals.interface-edit.identifier.placeholder')" type="text">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.displayname') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.mode.label') }}</label>
<select v-model="formData.Mode" class="form-select"> <select v-model="formData.Mode" class="form-select">
<option value="server">Server Mode</option> <option value="server">{{ $t('modals.interface-edit.mode.server') }}</option>
<option value="client">Client Mode</option> <option value="client">{{ $t('modals.interface-edit.mode.client') }}</option>
<option value="any">Custom Mode</option> <option value="any">{{ $t('modals.interface-edit.mode.any') }}</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.displayname') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.display-name.label') }}</label>
<input v-model="formData.DisplayName" class="form-control" placeholder="A descriptive name of the interface" type="text"> <input v-model="formData.DisplayName" class="form-control" :placeholder="$t('modals.interface-edit.display-name.placeholder')" type="text">
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend class="mt-4">Cryptography</legend> <legend class="mt-4">{{ $t('modals.interface-edit.header-crypto') }}</legend>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.privatekey') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.private-key.label') }}</label>
<input v-model="formData.PrivateKey" class="form-control" placeholder="The private key" required type="email"> <input v-model="formData.PrivateKey" class="form-control" :placeholder="$t('modals.interface-edit.private-key.placeholder')" required type="email">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.publickey') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.public-key.label') }}</label>
<input v-model="formData.PublicKey" class="form-control" placeholder="The public key" required type="email"> <input v-model="formData.PublicKey" class="form-control" :placeholder="$t('modals.interface-edit.public-key.placeholder')" required type="email">
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend class="mt-4">Networking</legend> <legend class="mt-4">{{ $t('modals.interface-edit.header-network') }}</legend>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.ips') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.ip.label') }}</label>
<vue3-tags-input class="form-control" :tags="formData.Addresses" <vue3-tags-input class="form-control" :tags="formData.Addresses"
placeholder="IP Addresses (CIDR format)" :placeholder="$t('modals.interface-edit.ip.placeholder')"
:add-tag-on-keys="[13, 188, 32, 9]" :add-tag-on-keys="[13, 188, 32, 9]"
:validate="validateCIDR" :validate="validateCIDR"
@on-tags-changed="handleChangeAddresses"/> @on-tags-changed="handleChangeAddresses"/>
</div> </div>
<div v-if="formData.Mode==='server'" class="form-group"> <div v-if="formData.Mode==='server'" class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.listenport') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.listen-port.label') }}</label>
<input v-model="formData.ListenPort" class="form-control" placeholder="Listen Port" type="number"> <input v-model="formData.ListenPort" class="form-control" :placeholder="$t('modals.interface-edit.listen-port.placeholder')" type="number">
</div> </div>
<div v-if="formData.Mode!=='server'" class="form-group"> <div v-if="formData.Mode!=='server'" class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.dns') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.dns.label') }}</label>
<vue3-tags-input class="form-control" :tags="formData.Dns" <vue3-tags-input class="form-control" :tags="formData.Dns"
placeholder="DNS Servers" :placeholder="$t('modals.interface-edit.dns.placeholder')"
:add-tag-on-keys="[13, 188, 32, 9]" :add-tag-on-keys="[13, 188, 32, 9]"
:validate="validateIP" :validate="validateIP"
@on-tags-changed="handleChangeDns"/> @on-tags-changed="handleChangeDns"/>
</div> </div>
<div v-if="formData.Mode!=='server'" class="form-group"> <div v-if="formData.Mode!=='server'" class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.dnssearch') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.dns-search.label') }}</label>
<vue3-tags-input class="form-control" :tags="formData.DnsSearch" <vue3-tags-input class="form-control" :tags="formData.DnsSearch"
placeholder="DNS Search prefixes" :placeholder="$t('modals.interface-edit.dns-search.placeholder')"
:add-tag-on-keys="[13, 188, 32, 9]" :add-tag-on-keys="[13, 188, 32, 9]"
:validate="validateDomain" :validate="validateDomain"
@on-tags-changed="handleChangeDnsSearch"/> @on-tags-changed="handleChangeDnsSearch"/>
</div> </div>
<div class="row"> <div class="row">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.mtu') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.mtu.label') }}</label>
<input v-model="formData.Mtu" class="form-control" placeholder="Client MTU (0 = default)" type="number"> <input v-model="formData.Mtu" class="form-control" :placeholder="$t('modals.interface-edit.mtu.placeholder')" type="number">
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.firewallmark') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.firewall-mark.label') }}</label>
<input v-model="formData.FirewallMark" class="form-control" placeholder="Firewall Mark (0 = default)" type="number"> <input v-model="formData.FirewallMark" class="form-control" :placeholder="$t('modals.interface-edit.firewall-mark.placeholder')" type="number">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.routingtable') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.routing-table.label') }}</label>
<input v-model="formData.RoutingTable" class="form-control" placeholder="Routing Table (0 = default)" type="number"> <input v-model="formData.RoutingTable" aria-describedby="routingTableHelp" class="form-control" :placeholder="$t('modals.interface-edit.routing-table.placeholder')" type="text">
<small id="routingTableHelp" class="form-text text-muted">{{ $t('modals.interface-edit.routing-table.description') }}</small>
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6">
</div> </div>
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend class="mt-4">Hooks</legend> <legend class="mt-4">{{ $t('modals.interface-edit.header-hooks') }}</legend>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.preup') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.pre-up.label') }}</label>
<textarea v-model="formData.PreUp" class="form-control" rows="2"></textarea> <textarea v-model="formData.PreUp" class="form-control" rows="2" :placeholder="$t('modals.interface-edit.pre-up.placeholder')"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.postup') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.post-up.label') }}</label>
<textarea v-model="formData.PostUp" class="form-control" rows="2"></textarea> <textarea v-model="formData.PostUp" class="form-control" rows="2" :placeholder="$t('modals.interface-edit.post-up.placeholder')"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.predown') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.pre-down.label') }}</label>
<textarea v-model="formData.PreDown" class="form-control" rows="2"></textarea> <textarea v-model="formData.PreDown" class="form-control" rows="2" :placeholder="$t('modals.interface-edit.pre-down.placeholder')"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.postdown') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.post-down.label') }}</label>
<textarea v-model="formData.PostDown" class="form-control" rows="2"></textarea> <textarea v-model="formData.PostDown" class="form-control" rows="2" :placeholder="$t('modals.interface-edit.post-down.placeholder')"></textarea>
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend class="mt-4">State</legend> <legend class="mt-4">{{ $t('modals.interface-edit.header-state') }}</legend>
<div class="form-check form-switch"> <div class="form-check form-switch">
<input v-model="formData.Disabled" class="form-check-input" type="checkbox"> <input v-model="formData.Disabled" class="form-check-input" type="checkbox">
<label class="form-check-label" >Disabled</label> <label class="form-check-label">{{ $t('modals.interface-edit.disabled.label') }}</label>
</div> </div>
<div class="form-check form-switch"> <div class="form-check form-switch">
<input v-model="formData.SaveConfig" checked="" class="form-check-input" type="checkbox"> <input v-model="formData.SaveConfig" checked="" class="form-check-input" type="checkbox">
<label class="form-check-label">Save Config to File</label> <label class="form-check-label">{{ $t('modals.interface-edit.save-config.label') }}</label>
</div> </div>
</fieldset> </fieldset>
</div> </div>
<div id="peerdefaults" class="tab-pane fade"> <div id="peerdefaults" class="tab-pane fade">
<fieldset> <fieldset>
<legend class="mt-4">Networking</legend> <legend class="mt-4">{{ $t('modals.interface-edit.header-network') }}</legend>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.endpoint') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.endpoint') }}</label>
<input v-model="formData.PeerDefEndpoint" class="form-control" placeholder="Endpoint Addresses" type="text"> <input v-model="formData.PeerDefEndpoint" class="form-control" placeholder="Endpoint Addresses" type="text">
<small class="form-text text-muted">The endpoint address that peers will connect to.</small> <small class="form-text text-muted">The endpoint address that peers will connect to.</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.networks') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.networks') }}</label>
<vue3-tags-input class="form-control" :tags="formData.PeerDefNetwork" <vue3-tags-input class="form-control" :tags="formData.PeerDefNetwork"
placeholder="Network Addresses" placeholder="Network Addresses"
:add-tag-on-keys="[13, 188, 32, 9]" :add-tag-on-keys="[13, 188, 32, 9]"
@ -427,7 +428,7 @@ async function del() {
<small class="form-text text-muted">Peers will get IP addresses from those subnets.</small> <small class="form-text text-muted">Peers will get IP addresses from those subnets.</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.allowedips') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.allowedips') }}</label>
<vue3-tags-input class="form-control" :tags="formData.PeerDefAllowedIPs" <vue3-tags-input class="form-control" :tags="formData.PeerDefAllowedIPs"
placeholder="Default Allowed IP Addresses" placeholder="Default Allowed IP Addresses"
:add-tag-on-keys="[13, 188, 32, 9]" :add-tag-on-keys="[13, 188, 32, 9]"
@ -435,7 +436,7 @@ async function del() {
@on-tags-changed="handleChangePeerDefAllowedIPs"/> @on-tags-changed="handleChangePeerDefAllowedIPs"/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.dns') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.dns') }}</label>
<vue3-tags-input class="form-control" :tags="formData.PeerDefDns" <vue3-tags-input class="form-control" :tags="formData.PeerDefDns"
placeholder="DNS Servers" placeholder="DNS Servers"
:add-tag-on-keys="[13, 188, 32, 9]" :add-tag-on-keys="[13, 188, 32, 9]"
@ -443,7 +444,7 @@ async function del() {
@on-tags-changed="handleChangePeerDefDns"/> @on-tags-changed="handleChangePeerDefDns"/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.dnssearch') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.dnssearch') }}</label>
<vue3-tags-input class="form-control" :tags="formData.PeerDefDnsSearch" <vue3-tags-input class="form-control" :tags="formData.PeerDefDnsSearch"
placeholder="DNS Search prefix" placeholder="DNS Search prefix"
:add-tag-on-keys="[13, 188, 32, 9]" :add-tag-on-keys="[13, 188, 32, 9]"
@ -452,41 +453,41 @@ async function del() {
</div> </div>
<div class="row"> <div class="row">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.mtu') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.mtu') }}</label>
<input v-model="formData.PeerDefMtu" class="form-control" placeholder="Client MTU (0 = default)" type="number"> <input v-model="formData.PeerDefMtu" class="form-control" placeholder="Client MTU (0 = default)" type="number">
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.firewallmark') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.firewallmark') }}</label>
<input v-model="formData.PeerDefFirewallMark" class="form-control" placeholder="Firewall Mark (0 = default)" type="number"> <input v-model="formData.PeerDefFirewallMark" class="form-control" placeholder="Firewall Mark (0 = default)" type="number">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.routingtable') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.routingtable') }}</label>
<input v-model="formData.PeerDefRoutingTable" class="form-control" placeholder="Routing Table (0 = default)" type="number"> <input v-model="formData.PeerDefRoutingTable" class="form-control" placeholder="Routing Table (0 = default)" type="number">
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.keepalive') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.keepalive') }}</label>
<input v-model="formData.PeerDefPersistentKeepalive" class="form-control" placeholder="Persistent Keepalive (0 = default)" type="number"> <input v-model="formData.PeerDefPersistentKeepalive" class="form-control" placeholder="Persistent Keepalive (0 = default)" type="number">
</div> </div>
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend class="mt-4">Hooks</legend> <legend class="mt-4">{{ $t('modals.interface-edit.header-peer-hooks') }}</legend>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.preup') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.preup') }}</label>
<textarea v-model="formData.PeerDefPreUp" class="form-control" rows="2"></textarea> <textarea v-model="formData.PeerDefPreUp" class="form-control" rows="2"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.postup') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.postup') }}</label>
<textarea v-model="formData.PeerDefPostUp" class="form-control" rows="2"></textarea> <textarea v-model="formData.PeerDefPostUp" class="form-control" rows="2"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.predown') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.predown') }}</label>
<textarea v-model="formData.PeerDefPreDown" class="form-control" rows="2"></textarea> <textarea v-model="formData.PeerDefPreDown" class="form-control" rows="2"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.postdown') }}</label> <label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.postdown') }}</label>
<textarea v-model="formData.PeerDefPostDown" class="form-control" rows="2"></textarea> <textarea v-model="formData.PeerDefPostDown" class="form-control" rows="2"></textarea>
</div> </div>
</fieldset> </fieldset>
@ -499,10 +500,10 @@ async function del() {
</template> </template>
<template #footer> <template #footer>
<div class="flex-fill text-start"> <div class="flex-fill text-start">
<button v-if="props.interfaceId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">Delete</button> <button v-if="props.interfaceId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{ $t('general.delete') }}</button>
</div> </div>
<button class="btn btn-primary me-1" type="button" @click.prevent="save">Save</button> <button class="btn btn-primary me-1" type="button" @click.prevent="save">{{ $t('general.save') }}</button>
<button class="btn btn-secondary" type="button" @click.prevent="close">Discard</button> <button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
</template> </template>
</Modal> </Modal>
</template> </template>

View File

@ -243,7 +243,89 @@
"headline": "Config for Interface:" "headline": "Config for Interface:"
}, },
"interface-edit": { "interface-edit": {
"privatekey": "Private Key" "headline-edit": "Edit Interface:",
"headline-new": "New Interface",
"tab-interface": "Interface",
"tab-peerdef": "Peer Defaults",
"header-general": "General",
"header-network": "Network",
"header-crypto": "Cryptography",
"header-hooks": "Interface Hooks",
"header-peer-hooks": "Hooks",
"header-state": "State",
"identifier": {
"label": "Identifier",
"placeholder": "The unique interface identifier"
},
"mode": {
"label": "Interface Mode",
"server": "Server Mode",
"client": "Client Mode",
"any": "Unknown Mode"
},
"display-name": {
"label": "Display Name",
"placeholder": "The descriptive name for the interface"
},
"private-key": {
"label": "Private Key",
"placeholder": "The private key"
},
"public-key": {
"label": "Public Key",
"placeholder": "The public key"
},
"ip": {
"label": "IP Addresses",
"placeholder": "IP Addresses (CIDR format)"
},
"listen-port": {
"label": "Listen Port",
"placeholder": "The listening port"
},
"dns": {
"label": "DNS Server",
"placeholder": "The DNS servers that should be used"
},
"dns-search": {
"label": "DNS Search Domains",
"placeholder": "DNS search prefixes"
},
"mtu": {
"label": "MTU",
"placeholder": "The interface MTU (0 = keep default)"
},
"firewall-mark": {
"label": "Firewall Mark",
"placeholder": "Firewall mark that is applied to outgoing traffic. (0 = automatic)"
},
"routing-table": {
"label": "Routing Table",
"placeholder": "The routing table ID",
"description": "Special cases: off = do not manage routes, 0 = automatic"
},
"pre-up": {
"label": "Pre-Up",
"placeholder": "One or multiple bash commands"
},
"post-up": {
"label": "Post-Up",
"placeholder": "One or multiple bash commands"
},
"pre-down": {
"label": "Pre-Down",
"placeholder": "One or multiple bash commands"
},
"post-down": {
"label": "Post-Down",
"placeholder": "One or multiple bash commands"
},
"disabled": {
"label": "Interface Disabled"
},
"save-config": {
"label": "Automatically save wg-quick config"
}
}, },
"peer-view": { "peer-view": {
"headline-peer": "Peer:", "headline-peer": "Peer:",

12
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/gin-contrib/cors v1.4.0 github.com/gin-contrib/cors v1.4.0
github.com/gin-contrib/sessions v0.0.5 github.com/gin-contrib/sessions v0.0.5
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/glebarez/sqlite v1.8.0 github.com/glebarez/sqlite v1.9.0
github.com/go-ldap/ldap/v3 v3.4.5 github.com/go-ldap/ldap/v3 v3.4.5
github.com/prometheus-community/pro-bing v0.3.0 github.com/prometheus-community/pro-bing v0.3.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
@ -18,9 +18,10 @@ require (
github.com/vardius/message-bus v1.1.5 github.com/vardius/message-bus v1.1.5
github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netlink v1.1.0
github.com/xhit/go-simple-mail/v2 v2.15.0 github.com/xhit/go-simple-mail/v2 v2.15.0
github.com/yeqown/go-qrcode/v2 v2.2.1 github.com/yeqown/go-qrcode/v2 v2.2.2
golang.org/x/crypto v0.11.0 golang.org/x/crypto v0.11.0
golang.org/x/oauth2 v0.10.0 golang.org/x/oauth2 v0.10.0
golang.org/x/sys v0.10.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
gorm.io/driver/mysql v1.5.1 gorm.io/driver/mysql v1.5.1
@ -39,7 +40,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.1 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect
@ -91,16 +92,15 @@ require (
golang.org/x/arch v0.3.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/net v0.12.0 // indirect golang.org/x/net v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.9.3 // indirect golang.org/x/tools v0.9.3 // indirect
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.3 // indirect modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.21.1 // indirect modernc.org/sqlite v1.23.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect
) )

20
go.sum
View File

@ -45,10 +45,10 @@ github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/glebarez/go-sqlite v1.21.1 h1:7MZyUPh2XTrHS7xNEHQbrhfMZuPSzhkm2A1qgg0y5NY= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.1/go.mod h1:ISs8MF6yk5cL4n/43rSOmVMGJJjHYr7L2MbZZ5Q4E2E= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.8.0 h1:02X12E2I/4C1n+v90yTqrjRa8yuo7c3KeHI3FRznCvc= github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs=
github.com/glebarez/sqlite v1.8.0/go.mod h1:bpET16h1za2KOOMb8+jCp6UBP/iahDpfPQqSaYLTLx8= github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
@ -246,8 +246,8 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xhit/go-simple-mail/v2 v2.15.0 h1:qMXeqcZErUW/Dw6EXxmPuxHzVI8MdxWnEnu2xcisohU= github.com/xhit/go-simple-mail/v2 v2.15.0 h1:qMXeqcZErUW/Dw6EXxmPuxHzVI8MdxWnEnu2xcisohU=
github.com/xhit/go-simple-mail/v2 v2.15.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= github.com/xhit/go-simple-mail/v2 v2.15.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
github.com/yeqown/go-qrcode/v2 v2.2.1 h1:Jc1Q916fwC05R8C7mpWDbrT9tyLPaLLKDABoC5XBCe8= github.com/yeqown/go-qrcode/v2 v2.2.2 h1:0comk6jEwi0oWNhKEmzx4JI+Q7XIneAApmFSMKWmSVc=
github.com/yeqown/go-qrcode/v2 v2.2.1/go.mod h1:2Qsk2APUCPne0TsRo40DIkI5MYnbzYKCnKGEFWrxd24= github.com/yeqown/go-qrcode/v2 v2.2.2/go.mod h1:2Qsk2APUCPne0TsRo40DIkI5MYnbzYKCnKGEFWrxd24=
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0= github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM= github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -368,14 +368,14 @@ gorm.io/driver/sqlserver v1.5.1/go.mod h1:AYHzzte2msKTmYBYsSIq8ZUsznLJwBdkB2wpI+
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU= modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

View File

@ -5,8 +5,11 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"github.com/h44z/wg-portal/internal/app"
"github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain" "github.com/h44z/wg-portal/internal/domain"
"github.com/sirupsen/logrus"
evbus "github.com/vardius/message-bus"
"github.com/yeqown/go-qrcode/v2" "github.com/yeqown/go-qrcode/v2"
"io" "io"
"os" "os"
@ -15,6 +18,7 @@ import (
type Manager struct { type Manager struct {
cfg *config.Config cfg *config.Config
bus evbus.MessageBus
tplHandler *TemplateHandler tplHandler *TemplateHandler
fsRepo FileSystemRepo // can be nil if storing the configuration is disabled fsRepo FileSystemRepo // can be nil if storing the configuration is disabled
@ -22,7 +26,7 @@ type Manager struct {
wg WireguardDatabaseRepo wg WireguardDatabaseRepo
} }
func NewConfigFileManager(cfg *config.Config, users UserDatabaseRepo, wg WireguardDatabaseRepo, fsRepo FileSystemRepo) (*Manager, error) { func NewConfigFileManager(cfg *config.Config, bus evbus.MessageBus, users UserDatabaseRepo, wg WireguardDatabaseRepo, fsRepo FileSystemRepo) (*Manager, error) {
tplHandler, err := newTemplateHandler() tplHandler, err := newTemplateHandler()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize template handler: %w", err) return nil, fmt.Errorf("failed to initialize template handler: %w", err)
@ -30,6 +34,7 @@ func NewConfigFileManager(cfg *config.Config, users UserDatabaseRepo, wg Wiregua
m := &Manager{ m := &Manager{
cfg: cfg, cfg: cfg,
bus: bus,
tplHandler: tplHandler, tplHandler: tplHandler,
fsRepo: fsRepo, fsRepo: fsRepo,
@ -58,6 +63,51 @@ func (m Manager) createStorageDirectory() error {
return nil return nil
} }
func (m Manager) connectToMessageBus() {
if m.fsRepo == nil {
return // skip subscription
}
_ = m.bus.Subscribe(app.TopicInterfaceUpdated, m.handleInterfaceUpdatedEvent)
_ = m.bus.Subscribe(app.TopicPeerInterfaceUpdated, m.handleInterfaceUpdatedEvent)
}
func (m Manager) handleInterfaceUpdatedEvent(iface *domain.Interface) {
logrus.Errorf("handling interface updated event for %s", iface.Identifier)
if !iface.SaveConfig || m.fsRepo == nil {
return
}
err := m.PersistInterfaceConfig(context.Background(), iface.Identifier)
if err != nil {
logrus.Errorf("failed to automatically persist interface config for %s: %v", iface.Identifier, err)
}
}
func (m Manager) handlePeerInterfaceUpdatedEvent(id domain.InterfaceIdentifier) {
logrus.Errorf("handling interface updated event for %s", id)
if m.fsRepo == nil {
return
}
peerInterface, err := m.wg.GetInterface(context.Background(), id)
if err != nil {
logrus.Errorf("failed to load interface %s: %v", id, err)
return
}
if !peerInterface.SaveConfig {
return
}
err = m.PersistInterfaceConfig(context.Background(), peerInterface.Identifier)
if err != nil {
logrus.Errorf("failed to automatically persist interface config for %s: %v", peerInterface.Identifier, err)
}
}
func (m Manager) GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error) { func (m Manager) GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error) {
iface, peers, err := m.wg.GetInterfaceAndPeers(ctx, id) iface, peers, err := m.wg.GetInterfaceAndPeers(ctx, id)
if err != nil { if err != nil {

View File

@ -7,3 +7,5 @@ const TopicUserDeleted = "user:deleted"
const TopicAuthLogin = "auth:login" const TopicAuthLogin = "auth:login"
const TopicRouteUpdate = "route:update" const TopicRouteUpdate = "route:update"
const TopicRouteRemove = "route:remove" const TopicRouteRemove = "route:remove"
const TopicInterfaceUpdated = "interface:updated"
const TopicPeerInterfaceUpdated = "peer:interface:updated"

View File

@ -414,6 +414,8 @@ func (m Manager) saveInterface(ctx context.Context, iface *domain.Interface, pee
return nil, fmt.Errorf("post-save hooks failed: %w", err) return nil, fmt.Errorf("post-save hooks failed: %w", err)
} }
m.bus.Publish(app.TopicInterfaceUpdated, iface)
return iface, nil return iface, nil
} }

View File

@ -284,6 +284,10 @@ func (m Manager) savePeers(ctx context.Context, peers ...*domain.Peer) error {
m.bus.Publish(app.TopicRouteUpdate, "peers updated") m.bus.Publish(app.TopicRouteUpdate, "peers updated")
} }
for iface := range interfaces {
m.bus.Publish(app.TopicPeerInterfaceUpdated, iface)
}
return nil return nil
} }

View File

@ -122,13 +122,17 @@ func (c Cidr) BroadcastAddr() Cidr {
a16[off+byteNum] |= 1 << uint(bitInByte) a16[off+byteNum] |= 1 << uint(bitInByte)
} }
if prefix.Addr().Is4() { if prefix.Addr().Is4() {
addr := netip.AddrFrom16(a16).Unmap()
return Cidr{ return Cidr{
Addr: netip.AddrFrom16(a16).Unmap().String(), Cidr: netip.PrefixFrom(addr, prefix.Bits()).String(),
Addr: addr.String(),
NetLength: prefix.Bits(), NetLength: prefix.Bits(),
} }
} else { } else {
addr := netip.AddrFrom16(a16) // doesn't unmap
return Cidr{ return Cidr{
Addr: netip.AddrFrom16(a16).String(), // doesn't unmap Cidr: netip.PrefixFrom(addr, prefix.Bits()).String(),
Addr: addr.String(), // doesn't unmap
NetLength: prefix.Bits(), NetLength: prefix.Bits(),
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 107 KiB