WIP: support different interface types: update config templates

This commit is contained in:
Christoph Haas
2021-04-03 22:38:22 +02:00
parent aa17303cec
commit 3bfcbe0209
7 changed files with 198 additions and 131 deletions

View File

@@ -7,13 +7,13 @@ import (
"crypto/md5"
"fmt"
"net"
"reflect"
"regexp"
"sort"
"strings"
"text/template"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
"github.com/h44z/wg-portal/internal/common"
@@ -42,7 +42,6 @@ var cidrList validator.Func = func(fl validator.FieldLevel) bool {
var ipList validator.Func = func(fl validator.FieldLevel) bool {
ipListStr := fl.Field().String()
ipList := common.ParseStringList(ipListStr)
for i := range ipList {
ip := net.ParseIP(ipList[i])
@@ -65,15 +64,16 @@ func init() {
//
type Peer struct {
Peer *wgtypes.Peer `gorm:"-"` // WireGuard peer
Device *Device `gorm:"foreignKey:DeviceName"` // linked WireGuard device
Peer *wgtypes.Peer `gorm:"-"` // WireGuard peer
Device *Device `gorm:"foreignKey:DeviceName" binding:"-"` // linked WireGuard device
Config string `gorm:"-"`
UID string `form:"uid" binding:"alphanum"` // uid for html identification
DeviceName string `gorm:"index"`
Identifier string `form:"identifier" binding:"required,max=64"` // Identifier AND Email make a WireGuard peer unique
Email string `gorm:"index" form:"mail" binding:"omitempty,email"`
IgnoreGlobalSettings bool `form:"ignoreglobalsettings"`
UID string `form:"uid" binding:"required,alphanum"` // uid for html identification
DeviceName string `gorm:"index" form:"device" binding:"required"`
DeviceType DeviceType `gorm:"-" form:"devicetype" binding:"required,oneof=client server custom"`
Identifier string `form:"identifier" binding:"required,max=64"` // Identifier AND Email make a WireGuard peer unique
Email string `gorm:"index" form:"mail" binding:"required,email"`
IgnoreGlobalSettings bool `form:"ignoreglobalsettings"`
IsOnline bool `gorm:"-"`
IsNew bool `gorm:"-"`
@@ -81,16 +81,19 @@ type Peer struct {
LastHandshakeTime string `gorm:"-"`
// Core WireGuard Settings
PublicKey string `gorm:"primaryKey" form:"pubkey" binding:"required,base64"`
PublicKey string `gorm:"primaryKey" form:"pubkey" binding:"required,base64"` // the public key of the peer itself
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
Endpoint string `form:"endpoint" binding:"omitempty,hostname_port"`
PersistentKeepalive int `form:"keepalive" binding:"gte=0"`
// Misc. WireGuard Settings
PrivateKey string `form:"privkey" binding:"omitempty,base64"`
IPsStr string `form:"ip" binding:"cidrlist"` // a comma separated list of IPs of the client
DNSStr string `form:"dns" binding:"iplist"` // comma separated list of the DNS servers for the client
EndpointPublicKey string `form:"endpointpubkey" binding:"required,base64"` // the public key of the remote endpoint
PrivateKey string `form:"privkey" binding:"omitempty,base64"`
IPsStr string `form:"ip" binding:"cidrlist,required_if=devicetype server"` // a comma separated list of IPs of the client
DNSStr string `form:"dns" binding:"iplist"` // comma separated list of the DNS servers for the client
// Global Device Settings (can be ignored, only make sense if device is in server mode)
Mtu int `form:"mtu" binding:"gte=0,lte=1500"`
DeactivatedAt *time.Time
CreatedBy string
@@ -131,40 +134,49 @@ func (p Peer) GetConfig() wgtypes.PeerConfig {
presharedKey = &presharedKeyTmp
}
var endpoint *net.UDPAddr
if p.Endpoint != "" {
addr, err := net.ResolveUDPAddr("udp", p.Endpoint)
if err == nil {
endpoint = addr
}
}
var keepAlive *time.Duration
if p.PersistentKeepalive != 0 {
keepAliveDuration := time.Duration(p.PersistentKeepalive) * time.Second
keepAlive = &keepAliveDuration
}
peerAllowedIPs := p.GetAllowedIPs()
allowedIPs := make([]net.IPNet, len(peerAllowedIPs))
for i, ip := range peerAllowedIPs {
_, ipNet, err := net.ParseCIDR(ip)
if err == nil {
allowedIPs[i] = *ipNet
}
}
cfg := wgtypes.PeerConfig{
PublicKey: publicKey,
Remove: false,
UpdateOnly: false,
PresharedKey: presharedKey,
Endpoint: nil,
PersistentKeepaliveInterval: nil,
Endpoint: endpoint,
PersistentKeepaliveInterval: keepAlive,
ReplaceAllowedIPs: true,
AllowedIPs: make([]net.IPNet, len(p.GetIPAddresses())),
}
for i, ip := range p.GetIPAddresses() {
_, ipNet, err := net.ParseCIDR(ip)
if err == nil {
cfg.AllowedIPs[i] = *ipNet
}
AllowedIPs: allowedIPs,
}
return cfg
}
func (p Peer) GetConfigFile(device Device) ([]byte, error) {
tpl, err := template.New("client").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(ClientCfgTpl)
if err != nil {
return nil, errors.Wrap(err, "failed to parse client template")
}
var tplBuff bytes.Buffer
err = tpl.Execute(&tplBuff, struct {
Client Peer
Server Device
}{
Client: p,
Server: device,
err := templateCache.ExecuteTemplate(&tplBuff, "peer.tpl", gin.H{
"Peer": p,
"Interface": device,
})
if err != nil {
return nil, errors.Wrap(err, "failed to execute client template")
@@ -192,26 +204,6 @@ func (p Peer) IsValid() bool {
return true
}
func (p Peer) ToMap() map[string]string {
out := make(map[string]string)
v := reflect.ValueOf(p)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get("form"); tagv != "" {
// set key of map to value in struct field
out[tagv] = v.Field(i).String()
}
}
return out
}
func (p Peer) GetConfigFileName() string {
reg := regexp.MustCompile("[^a-zA-Z0-9_-]+")
return reg.ReplaceAllString(strings.ReplaceAll(p.Identifier, " ", "-"), "") + ".conf"
@@ -238,7 +230,7 @@ type Device struct {
// Core WireGuard Settings (Interface section)
PrivateKey string `form:"privkey" binding:"required,base64"`
ListenPort int `form:"port" binding:"gt=0,lt=65535"`
ListenPort int `form:"port" binding:"omitempty,gt=0,lt=65535,required_if=devicetype server"`
FirewallMark int32 `form:"firewallmark" binding:"gte=0"`
// Misc. WireGuard Settings
PublicKey string `form:"pubkey" binding:"required,base64"`
@@ -253,7 +245,7 @@ type Device struct {
SaveConfig bool `form:"saveconfig"` // if set to `true', the configuration is saved from the current state of the interface upon shutdown, wg-quick addition
// Settings that are applied to all peer by default
DefaultEndpoint string `form:"endpoint" binding:"omitempty,hostname_port"`
DefaultEndpoint string `form:"endpoint" binding:"omitempty,hostname_port,required_if=devicetype server"`
DefaultAllowedIPsStr string `form:"allowedip" binding:"cidrlist"` // comma separated list of IPs that are used in the client config file
DefaultPersistentKeepalive int `form:"keepalive" binding:"gte=0"`
@@ -317,28 +309,23 @@ func (d Device) GetConfig() wgtypes.Config {
privateKey = &pKey
}
fwMark := int(d.FirewallMark)
cfg := wgtypes.Config{
PrivateKey: privateKey,
ListenPort: &d.ListenPort,
PrivateKey: privateKey,
ListenPort: &d.ListenPort,
FirewallMark: &fwMark,
}
return cfg
}
func (d Device) GetConfigFile(peers []Peer) ([]byte, error) {
tpl, err := template.New("server").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(DeviceCfgTpl)
if err != nil {
return nil, errors.Wrap(err, "failed to parse server template")
}
var tplBuff bytes.Buffer
err = tpl.Execute(&tplBuff, struct {
Clients []Peer
Server Device
}{
Clients: peers,
Server: d,
err := templateCache.ExecuteTemplate(&tplBuff, "interface.tpl", gin.H{
"Peers": peers,
"Interface": d,
})
if err != nil {
return nil, errors.Wrap(err, "failed to execute server template")

View File

@@ -1,53 +1,20 @@
package wireguard
var (
ClientCfgTpl = `#{{ .Client.Identifier }}
[Interface]
Address = {{ .Client.IPsStr }}
PrivateKey = {{ .Client.PrivateKey }}
{{- if .Server.DNSStr}}
DNS = {{ .Server.DNSStr }}
{{- end}}
{{- if ne .Server.Mtu 0}}
MTU = {{.Server.Mtu}}
{{- end}}
[Peer]
PublicKey = {{ .Server.PublicKey }}
{{- if .Client.PresharedKey}}
PresharedKey = {{ .Client.PresharedKey }}
{{- end}}
AllowedIPs = {{ .Client.AllowedIPsStr }}
Endpoint = {{ .Server.Endpoint }}
{{- if and (ne .Server.PersistentKeepalive 0) (not .Client.IgnorePersistentKeepalive)}}
PersistentKeepalive = {{.Server.PersistentKeepalive}}
{{- end}}
`
DeviceCfgTpl = `# AUTOGENERATED FILE - DO NOT EDIT
# Updated: {{ .Server.UpdatedAt }} / Created: {{ .Server.CreatedAt }}
[Interface]
{{- range .Server.IPs}}
Address = {{ . }}
{{- end}}
ListenPort = {{ .Server.ListenPort }}
PrivateKey = {{ .Server.PrivateKey }}
{{- if ne .Server.Mtu 0}}
MTU = {{.Server.Mtu}}
{{- end}}
PreUp = {{ .Server.PreUp }}
PostUp = {{ .Server.PostUp }}
PreDown = {{ .Server.PreDown }}
PostDown = {{ .Server.PostDown }}
{{range .Clients}}
{{if not .DeactivatedAt -}}
# {{.Identifier}} / {{.Email}} / Updated: {{.UpdatedAt}} / Created: {{.CreatedAt}}
[Peer]
PublicKey = {{ .PublicKey }}
{{- if .PresharedKey}}
PresharedKey = {{ .PresharedKey }}
{{- end}}
AllowedIPs = {{ StringsJoin .IPs ", " }}
{{- end}}
{{end}}`
import (
"embed"
"html/template"
"strings"
)
//go:embed tpl/*
var Templates embed.FS
var templateCache *template.Template
func init() {
var err error
templateCache, err = template.New("server").Funcs(template.FuncMap{"StringsJoin": strings.Join}).ParseFS(Templates, "tpl/*.tpl")
if err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,58 @@
# AUTOGENERATED FILE - DO NOT EDIT
# -WGP- Interface: {{ .Interface.DeviceName }} / Updated: {{ .Interface.UpdatedAt }} / Created: {{ .Interface.CreatedAt }}
# -WGP- Interface display name: {{ .Interface.DisplayName }}
# -WGP- Interface mode: {{ .Interface.Type }}
# -WGP- PublicKey = {{ .Interface.PublicKey }}
[Interface]
# Core settings
PrivateKey = {{ .Interface.PrivateKey }}
Address = {{ .Interface.IPsStr }}
# Misc. settings
{{- if ne .Interface.ListenPort 0}}
ListenPort = {{ .Interface.ListenPort }}
{{- end}}
{{- if ne .Interface.Mtu 0}}
MTU = {{.Interface.Mtu}}
{{- end}}
{{- if ne .Interface.FirewallMark 0}}
FwMark = {{.Interface.FirewallMark}}
{{- end}}
{{- if ne .Interface.RoutingTable ""}}
Table = {{.Interface.RoutingTable}}
{{- end}}
{{- if .Interface.SaveConfig}}
SaveConfig = true
{{- end}}
# Interface hooks
PreUp = {{ .Interface.PreUp }}
PostUp = {{ .Interface.PostUp }}
PreDown = {{ .Interface.PreDown }}
PostDown = {{ .Interface.PostDown }}
#
# Peers
#
{{range .Peers}}
{{- if not .DeactivatedAt}}
# -WGP- Peer: {{.Identifier}} / Updated: {{.UpdatedAt}} / Created: {{.CreatedAt}}
# -WGP- Peer email: {{.Email}}
# -WGP- PrivateKey: {{.PrivateKey}}
[Peer]
PublicKey = {{ .PublicKey }}
{{- if .PresharedKey}}
PresharedKey = {{ .PresharedKey }}
{{- end}}
AllowedIPs = {{ .AllowedIPsStr }}
{{- if ne .Endpoint ""}}
Endpoint = {{ .Endpoint }}
{{- end}}
{{- if ne .PersistentKeepalive 0}}
PersistentKeepalive = {{ .PersistentKeepalive }}
{{- end}}
{{- end}}
{{end}}

View File

@@ -0,0 +1,28 @@
# AUTOGENERATED FILE - PROVIDED BY WIREGUARD PORTAL
# WireGuard configuration: {{ .Peer.Identifier }}
# -WGP- PublicKey: {{ .Peer.PublicKey }}
[Interface]
# Core settings
PrivateKey = {{ .Peer.PrivateKey }}
Address = {{ .Peer.IPsStr }}
# Misc. settings
{{- if .Peer.DNSStr}}
DNS = {{ .Peer.DNSStr }}
{{- end}}
{{- if ne .Peer.Mtu 0}}
MTU = {{.Peer.Mtu}}
{{- end}}
[Peer]
PublicKey = {{ .Peer.EndpointPublicKey }}
Endpoint = {{ .Server.Endpoint }}
AllowedIPs = {{ .Peer.AllowedIPsStr }}
{{- if .Peer.PresharedKey}}
PresharedKey = {{ .Peer.PresharedKey }}
{{- end}}
{{- if ne .Peer.PersistentKeepalive 0}}
PersistentKeepalive = {{.Peer.PersistentKeepalive}}
{{- end}}