Skip to content

Commit

Permalink
fix: (windows) wireguard command permission elevation
Browse files Browse the repository at this point in the history
  • Loading branch information
l-hellmann committed May 5, 2024
1 parent 4504d1b commit db8b196
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 37 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea
.sandbox
include/
.sandbox/
.tool-versions
Expand Down
10 changes: 5 additions & 5 deletions src/archiveClient/handler_findGitFiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ package archiveClient
import (
"bufio"
"bytes"
"context"
"io"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/pkg/errors"
"github.com/zeropsio/zcli/src/cmdRunner"
)

func (h *Handler) FindGitFiles(workingDir string) (res []File, _ error) {
func (h *Handler) FindGitFiles(ctx context.Context, workingDir string) (res []File, _ error) {
workingDir, err := filepath.Abs(workingDir)
if err != nil {
return nil, err
Expand All @@ -28,8 +28,8 @@ func (h *Handler) FindGitFiles(workingDir string) (res []File, _ error) {
}
}

createCmd := func(name string, arg ...string) *exec.Cmd {
cmd := exec.Command(name, arg...)
createCmd := func(name string, arg ...string) *cmdRunner.ExecCmd {
cmd := cmdRunner.CommandContext(ctx, name, arg...)
cmd.Dir = workingDir
return cmd
}
Expand Down Expand Up @@ -102,7 +102,7 @@ func (h *Handler) FindGitFiles(workingDir string) (res []File, _ error) {
return res, nil
}

func (h *Handler) listFiles(cmd *exec.Cmd, fn func(path string) error) error {
func (h *Handler) listFiles(cmd *cmdRunner.ExecCmd, fn func(path string) error) error {
output, err := cmdRunner.Run(cmd)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/servicePush.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func servicePushCmd() *cmdBuilder.Cmd {
return err
}
defer os.Remove(tempFile)
files, err := arch.FindGitFiles(cmdData.Params.GetString("workingDir"))
files, err := arch.FindGitFiles(ctx, cmdData.Params.GetString("workingDir"))
if err != nil {
return err
}
Expand Down
19 changes: 7 additions & 12 deletions src/cmd/vpnUp.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,14 @@ func vpnUpCmd() *cmdBuilder.Cmd {
return err
}

if err := func() error {
f, err := file.Open(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
if err != nil {
return err
}
defer f.Close()
f, err := file.Open(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileMode)
if err != nil {
return err
}
defer f.Close()

err = wg.GenerateConfig(f, privateKey, vpnSettings)
if err != nil {
return err
}
return nil
}(); err != nil {
err = wg.GenerateConfig(f, privateKey, vpnSettings)
if err != nil {
return err
}

Expand Down
46 changes: 46 additions & 0 deletions src/cmdRunner/execCmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cmdRunner

import (
"context"
"os/exec"
)

type Func func(ctx context.Context) error

func CommandContext(ctx context.Context, cmd string, args ...string) *ExecCmd {
return &ExecCmd{
Cmd: exec.CommandContext(ctx, cmd, args...),
ctx: ctx,
}
}

type ExecCmd struct {
*exec.Cmd
ctx context.Context

Check failure on line 19 in src/cmdRunner/execCmd.go

View workflow job for this annotation

GitHub Actions / Build && tests for linux amd64

found a struct that contains a context.Context field (containedctx)

Check failure on line 19 in src/cmdRunner/execCmd.go

View workflow job for this annotation

GitHub Actions / Build && tests for linux 386

found a struct that contains a context.Context field (containedctx)

Check failure on line 19 in src/cmdRunner/execCmd.go

View workflow job for this annotation

GitHub Actions / Build && tests for darwin amd64

found a struct that contains a context.Context field (containedctx)
before Func
after Func
}

func (e *ExecCmd) SetBefore(f Func) *ExecCmd {
e.before = f
return e
}

func (e *ExecCmd) execBefore() error {
if e.before == nil {
return nil
}
return e.before(e.ctx)
}

func (e *ExecCmd) SetAfter(f Func) *ExecCmd {
e.after = f
return e
}

func (e *ExecCmd) execAfter() error {
if e.after == nil {
return nil
}
return e.after(e.ctx)
}
17 changes: 10 additions & 7 deletions src/cmdRunner/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,8 @@ var ErrIpAlreadySet = errors.New("RTNETLINK answers: File exists")
var ErrCannotFindDevice = errors.New(`Cannot find device "wg0"`)
var ErrOperationNotPermitted = errors.New(`Operation not permitted`)

type ExecErrInterface interface {
error
ExitCode() int
}

type execError struct {
cmd *exec.Cmd
cmd *ExecCmd
prev error
exitCode int
}
Expand All @@ -39,13 +34,17 @@ func (e execError) Is(target error) bool {
return errors.Is(e.prev, target)
}

func Run(cmd *exec.Cmd) ([]byte, ExecErrInterface) {
func Run(cmd *ExecCmd) ([]byte, error) {
output := &bytes.Buffer{}
errOutput := &bytes.Buffer{}
cmd.Stdout = output
cmd.Stderr = errOutput
cmd.Env = append(os.Environ(), cmd.Env...)

if err := cmd.execBefore(); err != nil {
return nil, err
}

if err := cmd.Run(); err != nil {
exitCode := 0
var exitError *exec.ExitError
Expand Down Expand Up @@ -81,5 +80,9 @@ func Run(cmd *exec.Cmd) ([]byte, ExecErrInterface) {
return nil, execError
}

if err := cmd.execAfter(); err != nil {
return nil, err
}

return output.Bytes(), nil
}
9 changes: 5 additions & 4 deletions src/wg/darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"text/template"

"github.com/pkg/errors"
"github.com/zeropsio/zcli/src/cmdRunner"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"

"github.com/zeropsio/zcli/src/i18n"
Expand All @@ -34,12 +35,12 @@ func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.Proj
return template.Must(template.New("wg template").Parse(vpnTmpl)).Execute(f, data)
}

func UpCmd(ctx context.Context, filePath string) (err *exec.Cmd) {
return exec.CommandContext(ctx, "wg-quick", "up", filePath)
func UpCmd(ctx context.Context, filePath string) (err *cmdRunner.ExecCmd) {
return cmdRunner.CommandContext(ctx, "wg-quick", "up", filePath)
}

func DownCmd(ctx context.Context, filePath, _ string) (err *exec.Cmd) {
return exec.CommandContext(ctx, "wg-quick", "down", filePath)
func DownCmd(ctx context.Context, filePath, _ string) (err *cmdRunner.ExecCmd) {
return cmdRunner.CommandContext(ctx, "wg-quick", "down", filePath)
}

var vpnTmpl = `
Expand Down
9 changes: 5 additions & 4 deletions src/wg/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"text/template"

"github.com/pkg/errors"
"github.com/zeropsio/zcli/src/cmdRunner"
"github.com/zeropsio/zcli/src/i18n"
"github.com/zeropsio/zerops-go/dto/output"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
Expand All @@ -33,12 +34,12 @@ func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.Proj
return template.Must(template.New("wg template").Parse(vpnTmpl)).Execute(f, data)
}

func UpCmd(ctx context.Context, filePath string) (err *exec.Cmd) {
return exec.CommandContext(ctx, "wg-quick", "up", filePath)
func UpCmd(ctx context.Context, filePath string) (err *cmdRunner.ExecCmd) {
return cmdRunner.CommandContext(ctx, "wg-quick", "up", filePath)
}

func DownCmd(ctx context.Context, filePath, _ string) (err *exec.Cmd) {
return exec.CommandContext(ctx, "wg-quick", "down", filePath)
func DownCmd(ctx context.Context, filePath, _ string) (err *cmdRunner.ExecCmd) {
return cmdRunner.CommandContext(ctx, "wg-quick", "down", filePath)
}

var vpnTmpl = `
Expand Down
88 changes: 84 additions & 4 deletions src/wg/windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@ package wg
import (
"context"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"text/template"

"github.com/pkg/errors"
"github.com/zeropsio/zcli/src/cmdRunner"
"github.com/zeropsio/zcli/src/constants"
"github.com/zeropsio/zcli/src/i18n"
"github.com/zeropsio/zerops-go/dto/output"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)

// To install wireguard tunnel in windows wireguard.exe has to be run with elevated permissions.
// Only (simple) way I found to achieve this is to run Start-Process cmdlet with param '-Verb RunAS'
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.4

func CheckWgInstallation() error {
_, err := exec.LookPath("wireguard")
if err != nil {
Expand All @@ -33,12 +42,72 @@ func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.Proj
return template.Must(template.New("wg template").Parse(vpnTmpl)).Execute(f, data)
}

func UpCmd(ctx context.Context, filePath string) (err *exec.Cmd) {
return exec.CommandContext(ctx, "wireguard", "/installtunnelservice", filePath)
func UpCmd(ctx context.Context, filePath string) (err *cmdRunner.ExecCmd) {
return cmdRunner.CommandContext(ctx,
"powershell",
"-Command",
"Start-Process", "wireguard",
"-Verb", "RunAs",
"-ArgumentList "+formatArgumentList("/installtunnelservice", filePath),
).
SetBefore(beforeUp(filePath))
}

func DownCmd(ctx context.Context, _, interfaceName string) (err *exec.Cmd) {
return exec.CommandContext(ctx, "wireguard", "/uninstalltunnelservice", interfaceName)
// beforeUp this function tries to remove previous zerops.conf from usual wireguard configuration
// dir (at %ProgramFiles%\WireGuard\Data\Configurations) and copy a newly generated one.
// It fails with error = nil because it's only for windows wireguard GUI.
func beforeUp(zeropsConfPath string) cmdRunner.Func {
return func(_ context.Context) error {
programFiles, set := os.LookupEnv("ProgramFiles")
if !set {
return nil
}

wgConfigDir := filepath.Join(programFiles, "WireGuard", "Data", "Configurations")
stat, err := os.Stat(wgConfigDir)
if err != nil {
return nil
}
if !stat.IsDir() {
return nil
}

wgConfFile := filepath.Join(wgConfigDir, constants.WgConfigFile)
// remove previous zerops.conf encrypted by wireguard.exe thus ending with .dpapi
// https://git.zx2c4.com/wireguard-windows/about/docs/enterprise.md
_ = os.Remove(wgConfFile + ".dpapi")

wgConf, err := os.OpenFile(filepath.Join(wgConfigDir, constants.WgConfigFile), os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return nil
}
defer wgConf.Close()

zeropsConf, err := os.OpenFile(zeropsConfPath, os.O_RDONLY, 0666)
if err != nil {
_ = os.Remove(wgConfFile)
return nil
}
defer zeropsConf.Close()

_, err = io.Copy(wgConf, zeropsConf)
if err != nil {
_ = os.Remove(wgConfFile)
return nil
}

return nil
}
}

func DownCmd(ctx context.Context, _, interfaceName string) (err *cmdRunner.ExecCmd) {
return cmdRunner.CommandContext(ctx,
"powershell",
"-Command",
"Start-Process", "wireguard",
"-Verb", "RunAs",
"-ArgumentList "+formatArgumentList("/uninstalltunnelservice", interfaceName),
)
}

var vpnTmpl = `
Expand All @@ -60,3 +129,14 @@ Endpoint = {{.ProjectIpv4SharedEndpoint}}
PersistentKeepalive = 5
`

func formatArgumentList(args ...string) string {
for i, a := range args {
args[i] = quote(a)
}
return strings.Join(args, ", ")
}

func quote(in string) string {
return `"` + in + `"`
}

0 comments on commit db8b196

Please sign in to comment.