diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afa094d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +wireguard-ui diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6f24780 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/embarkstudios/wireguard-ui + +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/mdlayher/wireguardctrl v0.0.0-20190419142446-a4a944b88a6b + github.com/sirupsen/logrus v1.4.1 + github.com/vishvananda/netlink v1.0.0 + github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect + gopkg.in/alecthomas/kingpin.v2 v2.2.6 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7dea598 --- /dev/null +++ b/go.sum @@ -0,0 +1,38 @@ +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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +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/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/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/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= +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/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +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/net v0.0.0-20190419010253-1f3472d942ba h1:h0zCzEL5UW1mERvwTN6AXcc75PpLkY6OcReia6Dq1BM= +golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a3ba2a3 --- /dev/null +++ b/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "strings" + + log "github.com/sirupsen/logrus" + "gopkg.in/alecthomas/kingpin.v2" +) + +var ( + logLevel = kingpin.Flag("log-level", "The level of logging").Default("info").Enum("debug", "info", "warn", "error", "panic", "fatal") +) + +func main() { + kingpin.HelpFlag.Short('h') + kingpin.CommandLine.DefaultEnvars() + kingpin.Parse() + switch strings.ToLower(*logLevel) { + case "debug": + log.SetLevel(log.DebugLevel) + case "warn": + log.SetLevel(log.WarnLevel) + case "error": + log.SetLevel(log.ErrorLevel) + case "panic": + log.SetLevel(log.PanicLevel) + default: + log.SetLevel(log.InfoLevel) + } + + log.Info("Starting") + + server := NewServer() + err := server.Start() + if err != nil { + log.Fatal(err) + } +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..11f5244 --- /dev/null +++ b/server.go @@ -0,0 +1,98 @@ +package main + +import ( + "net/http" + "os" + + "github.com/mdlayher/wireguardctrl" + "github.com/mdlayher/wireguardctrl/wgtypes" + log "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" + "gopkg.in/alecthomas/kingpin.v2" +) + +var ( + listenAddr = kingpin.Flag("listen-address", "Address to listen to").Default(":8080").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() +) + +type Server struct { + mux *http.ServeMux + storage *Storage + config *ServerConfig +} + +type WgLink struct { + attrs *netlink.LinkAttrs +} + +func (w *WgLink) Attrs() *netlink.LinkAttrs { + return w.attrs +} + +func (w *WgLink) Type() string { + return "wireguard" +} + +func NewServer() *Server { + storage := NewStorage() + + server := &Server{ + mux: http.NewServeMux(), + storage: storage, + config: storage.GetServerConfig(), + } + + server.mux.HandleFunc("/", server.Hello) + + return server +} + +func (s *Server) initInterface() error { + attrs := netlink.NewLinkAttrs() + attrs.Name = *wgLinkName + + link := WgLink{ + attrs: &attrs, + } + + err := netlink.LinkAdd(&link) + if os.IsExist(err) { + log.Infof("Wireguard interface %s already exists. Reusing.", *wgLinkName) + } else if err != nil { + return err + } + + wg, err := wireguardctrl.New() + if err != nil { + return err + } + + key, err := wgtypes.ParseKey(s.config.PrivateKey) + if err != nil { + return err + } + + cfg := wgtypes.Config{ + PrivateKey: &key, + ListenPort: wgListenPort, + } + wg.ConfigureDevice(*wgLinkName, cfg) + + return nil +} + +func (s *Server) Start() error { + err := s.initInterface() + if err != nil { + return err + } + + log.WithField("listenAddr", *listenAddr).Info("Starting server") + return http.ListenAndServe(*listenAddr, s.mux) +} + +func (s *Server) Hello(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello World")) +} diff --git a/storage.go b/storage.go new file mode 100644 index 0000000..b762342 --- /dev/null +++ b/storage.go @@ -0,0 +1,91 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "os" + "path" + + "github.com/mdlayher/wireguardctrl/wgtypes" + log "github.com/sirupsen/logrus" + "gopkg.in/alecthomas/kingpin.v2" +) + +var ( + dataDir = kingpin.Flag("data-dir", "Directory used for storage").Default("/var/lib/wireguard-ui").String() +) + +type ServerConfig struct { + PrivateKey string + PublicKey string +} + +type UserConfig struct { + Name string + Devices []DeviceConfig +} + +type DeviceConfig struct { + PublicKey string +} + +type Storage struct { + serverConfigPath string +} + +func NewStorage() *Storage { + err := os.MkdirAll(*dataDir, 0700) + if err != nil { + log.WithError(err).Fatalf("Error initializing data directory: %s", *dataDir) + } + + s := Storage{ + serverConfigPath: path.Join(*dataDir, "config.json"), + } + log.Debug("Storage initialized: ", *dataDir) + return &s +} + +func NewServerConfig() *ServerConfig { + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + log.Fatal(err) + } + + cfg := ServerConfig{ + PrivateKey: key.String(), + PublicKey: key.PublicKey().String(), + } + + return &cfg +} + +func (cfg *ServerConfig) WriteServerConfig(path string) error { + data, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + return ioutil.WriteFile(path, data, 0600) +} + +func (s *Storage) GetServerConfig() *ServerConfig { + config := NewServerConfig() + + f, err := os.Open(s.serverConfigPath) + if err == nil { + if err = json.NewDecoder(f).Decode(config); err != nil { + log.Fatal(err) + } + log.Debug("Read server config from file: ", s.serverConfigPath) + } else if os.IsNotExist(err) { + log.Debug("No config found. Creating new: ", s.serverConfigPath) + err = config.WriteServerConfig(s.serverConfigPath) + } + + if err != nil { + log.Fatal(err) + } + + log.Debug("Configuration loaded with public key: ", config.PublicKey) + return config +}