OpenVPN / openvpn-gui

OpenVPN GUI is a graphical frontend for OpenVPN running on Windows 7 / 8 / 10. It creates an icon in the notification area from which you can control OpenVPN to start/stop your VPN tunnels, view the log and do other useful things.
Other
1.44k stars 404 forks source link

OpenVPN GUI not connecting when started by Windows Task Scheduler #626

Closed MWinhard closed 1 year ago

MWinhard commented 1 year ago

Environment:

The following line is working as expected when called from a cmd file and running this cmd file by double clicking it in Windows Explorer:

start /b "" "C:\Program Files\OpenVPN\bin\openvpn-gui.exe" --silent_connection 1 --disable_popup_messages --show_script_window 0 --show_balloon 0 --command connect myconnection.ovpn

Problem:

Calling the same cmd file from Windows Task Scheduler doesn't connect, even if I'm logged on to the server via RDP at the same time. The myconnection.log file is not touched.

selvanair commented 1 year ago

Do you have "Run whether user is logged on or not" selected? That option will mark the task as non-interactive and the taskwill have no access to the desktop. Then UI will not show up even if the user is logged in when the task triggers. Set it to run only when the user is logged in.

If you want to keep up a connection at all times, and do not require password input or other user interaction, consider starting it at boot using OpenVPNService. No task scheduling needed. In recent versions of the GUI, such configs will be visible in the GUI menu as well and can be interactively controlled if/when required. But a running GUI process is not needed to keep it connected. See "Persistent Connections" in the README of this repo. Note that openvpn will run as SYSTEM in this case.

MWinhard commented 1 year ago

Thank you for the quick reply.

Yes, I have "Run whether user is logged on or not" selected.

I cannot set it to run only when the user is logged in, because usually there's no user logged in when this task runs.

I cannot run openvpn.exe because my user on this machine is not a member of the Administrators group.

The machine is a rented Server 2022 VM in data center. The only people with admin privilege are the data center admins.

Keep up a vpn connection at all times is also not allowed for security reasons. Says the admin of the target network.

What I have to do here every night is open a vpn connection to some firewall, copy some files to a NAS behind the firewall, then close the connection.

In the end this process will be replicated for several target networks, one after the other. Every night.

selvanair commented 1 year ago

Your option would be to write a script to start openvpn.exe via the interactive service without using the GUI. Interactive service is required for privileged operations (like setting up route(s)) as openvpn.exe will run as user.

This should be easy to do using a powershell script but we do not have any docs on how to write one. You may have to consult the GUI source and see how the GUI does it and then adapt it (see openvpn.c). Essentially one has to open the service pipe \\.\pipe\openvpn\service and write a ~double~ null-terminated string made of config-directory-name, openvpn-commandline-options and management-password to the pipe. The password could be an empty string if not required or management interface is not in use. Create an event and pass its name as ~--exit-event <event-name>~ --service <event-name> in the options string so that the connection can be stopped by triggering it.

Running openvpn.exe this way does not require any desktop access and will work whether user is logged-in or not.

MWinhard commented 1 year ago

Thank you for the technical insights. I'll give it a try. Some questions remaining, though.

I haven't configured any management password. So, with an empty string as my password I have to write 1 null-terminator after the config-directory-name, and 3 null-terminators after the openvpn-commandline-options, like this?

"config-directory-name" + "\0" + "openvpn-commandline-options" + "\0\0\0"

Is the exit event really necessary? Or can I just call the named pipe a second time to disconnect, as I do it with openvpn-gui.exe?

Before opening this issue I found several questions like mine using google search. But no easy solution. How about a new switch --nogui for future openvpn-gui.exe? 😊

selvanair commented 1 year ago

"config-directory-name" + "\0" + "openvpn-commandline-options" + "\0\0\0"

IIRC, just two terminators (not 3) at the end should be enough in this case. It's parsed as three strings, not as a double-null terminated string of strings (unlike what I assumed in my previous response).

Is the exit event really necessary? Or can I just call the named pipe a second time to disconnect, as I do it with openvpn-gui.exe?

Without an exit-event you will have to force-kill the process to disconnect. There is no way to ask the service to stop the connection. Alternatively you can read the service pipe and parse the message to get the process ID. Then send ctrl-break to the process when ready to exit. Or enable the management interface and send "signal SIGHUP". Using an exit-event is much easier.

Having an option for the GUI to just start openvpn.exe with no user interaction doesn't look attractive to me when a small script could do it. A separate script/program for this, or a "howto" would be useful though.

MWinhard commented 1 year ago

Looks like I'm doing something wrong. There's no error message. But the log file myconnection.log is not touched. I.e., my script does nothing.

Any idea what I'm doing wrong?

function Send-NamedPipeMessage
{
    param(
    # The named pipe to send the message on.
    #[String]$PipeName = "\\.\pipe\openvpn\service"
    [String]$PipeName = "openvpn\service",
    # The computer the named pipe exists on.
    [String]$ComputerName = ".",
    # The message to send the named pipe on.
    [string]$Message,
    # The type of encoding to encode the string with
    [System.Text.Encoding]$Encoding = [System.Text.Encoding]::Unicode,
    # The number of milliseconds before the connection times out
    [int]$ConnectTimeout = 5000
    )

    $stream = New-Object -TypeName System.IO.Pipes.NamedPipeClientStream -ArgumentList $ComputerName,$PipeName,([System.IO.Pipes.PipeDirection]::Out), ([System.IO.Pipes.PipeOptions]::None),([System.Security.Principal.TokenImpersonationLevel]::Impersonation)
    $stream.Connect($ConnectTimeout)
    $bRequest = $Encoding.GetBytes($Message)
    $cbRequest = $bRequest.Length; 
    $stream.Write($bRequest, 0, $cbRequest); 
    $stream.Dispose()
}

$OVPN_CONFIG = "C:\Users\me\OpenVPN\config\myconnection.ovpn"
$OVPN_CREDENTIALS = "D:\Scripts\mycredentials.txt"

$Message = "C:\Users\me\OpenVPN\config'0"
$Message = $Message + "--config " + $OVPN_CONFIG + " --auth-user-pass " + $OVPN_CREDENTIALS + " --auth-nocache'0"
$Message = $Message + "'0"

Send-NamedPipeMessage -Message $Message
selvanair commented 1 year ago

Looks good to me except that '0 for NUL should use back-tick but that may be mangled by github. Check the eventlog for any errors from the service. If there are no errors it will log an informational message saying its athorizing you to run this config based on your group membership. If OpenVPN.exe exits due to errors in the config that also gets logged to the eventlog.

Do you have --log ... in the config file? The GUI adds it automatically but you have to add it manually in this case. Check the taskmgr and kill any running openvpn.exe processes: openvpn.exe started without a log may hang around as stdout gets redirected to NUL in that case. To troubleshoot, I would add --log test.log before --config .... That will override any --log .. option in the config. If you do this, the log will go to C:\Users\me\OpenVPN\config\test.log based on the startup directory.

If that works, rerun with --log C:\Users\me\OpenVPN\logs\<profile-name>.log to get logs in the standard location or add that option to the config file as the first line.

MWinhard commented 1 year ago

Changed the ' to back-tick and it works. 😁 Thank you very much. Also for the log file hints.

Now on with creating an event in powershell. Never did this before, too. 😉

jochenkirstaetter commented 1 year ago

Hi,

Joining the conversation with a few more details. FYI, I'm working with @MWinhard on the same issue and stumbled initially over this observation. However Markus was faster posting the issue here. 😺

As Markus mentioned, it works with the information provided and I checked the openvpn.c sources for a few more details. One of the early issues I came across in my environment is the handling of DNS resolution which could be an issue.

2023-05-15 13:01:11 OpenVPN 2.6.4 [git:v2.6.4/b4f749f14a8edc75] Windows-MSVC [SSL (OpenSSL)] [LZO] [LZ4] [PKCS11] [AEAD] [DCO] built on May 11 2023
2023-05-15 13:01:11 Windows version 10.0 (Windows 10 or greater), amd64 executable
2023-05-15 13:01:11 library versions: OpenSSL 3.1.0 14 Mar 2023, LZO 2.10
2023-05-15 13:01:11 DCO version: v0
2023-05-15 13:01:11 RESOLVE: Cannot resolve host address: www.example.com:1194 (No such host is known. )
2023-05-15 13:01:11 RESOLVE: Cannot resolve host address: www.example.com:1194 (No such host is known. )
2023-05-15 13:01:11 Could not determine IPv4/IPv6 protocol
2023-05-15 13:01:11 SIGUSR1[soft,Could not determine IPv4/IPv6 protocol] received, process restarting

How is this handled in the openvpn-gui client? I see similar in the log there but it gets resolved after a few attempts though. What would be recommendations for PowerShell scripting? Any retry feature available here?

Also, I checked the parameters and options of the creation of the named pipe in the C++ source (READ | WRITE) and it seems that the sample of Markus uses different options (OUT). What are the recommended ones?

Additionally, I see that the openvpn-gui applies a filter to ignore the configured route method in line 2559. Shall we applied same option in our script?

Thanks for your assistance and time. JoKi

selvanair commented 1 year ago

The GUI doesn't do anything to set DNS, it's all done by openvpn.exe using the host DNS settings. Could be that you have some routes or DNS settings left behind from a previous connection that was not gracefully terminated? Use the OS tools (ipconfig, route etc.) to check the current DNS and route settings.

Making a setup like this robust may take some work -- at the very least ensure openvpn.exe is not already running to avoid multiple instances using the same profile which will not work. Based on your use case, if you do not need features like redirect-gateway, block-outside-dns, DNS resolution through VPN etc, do not use them. Also, if possible, use IP address instead of hostname for the server or have a way to resolve the server address out-of-band (not through the VPN).

The "pull-filter ignore" for route method is added by the GUI to work around servers that push such options which break setting routes. If your server is not pushing it (or if you have control over the server not to do that) no need for the filter. But adding it can't hurt. We need to see client logs with verb=4 to see what the server is pushing, what routes are being set etc.

selvanair commented 1 year ago

Correction: the option for exit-event is --service <event-name> with an optional second argument for the initial state.

jochenkirstaetter commented 1 year ago

Hello @selvanair

Thanks for your patience. Meanwhile, I worked through the C++ sources and got some inspiration for a few things to work on. Quick feedback on the items resolved.

Show me the code is always best, so here's what I have so far.

$Message = $OVPN_CONFIG_DIR + "`0"
# Ref: https://github.com/OpenVPN/openvpn-gui/blob/4921b1e544cea6657d187739262b8ce59a70c3bb/openvpn.c#L2559
# Ref: https://github.com/OpenVPN/openvpn-gui/blob/4921b1e544cea6657d187739262b8ce59a70c3bb/openvpn.c#L2519
$Message = $Message + "--log " + $OVPN_LOG_FILE + " --config " + $OVPN_CONFIG_FILE + " --service " + $EXIT_EVENT_NAME + " 0 --pull-filter ignore route-method" + "`0"
$Message = $Message + "`0"

Send-NamedPipeMessage -Message $Message

According to my internet searches, PowerShell is not able to deal with named Event Objects. Following the C++ sources I therefore replicated the Win32 API Calls using C# System.Runtime.InteropServices to run unmanaged code. Right now, from CreateEvent I'm getting a positive handle (IntPtr casted to long), store it in a variable, and later calling CloseHandle with that value returns true.

Again, here's the relevant code:

$Code = @" 
using System;  
using System.Runtime.InteropServices;  

namespace JoKi  
{
    public class Kernel_32
    {
        [DllImport("kernel32.dll")]
        public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);

        [DllImport("kernel32.dll", SetLastError=true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);
    }

    public class OpenVPN
    {
        public static long CreateEvent(string lpName)
        {
            var result = Kernel_32.CreateEvent(IntPtr.Zero, true, false, lpName);
            return (long)result;
        }

        public static bool CloseHandle(long hwnd)
        {
            return Kernel_32.CloseHandle((IntPtr)hwnd);
        }
    }
}
"@
Add-Type $Code
$Exit_Event = [JoKi.OpenVPN]::CreateEvent($EXIT_EVENT_NAME)

However, the process of openvpn.exe remains alive and kicking. :laughing: Maybe something wrong with the casting? I'm not a C++ guy but rather C#. Perhaps I'm spinning up a dummy console project just to be able to debug better. I'll give it a try avoiding the type casting between IntPtr and long.

One thing I have noticed and still have to do: Using quotes for the --log and --config parameters to accept blanks. Currently not important as my test paths do not contain blanks.

What's your take on the stuff posted? Anything smelling fishy or does it look as described in the Wiki page of the OpenVPN Interactive Service?

Many thanks for your time to look into that. And going to use verb=4 as suggested by you.

BTW, nice choice using Named Pipes. I work with them in other projects, too.

jochenkirstaetter commented 1 year ago

Additional note: It's working to establish the VPN connection and set the routes, all as described and as expected. The remaining issue is the proper termination of openvpn.exe using the exit-event instead of taskkill.

Is there significance regarding the exit event name of 17 characters? And does it have to be a combination of process ID and thread ID?

Perhaps that's exactly where I might be going wrong, not respecting an expected format?

Kindly advise. I'm going to dig through the sources of openvpn.exe to understand the --service parameter better.

Best regards, JoKi

selvanair commented 1 year ago

I see no SetEvent() in your code. You have to trigger the event by setting it to make OpenVPN exit. We generally Create the event first, save the handle before launching OpenVPN, and then trigger it when ready to terminate. But it should be okay to just pass the name, and call CreateEvent() and SetEvent() when ready to close the connection.

I would call SetEvent(), wait on the process with a timeout until it exits and then force-terminate in case the process is still active. For exmaple, see DisconnectDaemon() in the GUI sources. Alternatively you can keep the taskkill at the start of the script as a cleanup step in case the process is still running.

The exit event name could be anything but it exists in the global namespace, so avoid name collisions. You could add "Local" prefix to make it session-local if that is appropriate. See docs on CreateEvent. Something like "OpenVPN"+date-time or process-id is a possible choice. Although all these strings are Unicode (UTF16 encoded), stick to ascii characters in this name --- I'm not entirely sure whether non ascii characters will work correctly at the moment. The whole command (message) passed to the service pipe is a UTF16 string, though.

selvanair commented 1 year ago

As for setting a named event using powershell, the following works for me: With openvpn started with --service test

PS > $ev = [System.Threading.EventWaitHandle]::OpenExisting("test")
PS> $ev.set()
PS> $ev.reset()
PS> $ev.close()
jochenkirstaetter commented 1 year ago

Hello @selvanair

Perfect! We have it running as expected.

$Message = $OVPN_CONFIG_DIR + "`0"
$Message = $Message + "--log " + $OVPN_LOG_FILE + " --config " + $OVPN_CONFIG_FILE + " --service " + $EXIT_EVENT_NAME + " 0 --pull-filter ignore route-method" + "`0"
$Message = $Message + "`0"

Send-NamedPipeMessage -Message $Message

$ev = [System.Threading.EventWaitHandle]::OpenExisting($EXIT_EVENT_NAME)
$ev.set()
$ev.reset()
$ev.close()

I put a break point on ev.set() and everything worked nicely with an active connection. Executing that line send SIGHARD signal and terminated the connection. Now we have proper opening and closing of an on-demand OpenVPN connection.

Awesome job, Selva.

Best regards, JoKi

cron2 commented 1 year ago

I wonder if it's possible to polish / generalize this a bit, and add to the OpenVPN (or -GUI) repo, as openvpn-windows-cli of some sorts...?

jochenkirstaetter commented 1 year ago

Hi @cron2

Actually, I'm going to put a blog article about this together and I would like to contribute this to the official OpenVPN wiki. Perhaps it could be of interest that the final polished PowerShell script is added to the package and part of the installation under Windows.

All resources I found online regarding Task Scheduler all deal with "When I log on" but there is no such tutorial as here. Having scheduled on-demand connection(s) does seem to be a valid use case however.

Best regards, JoKi

cron2 commented 1 year ago

Actually, I'm going to put a blog article about this together and I would like to contribute this to the official OpenVPN wiki.

This sounds like a great plan.

Perhaps it could be of interest that the final polished PowerShell script is added to the package and part of the installation under Windows.

I definitely find this interesting. @lstipakov and @flichtenheld need to do the installer changes, but they are always open for new greatness.

MWinhard commented 1 year ago

Sorry, but the powershell solution is NOT running as expected. It also works only in interactive mode, not when called from task scheduler. When called from task scheduler our own log file is written, but ping command says the target ist not available.

This is our current code:

` $OVPN_CNN = "myconnection" $OVPN_BASE_DIR = $env:USERPROFILE + "\OpenVPN"

$OVPN_CONFIG_DIR = $OVPN_BASE_DIR + "\config" $OVPN_CONFIG_FILE = $OVPN_CONFIG_DIR + "\" + $OVPN_CNN + ".ovpn" $OVPN_LOG_DIR = $OVPN_BASE_DIR + "\log" $OVPN_LOG_FILE = $OVPN_LOG_DIR + "\" + $OVPN_CNN + ".log" $OVPN_CREDENTIALS = "D:\Scripts\myconnection.txt"

$EXIT_EVENTNAME = "myconnection" + $PID

function Write-Log($text) { "$(get-date -format "yyyy-MM-dd HH:mm:ss"): $($text)" | out-file "D:\Scripts\myconnection.log" -Append -Encoding Default }

function Send-NamedPipeMessage($Message) { $PipeName = "openvpn\service" $ComputerName = "."

[int]$ConnectTimeout = 5000

$stream = New-Object -TypeName System.IO.Pipes.NamedPipeClientStream -ArgumentList `
    $ComputerName, `
    $PipeName, `
    ([System.IO.Pipes.PipeDirection]::Out), `
    ([System.IO.Pipes.PipeOptions]::None), `
    ([System.Security.Principal.TokenImpersonationLevel]::Impersonation)
$stream.Connect($ConnectTimeout)
$bRequest = $Encoding.GetBytes($Message)
$stream.Write($bRequest, 0, $bRequest.Length)
$stream.Dispose()

}

$Message = $OVPN_CONFIG_DIR + "0" $Message = $Message + "--log " + $OVPN_LOG_FILE + " --config " + $OVPN_CONFIG_FILE + " --verb 4" + " --service " + $EXIT_EVENT_NAME + " 0" + " --pull-filter ignore route-method" + "0" $Message = $Message + "0"

Write-Log "Start"

Write-Log "Exit Event Name: $EXIT_EVENT_NAME" $EXIT_EVENT_NAME

List all registered Named Pipes.

[System.IO.Directory]::GetFiles("\.\pipe\") | out-file "D:\Scripts\myconnection.log" -Append -Encoding Default

Write-Log "Send-NamedPipeMessage " + $Message Send-NamedPipeMessage $Message

Start-Sleep 10 ping 192.168.1.195 | out-file "D:\Scripts\myconnection.log" -Append -Encoding Default

Start-Sleep 10 Write-Log "Fire Exit Event" $ev = [System.Threading.EventWaitHandle]::OpenExisting($EXIT_EVENT_NAME) $ev.set() | Out-Null $ev.reset() | Out-Null $ev.close() | Out-Null

ping 192.168.1.195 | out-file "D:\Scripts\myconnection.log" -Append -Encoding Default

Write-Log "Endrnrn" ` Windows event log shows an error every time I run the code above in task scheduler.

openvpnserv error: 0x20000000 OpenVPN exited with error: exit code = 3221225794

As with openvpn-gui.exe the log file is not touched when called from task scheduler.

Any idea what we are doing wrong?

selvanair commented 1 year ago

This works as expected in my limited tests on Windows 10 with user logged in or not.

openvpnserv error: 0x20000000 OpenVPN exited with error: exit code = 3221225794

Error 0xC000142 (3221225794) indicates "The Application was Unable to Start correctly". So, the process is launched by the service but it fails to startup. Could this be due to some resource limitation when run in a non-interactive session?

Does it work if openvpn.exe is directly started from a scheduled task with user not logged in? It may fail to set routes etc., but good to check whether openvpn.exe can startup or not.

MWinhard commented 1 year ago

@selvanair As we can reproduce the problem on two different machines, there must be something different with our setups. I assume, your scheduled task is running as a user, that's a member of the Administrators group. Is it?

Our scheduled tasks user accounts are no member of the Administrators group. Instead they are

It doesn't make a difference if the scheduled task user is currently logged on via RDP or not.

MWinhard commented 1 year ago

If I choose "Run only when user is logged on" in task scheduler, everything works as expected. Unfortunately that's no option.

MWinhard commented 1 year ago

It also works when the scheduled task user is a member of the Administrators group. But that's no option, too.

MWinhard commented 1 year ago

Running the scheduled task as user SYSTEM works, as user LOCAL SERVICE does not. Both are no option for us. But perhaps it helps narrowing the problem's source.

jochenkirstaetter commented 1 year ago

Hi again, Thanks for the summary and feedback. I forgot to check the task scheduler environment yesterday, probably because I was too excited about the PowerShell "client" for OpenVPN for Windows.

Either way, today I went through the whole range of constellations and I have to report, unfortunately, that a scheduled task that is configured to run as an unprivileged user does not establish the connection. There is an attempt to start the openvpn.exe as monitored with ProcMon but the whole process then terminates with openvpnserv error: 0x20000000 OpenVPN exited with error: exit code = 3221225794 as reported by @MWinhard

Let's put a few terms together.

Unprivileged user (here "MSCC")

Screenshot 2023-05-17 164808

Scheduled Task

Posted as images as GitHub won't accept the XML definition of the task as attachment.

Screenshot 2023-05-17 161143

Screenshot 2023-05-17 161310

Screenshot 2023-05-17 161631

The choice between modern PowerShell Core (pwsh) or built-in (powershell) has no impact on the outcome; same error messages.

Now, in the Window Event Viewer I get the following two entries.

FYI: In case that the script runs elevated or with a privileged user account the first entry then show 'Administrators' and the second entry is absent. Fun Fact: Using SYSTEM account also works as scheduled task. Whereas LOCAL SERVICE fails with same error code.

Also, I ran those constellation with active monitoring using ProcMon from the Microsoft Sysinternals Suite and filtered the entries to include openvpnserv.exe and openvpn.exe only. The ProcMon protocol shows all the specified parameters from the PowerShell are handed over to the Interactive Service which in return then passes the appropriate options to the OpenVPN client application. Meaning, openvpn.exe is started by the scheduled task with the same parameters when launching the script manually. Either way there are no FAILURE results logged but SUCCESS and resolved xyz NOT FOUND entries only. No actual indicator that there could be a permission denied situation. Perhaps my filter might be to narrow including those two applications/processes only. I'm going to verify that.

While playing around with the various user accounts and group assignments I noticed to entries in the Event Viewer:

openvpnserv.exe 
   2.6.4.0 
   645cb955 
   ucrtbase.dll 
   10.0.19041.789 
   2bd748bf 
   c0000409 
   0000000000071208 
   5b80 
   01d9889f8691751c 
   C:\Program Files\OpenVPN\bin\openvpnserv.exe 
   C:\WINDOWS\System32\ucrtbase.dll 
   59fd8457-582d-48dd-87fe-568a9135a1cb 

and another one:

 1312317955627784568 
   5 
   BEX64 
   Not available 
   0 
   openvpnserv.exe 
   2.6.4.0 
   645cb955 
   ucrtbase.dll 
   10.0.19041.789 
   2bd748bf 
   0000000000071208 
   c0000409 
   0000000000000005 

   \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERD208.tmp.dmp \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERD266.tmp.WERInternalMetadata.xml \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERD277.tmp.xml \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERD285.tmp.csv \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WERD2B4.tmp.txt 
   \\?\C:\ProgramData\Microsoft\Windows\WER\ReportArchive\AppCrash_openvpnserv.exe_6dc84221ee8158a35483d694e071e4a6628f97_5a017d55_9aff1096-886b-4e64-8093-ca3f3491e932 

   0 
   59fd8457-582d-48dd-87fe-568a9135a1cb 
   268435456 
   2553f2508d0a614952364a3a498f9978 
   0 

Both entries pointing to that C:\WINDOWS\System32\ucrtbase.dll dynamic link library. Maybe that could be a lead?

Final remark, I also opened a Command Prompt / PowerShell terminal as the unprivileged user account using runas /user:MSCC cmd (or powershell or pwsh) and launched the PowerShell script manually. This also worked successfully and as expected.

...
2023-05-17 16:38:47 us=875000 interactive service msg_channel=464
2023-05-17 16:38:47 us=875000 open_tun
2023-05-17 16:38:47 us=890000 tap-windows6 device [Local Area Connection 2] opened
2023-05-17 16:38:47 us=890000 TAP-Windows Driver Version 9.26 
2023-05-17 16:38:47 us=890000 TAP-Windows MTU=1500
2023-05-17 16:38:47 us=890000 Set TAP-Windows TUN subnet mode network/local/netmask = 10.242.2.0/10.242.2.15/255.255.255.0 [SUCCEEDED]
2023-05-17 16:38:47 us=890000 Notified TAP-Windows driver to set a DHCP IP/netmask of 10.242.2.15/255.255.255.0 on interface {0B0A0BC9-EA4A-41AD-AB54-546CA14EF630} [DHCP-serv: 10.242.2.0, lease-time: 31536000]
2023-05-17 16:38:47 us=890000 DHCP option string: 0f0f6f6d 732d736f 66747761 72652e64 650608c0 a8910ac0 a8910b2c 04c0a891 0a
2023-05-17 16:38:47 us=890000 Successful ARP Flush on interface [6] {0B0A0BC9-EA4A-41AD-AB54-546CA14EF630}
2023-05-17 16:38:47 us=953000 do_ifconfig, ipv4=1, ipv6=0
2023-05-17 16:38:47 us=953000 IPv4 MTU set to 1500 on interface 6 using service
2023-05-17 16:38:47 us=953000 Data Channel MTU parms [ mss_fix:1362 max_frag:0 tun_mtu:1500 tun_max_mtu:1600 headroom:136 payload:1768 tailroom:562 ET:0 ]
...
2023-05-17 16:38:51 us=31000 Route addition via service succeeded
...

I'm trying to verify and double-check my findings later today, and maybe I'm able to find a few more details to add.

In conclusion, it seems that for the moment openvpn.exe struggles with permissions when launched by the Windows Task Scheduler compared to a manually launched process - either current active user or secondary login using runas command.

Hope this helps. @MWinhard kindly feel free to add any observations on your side based on our activities today.

Are there flags to set or would someone need more log files for more details?

Thanks everyone for their time, patience and assistance. Really looking forward to resolve this and to improve OpenVPN.

Best regards, JoKi

selvanair commented 1 year ago

@jochenkirstaetter @MWinhard FWIW, I can reproduce the error when run as a limited user. As you stated, it works when the user is in Administrators group, but even in that case the task is run with limited privileges (not elevated using UAC). So this difference in behaviour is totally unexpected.

@d12fk Any thoughts on why openvpn.exe started via interactive service could fail with 0xc0000142 when run from a non-interactive session in a scheduled task as a limited user? As a quick summary: The goal is to run openvpn from powershell script that talks to the interactive service (like the GUI). The script is scheduled to run in the Windows task scheduler. All work fine if the task is set to run only when the user is logged in, not otherwise. The process (openvpn) does get launched by the service, so pipe-client impersonation, setting up the security descriptor, and CreateProcessAsUserW() all succeed. But the process fails to startup with that obscure error.

One thing missing in the service is loading of userprofile and setting up HKCU which may be required for non-interactive sessions. But that is unlikely to be the cause as non-interactive use does work correctly if the user in Administrators group even when the group is not enabled in the token (i.e., not elevated). Is this related to some security policy in Windows?

cron2 commented 1 year ago

@lstipakov any ideas?

cron2 commented 1 year ago

I wonder what happens if openvpn.exe is started directly by the task scheduler? It will fail, of course (because it has no privileges to install routes and does not know how to contact the iservice), but it might "start up fine and then fail to install routes" or "crash the same way"... (include log c:\somewhere.txt to avoid trying to write to stdout/stderr).

selvanair commented 1 year ago

I wonder what happens if openvpn.exe is started directly by the task scheduler? It will fail, of course (because it has no privileges to install routes and does not know how to contact the iservice), but it might "start up fine and then fail to install routes" or "crash the same way"... (include log c:\somewhere.txt to avoid trying to write to stdout/stderr

I had suggested @MWinhard to test this and it seems they did confirm that it works. Anyway, I tested this too and it works. Something goes wrong when launched with a user token obtained through the named-pipe client, when the client is running in a non-interactive session. If the user is in Administrators group it works fine even if not run with elevated privileges. Bizarre.

selvanair commented 1 year ago

@jochenkirstaetter @MWinhard could you please check whether "openvpnserv.exe" from the GHA binary in the link below fixes this issue? artifacts Scroll down to artifacts-x64. Corresponding branch is here: https://github.com/selvanair/openvpn/commits/master

Replacing only the service binary C:\Program Files\OpenVPN\bin\openvpnserv.exe" by the one in the zip file should be enough. To do that you will have to stop "OpenVPNServiceInteractive" first and then restart it after copying the binary. As you have no admin access on the server, you may have to test using another machine.

jochenkirstaetter commented 1 year ago

Hi everyone,

Thanks for picking this up.

One thing missing in the service is loading of userprofile and setting up HKCU which may be required for non-interactive sessions. But that is unlikely to be the cause as non-interactive use does work correctly if the user in Administrators group even when the group is not enabled in the token (i.e., not elevated). Is this related to some security policy in Windows?

@selvanair
According to the ProcMon logging anything user-specific is picked up and evaluated. I'm using $OVPN_BASE_DIR = $env:USERPROFILE + "\OpenVPN" in the script. And access to HKCU of that non-interactive session is also correct.

I wonder what happens if openvpn.exe is started directly by the task scheduler? It will fail, of course (because it has no privileges to install routes and does not know how to contact the iservice), but it might "start up fine and then fail to install routes" or "crash the same way"... (include log c:\somewhere.txt to avoid trying to write to stdout/stderr).

@cron2 Correct, I verified that. The task however then lasts until either terminated manually, times out or is taskkill'd ;-)

@jochenkirstaetter @MWinhard could you please check whether "openvpnserv.exe" from the GHA binary in the link below fixes this issue?

@selvanair Guess what? It's working as a scheduled task, as expected. Wonderful. I replaced the openvpnserv.exe 2.6.4 with the one 2.7.0 that you offered only. I'm getting just one entry in the Event Viewer:

openvpnserv:
  Authorizing user 'MSCC@IOSI8' by virtue of membership in group 'OpenVPN Administrators'

And no further error message. The OpenVPN log of the non-interactive session now looks identical to the interactive one. Here are some entries using --verb 4 (some values masked).

...
2023-05-18 10:32:36 us=609000 Current Parameter Settings:
2023-05-18 10:32:36 us=609000   config = 'C:\Users\MSCC\OpenVPN\config\Test.ovpn'
...
2023-05-18 10:32:36 us=609000   Pull filters:
2023-05-18 10:32:36 us=609000     ignore "route-method"
...
2023-05-18 10:32:36 us=609000   tmp_dir = 'C:\Users\MSCC\AppData\Local\Temp\'
...
2023-05-18 10:32:36 us=609000 OpenVPN 2.6.4 [git:v2.6.4/b4f749f14a8edc75] Windows-MSVC [SSL (OpenSSL)] [LZO] [LZ4] [PKCS11] [AEAD] [DCO] built on May 11 2023
2023-05-18 10:32:36 us=609000 Windows version 10.0 (Windows 10 or greater), amd64 executable
2023-05-18 10:32:36 us=609000 library versions: OpenSSL 3.1.0 14 Mar 2023, LZO 2.10
2023-05-18 10:32:36 us=609000 DCO version: v0
...
2023-05-18 10:32:40 us=46000 interactive service msg_channel=616
2023-05-18 10:32:40 us=46000 open_tun
2023-05-18 10:32:40 us=62000 tap-windows6 device [Local Area Connection 2] opened
...
2023-05-18 10:32:44 us=15000 TEST ROUTES: 3/3 succeeded len=3 ret=1 a=0 u/d=up
2023-05-18 10:32:44 us=15000 C:\WINDOWS\system32\route.exe ADD <masked IP> MASK 255.255.255.255 192.168.180.1
2023-05-18 10:32:44 us=15000 Route addition via service succeeded
...
2023-05-18 10:32:44 us=31000 Initialization Sequence Completed

NOTE: 15 seconds waiting state to simulate other tasks happening

2023-05-18 10:32:51 us=578000 TCP/UDP: Closing socket
2023-05-18 10:32:51 us=578000 C:\WINDOWS\system32\route.exe DELETE <masked IP> MASK 255.255.255.255 192.168.180.1
2023-05-18 10:32:51 us=593000 Route deletion via service succeeded
2023-05-18 10:32:51 us=593000 Closing TUN/TAP interface
2023-05-18 10:32:51 us=750000 TAP: DHCP address released
2023-05-18 10:32:51 us=750000 SIGTERM[hard,] received, process exiting

Now, there are two things left to do. That new version lacks code signing and shall be officially released (?) soon, and @MWinhard has to ask the server admins to install the latest official OpenVPN package for final testing.

Right now, it looks good on my side to have a non-interactive session. And if I understand the commit message correctly this shall enable openvpn-gui.exe to be used for non-interactive sessions, too. Meaning our approach using the PowerShell script might be rendered obsolete or at least optional.

Thanks @selvanair - great job, timely response and overall cool assistance.

Best regards, JoKi

MWinhard commented 1 year ago

Looks like running from task scheduler under a non-admin account finally works. 😁😁😁

I'll be happy to have the data center admins install the next OpenVPN setup as soon as you released it.

selvanair commented 1 year ago

And if I understand the commit message correctly this shall enable openvpn-gui.exe to be used for non-interactive sessions, too. Meaning our approach using the PowerShell script might be rendered obsolete or at least optional.

That's not the case. The GUI still requires an interactive desktop as its a "GUI". OpenVPN.exe is a commandline program and forcing it on to the interactive desktop by the service is what is stopped by the commit.

You will require the script and its general usefulness that @cron2 pointed out still applies.

selvanair commented 1 year ago

I'll be happy to have the data center admins install the next OpenVPN setup as soon as you released it.

I'll submit the patch today after some more tests and expect it to be in 2.6.5. As only the Windows port is affected, you may be able to lobby @cron2 to make a quick Windows-only release ;)

cron2 commented 1 year ago

Are we in a big hurry here? 2.6.5 is planned for roughly 4-8 weeks after 2.6.4 release, which would make it something like "June 7"-ish. I see some more fixes coming in (Lev is repairing --dev-node with win-dco, plus tapctl improvements), so unless this is really totally urgent I'd prefer to just wait these few weeks.

jochenkirstaetter commented 1 year ago

Pinging @MWinhard - he tasked me to implement the original scenario. :wink:

jochenkirstaetter commented 1 year ago

Funny idea... @cron2 , maybe @MWinhard can buy you a :beer: (or two :beers: or something else :cocktail: )? You're literally only a few kilometers away from each other. I'd assume that Markus wouldn't mind driving down to Munich. :rofl:

Cheers, JoKi

MWinhard commented 1 year ago

@cron2 You can imagine that the time for finding the source of this bug wasn't planned. So my project is already way over time and budget. 😉

It will be on hold until you offer an official setup that includes the fix. So, the sooner the better. 😊

And for JoKi's funny idea: If we have sunny weather and you find the time, I'll be happy to meet you in a beer garden/restaurant/bar /whatever near your home (and pay the bill). 😉 And that's regardless of the next setup's release date.

selvanair commented 1 year ago

You can imagine that the time for finding the source of this bug wasn't planned. So my project is already way over time and budget.

Funny... where should I send my invoice?

MWinhard commented 1 year ago

@selvanair I have convinced the data center admins to install your openvpnserv.exe bugfix version on our production VM. And I can confirm it works. :-) :-) :-) Thank you very much once more.

@cron2 No need to hurry any more. We can wait for the next release, as long as it takes.

MWinhard commented 1 year ago

I have installed OpenVPN 2.6.5 and I can confirm that my powershell client called from a scheduled task still works. 😁

cron2 commented 1 year ago

I have installed OpenVPN 2.6.5 and I can confirm that my powershell client called from a scheduled task still works.

That was the plan! ;-)

Thanks for testing and confirming that we packed the correct sources!

gert

MWinhard commented 1 year ago

Here's a simplified version of the powershell skript @jochenkirstaetter built with massive help of @selvanair. You can call it directly or from a scheduled task. Feel free to adopt it to your needs and to distribute it with OpenVPN.


# Name of your *.ovpn file. Please change.
$OVPN_CNN = "MyConnection"
# IP address of the device you want to acceess thru the VPN connection. Please change.
$TARGET_IP = "192.168.1.195"

$EXIT_EVENT_NAME = "OVPN_" + $PID
$OVPN_CONFIG_DIR = $env:USERPROFILE + "\OpenVPN\config"
$OVPN_CONFIG_FILE = $OVPN_CONFIG_DIR + "\" + $OVPN_CNN + ".ovpn"
$OVPN_LOG_FILE = $env:USERPROFILE + "\OpenVPN\log\" + $OVPN_CNN + ".log"

function Send-NamedPipeMessage($Message)
{
    #$PipeName_OpenVPN3 = "ovpnagent"
    $PipeName = "openvpn\service"
    $ComputerName = "."
    [System.Text.Encoding]$Encoding = [System.Text.Encoding]::Unicode
    [int]$ConnectTimeout = 5000

    $stream = New-Object -TypeName System.IO.Pipes.NamedPipeClientStream -ArgumentList `
        $ComputerName, `
        $PipeName, `
        ([System.IO.Pipes.PipeDirection]::Out), `
        ([System.IO.Pipes.PipeOptions]::None), `
        ([System.Security.Principal.TokenImpersonationLevel]::Impersonation)
    $stream.Connect($ConnectTimeout)
    $bRequest = $Encoding.GetBytes($Message)
    $stream.Write($bRequest, 0, $bRequest.Length)
    $stream.Dispose()
}

function Fire-ExitEvent($EventName)
{
    $ev = [System.Threading.EventWaitHandle]::OpenExisting($EventName)
    # Next line always returning True.
    # Even when the connection was not closed.
    $ev.set()   | Out-Null
    $ev.reset() | Out-Null
    $ev.close() | Out-Null
}

$Message = $OVPN_CONFIG_DIR + "`0"
$Message = $Message + `
    "--log " + $OVPN_LOG_FILE + `
    " --config " + $OVPN_CONFIG_FILE + `
    " --service " + $EXIT_EVENT_NAME + " 0" + `
    " --pull-filter ignore route-method" + "`0"
$Message = $Message + "`0"

# Start VPN connection.
Send-NamedPipeMessage $Message
Start-Sleep 10

# VPN connection established. Do something.
ping -n 1 $TARGET_IP | Select -Skip 1 -First 2
"Do something here..."

# Close VPN connection.
Fire-ExitEvent $EXIT_EVENT_NAME
Start-Sleep 2

# Run ping command again to proof that the connection is closed.
ping -n 1 $TARGET_IP | Select -Skip 1 -First 2
`