Compare commits

...

8 Commits

Author SHA1 Message Date
h44z
1e35fb2538 Use Github Container Registry
Also publish docker images on ghcr.io
2021-06-30 17:57:41 +02:00
Christoph Haas
400259a0be convert input to email-token on focus loss (#28) 2021-06-30 17:36:39 +02:00
Christoph Haas
96c713a513 update bootstrap-tokenfield lib, fix enter bug (#27)
related: https://github.com/sliptree/bootstrap-tokenfield/issues/308
2021-06-30 17:28:25 +02:00
Christoph Haas
3645d75d8d fix auto-creation of peers on login (#30) 2021-06-30 17:03:16 +02:00
h44z
a017775f8a Add minimum Go version to Readme (#29) 2021-06-25 17:01:16 +02:00
Christoph Haas
e0968b3239 support AllowIPs for peers in server config (#24) 2021-06-18 14:13:44 +02:00
Christoph Haas
e1db939a18 update readme to clarify some things 2021-06-18 14:12:22 +02:00
Christoph Haas
92d09535bc fix foreign key problem (#23) 2021-06-08 16:17:30 +02:00
12 changed files with 135 additions and 32 deletions

59
.github/workflows/docker-publish.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: Docker
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
push:
branches: [ master ]
# Publish vX.X.X tags as releases.
tags: [ 'v*.*.*' ]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -9,11 +9,11 @@
[![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/)
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 the VPN The portal uses the WireGuard [wgctrl](https://github.com/WireGuard/wgctrl-go) library to manage existing VPN
interface. 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
connections. connections.
The configuration portal currently supports using SQLite, MySQL as a user source for authentication and profile data. The configuration portal currently supports using SQLite and MySQL as a user source for authentication and profile data.
It also supports LDAP (Active Directory or OpenLDAP) as authentication provider. It also supports LDAP (Active Directory or OpenLDAP) as authentication provider.
## Features ## Features
@@ -35,6 +35,8 @@ It also supports LDAP (Active Directory or OpenLDAP) as authentication provider.
![Screenshot](screenshot.png) ![Screenshot](screenshot.png)
## Setup ## Setup
Make sure that your host system has at least one WireGuard interface (for example wg0) available.
If you did not start up a WireGuard interface yet, take a look at [wg-quick](https://manpages.debian.org/unstable/wireguard-tools/wg-quick.8.en.html) in order to get started.
### Docker ### Docker
The easiest way to run WireGuard Portal is to use the Docker image provided. The easiest way to run WireGuard Portal is to use the Docker image provided.
@@ -81,11 +83,11 @@ services:
- LDAP_ADMIN_GROUP=CN=WireGuardAdmins,OU=Users,DC=COMPANY,DC=LOCAL - LDAP_ADMIN_GROUP=CN=WireGuardAdmins,OU=Users,DC=COMPANY,DC=LOCAL
``` ```
Please note that mapping ```/etc/wireguard``` to ```/etc/wireguard``` inside the docker, will erase your host's current configuration. Please note that mapping ```/etc/wireguard``` to ```/etc/wireguard``` inside the docker, will erase your host's current configuration.
If needed, please make sure to backup your files from ```/etc/wireguard```. If needed, please make sure to back up your files from ```/etc/wireguard```.
For a full list of configuration options take a look at the source file [internal/server/configuration.go](internal/server/configuration.go#L56). For a full list of configuration options take a look at the source file [internal/server/configuration.go](internal/server/configuration.go#L56).
### Standalone ### Standalone
For a standalone application, use the Makefile provided in the repository to build the application. For a standalone application, use the Makefile provided in the repository to build the application. Go version 1.16 or higher has to be installed to build WireGuard Portal.
``` ```
make make
@@ -200,10 +202,10 @@ The API is documented using OpenAPI 2.0, the Swagger UI can be found
under the URL `http://<your wg-portal ip/domain>/swagger/index.html`. under the URL `http://<your wg-portal ip/domain>/swagger/index.html`.
## What is out of scope ## What is out of scope
* Creating or removing WireGuard (wgX) interfaces.
* Generation or application of any `iptables` or `nftables` rules * 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 * Setting up or changing IP-addresses of the WireGuard interface on operating systems other than linux.
* Importing private keys of an existing WireGuard setup * Importing private keys of an existing WireGuard setup.
## Application stack ## Application stack
@@ -217,4 +219,4 @@ under the URL `http://<your wg-portal ip/domain>/swagger/index.html`.
* MIT License. [MIT](LICENSE.txt) or https://opensource.org/licenses/MIT * MIT License. [MIT](LICENSE.txt) or https://opensource.org/licenses/MIT
This project was inspired by [wg-gen-web](https://github.com/vx3r/wg-gen-web). This project was inspired by [wg-gen-web](https://github.com/vx3r/wg-gen-web).

View File

@@ -2,4 +2,4 @@
* bootstrap-tokenfield * bootstrap-tokenfield
* https://github.com/sliptree/bootstrap-tokenfield * https://github.com/sliptree/bootstrap-tokenfield
* Copyright 2013-2014 Sliptree and other contributors; Licensed MIT * Copyright 2013-2014 Sliptree and other contributors; Licensed MIT
*/@-webkit-keyframes 'blink'{0%{border-color:#ededed}100%{border-color:#b94a48}}@-moz-keyframes 'blink'{0%{border-color:#ededed}100%{border-color:#b94a48}}@keyframes 'blink'{0%{border-color:#ededed}100%{border-color:#b94a48}}.tokenfield{height:auto;min-height:34px;padding-bottom:0}.tokenfield.focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.tokenfield .token{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;display:inline-block;border:1px solid #d9d9d9;background-color:#ededed;white-space:nowrap;margin:-1px 5px 5px 0;height:22px;vertical-align:top;cursor:default}.tokenfield .token:hover{border-color:#b9b9b9}.tokenfield .token.active{border-color:#52a8ec;border-color:rgba(82,168,236,.8)}.tokenfield .token.duplicate{border-color:#ebccd1;-webkit-animation-name:blink;animation-name:blink;-webkit-animation-duration:.1s;animation-duration:.1s;-webkit-animation-direction:normal;animation-direction:normal;-webkit-animation-timing-function:ease;animation-timing-function:ease;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.tokenfield .token.invalid{background:0 0;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border-bottom:1px dotted #d9534f}.tokenfield .token.invalid.active{background:#ededed;border:1px solid #ededed;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.tokenfield .token .token-label{display:inline-block;overflow:hidden;text-overflow:ellipsis;padding-left:4px;vertical-align:top}.tokenfield .token .close{font-family:Arial;display:inline-block;line-height:100%;font-size:1.1em;line-height:1.49em;margin-left:5px;float:none;height:100%;vertical-align:top;padding-right:4px}.tokenfield .token-input{background:0 0;width:60px;min-width:60px;border:0;height:20px;padding:0;margin-bottom:6px;-webkit-box-shadow:none;box-shadow:none}.tokenfield .token-input:focus{border-color:transparent;outline:0;-webkit-box-shadow:none;box-shadow:none}.tokenfield.disabled{cursor:not-allowed;background-color:#eee}.tokenfield.disabled .token-input{cursor:not-allowed}.tokenfield.disabled .token:hover{cursor:not-allowed;border-color:#d9d9d9}.tokenfield.disabled .token:hover .close{cursor:not-allowed;opacity:.2;filter:alpha(opacity=20)}.has-warning .tokenfield.focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-error .tokenfield.focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-success .tokenfield.focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.tokenfield.input-sm,.input-group-sm .tokenfield{min-height:30px;padding-bottom:0}.input-group-sm .token,.tokenfield.input-sm .token{height:20px;margin-bottom:4px}.input-group-sm .token-input,.tokenfield.input-sm .token-input{height:18px;margin-bottom:5px}.tokenfield.input-lg,.input-group-lg .tokenfield{min-height:45px;padding-bottom:4px}.input-group-lg .token,.tokenfield.input-lg .token{height:25px}.input-group-lg .token-label,.tokenfield.input-lg .token-label{line-height:23px}.input-group-lg .token .close,.tokenfield.input-lg .token .close{line-height:1.3em}.input-group-lg .token-input,.tokenfield.input-lg .token-input{height:23px;line-height:23px;margin-bottom:6px;vertical-align:top}.tokenfield.rtl{direction:rtl;text-align:right}.tokenfield.rtl .token{margin:-1px 0 5px 5px}.tokenfield.rtl .token .token-label{padding-left:0;padding-right:4px} */@-webkit-keyframes blink{0%{border-color:#ededed}100%{border-color:#b94a48}}@-moz-keyframes blink{0%{border-color:#ededed}100%{border-color:#b94a48}}@keyframes blink{0%{border-color:#ededed}100%{border-color:#b94a48}}.tokenfield{height:auto;min-height:34px;padding-bottom:0}.tokenfield.focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.tokenfield .token{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;display:inline-block;border:1px solid #d9d9d9;background-color:#ededed;white-space:nowrap;margin:-1px 5px 5px 0;height:22px;vertical-align:top;cursor:default}.tokenfield .token:hover{border-color:#b9b9b9}.tokenfield .token.active{border-color:#52a8ec;border-color:rgba(82,168,236,.8)}.tokenfield .token.duplicate{border-color:#ebccd1;-webkit-animation-name:blink;animation-name:blink;-webkit-animation-duration:.1s;animation-duration:.1s;-webkit-animation-direction:normal;animation-direction:normal;-webkit-animation-timing-function:ease;animation-timing-function:ease;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.tokenfield .token.invalid{background:0 0;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border-bottom:1px dotted #d9534f}.tokenfield .token.invalid.active{background:#ededed;border:1px solid #ededed;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.tokenfield .token .token-label{display:inline-block;overflow:hidden;text-overflow:ellipsis;padding-left:4px;vertical-align:top}.tokenfield .token .close{font-family:Arial;display:inline-block;line-height:100%;font-size:1.1em;line-height:1.49em;margin-left:5px;float:none;height:100%;vertical-align:top;padding-right:4px}.tokenfield .token-input{background:0 0;width:60px;min-width:60px;border:0;height:20px;padding:0;margin-bottom:6px;-webkit-box-shadow:none;box-shadow:none}.tokenfield .token-input:focus{border-color:transparent;outline:0;-webkit-box-shadow:none;box-shadow:none}.tokenfield.disabled{cursor:not-allowed;background-color:#eee}.tokenfield.disabled .token-input{cursor:not-allowed}.tokenfield.disabled .token:hover{cursor:not-allowed;border-color:#d9d9d9}.tokenfield.disabled .token:hover .close{cursor:not-allowed;opacity:.2;filter:alpha(opacity=20)}.has-warning .tokenfield.focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-error .tokenfield.focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-success .tokenfield.focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.tokenfield.input-sm,.input-group-sm .tokenfield{min-height:30px;padding-bottom:0}.input-group-sm .token,.tokenfield.input-sm .token{height:20px;margin-bottom:4px}.input-group-sm .token-input,.tokenfield.input-sm .token-input{height:18px;margin-bottom:5px}.tokenfield.input-lg,.input-group-lg .tokenfield{height:auto;min-height:45px;padding-bottom:4px}.input-group-lg .token,.tokenfield.input-lg .token{height:25px}.input-group-lg .token-label,.tokenfield.input-lg .token-label{line-height:23px}.input-group-lg .token .close,.tokenfield.input-lg .token .close{line-height:1.3em}.input-group-lg .token-input,.tokenfield.input-lg .token-input{height:23px;line-height:23px;margin-bottom:6px;vertical-align:top}.tokenfield.rtl{direction:rtl;text-align:right}.tokenfield.rtl .token{margin:-1px 0 5px 5px}.tokenfield.rtl .token .token-label{padding-left:0;padding-right:4px}

File diff suppressed because one or more lines are too long

View File

@@ -52,11 +52,19 @@
if (!valid) { if (!valid) {
$(e.relatedTarget).addClass('invalid') $(e.relatedTarget).addClass('invalid')
} }
}).on('tokenfield:createtoken', function (e) {
var existingTokens = $(this).tokenfield('getTokens');
$.each(existingTokens, function(index, token) {
if (token.value === e.attrs.value)
e.preventDefault();
});
}).tokenfield({ }).tokenfield({
autocomplete: { autocomplete: {
source: [{{range $i, $u :=.Users}}{{$u.Email}},{{end}}], source: [{{range $i, $u :=.Users}}{{if ne $i 0}},{{end}}'{{$u.Email}}'{{end}}],
delay: 100 delay: 100
}, },
inputType: 'email',
createTokensOnBlur: true,
showAutocompleteOnFocus: false showAutocompleteOnFocus: false
})</script> })</script>
</body> </body>

View File

@@ -82,6 +82,12 @@
<input type="text" name="allowedip" class="form-control" id="server_AllowedIP" value="{{.Peer.AllowedIPsStr}}"> <input type="text" name="allowedip" class="form-control" id="server_AllowedIP" value="{{.Peer.AllowedIPsStr}}">
</div> </div>
</div> </div>
<div class="form-row">
<div class="form-group col-md-12">
<label for="server_AllowedIPSrv">Extra Allowed IPs (Server sided)</label>
<input type="text" name="allowedipSrv" class="form-control" id="server_AllowedIPSrv" value="{{.Peer.AllowedIPsSrvStr}}">
</div>
</div>
<div class="form-row"> <div class="form-row">
<div class="form-group col-md-12 global-config"> <div class="form-group col-md-12 global-config">
<label for="server_DNS">Client DNS Servers</label> <label for="server_DNS">Client DNS Servers</label>

View File

@@ -29,6 +29,13 @@ func init() {
return nil return nil
}, },
}) })
migrations = append(migrations, Migration{
version: "1.0.8",
migrateFn: func(db *gorm.DB) error {
logrus.Infof("upgraded database format to version 1.0.8")
return nil
},
})
} }
type SupportedDatabase string type SupportedDatabase string

View File

@@ -64,6 +64,7 @@ func (s *Server) PostAdminEditPeer(c *gin.Context) {
// Clean list input // Clean list input
formPeer.IPsStr = common.ListToString(common.ParseStringList(formPeer.IPsStr)) formPeer.IPsStr = common.ListToString(common.ParseStringList(formPeer.IPsStr))
formPeer.AllowedIPsStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsStr)) formPeer.AllowedIPsStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsStr))
formPeer.AllowedIPsSrvStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsSrvStr))
disabled := c.PostForm("isdisabled") != "" disabled := c.PostForm("isdisabled") != ""
now := time.Now() now := time.Now()
@@ -121,6 +122,7 @@ func (s *Server) PostAdminCreatePeer(c *gin.Context) {
// Clean list input // Clean list input
formPeer.IPsStr = common.ListToString(common.ParseStringList(formPeer.IPsStr)) formPeer.IPsStr = common.ListToString(common.ParseStringList(formPeer.IPsStr))
formPeer.AllowedIPsStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsStr)) formPeer.AllowedIPsStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsStr))
formPeer.AllowedIPsSrvStr = common.ListToString(common.ParseStringList(formPeer.AllowedIPsSrvStr))
disabled := c.PostForm("isdisabled") != "" disabled := c.PostForm("isdisabled") != ""
now := time.Now() now := time.Now()

View File

@@ -309,6 +309,11 @@ func (s *Server) DeleteUser(user users.User) error {
} }
func (s *Server) CreateUserDefaultPeer(email, device string) error { func (s *Server) CreateUserDefaultPeer(email, device string) error {
// Check if automatic peer creation is enabled
if !s.config.Core.CreateDefaultPeer {
return nil
}
// Check if user is active, if not, quit // Check if user is active, if not, quit
var existingUser *users.User var existingUser *users.User
if existingUser = s.users.GetUser(email); existingUser == nil { if existingUser = s.users.GetUser(email); existingUser == nil {
@@ -316,18 +321,26 @@ func (s *Server) CreateUserDefaultPeer(email, device string) error {
} }
// Check if user already has a peer setup, if not, create one // Check if user already has a peer setup, if not, create one
if s.config.Core.CreateDefaultPeer { peers := s.peers.GetPeersByMail(email)
peers := s.peers.GetPeersByMail(email) if len(peers) != 0 {
if len(peers) == 0 { // Create default vpn peer return nil
if err := s.CreatePeer(device, wireguard.Peer{ }
Identifier: existingUser.Firstname + " " + existingUser.Lastname + " (Default)",
Email: existingUser.Email, // Create default vpn peer
CreatedBy: existingUser.Email, peer, err := s.PrepareNewPeer(device)
UpdatedBy: existingUser.Email, if err != nil {
}); err != nil { return errors.WithMessage(err, "failed to prepare new peer")
return errors.WithMessagef(err, "failed to automatically create vpn peer for %s", email) }
} peer.Email = email
} if existingUser.Firstname != "" && existingUser.Lastname != "" {
peer.Identifier = fmt.Sprintf("%s %s (%s)", existingUser.Firstname, existingUser.Lastname, "Default")
} else {
peer.Identifier = fmt.Sprintf("%s (%s)", existingUser.Email, "Default")
}
peer.CreatedBy = existingUser.Email
peer.UpdatedBy = existingUser.Email
if err := s.CreatePeer(device, peer); err != nil {
return errors.WithMessagef(err, "failed to automatically create vpn peer for %s", email)
} }
return nil return nil

View File

@@ -1,4 +1,4 @@
package server package server
var Version = "testbuild" var Version = "testbuild"
var DatabaseVersion = "1.0.7" var DatabaseVersion = "1.0.8"

View File

@@ -63,8 +63,7 @@ func init() {
// //
type Peer struct { type Peer struct {
Peer *wgtypes.Peer `gorm:"-" json:"-"` // WireGuard peer Peer *wgtypes.Peer `gorm:"-" json:"-"` // WireGuard peer
Device *Device `gorm:"foreignKey:DeviceName" binding:"-" json:"-"` // linked WireGuard device
Config string `gorm:"-" json:"-"` Config string `gorm:"-" json:"-"`
UID string `form:"uid" binding:"required,alphanum" json:"-"` // uid for html identification UID string `form:"uid" binding:"required,alphanum" json:"-"` // uid for html identification
@@ -82,7 +81,8 @@ type Peer struct {
// Core WireGuard Settings // Core WireGuard Settings
PublicKey string `gorm:"primaryKey" form:"pubkey" binding:"required,base64"` // the public key of the peer itself PublicKey string `gorm:"primaryKey" form:"pubkey" binding:"required,base64"` // the public key of the peer itself
PresharedKey string `form:"presharedkey" binding:"omitempty,base64"` PresharedKey string `form:"presharedkey" binding:"omitempty,base64"`
AllowedIPsStr string `form:"allowedip" binding:"cidrlist"` // a comma separated list of IPs that are used in the client config file AllowedIPsStr string `form:"allowedip" binding:"cidrlist"` // a comma separated list of IPs that are used in the client config file
AllowedIPsSrvStr string `form:"allowedipSrv" binding:"cidrlist"` // a comma separated list of IPs that are used in the server config file
Endpoint string `form:"endpoint" binding:"omitempty,hostname_port"` Endpoint string `form:"endpoint" binding:"omitempty,hostname_port"`
PersistentKeepalive int `form:"keepalive" binding:"gte=0"` PersistentKeepalive int `form:"keepalive" binding:"gte=0"`
@@ -124,6 +124,10 @@ func (p Peer) GetAllowedIPs() []string {
return common.ParseStringList(p.AllowedIPsStr) return common.ParseStringList(p.AllowedIPsStr)
} }
func (p Peer) GetAllowedIPsSrv() []string {
return common.ParseStringList(p.AllowedIPsSrvStr)
}
func (p Peer) GetConfig(dev *Device) wgtypes.PeerConfig { func (p Peer) GetConfig(dev *Device) wgtypes.PeerConfig {
publicKey, _ := wgtypes.ParseKey(p.PublicKey) publicKey, _ := wgtypes.ParseKey(p.PublicKey)
@@ -154,6 +158,7 @@ func (p Peer) GetConfig(dev *Device) wgtypes.PeerConfig {
peerAllowedIPs = p.GetAllowedIPs() peerAllowedIPs = p.GetAllowedIPs()
case DeviceTypeServer: case DeviceTypeServer:
peerAllowedIPs = p.GetIPAddresses() peerAllowedIPs = p.GetIPAddresses()
peerAllowedIPs = append(peerAllowedIPs, p.GetAllowedIPsSrv()...)
} }
for _, ip := range peerAllowedIPs { for _, ip := range peerAllowedIPs {
_, ipNet, err := net.ParseCIDR(ip) _, ipNet, err := net.ParseCIDR(ip)
@@ -236,6 +241,7 @@ const (
type Device struct { type Device struct {
Interface *wgtypes.Device `gorm:"-" json:"-"` Interface *wgtypes.Device `gorm:"-" json:"-"`
Peers []Peer `gorm:"foreignKey:DeviceName" binding:"-" json:"-"` // linked WireGuard peers
Type DeviceType `form:"devicetype" binding:"required,oneof=client server"` Type DeviceType `form:"devicetype" binding:"required,oneof=client server"`
DeviceName string `form:"device" gorm:"primaryKey" binding:"required,alphanum"` DeviceName string `form:"device" gorm:"primaryKey" binding:"required,alphanum"`
@@ -375,7 +381,7 @@ func NewPeerManager(db *gorm.DB, wg *Manager) (*PeerManager, error) {
} }
} }
if err := pm.db.AutoMigrate(&Peer{}, &Device{}); err != nil { if err := pm.db.AutoMigrate(&Device{}, &Peer{}); err != nil {
return nil, errors.WithMessage(err, "failed to migrate peer database") return nil, errors.WithMessage(err, "failed to migrate peer database")
} }

View File

@@ -61,7 +61,7 @@ PublicKey = {{ .PublicKey }}
PresharedKey = {{ .PresharedKey }} PresharedKey = {{ .PresharedKey }}
{{- end}} {{- end}}
{{- if eq $.Interface.Type "server"}} {{- if eq $.Interface.Type "server"}}
AllowedIPs = {{ .IPsStr }} AllowedIPs = {{ .IPsStr }}{{if ne .AllowedIPsSrvStr ""}}, {{ .AllowedIPsSrvStr }}{{end}}
{{- end}} {{- end}}
{{- if eq $.Interface.Type "client"}} {{- if eq $.Interface.Type "client"}}
{{- if .AllowedIPsStr}} {{- if .AllowedIPsStr}}