Skip to content

Commit

Permalink
Merge pull request #534 from Darkren/feature/run-vpn-non-root
Browse files Browse the repository at this point in the history
Make VPN client runnable without root
  • Loading branch information
jdknives authored Sep 30, 2020
2 parents cd93eb5 + b386524 commit 2af0ffa
Show file tree
Hide file tree
Showing 149 changed files with 6,460 additions and 7,801 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ require (
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.6.1
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
github.com/tjfoc/gmsm v1.3.2 // indirect
github.com/xtaci/kcp-go v5.4.20+incompatible
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect
go.etcd.io/bbolt v1.3.5
golang.org/x/net v0.0.0-20200625001655-4c5254603344
golang.org/x/sys v0.0.0-20200929083018-4d22bbb62b3c // indirect
golang.zx2c4.com/wireguard v0.0.20200320
nhooyr.io/websocket v1.8.2
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
Expand Down Expand Up @@ -330,6 +332,8 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200929083018-4d22bbb62b3c h1:/h0vtH0PyU0xAoZJVcRw1k0Ng+U0JAy3QDiFmppIlIE=
golang.org/x/sys v0.0.0-20200929083018-4d22bbb62b3c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down
41 changes: 33 additions & 8 deletions internal/vpn/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ const (

// Client is a VPN client.
type Client struct {
cfg ClientConfig
log logrus.FieldLogger
conn net.Conn
directIPSMu sync.Mutex
directIPs []net.IP
defaultGateway net.IP
closeC chan struct{}
closeOnce sync.Once
cfg ClientConfig
log logrus.FieldLogger
conn net.Conn
directIPSMu sync.Mutex
directIPs []net.IP
defaultGateway net.IP
sysPrivilegesMx sync.Mutex
closeC chan struct{}
closeOnce sync.Once
}

// NewClient creates VPN client instance.
Expand Down Expand Up @@ -111,6 +112,13 @@ func (c *Client) AddDirectRoute(ip net.IP) error {

c.directIPs = append(c.directIPs, ip)

suid, err := c.setupSysPrivileges()
if err != nil {
return fmt.Errorf("failed to setup system privileges: %w", err)
}

defer c.releaseSysPrivileges(suid)

if err := c.setupDirectRoute(ip); err != nil {
return err
}
Expand Down Expand Up @@ -148,15 +156,23 @@ func (c *Client) Serve() error {
c.log.Infof("Local TUN IP: %s", tunIP.String())
c.log.Infof("Local TUN gateway: %s", tunGateway.String())

suid, err := c.setupSysPrivileges()
if err != nil {
return fmt.Errorf("failed to setup system privileges: %w", err)
}

tun, err := newTUNDevice()
if err != nil {
c.releaseSysPrivileges(suid)
return fmt.Errorf("error allocating TUN interface: %w", err)
}
defer func() {
tunName := tun.Name()
if err := tun.Close(); err != nil {
c.log.WithError(err).Errorf("Error closing TUN %s", tunName)
}

c.releaseSysPrivileges(suid)
}()

c.log.Infof("Allocated TUN %s", tun.Name())
Expand Down Expand Up @@ -184,6 +200,9 @@ func (c *Client) Serve() error {
return fmt.Errorf("error routing traffic through TUN %s: %w", tun.Name(), err)
}

// we release privileges here (user is not root for Mac OS systems from here on)suid
c.releaseSysPrivileges(suid)

connToTunDoneCh := make(chan struct{})
tunToConnCh := make(chan struct{})
// read all system traffic and pass it to the remote VPN server
Expand All @@ -209,6 +228,12 @@ func (c *Client) Serve() error {
case <-c.closeC:
}

// we setup system privileges again here, so that the deferred call could perform clean up
suid, err = c.setupSysPrivileges()
if err != nil {
c.log.WithError(err).Errorln("Failed to setup system privileges for clean up")
}

return nil
}

Expand Down
28 changes: 28 additions & 0 deletions internal/vpn/client_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//+build darwin

package vpn

import (
"fmt"
"syscall"
)

func (c *Client) setupSysPrivileges() (suid int, err error) {
c.sysPrivilegesMx.Lock()

suid = syscall.Getuid()

if err := Setuid(0); err != nil {
return 0, fmt.Errorf("failed to setuid 0: %w", err)
}

return suid, nil
}

func (c *Client) releaseSysPrivileges(suid int) {
c.sysPrivilegesMx.Unlock()

if err := Setuid(suid); err != nil {
c.log.WithError(err).Errorf("Failed to set uid %d", suid)
}
}
53 changes: 53 additions & 0 deletions internal/vpn/client_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//+build linux

package vpn

import (
"fmt"
"sync"

"github.com/syndtr/gocapability/capability"
"golang.org/x/sys/unix"
)

var (
setupOnce sync.Once
)

func (c *Client) setupSysPrivileges() (suid int, err error) {
setupOnce.Do(func() {
var caps capability.Capabilities

caps, err = capability.NewPid2(0)
if err != nil {
err = fmt.Errorf("failed to init capabilities: %w", err)
return
}

err = caps.Load()
if err != nil {
err = fmt.Errorf("failed to load capabilities: %w", err)
return
}

// set `CAP_NET_ADMIN` capability to needed caps sets.
caps.Set(capability.CAPS|capability.BOUNDS|capability.AMBIENT, capability.CAP_NET_ADMIN)
if err := caps.Apply(capability.CAPS | capability.BOUNDS | capability.AMBIENT); err != nil {
err = fmt.Errorf("failed to apply capabilties: %w", err)
return
}

// let child process keep caps sets from the parent, so we may do calls to
// system utilities with these caps.
if err := unix.Prctl(unix.PR_SET_KEEPCAPS, 1, 0, 0, 0); err != nil {
err = fmt.Errorf("failed to set PR_SET_KEEPCAPS: %w", err)
return
}
})

return 0, nil
}

func (c *Client) releaseSysPrivileges(_ int) {
return
}
11 changes: 11 additions & 0 deletions internal/vpn/client_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//+build windows

package vpn

func (c *Client) setupSysPrivileges() (suid int, err error) {
return 0, nil
}

func (c *Client) releaseSysPrivileges(suid int) {
return
}
6 changes: 6 additions & 0 deletions internal/vpn/os_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package vpn
import (
"fmt"
"strconv"
"syscall"
)

// SetupTUN sets the allocated TUN interface up, setting its IP, gateway, netmask and MTU.
Expand Down Expand Up @@ -36,3 +37,8 @@ func DeleteRoute(ipCIDR, gateway string) error {

return run("route", "delete", "-net", ip, gateway, netmask)
}

// Setuid sets uid of current OS user.
func Setuid(uid int) error {
return syscall.Setuid(uid)
}
7 changes: 7 additions & 0 deletions internal/vpn/os_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,10 @@ func DeleteRoute(ipCIDR, gateway string) error {
cmd := fmt.Sprintf(deleteRouteCMDFmt, ip, netmask, gateway)
return run("cmd", "/C", cmd)
}

// Setuid sets uid of current OS user.
func Setuid(_ int) (err error) {
// this is unsupported yet, but we allow app to be run with super user,
// so no privilege escalation, and no error here.
return nil
}
24 changes: 24 additions & 0 deletions vendor/github.com/syndtr/gocapability/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 2af0ffa

Please sign in to comment.