From 3bfcbe0209946d6603f0fc8f41aac24f4db8022f Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Sat, 3 Apr 2021 22:38:22 +0200 Subject: [PATCH] WIP: support different interface types: update config templates --- assets/tpl/admin_edit_client.html | 45 ++++++++-- assets/tpl/admin_edit_interface.html | 2 +- go.mod | 4 +- internal/wireguard/peermanager.go | 125 ++++++++++++--------------- internal/wireguard/template.go | 67 ++++---------- internal/wireguard/tpl/interface.tpl | 58 +++++++++++++ internal/wireguard/tpl/peer.tpl | 28 ++++++ 7 files changed, 198 insertions(+), 131 deletions(-) create mode 100644 internal/wireguard/tpl/interface.tpl create mode 100644 internal/wireguard/tpl/peer.tpl diff --git a/assets/tpl/admin_edit_client.html b/assets/tpl/admin_edit_client.html index bdd5bb8..10fc99c 100644 --- a/assets/tpl/admin_edit_client.html +++ b/assets/tpl/admin_edit_client.html @@ -26,6 +26,9 @@
+ + + {{if .EditableKeys}}
@@ -74,22 +77,26 @@
-
+
-
+
-
+
+
+ + +
@@ -127,6 +134,8 @@ + + {{if .EditableKeys}}
@@ -136,7 +145,7 @@
- +
@@ -170,12 +179,18 @@
+ + +
+
+
+
-
+
@@ -209,6 +224,8 @@ + + {{if .EditableKeys}}
@@ -251,34 +268,44 @@
-
+
+ + +
+
+
+
-
+
-
+
-
+
+
+ + +
diff --git a/assets/tpl/admin_edit_interface.html b/assets/tpl/admin_edit_interface.html index c1a8c6e..dc1cf39 100644 --- a/assets/tpl/admin_edit_interface.html +++ b/assets/tpl/admin_edit_interface.html @@ -264,7 +264,7 @@
-
+
diff --git a/go.mod b/go.mod index 27c93e4..657a6aa 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/gin-contrib/sessions v0.0.3 github.com/gin-gonic/gin v1.6.3 github.com/go-ldap/ldap/v3 v3.2.4 - github.com/go-playground/validator/v10 v10.2.0 + github.com/go-playground/validator/v10 v10.4.1 github.com/gorilla/sessions v1.2.1 // indirect github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible github.com/kelseyhightower/envconfig v1.4.0 @@ -17,7 +17,7 @@ require ( github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e github.com/toorop/gin-logrus v0.0.0-20200831135515-d2ee50d38dae github.com/utrack/gin-csrf v0.0.0-20190424104817-40fb8d2c8fca - golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c gorm.io/driver/mysql v1.0.4 diff --git a/internal/wireguard/peermanager.go b/internal/wireguard/peermanager.go index 33a454e..57e6dc2 100644 --- a/internal/wireguard/peermanager.go +++ b/internal/wireguard/peermanager.go @@ -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") diff --git a/internal/wireguard/template.go b/internal/wireguard/template.go index 3b65e57..99cdb1c 100644 --- a/internal/wireguard/template.go +++ b/internal/wireguard/template.go @@ -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) + } +} diff --git a/internal/wireguard/tpl/interface.tpl b/internal/wireguard/tpl/interface.tpl new file mode 100644 index 0000000..8540c2a --- /dev/null +++ b/internal/wireguard/tpl/interface.tpl @@ -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}} \ No newline at end of file diff --git a/internal/wireguard/tpl/peer.tpl b/internal/wireguard/tpl/peer.tpl new file mode 100644 index 0000000..ac55305 --- /dev/null +++ b/internal/wireguard/tpl/peer.tpl @@ -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}} \ No newline at end of file