# Using WireGuard on Windows as non-admin - proper solution?

Patrik Holmqvist patrik.holmqvist at su.se
Sun Nov 15 16:28:27 CET 2020

Hi!

We (Stockholm University) are also interested in a version that does not require local administrator permissions to start the application and handle tunnel configuration.
We are currently evaluating WireGuard as a VPN solution for our employees (and maybe students in the future) since we are not really happy with our current solution.

Unfortunately we ran into an issue that has been discussed on this list before, that WireGuard for Windows requires the user to be Local Admin on the machine in order to start the application.

We are not very keen on making all our users Local Admin on their managed machines, and we think we share that with most larger enterprise environments since it goes against best security practices [0] [1]. We thought that the builtin group "Network Configuration Operators" could be a good alternative to Local Admin as to who are allowed to manage WireGuard. So we decided to try it out and patched the code, see diff below. (This was just to test the concept. We do not claim that this is a good solution, but it was what we managed to produced with limited time and knowledge)

From our test it seems to work, the users in the NCO group are fully allowed to manage the tunnels. The installation is of course required to be done by a Local Admin or as in our setup by SCCM [2].

Do you think you could implement something like this to the upstream version of WireGuard in order to allow larger organizations to easier roll out WireGuard in a more selective manner? We are also fine with removing the requirement to be a member of any particular group if that could be achieved.

[2] https://en.wikipedia.org/wiki/Microsoft_System_Center_Configuration_Manager

Best regards and thank you for all the fine work with this great product!

--
Patrik

---
elevate/membership.go | 24 +++++++++++++++++++-----
main.go               |  2 ++
2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/elevate/membership.go b/elevate/membership.go
index 07c2ef69..c1fdcb7b 100644
--- a/elevate/membership.go
+++ b/elevate/membership.go
@@ -14,14 +14,28 @@ func isAdmin(token windows.Token) bool {
if err != nil {
return false
}
-                           var checkableToken windows.Token
-                           err = windows.DuplicateTokenEx(token, windows.TOKEN_QUERY|windows.TOKEN_IMPERSONATE, nil, windows.SecurityIdentification, windows.TokenImpersonation, &checkableToken)
if err != nil {
return false
}
-                           defer checkableToken.Close()
-                           return isAdmin && err == nil
+                          var checkableNetworkOperatorsToken windows.Token
+                          defer checkableNetworkOperatorsToken.Close()
+                          err = windows.DuplicateTokenEx(token, windows.TOKEN_QUERY|windows.TOKEN_IMPERSONATE, nil, windows.SecurityIdentification, windows.TokenImpersonation, &checkableAdminToken)
+                          if err != nil {
+                                                       return false
+                          }
+                          err = windows.DuplicateTokenEx(token, windows.TOKEN_QUERY|windows.TOKEN_IMPERSONATE, nil, windows.SecurityIdentification, windows.TokenImpersonation, &checkableNetworkOperatorsToken)
+                          if err != nil {
+                                                       return false
+                          }
+                          isNetworkOperator, err := checkableNetworkOperatorsToken.IsMember(builtinNetworkOperatorsGroup)
+                          if isAdmin || isNetworkOperator {
+                                                       return true && err == nil
+                          }
+                          return false && err == nil
}

func TokenIsElevatedOrElevatable(token windows.Token) bool {
diff --git a/main.go b/main.go
index 79dfcdfc..e8a48e8e 100644
--- a/main.go
+++ b/main.go
@@ -78,6 +78,7 @@ func checkForAdminGroup() {
}
defer processToken.Close()
if !elevate.TokenIsElevatedOrElevatable(processToken) {
+                                                       // TODO Logic for multiple groups needs to be added for the correct error message to be displayed
fatalf("WireGuard may only be used by users who are a member of the Builtin %s group.", elevate.AdminGroupName())
}
}
@@ -85,6 +86,7 @@ func checkForAdminGroup() {
if !adminDesktop && err == nil {
+                                                       // TODO Logic for multiple groups needs to be added for the correct error message to be displayed
fatalf("WireGuard is running, but the UI is only accessible from desktops of the Builtin %s group.", elevate.AdminGroupName())
}
}
--
2.20.1

-----Original Message-----
From: WireGuard <wireguard-bounces at lists.zx2c4.com> On Behalf Of Jason A. Donenfeld
Sent: den 13 november 2020 03:16
To: vh217 at werehub.org
Cc: WireGuard mailing list <wireguard at lists.zx2c4.com>
Subject: Re: Using WireGuard on Windows as non-admin - proper solution?

Hi Viktor,

I am actually interested in solving this. I took an initial stab at it here, but I'm not super comfortable with the implementation or the security implications:
https://git.zx2c4.com/wireguard-windows/commit/?h=jd/unprivd-knob

Aside from doing this from within our existing UI, the general solution using the service-based building blocks is to simply allow users to start and stop services that begin with "WireGuardTunnel$". So the flow is something like: 1. wireguard /installtunnelservice path\to\sometunnel.conf. 2. Change the ACLs on WireGuardTunnel$sometunnel to fit your user.
3. Have the user use net start and net stop, or similar, to control whether the service is up or down.

That's not super pretty, but it should work, and it is automatable.
Meanwhile, I'll keep thinking about various ways to do this in a more "first-party" way.

Jason