aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-11-21 15:45:08 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2020-11-22 22:00:32 +0100
commit224336aa077020c35d3feb8a534f92c814b232e9 (patch)
treea6b366f7061ba69c31138b1961f30994b9dfc38e
parentconf: allow administrators to add and remove configs easily (diff)
downloadwireguard-windows-224336aa077020c35d3feb8a534f92c814b232e9.tar.xz
wireguard-windows-224336aa077020c35d3feb8a534f92c814b232e9.zip
tunnel: enable {Pre,Post}{Up,Down} scripts gated behind admin knob
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--adminregistry.md9
-rw-r--r--conf/config.go4
-rw-r--r--conf/filewriter_windows.go2
-rw-r--r--conf/parser.go8
-rw-r--r--conf/writer.go13
-rw-r--r--services/errors.go3
-rw-r--r--tunnel/scriptrunner.go78
-rw-r--r--tunnel/service.go35
-rw-r--r--ui/confview.go25
-rw-r--r--ui/syntax/highlighter.go4
10 files changed, 169 insertions, 12 deletions
diff --git a/adminregistry.md b/adminregistry.md
index c7b8e559..1f4c8d44 100644
--- a/adminregistry.md
+++ b/adminregistry.md
@@ -16,3 +16,12 @@ users belonging to the Network Configuration Operators builtin group
- Quitting the manager is forbidden.
However, basic functionality such as starting and stopping tunnels remains intact.
+
+#### `HKLM\Software\WireGuard\DangerousScriptExecution`
+
+When this key is set to `DWORD(1)`, the tunnel service will execute the commands
+specified in the `PreUp`, `PostUp`, `PreDown`, and `PostDown` options of a
+tunnel configuration. Note that this execution is done as the Local System user,
+which runs with the highest permissions on the operating system, and is therefore
+a real target of malware. Therefore, you should enable this option only with the
+utmost trepidation.
diff --git a/conf/config.go b/conf/config.go
index 1ce1988d..9eb6157e 100644
--- a/conf/config.go
+++ b/conf/config.go
@@ -49,6 +49,10 @@ type Interface struct {
MTU uint16
DNS []net.IP
DNSSearch []string
+ PreUp string
+ PostUp string
+ PreDown string
+ PostDown string
}
type Peer struct {
diff --git a/conf/filewriter_windows.go b/conf/filewriter_windows.go
index 9fb1f566..8860ba31 100644
--- a/conf/filewriter_windows.go
+++ b/conf/filewriter_windows.go
@@ -69,4 +69,4 @@ func writeEncryptedFile(destination string, contents []byte) error {
return err
}
return nil
-} \ No newline at end of file
+}
diff --git a/conf/parser.go b/conf/parser.go
index da21e796..17b0e21f 100644
--- a/conf/parser.go
+++ b/conf/parser.go
@@ -282,6 +282,14 @@ func FromWgQuick(s string, name string) (*Config, error) {
conf.Interface.DNS = append(conf.Interface.DNS, a)
}
}
+ case "preup":
+ conf.Interface.PreUp = val
+ case "postup":
+ conf.Interface.PostUp = val
+ case "predown":
+ conf.Interface.PreDown = val
+ case "postdown":
+ conf.Interface.PostDown = val
default:
return nil, &ParseError{l18n.Sprintf("Invalid key for [Interface] section"), key}
}
diff --git a/conf/writer.go b/conf/writer.go
index 365e65e9..6b16f843 100644
--- a/conf/writer.go
+++ b/conf/writer.go
@@ -41,6 +41,19 @@ func (conf *Config) ToWgQuick() string {
output.WriteString(fmt.Sprintf("MTU = %d\n", conf.Interface.MTU))
}
+ if len(conf.Interface.PreUp) > 0 {
+ output.WriteString(fmt.Sprintf("PreUp = %s\n", conf.Interface.PreUp))
+ }
+ if len(conf.Interface.PostUp) > 0 {
+ output.WriteString(fmt.Sprintf("PostUp = %s\n", conf.Interface.PostUp))
+ }
+ if len(conf.Interface.PreDown) > 0 {
+ output.WriteString(fmt.Sprintf("PreDown = %s\n", conf.Interface.PreDown))
+ }
+ if len(conf.Interface.PostDown) > 0 {
+ output.WriteString(fmt.Sprintf("PostDown = %s\n", conf.Interface.PostDown))
+ }
+
for _, peer := range conf.Peers {
output.WriteString("\n[Peer]\n")
diff --git a/services/errors.go b/services/errors.go
index 19bfebca..86820b9f 100644
--- a/services/errors.go
+++ b/services/errors.go
@@ -30,6 +30,7 @@ const (
ErrorTrackTunnels
ErrorEnumerateSessions
ErrorDropPrivileges
+ ErrorRunScript
ErrorWin32
)
@@ -65,6 +66,8 @@ func (e Error) Error() string {
return "Unable to enumerate current sessions"
case ErrorDropPrivileges:
return "Unable to drop privileges"
+ case ErrorRunScript:
+ return "An error occurred while running a configuration script command"
case ErrorWin32:
return "An internal Windows error has occurred"
default:
diff --git a/tunnel/scriptrunner.go b/tunnel/scriptrunner.go
new file mode 100644
index 00000000..d34947db
--- /dev/null
+++ b/tunnel/scriptrunner.go
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2020 WireGuard LLC. All Rights Reserved.
+ */
+
+package tunnel
+
+import (
+ "bufio"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+
+ "golang.org/x/sys/windows"
+
+ "golang.zx2c4.com/wireguard/windows/conf"
+)
+
+func runScriptCommand(command, interfaceName string) error {
+ if len(command) == 0 {
+ return nil
+ }
+ if !conf.AdminBool("DangerousScriptExecution") {
+ log.Printf("Skipping execution of script, because dangerous script execution is safely disabled: %#q", command)
+ return nil
+ }
+ command = strings.ReplaceAll(command, "%i", interfaceName)
+ log.Printf("Executing: %#q", command)
+ comspec, _ := os.LookupEnv("COMSPEC")
+ if len(comspec) == 0 {
+ system32, err := windows.GetSystemDirectory()
+ if err != nil {
+ return err
+ }
+ comspec = filepath.Join(system32, "cmd.exe")
+ }
+
+ devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+ defer devNull.Close()
+ reader, writer, err := os.Pipe()
+ if err != nil {
+ return err
+ }
+ process, err := os.StartProcess(comspec, nil /* CmdLine below */, &os.ProcAttr{
+ Files: []*os.File{devNull, writer, writer},
+ Sys: &syscall.SysProcAttr{
+ HideWindow: true,
+ CmdLine: fmt.Sprintf("cmd /c %s", command),
+ },
+ })
+ writer.Close()
+ if err != nil {
+ reader.Close()
+ return err
+ }
+ go func() {
+ scanner := bufio.NewScanner(reader)
+ for scanner.Scan() {
+ log.Printf("cmd> %s", scanner.Text())
+ }
+ }()
+ state, err := process.Wait()
+ reader.Close()
+ if err != nil {
+ return err
+ }
+ if state.ExitCode() == 0 {
+ return nil
+ }
+ log.Printf("Command error exit status: %d", state.ExitCode())
+ return windows.ERROR_GENERIC_COMMAND_FAILED
+}
diff --git a/tunnel/service.go b/tunnel/service.go
index 33b5c116..e07fe139 100644
--- a/tunnel/service.go
+++ b/tunnel/service.go
@@ -41,6 +41,7 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
var uapi net.Listener
var watcher *interfaceWatcher
var nativeTun *tun.NativeTun
+ var config *conf.Config
var err error
serviceError := services.ErrorSuccess
@@ -84,6 +85,9 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
}
}()
+ if logErr == nil && dev != nil && config != nil {
+ logErr = runScriptCommand(config.Interface.PreDown, config.Name)
+ }
if watcher != nil {
watcher.Destroy()
}
@@ -93,6 +97,9 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
if dev != nil {
dev.Close()
}
+ if logErr == nil && dev != nil && config != nil {
+ _ = runScriptCommand(config.Interface.PostDown, config.Name)
+ }
stopIt <- true
log.Println("Shutting down")
}()
@@ -113,19 +120,19 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
}
}()
- conf, err := conf.LoadFromPath(service.Path)
+ config, err = conf.LoadFromPath(service.Path)
if err != nil {
serviceError = services.ErrorLoadConfiguration
return
}
- conf.DeduplicateNetworkEntries()
+ config.DeduplicateNetworkEntries()
err = CopyConfigOwnerToIPCSecurityDescriptor(service.Path)
if err != nil {
serviceError = services.ErrorLoadConfiguration
return
}
- logPrefix := fmt.Sprintf("[%s] ", conf.Name)
+ logPrefix := fmt.Sprintf("[%s] ", config.Name)
log.SetPrefix(logPrefix)
log.Println("Starting", version.UserAgent())
@@ -151,14 +158,14 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
}
log.Println("Resolving DNS names")
- uapiConf, err := conf.ToUAPI()
+ uapiConf, err := config.ToUAPI()
if err != nil {
serviceError = services.ErrorDNSLookup
return
}
log.Println("Creating Wintun interface")
- wintun, err := tun.CreateTUNWithRequestedGUID(conf.Name, deterministicGUID(conf), 0)
+ wintun, err := tun.CreateTUNWithRequestedGUID(config.Name, deterministicGUID(config), 0)
if err != nil {
serviceError = services.ErrorCreateWintun
return
@@ -171,7 +178,13 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
log.Printf("Using Wintun/%d.%d", (wintunVersion>>16)&0xffff, wintunVersion&0xffff)
}
- err = enableFirewall(conf, nativeTun)
+ err = runScriptCommand(config.Interface.PreUp, config.Name)
+ if err != nil {
+ serviceError = services.ErrorRunScript
+ return
+ }
+
+ err = enableFirewall(config, nativeTun)
if err != nil {
serviceError = services.ErrorFirewall
return
@@ -190,7 +203,7 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
dev = device.NewDevice(wintun, logger)
log.Println("Setting interface configuration")
- uapi, err = ipc.UAPIListen(conf.Name)
+ uapi, err = ipc.UAPIListen(config.Name)
if err != nil {
serviceError = services.ErrorUAPIListen
return
@@ -205,7 +218,7 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
log.Println("Bringing peers up")
dev.Up()
- watcher.Configure(dev, conf, nativeTun)
+ watcher.Configure(dev, config, nativeTun)
log.Println("Listening for UAPI requests")
go func() {
@@ -218,6 +231,12 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
}
}()
+ err = runScriptCommand(config.Interface.PostUp, config.Name)
+ if err != nil {
+ serviceError = services.ErrorRunScript
+ return
+ }
+
changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
log.Println("Startup complete")
diff --git a/ui/confview.go b/ui/confview.go
index 089f6572..c712c29d 100644
--- a/ui/confview.go
+++ b/ui/confview.go
@@ -50,6 +50,7 @@ type interfaceView struct {
mtu *labelTextLine
addresses *labelTextLine
dns *labelTextLine
+ scripts *labelTextLine
toggleActive *toggleActiveLine
lines []widgetsLine
}
@@ -305,6 +306,7 @@ func newInterfaceView(parent walk.Container) (*interfaceView, error) {
{l18n.Sprintf("MTU:"), &iv.mtu},
{l18n.Sprintf("Addresses:"), &iv.addresses},
{l18n.Sprintf("DNS servers:"), &iv.dns},
+ {l18n.Sprintf("Scripts:"), &iv.scripts},
}
if iv.lines, err = createLabelTextLines(items, parent, &disposables); err != nil {
return nil, err
@@ -402,6 +404,29 @@ func (iv *interfaceView) apply(c *conf.Interface) {
} else {
iv.dns.hide()
}
+
+ var scriptsInUse []string
+ if len(c.PreUp) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("pre-up"))
+ }
+ if len(c.PostUp) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("post-up"))
+ }
+ if len(c.PreDown) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("pre-down"))
+ }
+ if len(c.PostDown) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("post-down"))
+ }
+ if len(scriptsInUse) > 0 {
+ if conf.AdminBool("DangerousScriptExecution") {
+ iv.scripts.show(strings.Join(scriptsInUse, l18n.EnumerationSeparator()))
+ } else {
+ iv.scripts.show(l18n.Sprintf("disabled, per policy"))
+ }
+ } else {
+ iv.scripts.hide()
+ }
}
func (pv *peerView) widgetsLines() []widgetsLine {
diff --git a/ui/syntax/highlighter.go b/ui/syntax/highlighter.go
index fa6a6530..f6edc712 100644
--- a/ui/syntax/highlighter.go
+++ b/ui/syntax/highlighter.go
@@ -405,8 +405,6 @@ func (s stringSpan) field() field {
return fieldEndpoint
case s.isCaselessSame("PersistentKeepalive"):
return fieldPersistentKeepalive
- }
- /* TODO: uncomment this once we support these in the client
case s.isCaselessSame("PreUp"):
return fieldPreUp
case s.isCaselessSame("PostUp"):
@@ -415,7 +413,7 @@ func (s stringSpan) field() field {
return fieldPreDown
case s.isCaselessSame("PostDown"):
return fieldPostDown
- */
+ }
return fieldInvalid
}