Introduction.
OpenVPN supports an authentication method on which it can supply the user name and password (entered by the remote user) to a third party script file, then the script file can validate them and let OpenVPN know if they are valid. The script's exit code tells OpenVPN if the credentials were valid or not. Therefore, it is the script file what will initiate the process to validate the credentials against the Active Directory (AD).
I designed the VBscript from scratch and keeping security in mind. The communication with AD is secured and the credentials are disposed as soon as the authentication ends. The script is made of two files, the main VBS script file and an INI file that holds the AD settings and other options. The script code will read the INI file whenever an authentication attempt happens.
Script's features:
- Validate user name and passwords.
- Determine if users are members of the Security Group in AD allowed to use VPN.
- Log each successful and unsuccessful VPN logon attempts in the server's Application Logs.
Requirements.
- A working OpenVPN configuration without authentication.
- The OpenVPN service in the client computers must be stopped or disabled.
- The OpenVPN-GUI systray tool to get OpenVPN show the password prompt.
Installation:
1. Make sure to have a functional OpenVPN
configuration before setting up authentication. (I used this guide this first time I used OpenVPN)
2. Download the file Auth4OpenVPNv2.0.zip and extract the files in Program Files\OpenVPN\Config in the server computer.
3. Edit the parameters in the file Auth4OpenVPN.ini in the Program Files\OpenVPN\Config folder in the server. Use the information from your network environment to modify the sample settings.
4. Add this line to the server's OpenVPN configuration file and restart the OpenVPN Windows service in the server:
auth-user-pass-verify Auth4OpenVPN.vbs via-env
5. Now, add the two lines below to the client computers OpenVPN configuration file and then download and run the file Setup-OpenVPN-GUi.exe. That application prompts users for the user name and password.
auth-user-pass
auth-retry
interact
Troubleshooting.
- The Application Logs in the server will indicate any activity with the script which can be used to determine if things are working properly.
- To test the settings set in Auth4OpenVPN.ini, open a MS-DOS console window in the Config directory. Then run the script using this syntax: auth4openvpn.vbs <user> <password>
- The most common problem occurs when the LDAP path is setup incorrectly.
- The script requires the Group to be within the same LDAP path where the users are located.
- The script has been tested with W2K, XP SP2, Vista, W2K SP4 Server, W2K3, Windows 7 Pro and Windows 2008 R2 servers.
- For more information on the OpenVPN-GUI application go to http://openvpn.se
- The script works ok with OpenVPN 2.2, but higher releases require the following changes in order to allow the script to run: In the server's configuration file add the 2 lines below, make sure the path is correct.
- 3/16/2015 - Under Win 2008 and 2012 the script throws an object error, check this link for a work around. (Thanks to Marcel Post)
- For questions and suggestions I can be reached at amigo4life at gmail dot com.
The Code
Auth4OpenVPN.vbs file:
' Auth4OpenVPN.vbs
' Author: J. Ortega.
' 2007.
' http://amigo4life.googlepages.com/openvpn
'
'.............................................................................
Option Explicit
Const SEVERITY_INFO = &H00
Const SEVERITY_ERROR = &H01
Const SEVERITY_WARNING = &H02
Const ADS_SECURE_AUTHENTICATION = &H01
Const ADS_USE_ENCRYPTION = &H02
Const ADS_SERVER_BIND = &H0200
Const ADS_SCOPE_SUBTREE = &H02
Const cfgFilePath = "auth4openvpn.ini"
Dim fileContent, parameters,ovUser, ovPass
'.............................................................................
sub LogEvent(EventDescription, EventType)
Dim objShell
Set objShell = Wscript.CreateObject("Wscript.Shell")
objShell.LogEvent EventType, EventDescription
Set objShell = nothing
end sub
'.............................................................................
Sub Prn(textToPrint)
wscript.echo textToPrint
end sub
'.............................................................................
Function LogError (byVal ErrNumber)
if errnumber = 0 then
LogError = FALSE
exit function
end if
Select Case ErrNumber
case -2147217911, -2147023570
LogEvent "Auth4OpenVPN: Incorrect credentials." & _
vbNewline & _
"Username: " & Ucase(ovUser), SEVERITY_WARNING
case -2147217865
LogEvent "Auth4OpenVPN: Cannot find server" & _
" or LDAP path supplied.", SEVERITY_ERROR
case else
LogEvent "Auth4OpenVPN: " & errnumber & ", " & _
err.description, SEVERITY_ERROR
end Select
LogError = TRUE
end Function
'.............................................................................
Function IsAuthOK()
Dim adoConn, AdoCmd, rsUserDN, rsGroupDN, userDN, groupDN, adUser
Dim userGroupsArray, group
Dim oDSP, myDSP, root
Dim server, domain, ldapPath, adGroup, logging
server = parameters(0) : domain = parameters(1)
ldapPath = parameters(2) : adGroup = parameters(3)
logging = parameters(4)
'------------------------------------------------------------------------
'check credentials
Set myDSP = GetObject("LDAP:")
On Error Resume Next
Set root = myDSP.OpenDSObject("LDAP://" & server & "/" & _
"RootDSE", domain & "\" & ovUser, _
ovPass, ADS_SERVER_BIND AND _
ADS_USE_ENCRYPTION)
if LogError(err.number) then
on error goto 0 'disable on error resume
IsAuthOK = FALSE
Exit Function
end if
Set root = nothing
'------------------------------------------------------------------------
Set adoConn = CreateObject("ADODB.Connection")
Set adoCmd = CreateObject("ADODB.Command")
with adoConn
.Provider = "ADsDSOObject"
.Properties("User ID") = domain & "\" & ovUser
.Properties("Password") = ovPass
.Properties("Encrypt Password") = TRUE
.Properties("ADSI Flag") = &H01
.Properties("Timeout") = 10
.Open "Active Directory Provider"
end with
with adoCmd
Set .ActiveConnection = adoConn
.Properties("Page Size") = 10
.Properties("Cache Results") = FALSE
.Properties("Searchscope") = ADS_SCOPE_SUBTREE
'----------------------------------------------------------------
.CommandText = "SELECT distinguishedName FROM 'LDAP://" & _
server & "/" & ldapPath & "' " & _
"WHERE objectClass='user' " & _
"AND saMaccountName='" & ovUser & "'"
on error resume next
Set rsUserDN = .Execute
if LogError(err.number) then
on error goto 0 'disable on error resume
adoConn.Close
Set adoConn = nothing
Set AdoCmd = nothing
IsAuthOK = FALSE
Exit Function
end if
if rsUserDn.EOF then
IsAuthOK = FALSE
LogEvent "Auth4OpenVPN: Cannot find user under LDAP path" & _
" set in Auth4OpenVPN.ini file.", SEVERITY_ERROR
adoConn.Close
Set adoConn = nothing
Set AdoCmd = nothing
Exit Function
end if
userDN = rsUserDN.Fields(0)
'----------------------------------------------------------------
.CommandText = "SELECT distinguishedName " & _
"FROM 'LDAP://" & _
server & "/" & ldapPath & "' " & _
"WHERE objectClass='group' " & _
"AND name='" & adGroup & "'"
Set rsGroupDN = .Execute
if rsGroupDN.EOF then
IsAuthOK = FALSE
LogEvent "Auth4OpenVPN: Cannot find Group under LDAP path" & _
" set in Auth4OpenVPN.ini file.", SEVERITY_ERROR
adoConn.Close
Set adoConn = nothing
Set AdoCmd = nothing
Exit Function
end if
groupDN=rsGroupDN.fields(0)
.CommandText = "SELECT distinguishedName " & _
"FROM 'LDAP://" & _
server & "/" & ldapPath & "' " & _
"WHERE objectClass='group' " & _
"AND name='" & adGroup & "'" & _
"AND member='" & userDN & "'"
Set rsGroupDN = .Execute
if not rsGroupDN.EOF then
if Ucase(logging) = "ON" then
LogEvent "Auth4OpenVPN: " & _
"Authentication successful." & vbNewline & _
"Username: " & Ucase(ovUser), SEVERITY_INFO
end if
IsAuthOK = TRUE
adoConn.Close
Set adoConn = nothing
Set AdoCmd = nothing
Exit Function
end if
End With
'--------------------------------------------------------------------
IsAuthOK = FALSE
LogEvent "Auth4OpenVPN: User is not a member of the group: " & _
Ucase(adGroup) & vbNewline & "Username: " & _
Ucase(ovUser), SEVERITY_WARNING
Set adUser = nothing
Set oDSP = nothing
end Function
'.............................................................................
Function IsSettingsFileOK()
Dim objFSO, openFile
Set objFSO = CreateObject("Scripting.FileSystemObject")
If objFSO.FileExists(cfgFilePath) Then
On Error Resume Next
set openFile = objFSO.OpenTexTFile(cfgFilePath)
if err.number then
IsSettingsFileOK = FALSE
LogEvent "Auth4OpenVPN: Cannot read Auth4OpenVPN.ini file.", _
SEVERITY_ERROR
On Error Goto 0 'disable On Error Resume
Set objFSO = nothing
Exit Function
end if
fileContent = openFile.ReadAll
openFile.Close
Else
IsSettingsFileOK = FALSE
LogEvent "Auth4OpenVPN: The Auth4OpenVPN.ini file is missing.", _
SEVERITY_ERROR
Set objFSO = nothing
Exit Function
End If
Set objFSO = nothing
IsSettingsFileOK = TRUE
end Function
'.............................................................................
Function GetFirstMatch(PatternToMatch, StringToSearch)
Dim regEx, CurrentMatch, CurrentMatches
Set regEx = New RegExp
with regEx
.Pattern = PatternToMatch
.IgnoreCase = TRUE
.Global = TRUE
.MultiLine = TRUE
Set CurrentMatches = .Execute(StringToSearch)
end with
GetFirstMatch = ""
If CurrentMatches.Count >= 1 Then
Set CurrentMatch = CurrentMatches(0)
If CurrentMatch.SubMatches.Count >= 1 Then
GetFirstMatch = CurrentMatch.SubMatches (0)
End If
End If
Set regEx = Nothing
End Function
'.............................................................................
Function AreSettingsOK()
Dim pattern
Dim i
parameters = Array ("SERVER", "DOMAIN", "DN", "GROUP", "LOGGING")
For i = 0 to 4
pattern = ".*\r\n\s*" & parameters(i) & "\s*\=\s*\""(.*)\"""
parameters(i) = Trim(getFirstMatch(pattern, fileContent))
if Len(parameters(i)) = 0 then
AreSettingsOK = FALSE
LogEvent "Auth4OpenVPN: Missing settings in Auth4OpenVPN.ini" & _
" file.", SEVERITY_ERROR
Exit Function
end if
Next
AreSettingsOK = TRUE
End Function
'.............................................................................
Function AreCredentialsOK()
if Wscript.Arguments.Count = 0 then
'no cmd args, read OpenVPN supplied credentials
Dim myObj
Set myObj = Wscript.CreateObject("Wscript.Shell")
ovUser = myObj.ExpandEnvironmentStrings("%username%")
ovPass = myObj.ExpandEnvironmentStrings("%password%")
Set myObj = nothing
if Len(ovUser)=0 or Len(ovPass)=0 then
AreCredentialsOK = FALSE
LogEvent "Auth4OpenVPN: Empty username or password " & _
"are not allowed.", SEVERITY_ERROR
Exit Function
end if
AreCredentialsOK = TRUE
Exit Function
'-------------------------------------------------------------------
Elseif Wscript.Arguments.Count = 2 then
'cmd args present, read user and pass provided for testing.
ovUser = Trim(Wscript.Arguments(0))
OvPass = Trim(Wscript.Arguments(1))
if Len(ovUser)=0 or Len(ovPass)=0 then
AreCredentialsOK = FALSE
LogEvent "Auth4OpenVPN: Empty username or password " & _
"are not allowed.", SEVERITY_ERROR
Exit Function
end if
AreCredentialsOK = TRUE
Exit Function
Else
LogEvent "Auth4OpenVPN: Invalid number of arguments.", _
SEVERITY_ERROR
AreCredentialsOK = FALSE
End If
End Function
'.............................................................................
Sub Main
If AreCredentialsOk then
if IsSettingsFileOK then
if AreSettingsOK then
if IsAuthOk then
if Wscript.Arguments.Count = 0 then
wscript.quit(0)
else
Prn "Authentication successful." & vbNewline
wscript.quit(0)
end if
end if
end if
end if
end if
if Wscript.Arguments.Count = 0 then
wscript.quit(1)
else
Prn "Authentication failed." & vbNewline & _
"Check the Application logs for details." & vbNewline
wscript.quit(1)
end if
end sub
'.............................................................................
Main
'.............................................................................
The Auth4OpenVPN.ini file:
# Auth4OpenVPN.ini
# J. Ortega,
# 2007.
# http://amigo4life.googlepages.com/openvpn
# All parameters must be set with quotation marks (" ").
#--------------------------------------------------------------------
# Server: Specifies the host name or IP address for the Active
# Directory server.
Server = "10.234.234.33"
#--------------------------------------------------------------------
# Domain: Specifies the domain to be used when authenticating to
# access the Active Directory server. (NetBIOS name)
Domain = "mydomain"
#--------------------------------------------------------------------
# DN: Specifies the distinguished name in Active Directory under
# which the user and group objects are located. (LDAP path).
# Example: the DN for mydomain.local would be:
DN = "dc=mydomain,dc=local"
#--------------------------------------------------------------------
# Group: Sets the security group in Active Directory to which
# users must be members to pass authentication.
Group = "vpnusers"
#--------------------------------------------------------------------
# When set to "ON" this setting will log successful user logins.
# Errors and Warnings are always logged. Use the Event Viewer in
# Windows to see the logs.
Logging = "On"