Merge branch 'master' into preshared-key-optional

This commit is contained in:
Khanh Ngo 2022-01-29 09:10:21 +01:00 committed by GitHub
commit bbc8bd341d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 276 additions and 26 deletions

View file

@ -33,7 +33,9 @@ wireguard interface stats. See the `cap_add` and `network_mode` options on the d
Set the `SESSION_SECRET` environment variable to a random value.
In order to sent the wireguard configuration to clients via email (using sendgrid api) set the following environment variables
In order to sent the wireguard configuration to clients via email, set the following environment variables:
- using SendGrid API
```
SENDGRID_API_KEY: Your sendgrid api key
@ -41,6 +43,18 @@ EMAIL_FROM_ADDRESS: the email address you registered on sendgrid
EMAIL_FROM_NAME: the sender's email address
```
- using SMTP
```
SMTP_HOSTNAME
SMTP_PORT
SMTP_USERNAME
SMTP_PASSWORD
SMTP_AUTH_TYPE
EMAIL_FROM_ADDRESS: the sender's email address
EMAIL_FROM_NAME: the sender's name
```
### Using binary file
Download the binary file from the release and run it with command:

View file

@ -24,11 +24,15 @@ function renderClientList(data) {
<div class="overlay" id="paused_${obj.Client.id}"` + clientStatusHtml
+ `<i class="paused-client fas fa-3x fa-play" onclick="resumeClient('${obj.Client.id}')"></i>
</div>
<img src="${obj.QRCode}" />
<div class="info-box-content">
<div class="btn-group">
<a href="/download?clientid=${obj.Client.id}" class="btn btn-outline-success btn-sm">Download</a>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary btn-sm" data-toggle="modal"
data-target="#modal_qr_client" data-clientid="${obj.Client.id}"
data-clientname="${obj.Client.name}">Scan</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary btn-sm" data-toggle="modal"
data-target="#modal_email_client" data-clientid="${obj.Client.id}"

81
emailer/smtp.go Normal file
View file

@ -0,0 +1,81 @@
package emailer
import (
"crypto/tls"
"fmt"
"time"
mail "github.com/xhit/go-simple-mail/v2"
)
type SmtpMail struct {
hostname string
port int
username string
password string
authType mail.AuthType
noTLSCheck bool
fromName string
from string
}
func authType(authType string) mail.AuthType {
switch authType {
case "PLAIN":
return mail.AuthPlain
case "LOGIN":
return mail.AuthLogin
default:
return mail.AuthNone
}
}
func NewSmtpMail(hostname string, port int, username string, password string, noTLSCheck bool, auth string, fromName, from string) *SmtpMail {
ans := SmtpMail{hostname: hostname, port: port, username: username, password: password, noTLSCheck: noTLSCheck, fromName: fromName, from: from, authType: authType(auth)}
return &ans
}
func addressField(address string, name string) string {
if name == "" {
return address
}
return fmt.Sprintf("%s <%s>", name, address)
}
func (o *SmtpMail) Send(toName string, to string, subject string, content string, attachments []Attachment) error {
server := mail.NewSMTPClient()
server.Host = o.hostname
server.Port = o.port
server.Authentication = o.authType
server.Username = o.username
server.Password = o.password
server.Encryption = mail.EncryptionSTARTTLS
server.KeepAlive = false
server.ConnectTimeout = 10 * time.Second
server.SendTimeout = 10 * time.Second
if o.noTLSCheck {
server.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
smtpClient, err := server.Connect()
if err != nil {
return err
}
email := mail.NewMSG()
email.SetFrom("From "+addressField(o.from, o.fromName)).
AddTo(addressField(to, toName)).
SetSubject(subject).
SetBody(mail.TextHTML, content)
for _, v := range attachments {
email.Attach(&mail.File{Name: v.Name, Data: v.Data})
}
err = email.Send(smtpClient)
return err
}

1
go.mod
View file

@ -17,6 +17,7 @@ require (
github.com/sendgrid/rest v2.6.4+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.10.0+incompatible
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
github.com/xhit/go-simple-mail/v2 v2.10.0
golang.zx2c4.com/wireguard v0.0.20200121 // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect

2
go.sum
View file

@ -147,6 +147,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/xhit/go-simple-mail/v2 v2.10.0 h1:nib6RaJ4qVh5HD9UE9QJqnUZyWp3upv+Z6CFxaMj0V8=
github.com/xhit/go-simple-mail/v2 v2.10.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View file

@ -160,6 +160,12 @@ func NewClient(db store.IStore) echo.HandlerFunc {
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Allowed IPs must be in CIDR format"})
}
// validate extra AllowedIPs
if util.ValidateExtraAllowedIPs(client.ExtraAllowedIPs) == false {
log.Warnf("Invalid Extra AllowedIPs input from user: %v", client.ExtraAllowedIPs)
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Extra AllowedIPs must be in CIDR format"})
}
// gen ID
guid := xid.New()
client.ID = guid.String()
@ -274,6 +280,11 @@ func UpdateClient(db store.IStore) echo.HandlerFunc {
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Allowed IPs must be in CIDR format"})
}
if util.ValidateExtraAllowedIPs(_client.ExtraAllowedIPs) == false {
log.Warnf("Invalid Allowed IPs input from user: %v", _client.ExtraAllowedIPs)
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Extra Allowed IPs must be in CIDR format"})
}
// map new data
client.Name = _client.Name
client.Email = _client.Email
@ -281,6 +292,7 @@ func UpdateClient(db store.IStore) echo.HandlerFunc {
client.UseServerDNS = _client.UseServerDNS
client.AllocatedIPs = _client.AllocatedIPs
client.AllowedIPs = _client.AllowedIPs
client.ExtraAllowedIPs = _client.ExtraAllowedIPs
client.UpdatedAt = time.Now().UTC()
// write to the database
@ -628,7 +640,7 @@ func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
fmt.Sprintf("Cannot suggest ip allocation: failed to get available ip from network %s", cidr),
})
}
if (strings.Contains(ip, ":")) {
if strings.Contains(ip, ":") {
suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/128", ip))
} else {
suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/32", ip))

25
main.go
View file

@ -25,6 +25,12 @@ var (
// configuration variables
flagDisableLogin bool = false
flagBindAddress string = "0.0.0.0:5000"
flagSmtpHostname string = "127.0.0.1"
flagSmtpPort int = 25
flagSmtpUsername string
flagSmtpPassword string
flagSmtpAuthType string = "None"
flagSmtpNoTLSCheck bool = false
flagSendgridApiKey string
flagEmailFrom string
flagEmailFromName string = "WireGuard UI"
@ -45,6 +51,12 @@ func init() {
// command-line flags and env variables
flag.BoolVar(&flagDisableLogin, "disable-login", util.LookupEnvOrBool("DISABLE_LOGIN", flagDisableLogin), "Disable authentication on the app. This is potentially dangerous.")
flag.StringVar(&flagBindAddress, "bind-address", util.LookupEnvOrString("BIND_ADDRESS", flagBindAddress), "Address:Port to which the app will be bound.")
flag.StringVar(&flagSmtpHostname, "smtp-hostname", util.LookupEnvOrString("SMTP_HOSTNAME", flagSmtpHostname), "SMTP Hostname")
flag.IntVar(&flagSmtpPort, "smtp-port", util.LookupEnvOrInt("SMTP_PORT", flagSmtpPort), "SMTP Port")
flag.StringVar(&flagSmtpUsername, "smtp-username", util.LookupEnvOrString("SMTP_USERNAME", flagSmtpUsername), "SMTP Password")
flag.StringVar(&flagSmtpPassword, "smtp-password", util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword), "SMTP Password")
flag.BoolVar(&flagSmtpNoTLSCheck, "smtp-no-tls-check", util.LookupEnvOrBool("SMTP_NO_TLS_CHECK", flagSmtpNoTLSCheck), "Disable TLS verification for SMTP. This is potentially dangerous.")
flag.StringVar(&flagSmtpAuthType, "smtp-auth-type", util.LookupEnvOrString("SMTP_AUTH_TYPE", flagSmtpAuthType), "SMTP Auth Type : Plain or None.")
flag.StringVar(&flagSendgridApiKey, "sendgrid-api-key", util.LookupEnvOrString("SENDGRID_API_KEY", flagSendgridApiKey), "Your sendgrid api key.")
flag.StringVar(&flagEmailFrom, "email-from", util.LookupEnvOrString("EMAIL_FROM_ADDRESS", flagEmailFrom), "'From' email address.")
flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.")
@ -54,6 +66,12 @@ func init() {
// update runtime config
util.DisableLogin = flagDisableLogin
util.BindAddress = flagBindAddress
util.SmtpHostname = flagSmtpHostname
util.SmtpPort = flagSmtpPort
util.SmtpUsername = flagSmtpUsername
util.SmtpPassword = flagSmtpPassword
util.SmtpAuthType = flagSmtpAuthType
util.SmtpNoTLSCheck = flagSmtpNoTLSCheck
util.SendgridApiKey = flagSendgridApiKey
util.EmailFrom = flagEmailFrom
util.EmailFromName = flagEmailFromName
@ -103,7 +121,12 @@ func main() {
app.POST("/login", handler.Login(db))
}
sendmail := emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom)
var sendmail emailer.Emailer
if util.SendgridApiKey != "" {
sendmail = emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom)
} else {
sendmail = emailer.NewSmtpMail(util.SmtpHostname, util.SmtpPort, util.SmtpUsername, util.SmtpPassword, util.SmtpNoTLSCheck, util.SmtpAuthType, util.EmailFromName, util.EmailFrom)
}
app.GET("/_health", handler.Health())
app.GET("/logout", handler.Logout(), handler.ValidSession)

View file

@ -6,18 +6,19 @@ import (
// Client model
type Client struct {
ID string `json:"id"`
PrivateKey string `json:"private_key"`
PublicKey string `json:"public_key"`
PresharedKey string `json:"preshared_key"`
Name string `json:"name"`
Email string `json:"email"`
AllocatedIPs []string `json:"allocated_ips"`
AllowedIPs []string `json:"allowed_ips"`
UseServerDNS bool `json:"use_server_dns"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID string `json:"id"`
PrivateKey string `json:"private_key"`
PublicKey string `json:"public_key"`
PresharedKey string `json:"preshared_key"`
Name string `json:"name"`
Email string `json:"email"`
AllocatedIPs []string `json:"allocated_ips"`
AllowedIPs []string `json:"allowed_ips"`
ExtraAllowedIPs []string `json:"extra_allowed_ips"`
UseServerDNS bool `json:"use_server_dns"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ClientData includes the Client and extra data

View file

@ -163,6 +163,11 @@
<input type="text" data-role="tagsinput" class="form-control" id="client_allowed_ips"
value="0.0.0.0/0">
</div>
<div class="form-group">
<label for="client_extra_allowed_ips" class="control-label">Extra Allowed IPs</label>
<input type="text" data-role="tagsinput" class="form-control"
id="client_extra_allowed_ips">
</div>
<div class="form-group">
<div class="icheck-primary d-inline">
<input type="checkbox" id="use_server_dns" checked>
@ -293,6 +298,12 @@
const allocated_ips = $("#client_allocated_ips").val().split(",");
const allowed_ips = $("#client_allowed_ips").val().split(",");
let use_server_dns = false;
let extra_allowed_ips = [];
if ($("#client_extra_allowed_ips").val() !== "") {
extra_allowed_ips = $("#client_extra_allowed_ips").val().split(",");
}
if ($("#use_server_dns").is(':checked')){
use_server_dns = true;
@ -305,7 +316,7 @@
}
const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips,
"use_server_dns": use_server_dns, "enabled": enabled};
"extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled};
$.ajax({
cache: false,
@ -376,6 +387,16 @@
'placeholderColor': '#666666'
});
$("#client_extra_allowed_ips").tagsInput({
'width': '100%',
'height': '75%',
'interactive': true,
'defaultText': 'Add More',
'removeWithBackspace': true,
'minChars': 0,
'placeholderColor': '#666666'
});
// New client form validation
$(document).ready(function () {
$.validator.setDefaults({
@ -414,6 +435,7 @@
$("#client_name").val("");
$("#client_email").val("");
$("#client_allocated_ips").importTags('');
$("#client_extra_allowed_ips").importTags('');
updateIPAllocationSuggestion();
});
});

View file

@ -57,6 +57,24 @@ Wireguard Clients
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<div class="modal fade" id="modal_qr_client">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">QR Code</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<img id="qr_code" class="w-100" style="image-rendering: pixelated;" src="" alt="QR code" />
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<div class="modal fade" id="modal_edit_client">
<div class="modal-dialog">
@ -86,6 +104,11 @@ Wireguard Clients
<label for="_client_allowed_ips" class="control-label">Allowed IPs</label>
<input type="text" data-role="tagsinput" class="form-control" id="_client_allowed_ips">
</div>
<div class="form-group">
<label for="_client_extra_allowed_ips" class="control-label">Extra Allowed IPs</label>
<input type="text" data-role="tagsinput" class="form-control"
id="_client_extra_allowed_ips">
</div>
<div class="form-group">
<div class="icheck-primary d-inline">
<input type="checkbox" id="_use_server_dns">
@ -275,7 +298,7 @@ Wireguard Clients
// Edit client modal event
$(document).ready(function () {
$("#modal_edit_client").on('shown.bs.modal', function (event) {
$("#modal_edit_client").on('show.bs.modal', function (event) {
let modal = $(this);
const button = $(event.relatedTarget);
const client_id = button.data('clientid');
@ -302,6 +325,16 @@ Wireguard Clients
'placeholderColor': '#666666'
});
modal.find("#_client_extra_allowed_ips").tagsInput({
'width': '100%',
'height': '75%',
'interactive': true,
'defaultText': 'Add More',
'removeWithBackspace' : true,
'minChars': 0,
'placeholderColor': '#666666'
})
// update client modal data
$.ajax({
cache: false,
@ -327,6 +360,11 @@ Wireguard Clients
modal.find("#_client_allowed_ips").addTag(obj);
});
modal.find("#_client_extra_allowed_ips").importTags('');
client.extra_allowed_ips.forEach(function (obj) {
modal.find("#_client_extra_allowed_ips").addTag(obj);
});
modal.find("#_use_server_dns").prop("checked", client.use_server_dns);
modal.find("#_enabled").prop("checked", client.enabled);
},
@ -371,6 +409,11 @@ Wireguard Clients
const allocated_ips = $("#_client_allocated_ips").val().split(",");
const allowed_ips = $("#_client_allowed_ips").val().split(",");
let use_server_dns = false;
let extra_allowed_ips = [];
if( $("#_client_extra_allowed_ips").val() !== "" ) {
extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(",");
}
if ($("#_use_server_dns").is(':checked')){
use_server_dns = true;
@ -383,7 +426,7 @@ Wireguard Clients
}
const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips,
"allowed_ips": allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled};
"allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled};
$.ajax({
cache: false,
@ -415,7 +458,7 @@ Wireguard Clients
}
}
$("#modal_email_client").on('shown.bs.modal', function (event) {
$("#modal_email_client").on('show.bs.modal', function (event) {
let modal = $(this);
const button = $(event.relatedTarget);
const client_id = button.data('clientid');
@ -439,6 +482,31 @@ Wireguard Clients
});
});
$("#modal_qr_client").on('show.bs.modal', function (event) {
let modal = $(this);
const button = $(event.relatedTarget);
const client_id = button.data('clientid');
const QRCodeImg = modal.find("#qr_code");
QRCodeImg.hide();
$.ajax({
cache: false,
method: 'GET',
url: '/api/client/' + client_id,
dataType: 'json',
contentType: "application/json",
success: function (resp) {
const client = resp.Client;
modal.find(".modal-title").text("QR Code for " + client.name);
QRCodeImg.attr('src', resp.QRCode).show();
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);
toastr.error(responseJson['message']);
}
});
});
$(document).ready(function () {
$.validator.setDefaults({
submitHandler: function (form) {

View file

@ -20,5 +20,5 @@ PostDown = {{ .serverConfig.Interface.PostDown }}
[Peer]
PublicKey = {{ .Client.PublicKey }}
{{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }}
{{end}}AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}
{{end}}AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}}
{{end}}{{end}}

View file

@ -4,6 +4,12 @@ package util
var (
DisableLogin bool
BindAddress string
SmtpHostname string
SmtpPort int
SmtpUsername string
SmtpPassword string
SmtpNoTLSCheck bool
SmtpAuthType string
SendgridApiKey string
EmailFrom string
EmailFromName string

View file

@ -80,10 +80,18 @@ func ValidateCIDR(cidr string) bool {
}
// ValidateCIDRList to validate a list of network CIDR
func ValidateCIDRList(cidrs []string) bool {
func ValidateCIDRList(cidrs []string, allowEmpty bool) bool {
for _, cidr := range cidrs {
if ValidateCIDR(cidr) == false {
return false
if allowEmpty {
if len(cidr) > 0 {
if ValidateCIDR(cidr) == false {
return false
}
}
} else {
if ValidateCIDR(cidr) == false {
return false
}
}
}
return true
@ -91,7 +99,15 @@ func ValidateCIDRList(cidrs []string) bool {
// ValidateAllowedIPs to validate allowed ip addresses in CIDR format
func ValidateAllowedIPs(cidrs []string) bool {
if ValidateCIDRList(cidrs) == false {
if ValidateCIDRList(cidrs, false) == false {
return false
}
return true
}
// ValidateExtraAllowedIPs to validate extra Allowed ip addresses, allowing empty strings
func ValidateExtraAllowedIPs(cidrs []string) bool {
if ValidateCIDRList(cidrs, true) == false {
return false
}
return true
@ -99,7 +115,7 @@ func ValidateAllowedIPs(cidrs []string) bool {
// ValidateServerAddresses to validate allowed ip addresses in CIDR format
func ValidateServerAddresses(cidrs []string) bool {
if ValidateCIDRList(cidrs) == false {
if ValidateCIDRList(cidrs, false) == false {
return false
}
return true