diff --git a/internal/config/config.go b/internal/config/config.go index bf36aa4d..397ce585 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -16,7 +16,8 @@ import ( ) type AppConfig struct { - Web struct { + Mode string + Web struct { // ExternalAddress is the address that // clients should use to connect to this // server. It will be used in generated diff --git a/internal/services/manager.go b/internal/services/devices.go similarity index 96% rename from internal/services/manager.go rename to internal/services/devices.go index 342deb14..d1f32c04 100644 --- a/internal/services/manager.go +++ b/internal/services/devices.go @@ -8,7 +8,6 @@ import ( "github.com/pkg/errors" "github.com/place1/wireguard-access-server/internal/storage" - "github.com/place1/wireguard-access-server/internal/wg" "github.com/sirupsen/logrus" ) @@ -25,11 +24,11 @@ func nextPeerID() int { } type DeviceManager struct { - wgserver *wg.Server + wgserver *WireGuard storage storage.Storage } -func NewDeviceManager(w *wg.Server, s storage.Storage) *DeviceManager { +func NewDeviceManager(w *WireGuard, s storage.Storage) *DeviceManager { return &DeviceManager{w, s} } diff --git a/internal/services/network.go b/internal/services/network.go new file mode 100644 index 00000000..9585f90c --- /dev/null +++ b/internal/services/network.go @@ -0,0 +1,49 @@ +package services + +import ( + "github.com/coreos/go-iptables/iptables" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" +) + +func ConfigureRouting(wgIface string) error { + // Networking configuration (ip links and route tables) + // to ensure that network traffic in the VPN subnet + // moves through the wireguard interface + link, err := netlink.LinkByName(wgIface) + if err != nil { + return errors.Wrap(err, "failed to find wireguard interface") + } + addr, err := netlink.ParseAddr("10.0.0.1/24") + if err != nil { + return errors.Wrap(err, "failed to parse subnet address") + } + if err := netlink.AddrAdd(link, addr); err != nil { + logrus.Warn(errors.Wrap(err, "failed to add subnet to wireguard interface")) + } + if err := netlink.LinkSetUp(link); err != nil { + logrus.Warn(errors.Wrap(err, "failed to bring wireguard interface up")) + } + return nil +} + +func ConfigureForwarding(wgIface string, gatewayIface string) error { + // Networking configuration (iptables) configuration + // to ensure that traffic from clients the wireguard interface + // is sent to the provided network interface + ipt, err := iptables.New() + if err != nil { + return errors.Wrap(err, "failed to init iptables") + } + if err := ipt.AppendUnique("filter", "FORWARD", "-s", "10.0.0.1/24", "-o", wgIface, "-j", "ACCEPT"); err != nil { + return errors.Wrap(err, "failed to set ip tables rule") + } + if err := ipt.AppendUnique("filter", "FORWARD", "-s", "10.0.0.1/24", "-i", wgIface, "-j", "ACCEPT"); err != nil { + return errors.Wrap(err, "failed to set ip tables rule") + } + if err := ipt.AppendUnique("nat", "POSTROUTING", "-s", "10.0.0.1/24", "-o", gatewayIface, "-j", "MASQUERADE"); err != nil { + return errors.Wrap(err, "failed to set ip tables rule") + } + return nil +} diff --git a/internal/wg/server.go b/internal/services/wireguard.go similarity index 82% rename from internal/wg/server.go rename to internal/services/wireguard.go index e04dd7cc..35e8805a 100644 --- a/internal/wg/server.go +++ b/internal/services/wireguard.go @@ -1,4 +1,4 @@ -package wg +package services import ( "fmt" @@ -12,7 +12,7 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -type Server struct { +type WireGuard struct { client *wgctrl.Client iface string externalName string @@ -21,7 +21,7 @@ type Server struct { lock sync.Mutex } -func New(iface string, privateKey string, port int, externalName string) (*Server, error) { +func NewWireGuard(iface string, privateKey string, port int, externalName string) (*WireGuard, error) { // wgctrl.New() will search for a kernel implementation // of wireguard, then user implementations // user implementations are found in /var/run/wireguard/.sock @@ -34,7 +34,7 @@ func New(iface string, privateKey string, port int, externalName string) (*Serve if err != nil { return nil, errors.Wrap(err, "bad private key format") } - server := &Server{ + server := &WireGuard{ client: client, iface: iface, port: port, @@ -52,7 +52,7 @@ func New(iface string, privateKey string, port int, externalName string) (*Serve return server, nil } -func (s *Server) AddPeer(publicKey string, addressCIDR string) error { +func (s *WireGuard) AddPeer(publicKey string, addressCIDR string) error { logrus. WithField("publicKey", publicKey). WithField("address", addressCIDR). @@ -80,7 +80,7 @@ func (s *Server) AddPeer(publicKey string, addressCIDR string) error { }) } -func (s *Server) ListPeers() ([]wgtypes.Peer, error) { +func (s *WireGuard) ListPeers() ([]wgtypes.Peer, error) { d, err := s.Device() if err != nil { return nil, err @@ -88,7 +88,7 @@ func (s *Server) ListPeers() ([]wgtypes.Peer, error) { return d.Peers, nil } -func (s *Server) Peer(publicKey string) (*wgtypes.Peer, error) { +func (s *WireGuard) Peer(publicKey string) (*wgtypes.Peer, error) { peers, err := s.ListPeers() if err != nil { return nil, err @@ -101,7 +101,7 @@ func (s *Server) Peer(publicKey string) (*wgtypes.Peer, error) { return nil, fmt.Errorf("peer with public key '%s' not found", publicKey) } -func (s *Server) HasPeer(publicKey string) bool { +func (s *WireGuard) HasPeer(publicKey string) bool { peers, err := s.ListPeers() if err != nil { logrus.Error(errors.Wrap(err, "failed to list peers")) @@ -115,7 +115,7 @@ func (s *Server) HasPeer(publicKey string) bool { return false } -func (s *Server) RemovePeer(publicKey string) error { +func (s *WireGuard) RemovePeer(publicKey string) error { logrus.WithField("publicKey", publicKey).Debug("removing peer") key, err := wgtypes.ParseKey(publicKey) if err != nil { @@ -133,27 +133,27 @@ func (s *Server) RemovePeer(publicKey string) error { }) } -func (s *Server) PublicKey() string { +func (s *WireGuard) PublicKey() string { return s.publicKey.String() } -func (s *Server) Endpoint() string { +func (s *WireGuard) Endpoint() string { return fmt.Sprintf("%s:%d", s.externalName, s.port) } -func (s *Server) DNS() string { +func (s *WireGuard) DNS() string { return "1.1.1.1, 8.8.8.8" // TODO: dns stuff } -func (s *Server) Device() (*wgtypes.Device, error) { +func (s *WireGuard) Device() (*wgtypes.Device, error) { return s.client.Device(s.iface) } -func (s *Server) Close() error { +func (s *WireGuard) Close() error { return s.client.Close() } -func (s *Server) configure(cb func(*wgtypes.Config) error) error { +func (s *WireGuard) configure(cb func(*wgtypes.Config) error) error { s.lock.Lock() defer s.lock.Unlock() next := wgtypes.Config{} diff --git a/internal/services/wireguard_userspace.go b/internal/services/wireguard_userspace.go new file mode 100644 index 00000000..c6a0da23 --- /dev/null +++ b/internal/services/wireguard_userspace.go @@ -0,0 +1,41 @@ +package services + +import ( + "os/exec" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func ExecUserWireGuard(wgcommand string, ifaceName string) error { + logrus.Infof("using userspace wireguard implementation %s", wgcommand) + + // create the command to exec + // if it's "boringtun" we'll provide some non-standard + // flags to better support running within a docker container + var cmd *exec.Cmd + if wgcommand == "boringtun" { + cmd = exec.Command( + wgcommand, + ifaceName, + "--disable-drop-privileges=root", + "--foreground", + ) + } else { + cmd = exec.Command( + wgcommand, + "-f", + ifaceName, + ) + } + + entry := logrus.NewEntry(logrus.New()).WithField("process", wgcommand) + cmd.Stdout = entry.Writer() + cmd.Stderr = entry.Writer() + logrus.Infof("starting %s", cmd.String()) + if err := cmd.Run(); err != nil { + return errors.Wrap(err, "userspace wireguard exitted") + } + + return nil +} diff --git a/main.go b/main.go index 40bf865b..6bec767e 100644 --- a/main.go +++ b/main.go @@ -3,13 +3,8 @@ package main import ( "fmt" "net/http" - "os/exec" "time" - "github.com/coreos/go-iptables/iptables" - - "github.com/vishvananda/netlink" - "github.com/gorilla/mux" "github.com/pkg/errors" @@ -17,7 +12,6 @@ import ( "github.com/place1/wireguard-access-server/internal/services" "github.com/place1/wireguard-access-server/internal/storage" "github.com/place1/wireguard-access-server/internal/web" - "github.com/place1/wireguard-access-server/internal/wg" "github.com/sirupsen/logrus" ) @@ -27,31 +21,12 @@ func main() { // Userspace wireguard command if config.WireGuard.UserspaceImplementation != "" { go func() { - logrus.Infof("using userspace wireguard implementation %s", config.WireGuard.UserspaceImplementation) - var command *exec.Cmd - if config.WireGuard.UserspaceImplementation == "boringtun" { - command = exec.Command( - config.WireGuard.UserspaceImplementation, - config.WireGuard.InterfaceName, - "--disable-drop-privileges=root", - "--foreground", - ) - } else { - command = exec.Command( - config.WireGuard.UserspaceImplementation, - "-f", - config.WireGuard.InterfaceName, - ) - } - entry := logrus.NewEntry(logrus.New()).WithField("process", config.WireGuard.UserspaceImplementation) - command.Stdout = entry.Writer() - command.Stderr = entry.Writer() - logrus.Infof("starting %s", command.String()) - if err := command.Run(); err != nil { - logrus.Fatal(errors.Wrap(err, "userspace wireguard exitted")) + // execute the userspace wireguard implementation + // if it exists/crashes for some reason then we'll also crash + if err := services.ExecUserWireGuard(config.WireGuard.UserspaceImplementation, config.WireGuard.InterfaceName); err != nil { + logrus.Fatal(err) } }() - // Wait for the userspace wireguard process to // startup and create the wg0 interface // Super sorry if this just caused a race @@ -60,7 +35,7 @@ func main() { } // WireGuard - wgserver, err := wg.New( + wgserver, err := services.NewWireGuard( config.WireGuard.InterfaceName, config.WireGuard.PrivateKey, config.WireGuard.Port, @@ -73,36 +48,13 @@ func main() { logrus.Infof("wireguard server public key is %s", wgserver.PublicKey()) logrus.Infof("wireguard endpoint is %s", wgserver.Endpoint()) - // Networking configuration (ip links and route tables) - link, err := netlink.LinkByName(config.WireGuard.InterfaceName) - if err != nil { - logrus.Fatal(errors.Wrap(err, "failed to find wireguard interface")) - } - addr, err := netlink.ParseAddr("10.0.0.1/24") - if err != nil { - logrus.Fatal(errors.Wrap(err, "failed to parse subnet address")) + // Networking configuration + if err := services.ConfigureRouting(config.WireGuard.InterfaceName); err != nil { + logrus.Fatal(err) } - if err := netlink.AddrAdd(link, addr); err != nil { - logrus.Warn(errors.Wrap(err, "failed to add subnet to wireguard interface")) - } - if err := netlink.LinkSetUp(link); err != nil { - logrus.Warn(errors.Wrap(err, "failed to bring wireguard interface up")) - } - - // Networking configuration (iptables) if config.VPN.GatewayInterface != nil { - ipt, err := iptables.New() - if err != nil { - logrus.Fatal(errors.Wrap(err, "failed to init iptables")) - } - if err := ipt.AppendUnique("filter", "FORWARD", "-s", "10.0.0.1/24", "-o", config.WireGuard.InterfaceName, "-j", "ACCEPT"); err != nil { - logrus.Fatal(errors.Wrap(err, "failed to set ip tables rule")) - } - if err := ipt.AppendUnique("filter", "FORWARD", "-s", "10.0.0.1/24", "-i", config.WireGuard.InterfaceName, "-j", "ACCEPT"); err != nil { - logrus.Fatal(errors.Wrap(err, "failed to set ip tables rule")) - } - if err := ipt.AppendUnique("nat", "POSTROUTING", "-s", "10.0.0.1/24", "-o", config.VPN.GatewayInterface.Attrs().Name, "-j", "MASQUERADE"); err != nil { - logrus.Fatal(errors.Wrap(err, "failed to set ip tables rule")) + if err := services.ConfigureForwarding(config.WireGuard.InterfaceName, config.VPN.GatewayInterface.Attrs().Name); err != nil { + logrus.Fatal(err) } }