mirror of
https://github.com/Threnklyn/wg-ui.git
synced 2026-05-19 05:13:30 +02:00
Merge branch 'master' into master
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
steps:
|
||||
- name: Build container image
|
||||
agents:
|
||||
queue: default
|
||||
os: linux
|
||||
plugins:
|
||||
- EmbarkStudios/k8s#1.1.0:
|
||||
image: gcr.io/kaniko-project/executor:latest
|
||||
command:
|
||||
- --destination=embarkstudios/wireguard-ui
|
||||
- --context=/build
|
||||
- --reproducible
|
||||
- --cache=true
|
||||
- --cache-repo=kaniko-cache.buildkite.svc.cluster.local/kaniko/cache
|
||||
- --cache-dir=/cache
|
||||
mount-secret:
|
||||
- docker-hub-credentials:/kaniko/.docker
|
||||
mount-hostpath:
|
||||
- /mnt/disks/ssd0/cache/kaniko:/cache
|
||||
+5
-1
@@ -1 +1,5 @@
|
||||
wireguard-ui
|
||||
.DS_Store
|
||||
/wireguard-ui
|
||||
/bindata.go
|
||||
ui/public/bundle.*
|
||||
node_modules
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
FROM node:12-alpine AS ui
|
||||
WORKDIR /ui
|
||||
COPY ui .
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
FROM golang:1.12 AS build
|
||||
WORKDIR /wg
|
||||
RUN go get github.com/go-bindata/go-bindata/... &&\
|
||||
go get github.com/elazarl/go-bindata-assetfs/...
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
COPY --from=ui /ui/public ui/public
|
||||
RUN go-bindata-assetfs -prefix ui/public ui/public &&\
|
||||
go install .
|
||||
|
||||
FROM gcr.io/distroless/base:debug
|
||||
COPY --from=build /go/bin/wireguard-ui /
|
||||
ENTRYPOINT [ "/wireguard-ui" ]
|
||||
@@ -0,0 +1,19 @@
|
||||
.PHONY: binary container ui
|
||||
|
||||
binary: go-binary ui
|
||||
|
||||
go-binary:
|
||||
go-bindata-assetfs -prefix ui/public ui/public
|
||||
go build .
|
||||
|
||||
ui:
|
||||
cd ui && npm install && npm run build
|
||||
|
||||
container:
|
||||
docker build -t wireguard-ui .
|
||||
|
||||
run-dev: go-binary
|
||||
sudo ./wireguard-ui --log-level=debug --dev-ui-server=http://localhost:5000
|
||||
|
||||
run-dev-ui: ui
|
||||
cd ui && npm run dev
|
||||
@@ -1,6 +1,7 @@
|
||||
# Wireguard UI
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FEmbarkStudios%2Fwireguard-ui?ref=badge_shield)
|
||||
|
||||
[](https://buildkite.com/embark-studios/wireguard-ui)
|
||||
|
||||
A basic web UI for managing Wireguard clients.
|
||||
|
||||
@@ -11,5 +12,22 @@ A basic web UI for managing Wireguard clients.
|
||||
* Expiration
|
||||
|
||||
|
||||
## Developing
|
||||
|
||||
### Start frontend server
|
||||
```
|
||||
npm install --prefix=ui
|
||||
npm run --prefix=ui dev
|
||||
```
|
||||
|
||||
### Use frontend server when running the server
|
||||
|
||||
```
|
||||
go get -u github.com/go-bindata/go-bindata/...
|
||||
go get github.com/elazarl/go-bindata-assetfs/...
|
||||
go-bindata-assetfs -prefix ui/public ui/public
|
||||
go build .
|
||||
sudo ./wireguard-ui --log-level=debug --dev-ui-server http://localhost:5000
|
||||
```
|
||||
## License
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FEmbarkStudios%2Fwireguard-ui?ref=badge_large)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FEmbarkStudios%2Fwireguard-ui?ref=badge_large)
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/mdlayher/wireguardctrl/wgtypes"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
@@ -19,14 +19,15 @@ type ServerConfig struct {
|
||||
|
||||
type UserConfig struct {
|
||||
Name string
|
||||
Devices map[string]*DeviceConfig
|
||||
Clients map[string]*ClientConfig
|
||||
}
|
||||
|
||||
type DeviceConfig struct {
|
||||
type ClientConfig struct {
|
||||
Name string
|
||||
PrivateKey string
|
||||
PublicKey string
|
||||
IP net.IP
|
||||
Notes string
|
||||
}
|
||||
|
||||
func NewServerConfig(cfgPath string) *ServerConfig {
|
||||
@@ -74,24 +75,25 @@ func (cfg *ServerConfig) GetUserConfig(user string) *UserConfig {
|
||||
log.WithField("user", user).Info("No such user. Creating one.")
|
||||
c = &UserConfig{
|
||||
Name: user,
|
||||
Devices: make(map[string]*DeviceConfig),
|
||||
Clients: make(map[string]*ClientConfig),
|
||||
}
|
||||
cfg.Users[user] = c
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func NewDeviceConfig(ip net.IP) *DeviceConfig {
|
||||
func NewClientConfig(ip net.IP) *ClientConfig {
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cfg := DeviceConfig{
|
||||
Name: "Unnamed Device",
|
||||
cfg := ClientConfig{
|
||||
Name: "Unnamed Client",
|
||||
PrivateKey: key.String(),
|
||||
PublicKey: key.PublicKey().String(),
|
||||
IP: ip,
|
||||
Notes: "",
|
||||
}
|
||||
|
||||
return &cfg
|
||||
|
||||
@@ -5,13 +5,21 @@ go 1.12
|
||||
require (
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
|
||||
github.com/ddollar/forego v0.16.1 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/dln/luxaforce v0.0.0-20180531194703-db2a6b5fc72d // indirect
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0
|
||||
github.com/futurenda/google-auth-id-token-verifier v0.0.0-20170311140316-2a5b89f28b7e // indirect
|
||||
github.com/google/nftables v0.0.0-20190430150743-07c974e3643d
|
||||
github.com/julienschmidt/httprouter v1.2.0
|
||||
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d // indirect
|
||||
github.com/mattn/goreman v0.2.1 // indirect
|
||||
github.com/mdlayher/wireguardctrl v0.0.0-20190419142446-a4a944b88a6b
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
github.com/toqueteos/webbrowser v1.1.0 // indirect
|
||||
github.com/vishvananda/netlink v1.0.0
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20190904205523-599d41c32142
|
||||
google.golang.org/grpc v1.22.0 // indirect
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
)
|
||||
|
||||
@@ -1,31 +1,72 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/EmbarkStudios/src v0.0.0-20190708074312-9ce83ae72fd4 h1:DemX5XAjzL1lvIuLVAMfX2grfxjZo7CQ9JH7FnB+3VQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ddollar/forego v0.16.1 h1:HbAl3XyEdU1lw17PvZzqMzcx7b8BKZshEsdb4+1TI3Y=
|
||||
github.com/ddollar/forego v0.16.1/go.mod h1:moJFK5OqWdZeLVEYHynQRJqoKImixOuxgQBFCtoe0bU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dln/luxaforce v0.0.0-20180531194703-db2a6b5fc72d h1:0MwrV1xaSUg0I5dzeoRuc6tg0UQACo98/ABwQcOFJg4=
|
||||
github.com/dln/luxaforce v0.0.0-20180531194703-db2a6b5fc72d/go.mod h1:CahaXJvJI62kC6ikqP63GWz40sAB1igBuQDPlxY3qnk=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/futurenda/google-auth-id-token-verifier v0.0.0-20170311140316-2a5b89f28b7e h1:qFV0nTBo/TC3ckN/VvyT0B1/VZb94AvSANfU7XR5lcM=
|
||||
github.com/futurenda/google-auth-id-token-verifier v0.0.0-20170311140316-2a5b89f28b7e/go.mod h1:EX5Jbcw/PxsrlV7D2o77gpzcJevkXl3DQjkF8a0xNMI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/nftables v0.0.0-20190430150743-07c974e3643d h1:TC4HtISCtWOTUVPQmq4CAqe0+8R2fvnkJlQn47ep2hI=
|
||||
github.com/google/nftables v0.0.0-20190430150743-07c974e3643d/go.mod h1:H/FvZdk1xWxiE2Ad0wX6mq/oqHvEE1LDDujRgYwi3ns=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190503083013-8a08cb3e375e/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d h1:MFX8DxRnKMY/2M3H61iSsVbo/n3h0MWGmWNN1UViOU0=
|
||||
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d/go.mod h1:QHb4k4cr1fQikUahfcRVPcEXiUgFsdIstGqlurL0XL4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/goreman v0.2.1 h1:6Kdjka0uWKsVM5t4d9UqzzIm2jcPJIP7QWu0ByKvlDE=
|
||||
github.com/mattn/goreman v0.2.1/go.mod h1:8HCyYaC38XwX0AOu0+fuY02Y5Z7CkITW0oVJavbna4Q=
|
||||
github.com/mdlayher/genetlink v0.0.0-20190419142426-b806ce69960a h1:tIPUNU99ot2Tko8SorsytkeFC0to6pRJB6koxswpeB8=
|
||||
github.com/mdlayher/genetlink v0.0.0-20190419142426-b806ce69960a/go.mod h1:J+g63IQCVwjKLOxodrxOylt23f7d5vVB9rgNh+T13Uk=
|
||||
github.com/mdlayher/genetlink v0.0.0-20190513144241-4cdc5dab577c h1:Z7vEAfVdgfkjIzGSOF6vLt8BGu31+DuCJqXlTI7oj3o=
|
||||
github.com/mdlayher/genetlink v0.0.0-20190513144241-4cdc5dab577c/go.mod h1:Gxg/DEIMJtqdXDyq47mB98qcpBHmaLrvOAmKKNRE0Tg=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v0.0.0-20190419142405-71c9566a34ae h1:Ec6B7pKh4YZyKI9VtTeZmN1GGLvAIT/fs2votDcWnMU=
|
||||
github.com/mdlayher/netlink v0.0.0-20190419142405-71c9566a34ae/go.mod h1:TR9n0u8mie4+iszGTMjP6QRwUZQNynkhDbXTLF6DUi4=
|
||||
github.com/mdlayher/netlink v0.0.0-20190513144208-ba284d510044/go.mod h1:gOrA34zDL0K3RsACQe54bDYLF/CeFspQ9m5DOycycQ8=
|
||||
github.com/mdlayher/netlink v0.0.0-20190614145538-d8264f87dbe3 h1:3IPcWjiboJFnnvHeXxT4pYw33BiPJn/DC5BKhcGEbGk=
|
||||
github.com/mdlayher/netlink v0.0.0-20190614145538-d8264f87dbe3/go.mod h1:ISujvOTprADlNr00kvJIu0d23q57wk2NSV/PT/TEk4E=
|
||||
github.com/mdlayher/wireguardctrl v0.0.0-20190419142446-a4a944b88a6b h1:3EhP8SsJHU7mbXm4iWtO57HR2LI48KaCuSfDhAywrBg=
|
||||
github.com/mdlayher/wireguardctrl v0.0.0-20190419142446-a4a944b88a6b/go.mod h1:3oz/rt7iwoq/LQa+QaFheGKzGZvc+vlmaBvtxeh6Jtc=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/toqueteos/webbrowser v1.1.0 h1:Prj1okiysRgHPoe3B1bOIVxcv+UuSt525BDQmR5W0x0=
|
||||
github.com/toqueteos/webbrowser v1.1.0/go.mod h1:Hqqqmzj8AHn+VlZyVjaRWY20i25hoOZGAABCcg2el4A=
|
||||
github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM=
|
||||
github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
||||
@@ -33,14 +74,51 @@ github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmF
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 h1:O5YqonU5IWby+w98jVUG9h7zlCWCcH4RHyPVReBmhzk=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba h1:h0zCzEL5UW1mERvwTN6AXcc75PpLkY6OcReia6Dq1BM=
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180925112736-b09afc3d579e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be h1:mI+jhqkn68ybP0ORJqunXn+fq+Eeb4hHKqLQcFICjAc=
|
||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190830023255-19e00faab6ad h1:cCejgArrk10gX6kFqjWeLwXD7aVMqWoRpyUCaaJSggc=
|
||||
golang.org/x/sys v0.0.0-20190830023255-19e00faab6ad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.zx2c4.com/wireguard v0.0.20190805 h1:qPzediAzPEswzRxx2GzzPzNqnwqBZUUxEKTnC+7vjng=
|
||||
golang.zx2c4.com/wireguard v0.0.20190806-0.20190831134842-7937840f9631 h1:FtEzFPuASmrIKZ9yNwMs3tIaZSaw4HGholnL3IWrASc=
|
||||
golang.zx2c4.com/wireguard v0.0.20190806-0.20190831134842-7937840f9631/go.mod h1:LhfXh5z6bLC2lW2ve6BzYZFwnnsXK3OQjySR0Yh2dO8=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20190904205523-599d41c32142 h1:14yX5ra9TyR9frakvYpAohJ++d21zDP5a3JdLPYbvdc=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20190904205523-599d41c32142/go.mod h1:8wMPWlR886yRavUsaWCpfeZjPMIdIPgVtqDGLUEXsY4=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
|
||||
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -6,35 +6,40 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/mdlayher/wireguardctrl"
|
||||
"github.com/mdlayher/wireguardctrl/wgtypes"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
dataDir = kingpin.Flag("data-dir", "Directory used for storage").Default("/var/lib/wireguard-ui").String()
|
||||
|
||||
listenAddr = kingpin.Flag("listen-address", "Address to listen to").Default(":8080").String()
|
||||
natLink = kingpin.Flag("nat-device", "Network interface to masquerade").Default("wlp2s0").String()
|
||||
clientIPRange = kingpin.Flag("client-ip-range", "Client IP CIDR").Default("172.72.72.1/24").String()
|
||||
listenAddr = kingpin.Flag("listen-address", "Address to listen to").Default(":8080").String()
|
||||
natLink = kingpin.Flag("nat-device", "Network interface to masquerade").Default("wlp2s0").String()
|
||||
clientIPRange = kingpin.Flag("client-ip-range", "Client IP CIDR").Default("172.72.72.1/24").String()
|
||||
authUserHeader = kingpin.Flag("auth-user-header", "Header containing username").Default("X-Forwarded-User").String()
|
||||
|
||||
wgLinkName = kingpin.Flag("wg-device-name", "Wireguard network device name").Default("wg0").String()
|
||||
wgListenPort = kingpin.Flag("wg-listen-port", "Wireguard UDP port to listen to").Default("51820").Int()
|
||||
wgEndpoint = kingpin.Flag("wg-endpoint", "Wireguard endpoint address").Default("127.0.0.1:51820").String()
|
||||
wgAllowedIPs = kingpin.Flag("wg-allowed-ips", "Wireguard client allowed ips").Default("0.0.0.0/0").Strings()
|
||||
|
||||
devUIServer = kingpin.Flag("dev-ui-server", "Developer mode: If specified, proxy all static assets to this endpoint").String()
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
@@ -43,6 +48,7 @@ type Server struct {
|
||||
Config *ServerConfig
|
||||
ipAddr net.IP
|
||||
clientIPRange *net.IPNet
|
||||
assets http.Handler
|
||||
}
|
||||
|
||||
type WgLink struct {
|
||||
@@ -82,12 +88,14 @@ func NewServer() *Server {
|
||||
config := NewServerConfig(cfgPath)
|
||||
|
||||
log.Debug("Configuration loaded with public key: ", config.PublicKey)
|
||||
assets := http.FileServer(&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: ""})
|
||||
|
||||
s := Server{
|
||||
serverConfigPath: cfgPath,
|
||||
Config: config,
|
||||
ipAddr: ipAddr,
|
||||
clientIPRange: ipNet,
|
||||
assets: assets,
|
||||
}
|
||||
|
||||
log.Debug("Server initialized: ", *dataDir)
|
||||
@@ -175,7 +183,7 @@ func (s *Server) allocateIP() net.IP {
|
||||
allocated := make(map[string]bool)
|
||||
allocated[s.ipAddr.String()] = true
|
||||
for _, cfg := range s.Config.Users {
|
||||
for _, dev := range cfg.Devices {
|
||||
for _, dev := range cfg.Clients {
|
||||
allocated[dev.IP.String()] = true
|
||||
}
|
||||
}
|
||||
@@ -214,7 +222,7 @@ func (s *Server) reconfigure() {
|
||||
|
||||
func (s *Server) configureWireguard() error {
|
||||
log.Debugf("Reconfiguring wireguard interface %s", *wgLinkName)
|
||||
wg, err := wireguardctrl.New()
|
||||
wg, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -227,7 +235,7 @@ func (s *Server) configureWireguard() error {
|
||||
|
||||
peers := make([]wgtypes.PeerConfig, 0)
|
||||
for user, cfg := range s.Config.Users {
|
||||
for id, dev := range cfg.Devices {
|
||||
for id, dev := range cfg.Clients {
|
||||
pubKey, err := wgtypes.ParseKey(dev.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -241,7 +249,7 @@ func (s *Server) configureWireguard() error {
|
||||
AllowedIPs: allowedIPs,
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{"user": user, "device": id, "key": dev.PublicKey, "allowedIPs": peer.AllowedIPs}).Debug("Adding wireguard peer")
|
||||
log.WithFields(log.Fields{"user": user, "client": id, "key": dev.PublicKey, "allowedIPs": peer.AllowedIPs}).Debug("Adding wireguard peer")
|
||||
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
@@ -270,60 +278,69 @@ func (s *Server) Start() error {
|
||||
}
|
||||
|
||||
router := httprouter.New()
|
||||
router.GET("/", s.Index)
|
||||
router.GET("/api/v1/users/:user/devices/:device", s.withAuth(s.GetDevice))
|
||||
router.PUT("/api/v1/users/:user/devices/:device", s.withAuth(s.EditDevice))
|
||||
router.DELETE("/api/v1/users/:user/devices/:device", s.withAuth(s.DeleteDevice))
|
||||
router.GET("/api/v1/users/:user/devices", s.withAuth(s.GetDevices))
|
||||
router.POST("/api/v1/users/:user/devices", s.withAuth(s.CreateDevice))
|
||||
router.GET("/api/v1/whoami", s.WhoAmI)
|
||||
router.GET("/api/v1/users/:user/clients/:client", s.withAuth(s.GetClient))
|
||||
router.PUT("/api/v1/users/:user/clients/:client", s.withAuth(s.EditClient))
|
||||
router.DELETE("/api/v1/users/:user/clients/:client", s.withAuth(s.DeleteClient))
|
||||
router.GET("/api/v1/users/:user/clients", s.withAuth(s.GetClients))
|
||||
router.POST("/api/v1/users/:user/clients", s.withAuth(s.CreateClient))
|
||||
|
||||
if *devUIServer != "" {
|
||||
log.Debug("Serving static assets proxying from development server: ", *devUIServer)
|
||||
devProxy := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
url, _ := url.Parse(*devUIServer)
|
||||
if strings.HasPrefix(r.URL.Path, "/client/") || r.URL.Path == "/about" {
|
||||
r.URL.Path = "/"
|
||||
}
|
||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
r.URL.Host = url.Host
|
||||
r.URL.Scheme = url.Scheme
|
||||
r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
|
||||
r.Host = url.Host
|
||||
proxy.ServeHTTP(w, r)
|
||||
})
|
||||
router.NotFound = devProxy
|
||||
} else {
|
||||
log.Debug("Serving static assets embedded in binary")
|
||||
router.GET("/about", s.Index)
|
||||
router.GET("/client/:client", s.Index)
|
||||
router.NotFound = s.assets
|
||||
}
|
||||
|
||||
log.WithField("listenAddr", *listenAddr).Info("Starting server")
|
||||
return http.ListenAndServe(*listenAddr, router)
|
||||
|
||||
return http.ListenAndServe(*listenAddr, s.userFromHeader(router))
|
||||
}
|
||||
|
||||
func userFromJwtToken(r *http.Request) string {
|
||||
authHeader := r.Header.Get("authorization")
|
||||
if authHeader == "" {
|
||||
log.Debug("No Authorization header")
|
||||
return ""
|
||||
}
|
||||
func (s *Server) userFromHeader(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user := r.Header.Get(*authUserHeader)
|
||||
if user == "" {
|
||||
log.Debug("Unauthenticated request")
|
||||
user = "anonymous"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
log.Debug("Incorrect Authorization header: ", authHeader)
|
||||
return ""
|
||||
}
|
||||
cookie := http.Cookie{
|
||||
Name: "wguser",
|
||||
Value: user,
|
||||
Path: "/",
|
||||
}
|
||||
http.SetCookie(w, &cookie)
|
||||
|
||||
claims := jwt.MapClaims{}
|
||||
token, err := jwt.ParseWithClaims(authHeader[7:], &claims, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(""), nil
|
||||
ctx := context.WithValue(r.Context(), "user", user)
|
||||
handler.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
|
||||
if token == nil {
|
||||
log.Debug("Error parsing JWT token: ", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
user, ok := claims["email"]
|
||||
if ok {
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
user, ok = claims["sub"]
|
||||
if ok {
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Server) withAuth(handler httprouter.Handle) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
log.Debug("Auth required")
|
||||
|
||||
user := userFromJwtToken(r)
|
||||
if user == "" {
|
||||
user = "anonymous"
|
||||
log.Info("Unauthenticated user: ", user)
|
||||
user := r.Context().Value("user")
|
||||
if user == nil {
|
||||
log.Error("Error getting username from request context")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if user != ps.ByName("user") {
|
||||
@@ -332,27 +349,43 @@ func (s *Server) withAuth(handler httprouter.Handle) httprouter.Handle {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), "user", user)
|
||||
handler(w, r.WithContext(ctx), ps)
|
||||
handler(w, r, ps)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
log.Debug("Index")
|
||||
w.Write([]byte("Hello World"))
|
||||
}
|
||||
|
||||
func (s *Server) GetDevices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
user := r.Context().Value("user")
|
||||
func (s *Server) WhoAmI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
user := r.Context().Value("user").(string)
|
||||
log.Debug(user)
|
||||
err := json.NewEncoder(w).Encode(s.Config)
|
||||
err := json.NewEncoder(w).Encode(struct{ User string }{user})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) GetDevice(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
func (s *Server) GetClients(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
user := r.Context().Value("user").(string)
|
||||
log.Debug(user)
|
||||
clients := map[string]*ClientConfig{}
|
||||
userConfig := s.Config.Users[user]
|
||||
if userConfig != nil {
|
||||
clients = userConfig.Clients
|
||||
}
|
||||
|
||||
err := json.NewEncoder(w).Encode(clients)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
log.Debug("Serving single-page app from URL: ", r.URL)
|
||||
r.URL.Path = "/"
|
||||
s.assets.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (s *Server) GetClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
user := r.Context().Value("user").(string)
|
||||
usercfg := s.Config.Users[user]
|
||||
if usercfg == nil {
|
||||
@@ -360,8 +393,8 @@ func (s *Server) GetDevice(w http.ResponseWriter, r *http.Request, ps httprouter
|
||||
return
|
||||
}
|
||||
|
||||
device := usercfg.Devices[ps.ByName("device")]
|
||||
if device == nil {
|
||||
client := usercfg.Clients[ps.ByName("client")]
|
||||
if client == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
@@ -381,11 +414,11 @@ DNS = %s
|
||||
PublicKey = %s
|
||||
AllowedIPs = %s
|
||||
Endpoint = %s
|
||||
`, device.IP.String(), device.PrivateKey, "8.8.8.8", s.Config.PublicKey, allowedIPs, *wgEndpoint)
|
||||
`, client.IP.String(), client.PrivateKey, "8.8.8.8", s.Config.PublicKey, allowedIPs, *wgEndpoint)
|
||||
return
|
||||
}
|
||||
|
||||
err := json.NewEncoder(w).Encode(device)
|
||||
err := json.NewEncoder(w).Encode(client)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -393,7 +426,7 @@ Endpoint = %s
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) EditDevice(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
func (s *Server) EditClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
user := r.Context().Value("user").(string)
|
||||
usercfg := s.Config.Users[user]
|
||||
if usercfg == nil {
|
||||
@@ -401,13 +434,13 @@ func (s *Server) EditDevice(w http.ResponseWriter, r *http.Request, ps httproute
|
||||
return
|
||||
}
|
||||
|
||||
device := usercfg.Devices[ps.ByName("device")]
|
||||
if device == nil {
|
||||
client := usercfg.Clients[ps.ByName("client")]
|
||||
if client == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := DeviceConfig{}
|
||||
cfg := ClientConfig{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
|
||||
log.Warn("Error parsing request: ", err)
|
||||
@@ -415,23 +448,27 @@ func (s *Server) EditDevice(w http.ResponseWriter, r *http.Request, ps httproute
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("EditDevice: %#v", cfg)
|
||||
log.Debugf("EditClient: %#v", cfg)
|
||||
|
||||
if cfg.Name != "" {
|
||||
device.Name = cfg.Name
|
||||
client.Name = cfg.Name
|
||||
}
|
||||
|
||||
if cfg.Notes != "" {
|
||||
client.Notes = cfg.Notes
|
||||
}
|
||||
|
||||
s.reconfigure()
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(device); err != nil {
|
||||
if err := json.NewEncoder(w).Encode(client); err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) DeleteDevice(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
func (s *Server) DeleteClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
user := r.Context().Value("user").(string)
|
||||
usercfg := s.Config.Users[user]
|
||||
if usercfg == nil {
|
||||
@@ -439,32 +476,32 @@ func (s *Server) DeleteDevice(w http.ResponseWriter, r *http.Request, ps httprou
|
||||
return
|
||||
}
|
||||
|
||||
device := ps.ByName("device")
|
||||
if usercfg.Devices[device] == nil {
|
||||
client := ps.ByName("client")
|
||||
if usercfg.Clients[client] == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
delete(usercfg.Devices, device)
|
||||
delete(usercfg.Clients, client)
|
||||
s.reconfigure()
|
||||
|
||||
log.WithField("user", user).Debug("Deleted device: ", device)
|
||||
log.WithField("user", user).Debug("Deleted client: ", client)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *Server) CreateDevice(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
func (s *Server) CreateClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
user := r.Context().Value("user").(string)
|
||||
log.WithField("user", user).Debug("CreateDevice")
|
||||
log.WithField("user", user).Debug("CreateClient")
|
||||
|
||||
c := s.Config.GetUserConfig(user)
|
||||
log.Debugf("user config: %#v", c)
|
||||
|
||||
i := 0
|
||||
for k := range c.Devices {
|
||||
for k := range c.Clients {
|
||||
n, err := strconv.Atoi(k)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -478,12 +515,12 @@ func (s *Server) CreateDevice(w http.ResponseWriter, r *http.Request, ps httprou
|
||||
i = i + 1
|
||||
|
||||
ip := s.allocateIP()
|
||||
device := NewDeviceConfig(ip)
|
||||
c.Devices[strconv.Itoa(i)] = device
|
||||
client := NewClientConfig(ip)
|
||||
c.Clients[strconv.Itoa(i)] = client
|
||||
|
||||
s.reconfigure()
|
||||
|
||||
err := json.NewEncoder(w).Encode(device)
|
||||
err := json.NewEncoder(w).Encode(client)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
Generated
+3104
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "wireguard-ui",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"braces": "^3.0.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rollup": "^1.14.3",
|
||||
"rollup-plugin-commonjs": "^10.0.0",
|
||||
"rollup-plugin-livereload": "^1.0.0",
|
||||
"rollup-plugin-node-resolve": "^5.0.1",
|
||||
"rollup-plugin-svelte": "^5.0.3",
|
||||
"rollup-plugin-terser": "^5.0.0",
|
||||
"sirv-cli": "^0.4.3",
|
||||
"svelte": "^3.5.1",
|
||||
"svelte-routing": "1.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"autobuild": "rollup -c -w",
|
||||
"dev": "run-p start:dev autobuild",
|
||||
"start": "sirv public",
|
||||
"start:dev": "sirv public --dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-universal": "^2.0.16",
|
||||
"ui": "^0.2.4"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,4 @@
|
||||
body {
|
||||
padding-top: 5rem;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf8'>
|
||||
<meta name='viewport' content='width=device-width'>
|
||||
|
||||
<title>Wireguard VPN</title>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css" integrity="sha384-wXznGJNEXNG1NFsbm0ugrLFMQPWswR3lds2VeinahP8N0zJw9VWSopbjv2x7WCvX" crossorigin="anonymous">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||
<link rel='stylesheet' href='/global.css'>
|
||||
<link rel='stylesheet' href='/bundle.css'>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src='/bundle.js'></script>
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/popper.js@1.12.6/dist/umd/popper.js" integrity="sha384-fA23ZRQ3G/J53mElWqVJEGJzU0sTs+SvzG8fXVWP+kJQ1lwFAOkcUOysnlKJC33U" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/bootstrap-material-design@4.1.1/dist/js/bootstrap-material-design.js" integrity="sha384-CauSuKpEqAFajSpkdjv3z9t8E7RlpJ1UP0lKM/+NdtSarroVKu069AlsRPKkFBz9" crossorigin="anonymous"></script>
|
||||
<script>$(document).ready(function() { $('body').bootstrapMaterialDesign(); });</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import livereload from 'rollup-plugin-livereload';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
export default {
|
||||
input: 'src/main.js',
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
name: 'app',
|
||||
file: 'public/bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
svelte({
|
||||
// enable run-time checks when not in production
|
||||
dev: !production,
|
||||
// we'll extract any component CSS out into
|
||||
// a separate file — better for performance
|
||||
css: css => {
|
||||
css.write('public/bundle.css');
|
||||
}
|
||||
}),
|
||||
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration —
|
||||
// consult the documentation for details:
|
||||
// https://github.com/rollup/rollup-plugin-commonjs
|
||||
resolve(),
|
||||
commonjs(),
|
||||
|
||||
// Watch the `public` directory and refresh the
|
||||
// browser on changes when not in production
|
||||
!production && livereload('public'),
|
||||
|
||||
// If we're building for production (npm run build
|
||||
// instead of npm run dev), minify
|
||||
production && terser()
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
<h1>About</h1>
|
||||
|
||||
<p>
|
||||
Wireguard UI is an <a href="https://embark-studios.com/">Embark Studios</a> Open Source project.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For contributions and feedback, please see the
|
||||
<a href="https://github.com/EmbarkStudios/wireguard-ui">GitHub project</a>.
|
||||
</p>
|
||||
|
||||
<h2>License</h2>
|
||||
<p>
|
||||
Wireguard UI is licensed under <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>
|
||||
</p>
|
||||
|
||||
<p>Copyright © 2019, Embark Studios AB</p>
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { Router, Link, Route } from "svelte-routing";
|
||||
import About from "./About.svelte";
|
||||
import Clients from "./Clients.svelte";
|
||||
import EditClient from "./EditClient.svelte";
|
||||
import Nav from "./Nav.svelte";
|
||||
|
||||
import Cookie from "cookie-universal";
|
||||
const cookies = Cookie();
|
||||
export let user = cookies.get("wguser", { fromRes: true});
|
||||
|
||||
export let url = "";
|
||||
</script>
|
||||
|
||||
<Router url="{url}">
|
||||
<Nav user="{user}" />
|
||||
<main role="main" class="container">
|
||||
<div>
|
||||
<Route path="client/:clientId" component="{EditClient}" />
|
||||
<Route path="about" component="{About}" />
|
||||
<Route path="/"><Clients user="{user}" /></Route>
|
||||
</div>
|
||||
</main>
|
||||
</Router>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script>
|
||||
import { link } from "svelte-routing";
|
||||
|
||||
export let client;
|
||||
export let user;
|
||||
|
||||
let clientId = client[0];
|
||||
let dev = client[1];
|
||||
|
||||
var hash = 0;
|
||||
for (var i = 0; i < dev.PrivateKey.length; i++) {
|
||||
hash = dev.PrivateKey.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
const color = "hsl(" + (hash % 360) + ",50%,95%)";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body" style="background-color: {color}">
|
||||
<a href="/client/{clientId}" use:link replace role="button" class="btn btn-secondary material-icons float-right">edit</a>
|
||||
<i class="material-icons" aria-hidden="true">devices</i>
|
||||
<h4 class="card-title">{dev.Name}</h4>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">IP</dt>
|
||||
<dd class="col-sm-10">{dev.IP}</dd>
|
||||
<dt class="col-sm-2">Public Key</dt>
|
||||
<dd class="col-sm-10">{dev.PublicKey}</dd>
|
||||
</dl>
|
||||
|
||||
<a href="/api/v1/users/{user}/clients/{clientId}?format=config" role="button" class="btn btn-raised btn-primary">Download Config</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import Client from './Client.svelte';
|
||||
|
||||
export let user;
|
||||
|
||||
let clientsUrl = "/api/v1/users/" + user + "/clients";
|
||||
let clients = [];
|
||||
|
||||
async function getClients() {
|
||||
const res = await fetch(clientsUrl);
|
||||
clients = Object.entries(await res.json());
|
||||
console.log("Fetched clients", clients);
|
||||
}
|
||||
|
||||
async function handleNewClick(event) {
|
||||
const res = await fetch(clientsUrl, {
|
||||
method: "POST",
|
||||
}).then(getClients());
|
||||
let newClient = await res.json();
|
||||
console.log("New client added", newClient);
|
||||
}
|
||||
|
||||
onMount(getClients);
|
||||
</script>
|
||||
|
||||
|
||||
<h2>My Clients <small class="text-muted">({user})</small></h2>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
{#each clients as dev}
|
||||
<li><Client user={user} client={dev}/></li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<button on:click={handleNewClick} type="button" class="btn btn-primary bmd-btn-fab float-right">
|
||||
<i class="material-icons">add</i>
|
||||
</button>
|
||||
@@ -0,0 +1,114 @@
|
||||
<script>
|
||||
import Cookie from "cookie-universal";
|
||||
import { onMount } from 'svelte';
|
||||
import { link, navigate } from "svelte-routing";
|
||||
|
||||
export let clientId;
|
||||
|
||||
const user = Cookie().get("wguser", { fromRes: true});
|
||||
|
||||
const clientUrl = `/api/v1/users/` + user + `/clients/` + clientId;
|
||||
|
||||
let client = {};
|
||||
|
||||
async function getClient() {
|
||||
const res = await fetch(clientUrl);
|
||||
client = await res.json();
|
||||
console.log("Fetched client", client);
|
||||
}
|
||||
|
||||
async function handleSubmit(event) {
|
||||
const res = await fetch(clientUrl, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(client),
|
||||
});
|
||||
client = await res.json();
|
||||
navigate("/", { replace: true });
|
||||
console.log("Saved changes", res);
|
||||
}
|
||||
|
||||
|
||||
async function handleDeleteClick(event) {
|
||||
const res = await fetch(clientUrl, {
|
||||
method: "DELETE",
|
||||
});
|
||||
await res;
|
||||
navigate("/", { replace: true });
|
||||
console.log("Delete!");
|
||||
}
|
||||
|
||||
onMount(getClient);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<h2>Client Properties <small class="text-muted"> {client.Name}</h2>
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<div class="form-group">
|
||||
<label for="name" class="bmd-label-floating">Name</label>
|
||||
<input type="text" class="form-control" id="name" bind:value={client.Name}>
|
||||
<span class="bmd-help">Friendly name of client.</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes" class="bmd-label-floating">Notes</label>
|
||||
<textarea class="form-control" id="notes" rows="3" bind:value={client.Notes}/>
|
||||
<span class="bmd-help">Notes about client.</span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-raised btn-primary">Save Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h3>Additional Properties</h3>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">IP Address</dt>
|
||||
<dd class="col-sm-10">{client.IP}</dd>
|
||||
<dt class="col-sm-2">Private Key</dt>
|
||||
<dd class="col-sm-10">{client.PrivateKey}</dd>
|
||||
<dt class="col-sm-2">Public Key</dt>
|
||||
<dd class="col-sm-10">{client.PublicKey}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h3>Danger Zone</h3>
|
||||
<button type="button" class="btn btn-sm btn-secondary btn-raised btn-danger" data-toggle="modal" data-target="#deleteClient">
|
||||
Delete Client Config
|
||||
</button>
|
||||
|
||||
<div class="modal fade" id="deleteClient" tabindex="-1" role="dialog" aria-labelledby="deleteClientLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteClientLabel">Delete {client.Name}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Are you sure you want to delete this client configuration?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
||||
<button on:click={handleDeleteClick} type="button" class="btn btn-primary btn-danger" data-dismiss="modal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script>
|
||||
import { Router, Link, Route } from "svelte-routing";
|
||||
import About from "./About.svelte";
|
||||
import Clients from "./Clients.svelte";
|
||||
import NavLink from "./NavLink.svelte";
|
||||
|
||||
export let user;
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
|
||||
<a class="navbar-brand" href="/">Wireguard VPN</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item"><NavLink to="/">Clients</NavLink></li>
|
||||
<li class="nav-item"><NavLink to="about">About</NavLink></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="text-light">
|
||||
<small>Logged in as {user}</small>
|
||||
{#if user != "anonymous"}
|
||||
<a class="btn btn-outline-light" href="/oauth2/sign_out">Log out</a>
|
||||
{/if}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
import { Link } from "svelte-routing";
|
||||
export let to = "";
|
||||
|
||||
function getProps({ location, href, isPartiallyCurrent, isCurrent }) {
|
||||
const isActive = href === "/" ? isCurrent : isPartiallyCurrent || isCurrent;
|
||||
// The object returned here is spread on the anchor element's attributes
|
||||
if (isActive) {
|
||||
return { class: "nav-link active" };
|
||||
}
|
||||
return { class: "nav-link" };
|
||||
}
|
||||
</script>
|
||||
|
||||
<Link to="{to}" getProps="{getProps}">
|
||||
<slot />
|
||||
</Link>
|
||||
@@ -0,0 +1,7 @@
|
||||
import App from './App.svelte';
|
||||
|
||||
new App({
|
||||
target: document.body,
|
||||
// hydrate: true,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
const { createServer } = require("http");
|
||||
const app = require("./App.js");
|
||||
|
||||
createServer((req, res) => {
|
||||
const { html } = app.render({ url: req.url });
|
||||
|
||||
res.write(`
|
||||
<!DOCTYPE html>
|
||||
<body>${html}</body>
|
||||
<script src="/bundle.js"></script>
|
||||
`);
|
||||
|
||||
res.end();
|
||||
}).listen(5000);
|
||||
Reference in New Issue
Block a user