Active Directory Authentication for OpenVPN  For Windows Implementations

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)
 script-security 3
 auth-user-pass-verify "C:/Windows/System32/cscript.exe /H:cscript C:/Progra~1/OpenVPN/config/Auth4OpenVPN.vbs" via-env

  • 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"