Aug 06

Configuring AnyConnect SSL VPN Client Connections

Overview

ASA: 8.3+ (Written/Tested on 9.0)

Authentication: Local (Local ASA User Database)

Type: Split-tunnel OR Non split-tunnel

The below configurations will work with 8.3+, but was written and tested with 9.0.  When setting up a Anyconnect VPN tunnel, you can push all traffic from the client over the VPN (Tunnel all) or you can use a split tunnel to only push traffic destined for selected subnets over the VPN tunnel.  In laymen terms, the clients internet traffic originates from their ISP in a split tunnel, and it originates from the ASA when using tunnel all.  The below configuration examples assume you have a basic setup equivalent to running factory-default and are setup to authenticate locally to the ASA.  I will give examples of each configuration below.

Network Diagram

Network Diagram Anyconnect VPN

The ASA has a command that gives an overview of how to configure an Anyconnect SSL VPN, which in global configuration mode is vpnsetup ssl-remote-access steps.  Here are the results of that command:

ciscoasa(config)# vpnsetup ssl-remote-access steps

Steps to configure a remote access SSL VPN remote access connection and AnyConnect with examples:

1. Configure and enable interface

        interface GigabitEthernet0/0
         ip address 10.10.4.200 255.255.255.0
         nameif outside
         no shutdown

        interface GigabitEthernet0/1
         ip address 192.168.0.20 255.255.255.0
         nameif inside
         no shutdown

2. Enable WebVPN on the interface

        webvpn
         enable outside

3. Configure default route

        route outside 0.0.0.0 0.0.0.0 10.10.4.200

4. Configure AAA authentication and tunnel group

        tunnel-group DefaultWEBVPNGroup type remote-access
        tunnel-group DefaultWEBVPNGroup general-attributes
         authentication-server-group LOCAL

5. If using LOCAL database, add users to the Database

        username test password t3stP@ssw0rd
        username test attributes
         service-type remote-access

Proceed to configure AnyConnect VPN client:

6. Point the ASA to an AnyConnect image

        webvpn
         svc image anyconnect-win-2.1.0148-k9.pkg

7. enable AnyConnect

        svc enable

8. Add an address pool to assign an ip address to the AnyConnect client

        ip local pool client-pool 192.168.1.1-192.168.1.254 mask 255.255.255.0

9. Configure group policy

        group-policy DfltGrpPolicy internal
        group-policy DfltGrpPolicy attributes
         vpn-tunnel-protocol svc webvpn

There are a few important things to note from Cisco’s directions:

  1. They are using the default names for configuring the group policy and tunnel groups, which will throw a warning that they already exist since they’re defaults
  2. The directions do not specify that you MUST attach the VPN Address pool to the tunnel group, which is necessary for it to work!
  3. They do not include how to create a split tunnel or a tunnel all to allow internet from the Anyconnect client.
  4. They are using outdated “svc” commands, which were replaced with “anyconnect”.

With that said, let’s move on to the configurations!

Anyconnect Configuration 1: Tunnel All

!Enable WebVPN, Set Anyconnect Image, and Enable Anyconnect
config t
webvpn
enable outside
tunnel-group-list enable
anyconnect image disk0:/anyconnect-win-3.1.05178-k9.pkg 1
anyconnect image disk0:/anyconnect-macosx-i386-3.1.05178-k9.pkg 2
anyconnect image disk0:/anyconnect-linux-3.1.05178-k9.pkg 3
anyconnect enable
end
!Create DHCP Pool for Anyconnect Clients
config t
ip local pool pool-anyconnect 192.168.100.1-192.168.100.254 mask 255.255.255.0
end
!Create Group Policy for Anyconnect
config t
group-policy GroupPolicy_Anyconnect internal
group-policy GroupPolicy_Anyconnect attributes
vpn-tunnel-protocol ssl-client
end
!Create Tunnel Group for Anyconnect
config t
tunnel-group TunnelGroup_Anyconnect type remote-access
tunnel-group TunnelGroup_Anyconnect general-attributes
authentication-server-group LOCAL
default-group-policy GroupPolicy_Anyconnect
address-pool pool-anyconnect
tunnel-group TunnelGroup_Anyconnect webvpn-attributes
group-alias 1-Admin enable
end
!Create NAT Exemption and Enable Outside Traffic to Enter/Exit the Same Interface
config t
object-group network obj-anyconnect
network-object 192.168.100.0 255.255.255.0
exit
nat (outside,outside) after-auto source dynamic obj-anyconnect interface
same-security-traffic permit intra-interface
end
!Tunnel All Traffic Over Anyconnect VPN and Force Use of DNS Servers
config t
group-policy GroupPolicy_Anyconnect attributes
split-tunnel-policy tunnelall
split-tunnel-all-dns enable
end
!(OPTIONAL)Create Local User for Anyconnect
config t
username user1 password P@SSWORD
username user1 attributes
service-type remote-access
end
!(OPTIONAL) Add DNS Settings for Anyconnect Client
config t
group-policy GroupPolicy_Anyconnect attributes
dns-server value 10.1.1.10 10.1.1.11
default-domain value domain.local
end
!(OPTIONAL) Allow Anyconnect IP Pool to Manage ASA
config t
ssh 192.168.100.0 255.255.255.0 inside
http 192.168.100.0 255.255.255.0 inside
management-access inside
end
!(OPTIONAL)Auto launch anyconnect
config t
group-policy GroupPolicy_Anyconnect attributes
webvpn
anyconnect ask none default anyconnect
end

 Anyconnect Configuration 2: Split Tunnel

!Enable WebVPN, Set Anyconnect Image, and Enable Anyconnect
config t
webvpn
enable outside
tunnel-group-list enable
anyconnect image disk0:/anyconnect-win-3.1.05178-k9.pkg 1
anyconnect image disk0:/anyconnect-macosx-i386-3.1.05178-k9.pkg 2
anyconnect image disk0:/anyconnect-linux-3.1.05178-k9.pkg 3
anyconnect enable
end
!Create DHCP Pool for Anyconnect Clients
config t
ip local pool pool-anyconnect 192.168.100.1-192.168.100.254 mask 255.255.255.0
end
!Create Group Policy for Anyconnect
config t
group-policy GroupPolicy_Anyconnect internal
group-policy GroupPolicy_Anyconnect attributes
vpn-tunnel-protocol ssl-client
end
!Create Tunnel Group for Anyconnect
config t
tunnel-group TunnelGroup_Anyconnect type remote-access
tunnel-group TunnelGroup_Anyconnect general-attributes
authentication-server-group LOCAL
default-group-policy GroupPolicy_Anyconnect
address-pool pool-anyconnect
tunnel-group TunnelGroup_Anyconnect webvpn-attributes
group-alias 1-Admin enable
end
!Create NAT Exemption
config t
object-group network obj-anyconnect
network-object 192.168.100.0 255.255.255.0
exit
nat (inside,outside) 2 source static any any destination static obj-anyconnect obj-anyconnect no-proxy-arp route-lookup
end
!Create Split Tunnel, Allow Access to VPN and Inside Subnets, and Apply to Group Policy
config t
access-list ACL_split-tunnel standard permit 192.168.100.0 255.255.255.0
access-list ACL_split-tunnel standard permit 10.1.1.0 255.255.255.0
group-policy GroupPolicy_Anyconnect attributes
split-tunnel-policy tunnelspecified
split-tunnel-network value ACL_split-tunnel
end
!(OPTIONAL)Create Local User for Anyconnect
config t
username user1 password P@SSWORD
username user1 attributes
service-type remote-access
end
!(OPTIONAL) Add DNS Settings for Anyconnect Client
config t
group-policy GroupPolicy_Anyconnect attributes
dns-server value 10.1.1.10 10.1.1.11
default-domain value domain.local
end
!(OPTIONAL) Allow Anyconnect IP Pool to Manage ASA
config t
ssh 192.168.100.0 255.255.255.0 inside
http 192.168.100.0 255.255.255.0 inside
management-access inside
end
!(OPTIONAL)Auto launch anyconnect
config t
group-policy GroupPolicy_Anyconnect attributes
webvpn
anyconnect ask none default anyconnect
end

 

Testing the Configuration

Open a web browser, connect to your ASA (https://vpn.domain.com OR https://172.31.100.1), and you’ll be prompted to login.  You can login with the user account you’ve created in the configuration above.  The Anyconnect client will automatically install, if it fails you may need to download and manually install it.  Once installed, you can connect to your ASA by the outside interface (vpn.domain.com OR 172.31.100.1) and authenticate with the user you’ve created.

Additional Notes

You may wish to use a 3rd party SSL certificates (ie: Verisign, Thawte, Godaddy, etc) so end users do not get prompted about certificate warnings.  Cisco ASA’s will regenerate it’s certificate upon reboot, and due to this you should create a self signed certificate whenever you cannot use a 3rd party.  You can create the self signed certificate as follows:

!Create Self Signed Certificate
config t
crypto ca trustpoint SELF
enroll
enrollment self
fqdn vpn.domain.com
subject cn=vpn.domain.com,dc=domain,dc=com
exit
crypto ca enroll SELF
!The following warnings will generate, answer accordingly as below:
!
!WARNING: The certificate enrollment is configured with an fqdn
!that differs from the system fqdn. If this certificate will be
!used for VPN authentication this may cause connection problems.
!
!Would you like to continue with this enrollment? [yes/no]: yes
!
!The fully-qualified domain name in the certificate will be: vpn.domain.com
!
!Include the device serial number in the subject name? [yes/no]: no
!
!Generate Self-Signed Certificate? [yes/no]: yes
!
!Attach the certificate to the outside interface
ssl trust-point SELF outside
end

 

Aug 05

DirMirror.vbs

Option Explicit
'=========================================================================
' DirMirror.vbs
' VERSION: 1.0
' AUTHOR: Brian Steinmeyer
' EMAIL: sigkill@sigkillit.com
' WEB: http://sigkillit.com
' DATE: 9/26/2013
' REQUIREMENTS:
' - robocopy.exe (Built in to Server2008+, Windows 7+)
' - blat.exe, blat.dll. blat.lib in the same directory as the script (http://www.blat.net)
' - Blat profile installed to email results
' - Recommended to use Stunnel to Encrypt Email Sessions, Especially If the Email Server
' is not on your subnet ex: Gmail
' COMMENTS: Requires robocopy for the backup and blat to email results. Define
' the Directory Source(what you need to backup), the Directory Destination
' (where you need to backup to), path to blat, blat profile, email subject,
' and email body
'=========================================================================

' ------ SCRIPT CONFIGURATION ------
Const WindowStyle = 0  '0=Hide Robocopy Console, 1=Show Robocopy Console
Dim DirSource: DirSource = "\\server\share\directory"
Dim DirDestination: DirDestination = Left(WScript.ScriptFullName,InstrRev(WScript.ScriptFullName,"\")) & "backup"
Dim intReturn 'Generic Return
Dim logErrors: logErrors = Replace(WScript.ScriptFullName,".vbs","_log.txt")
Dim emailBlatExe: emailBlatExe = "C:\blat\blat.exe" 'Custom Path to Blat.exe Default is Same Folder as Script
Dim emailProfile: emailProfile = "gmail" 'Blat Profile Name See (http://www.blat.net) For Info
Dim emailRecipient: emailRecipient = "email@domain.com" 'Email to Receive Backup Result
Dim eSubject: eSubject = "Server1 Backup"
Dim eBody: eBody = "Server1 Backup"
' ------ END CONFIGURATION ------

'MAIN CALLS
Call Logger(logErrors, "[" & Now() & "] " & vbCrLf & Wscript.ScriptName & vbCrLf & "Server: Spiceworks", True)
Call AllowSingleInstance(".",logErrors, emailBlatExe, emailProfile, emailRecipient, eSubject, eBody)
Call ConfirmDirectories(logErrors, DirSource, DirDestination, emailBlatExe, emailProfile, emailRecipient, eSubject, eBody)
Call RoboDirMirror(logErrors, DirSource, DirDestination, emailBlatExe, emailProfile, emailRecipient, eSubject, eBody)



' ***************************************************************************************************
' Function RoboDirMirror - Mirrors a Source Directory to a Mirror Directory Using Robocopy
' ***************************************************************************************************
Private Sub RoboDirMirror(logName, strSource, strDestination, emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody)

	'On Error Resume Next
	
	Call Logger(logName, vbCrLf & "[" & Now() & "] " & vbCrLf & "Run Robocopy to Mirror Directory", False)
	'Ensure Directories Don't End with Slash or Robocopy Will Error
	If Mid(strSource,Len(strSource),1) = "\" Then
		strSource = Mid(strSource,1,Len(strSource) - 1)
	End If
	If Mid(strDestination,Len(strDestination),1) = "\" Then
		strDestination = Mid(strDestination,1,Len(strDestination) - 1)
	End If
	
	Dim objShell: Set objShell = WScript.CreateObject("WScript.Shell")
	Dim strCommand: strCommand = "robocopy"
	Dim cmdOptions: cmdOptions = "/MIR /FFT /Z /XA:H /W:5 /log+:" & chr(34) & logName & chr(34)
	'Robocopy \\SourceServer\Share \\DestinationServer\Share /MIR /FFT /Z /XA:H /W:5
	Dim intReturn: intReturn = objShell.Run(strCommand & " " & chr(34) & strSource & chr(34) & " " & chr(34) & strDestination & chr(34) & " " & cmdOptions, WindowStyle, True)
	Select Case intReturn
	Case 0
		'No Files Copied
		Call Logger(logName, "RESULT: Success - No Files Copied", False)
		emailSubject = emailSubject & " Success"
		emailBody = emailBody & " Success - No Files Copied"
	Case 1
		'Files Copied
		Call Logger(logName, "RESULT: Success - Files Copied", False)
		emailSubject = emailSubject & " Success"
		emailBody = emailBody & " Success - Files Copied"
	Case 2
		'File on Source Does Not Exist on Destination
		Call Logger(logName, "RESULT: Success - New Files Found in Source that are Not in Destination", False)
		emailSubject = emailSubject & " Success"
		emailBody = emailBody & " Success - New Files Found in Source that are Not in Destination"
	Case 3
		'Combination of Case1 and Case2
		Call Logger(logName, "RESULT: Success - Files Copied (New Files Found in Source that are Not in Destination)", False)
		emailSubject = emailSubject & " Success"
		emailBody = emailBody & " Success - Files Copied (New Files Found in Source that are Not in Destination)"
	Case Else
		'Error
		Call Logger(logName, "RESULT: !~ERROR~! - Error Occurred on Robocopy", False)
		emailSubject = emailSubject & " FAILED"
		emailBody = emailBody & " FAILED - Error Occurred on Robocopy"
	End Select
	
	If Err.Number <> 0 Then
		Err.Clear
		Call Logger(logName, "!~ERROR~! - Unknown Error Occurred in RoboDirMirror Sub", False)
		emailSubject = emailSubject & " FAILED"
		emailBody = emailBody & " FAILED - Unknown Error Occurred in RoboDirMirror Sub"
	End If
	
	Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
	
End Sub



' ***************************************************************************************************
' Function ConfirmDirectories - Confirm Source and Destination Directories Exist
' ***************************************************************************************************
Private Sub ConfirmDirectories(logName, strSource, strDestination, emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody)

	'On Error Resume Next
	
	Call Logger(logName, vbCrLf & "[" & Now() & "] " & vbCrLf & "Confirm Source/Destination Directories Exist", False)
	' Create Objects
	Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
	Call Logger(logName, "SOURCE: " & strSource, False)
	Call Logger(logName, "DESTINATION: " & strDestination, False)
	If Not objFSO.FolderExists(strSource) Then
		'Source Not Exist
		Call Logger(logName, "!~ERROR~! - Source Directory Does Not Exist, Quitting Script!", False)
		emailSubject = emailSubject & " FAILED"
		emailBody = emailBody & " Failed - Source Directory Does Not Exist"
		Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
		Wscript.Quit
	Else
		Call Logger(logName, "Success - Source Directory Exists", False)
	End If
	
	If Not objFSO.FolderExists(strDestination) Then
		'Destination Not Exist
		Call Logger(logName, "Warning - Destination Directory Does Not Exist, Attempting to Create It", False)
		'Attempt to Create
		Call CreateFileOrDir(strDestination)
		If Not objFSO.FolderExists(strDestination) Then
			Call Logger(logName, "!~ERROR~! - Failed to Create Destination Directory, Quitting Script!", False)
			emailSubject = emailSubject & " FAILED"
			emailBody = emailBody & " Failed - Destination Directory Does Not Exist and Failed to Create It"
			Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
			Wscript.Quit
		Else
			Call Logger(logName, "Success - Created Destination Directory", False)
		End If
	Else
		Call Logger(logName, "Success - Destination Directory Exists", False)
	End If
	
	If Err.Number <> 0 Then
		Err.Clear
		Call Logger(logName, "!~ERROR~! - Unknown Error in ConfirmDirectories Sub, Quitting Script", False)
		emailSubject = emailSubject & " FAILED"
		emailBody = emailBody & " Failed - Unknown Error in ConfirmDirectories Sub"
		Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
		Wscript.Quit
	End If
	
End Sub



' ***************************************************************************************************
' Function AllowSingleInstance - Checks if only a single instance of the script is running - useful 
'                                for repetitive scheduled tasks
' ***************************************************************************************************
Private Sub AllowSingleInstance(strComputer, logName, emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody)
	
	On Error Resume Next
	
	Call Logger(logName, vbCrLf & "[" & Now() & "] " & vbCrLf & "Confirm Single Instance of " & Wscript.ScriptName, False)
	Dim intReturn: intReturn = 0  '0=Success, 1=Error, 2=Warning
	Dim objWMIService: Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
	Dim colItems: Set colItems = objWMIService.ExecQuery("Select * from Win32_Process Where Name = 'cscript.exe'" & " OR Name = 'wscript.exe'")
	Dim intCount: intCount = 0
	Dim objItem, strTemp

	For Each objItem in colItems
		If Not IsNull(objItem.CommandLine) Then
			strTemp = Right(objItem.CommandLine, Len(objItem.CommandLine) - InStrRev(objItem.CommandLine, chr(34) & " " & chr(34)))
			strTemp = Trim(Replace(strTemp, chr(34), ""))
			If StrComp(Wscript.ScriptFullName, strTemp, 1) = 0 Then
				intCount = intCount + 1
			End If
		End If
	Next	
	
	Call Logger(logName, "Detected Instances: " & intCount, False)
	If intCount > 1 Then
		'Kill It
		Call Logger(logName, "!~ERROR~! - Only 1 instance is allowed, Quitting Script!", False)
		emailSubject = emailSubject & " FAILED"
		emailBody = emailBody & " Backup FAILED - Detected " & intCount & " running instances"
		Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
		Wscript.Quit
	End If
	
	If Err.Number <> 0 Then
		Err.Clear
		Call Logger(logName, "!~ERROR~! - Unknown Error in AllowSingleInstance Sub, Quitting Script!", False)
		emailSubject = emailSubject & " FAILED"
		emailBody = emailBody & " Backup FAILED - Unknown Error in AllowSingleInstance Sub"
		Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
		Wscript.Quit
	End If
	
	On Error Goto 0
	
End Sub



' ***************************************************************************************************
' Function CreateFileOrDir
' ***************************************************************************************************
Private Sub CreateFileOrDir(strPath)

    On Error Resume Next

    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    If objFSO.DriveExists(objFSO.GetDriveName(strPath)) Then
        If StrComp(objFSO.GetExtensionName(strPath), "", 1) = 0 Then
            If Not objFSO.FolderExists(strPath) Then
                If objFSO.FolderExists(objFSO.GetParentFolderName(strPath)) Then
                    objFSO.CreateFolder strPath 'Create Folder In Current Path
                Else
                    CreateFileOrDir(objFSO.GetParentFolderName(strPath)) 'Recurse Creating Parent Folder
                    CreateFileOrDir(strPath) 'Recurse Creating Current Folder
                End If
            End If
        Else
            If Not objFSO.FileExists(strPath) Then
                If objFSO.FolderExists(objFSO.GetParentFolderName(strPath)) Then
                    objFSO.CreateTextFile strPath, True  'Create File In Current Path
                Else
                    CreateFileOrDir(objFSO.GetParentFolderName(strPath))  'Recurse Creating Parent Folder
                    CreateFileOrDir(strPath)  'Recurse Creating Current Folder
                End If
            End If
        End If
    End If
   
    On Error Goto 0
   
End Sub



' ***************************************************************************************************
' Function SendBlatEmail - Sends Email Using Blat
' ***************************************************************************************************
Private Sub SendBlatEmail(blatPath, blatProfile, strRecipients, strSubject, strBody, strAttachment)

    'Need blat.exe, blat.dll, blat.lib
    On Error Resume Next
   
    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim objShell: Set objShell = WScript.CreateObject("WScript.Shell")
    Dim scriptPath: scriptPath = Left(WScript.ScriptFullName,InstrRev(WScript.ScriptFullName,"\"))

    'Ensure Blat Exists
    If Not objFSO.FileExists(blatPath) Then
        If Not objFSO.FileExists(scriptPath & "blat.exe") Then
            Exit Sub
        Else
            blatPath = scriptPath & "blat.exe"
        End If
    End If
   
    'Set Blat Email Command
    Dim commandText: commandText = chr(34) & blatPath & chr(34)
    commandText = commandText & " -p " & chr(34) & blatProfile & chr(34)
    commandText = commandText & " -to " & strRecipients
    commandText = commandText & " -subject " & chr(34) & strSubject & " " & chr(34) 'Keep Space to Prevent Escaping the Quote
    commandText = commandText & " -body " & chr(34) & strBody & " " & chr(34) 'Keep Space to Prevent Escaping the Quote

    'Append Attachment(s)
    If objFSO.FileExists(strAttachment) Then
        commandText = commandText & " -attach " & chr(34) & strAttachment & chr(34)
    Else
        If objFSO.FileExists(scriptPath & strAttachment) Then
            commandText = commandText & " -attach " & chr(34) & scriptPath & strAttachment & chr(34)
        End If
    End If
   
    'Send Blat Email
    objShell.run commandText, True
   
    Set objFSO = Nothing
    Set objShell = Nothing
   
    If Err.Number <> 0 Then
        Err.Clear
    End If

    On Error Goto 0
   
End Sub



' ***************************************************************************************************
' Function Logger 
' ***************************************************************************************************
Private Sub Logger(fileName, logMessage, blnNewLog)

    On Error Resume Next
   
    Const ForReading = 1, ForWriting = 2, ForAppending = 8
    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim scriptPath: scriptPath = Left(WScript.ScriptFullName,InstrRev(WScript.ScriptFullName,"\"))
    Dim logName
    If InStr(1,fileName,"\",1) > 0 Then
        logName = fileName
        If objFSO.DriveExists(objFSO.GetDriveName(logName)) Then
            If StrComp(objFSO.GetExtensionName(logName), "", 1) = 0 Then
                If Not objFSO.FolderExists(logName) Then
                    If objFSO.FolderExists(objFSO.GetParentFolderName(logName)) Then
                        objFSO.CreateFolder logName 'Create Folder In Current Path
                        Exit Sub
                    Else
                        Call Logger(objFSO.GetParentFolderName(logName), logMessage, blnNewLog) 'Recurse Creating Parent Folder
                        Call Logger(logName, logMessage, blnNewLog) 'Recurse Creating Current Folder
                        Exit Sub
                    End If
                End If
            Else
                If Not objFSO.FileExists(logName) Then
                    If Not objFSO.FolderExists(objFSO.GetParentFolderName(logName)) Then
                        Call Logger(objFSO.GetParentFolderName(logName), logMessage, blnNewLog)  'Recurse Creating Parent Folder
                        Call Logger(logName, logMessage, blnNewLog)  'Recurse Creating Current Folder
                    End If
                End If
            End If
        End If
    Else
        logName = scriptPath & fileName
    End If
    Dim logFile
    If blnNewLog = True Then
        Set logFile = objFSO.CreateTextFile(logName, True)
    Else
        If objFSO.FileExists(logName) Then
            Set logFile = objFSO.OpenTextFile(logName, ForAppending, True)
        Else
            Set logFile = objFSO.CreateTextFile(logName, True)
        End If
    End If
    logFile.WriteLine logMessage
    logFile.Close
    Set objFSO = Nothing
   
    On Error Goto 0

End Sub

 

Jul 28

How to Configure a Cisco ASA Site-to-Site IPSec VPN

This article will explain how to configure a Site-to-Site IPSec VPN using Cisco ASA 55XX’s using IKEV1.  My example below shows how to configure VPN’s between 3 sites but can be modified for the following scenarios without much explanation:

  • site-to-site VPN between 2 sites (Just remove SiteC… duh!)
  • site-to-site to 3+ sites (just follow the example and modify for a N+1 sites.  If You’re doing more than 3 sites, you may wish to look at a hub and spoke model to simply the network and backups, etc)
  • Hub and spoke VPN, where 2 remote offices only connect to a main office (If SiteA is the Hub, on SiteB remove the SiteC configuration, on SiteC remove the SiteB configuration, etc…)

Overview

Network Diagram

ASA_Multi_Site-to-site_IPSEC_VPN

Network Diagram

Phase 1 Settings

Attribute Value
Authentication Preshared Keys
Encryption 3DES
Hash MD5
DH Group Group 2
Lifetime 86400 seconds

Phase 2 Settings

Attribute Value
Mode Tunnel
Encryption 3DES
Hash SHA-1
PFS Enabled
Lifetime 86400 seconds

Configure SiteA

!-----------------
!CONFIGURE OBJECTS
!-----------------
!Create Object for Local LAN SiteA
config t
object-group network obj-local
network-object 10.0.1.0 255.255.255.0
end
!Create Object for Remote LAN SiteB
config t
object-group network obj-SiteB
network-object 10.0.2.0 255.255.255.0
end
!Create Object for Remote LAN SiteC
config t
object-group network obj-SiteC
network-object 10.0.3.0 255.255.255.0
end
!
!---------------
!CONFIGURE ACL's
!---------------
!Configure VPN ACL SiteB
config t
access-list ACL_SiteB extended permit ip object-group obj-local object-group obj-SiteB
end
!Configure VPN ACL SiteC
config t
access-list ACL_SiteC extended permit ip object-group obj-local object-group obj-SiteC
end
!
!------------------------
!CONFIGURE NAT EXEMPTIONS
!------------------------
!Configure NAT Exemption SiteB
config t
nat (inside,outside) 1 source static obj-local obj-local destination static obj-SiteB obj-SiteB no-proxy-arp route-lookup
end
!Configure NAT Exemption SiteC
config t
nat (inside,outside) 1 source static obj-local obj-local destination static obj-SiteC obj-SiteC no-proxy-arp route-lookup
end
!
!-----------------------------------------------------
!CONFIGURE PHASE1 PROPOSAL (pre-g2-3des-md5 SA:84600s)
!-----------------------------------------------------
config t
crypto ikev1 policy 10
authentication pre-share
encryption 3des
hash md5
group 2
lifetime 86400
crypto ikev1 enable outside
end
!
!-------------------------------------------------------------------
!CONFIGURE PHASE 2 PROPOSALS 
!-------------------------------------------------------------------
!Configure Transform Set(g2-esp-3des-sha SA:84600s PFS:enabled)
config t
crypto ipsec ikev1 transform-set ESP-3DES-SHA esp-3des esp-sha-hmac
end
!Configure Transform Set (g2-esp-3des-md5 SA:84600s PFS:enabled)
config t
crypto ipsec ikev1 transform-set ESP-3DES-MD5 esp-3des esp-md5-hmac
end
!Configure Tunnel Group Remote WAN IP SiteB(Replace VPNSHAREDKEYPW)
config t
tunnel-group 1.1.1.2 type ipsec-l2l
tunnel-group 1.1.1.2 ipsec-attributes 
pre-shared-key VPNSHAREDKEYPW
end
!Configure Tunnel Group Remote WAN IP SiteC(Replace VPNSHAREDKEYPW)
config t
tunnel-group 1.1.1.3 type ipsec-l2l
tunnel-group 1.1.1.3 ipsec-attributes 
pre-shared-key VPNSHAREDKEYPW
end
!Configure Crypto Maps (You can only have 1 crypto map nam and each VPN needs an unique Map # to differentiate from each other)
!MAP: SiteA=1, SiteB=2, SiteC=3
config t
crypto map crypto-map 2 match address ACL_SiteB
crypto map crypto-map 2 set peer 1.1.1.2
crypto map crypto-map 2 set transform-set ESP-3DES-SHA
end
config t
crypto map crypto-map 3 match address ACL_SiteC
crypto map crypto-map 3 set peer 1.1.1.3
crypto map crypto-map 3 set transform-set ESP-3DES-SHA
end
!Attach Crypto Map to Interface
config t
crypto map crypto-map interface outside
end

 

Configure SiteB

!-----------------
!CONFIGURE OBJECTS
!-----------------
!Create Object for Local LAN SiteB
config t
object-group network obj-local
network-object 10.0.2.0 255.255.255.0
end
!Create Object for Remote LAN SiteA
config t
object-group network obj-SiteA
network-object 10.0.1.0 255.255.255.0
end
!Create Object for Remote LAN SiteC
config t
object-group network obj-SiteC
network-object 10.0.3.0 255.255.255.0
end
!
!---------------
!CONFIGURE ACL's
!---------------
!Configure VPN ACL SiteA
config t
access-list ACL_SiteA extended permit ip object-group obj-local object-group obj-SiteA
end
!Configure VPN ACL SiteC
config t
access-list ACL_SiteC extended permit ip object-group obj-local object-group obj-SiteC
end
!
!------------------------
!CONFIGURE NAT EXEMPTIONS
!------------------------
!Configure NAT Exemption SiteA
config t
nat (inside,outside) 1 source static obj-local obj-local destination static obj-SiteA obj-SiteA no-proxy-arp route-lookup
end
!Configure NAT Exemption SiteC
config t
nat (inside,outside) 1 source static obj-local obj-local destination static obj-SiteC obj-SiteC no-proxy-arp route-lookup
end
!
!-----------------------------------------------------
!CONFIGURE PHASE1 PROPOSAL (pre-g2-3des-md5 SA:84600s)
!-----------------------------------------------------
config t
crypto ikev1 policy 10
authentication pre-share
encryption 3des
hash md5
group 2
lifetime 86400
crypto ikev1 enable outside
end
!
!-------------------------------------------------------------------
!CONFIGURE PHASE 2 PROPOSALS 
!-------------------------------------------------------------------
!Configure Transform Set(g2-esp-3des-sha SA:84600s PFS:enabled)
config t
crypto ipsec ikev1 transform-set ESP-3DES-SHA esp-3des esp-sha-hmac
end
!Configure Transform Set (g2-esp-3des-md5 SA:84600s PFS:enabled)
config t
crypto ipsec ikev1 transform-set ESP-3DES-MD5 esp-3des esp-md5-hmac
end
!Configure Tunnel Group Remote WAN IP SiteA(Replace VPNSHAREDKEYPW)
config t
tunnel-group 1.1.1.1 type ipsec-l2l
tunnel-group 1.1.1.1 ipsec-attributes 
pre-shared-key VPNSHAREDKEYPW
end
!Configure Tunnel Group Remote WAN IP SiteC(Replace VPNSHAREDKEYPW)
config t
tunnel-group 1.1.1.3 type ipsec-l2l
tunnel-group 1.1.1.3 ipsec-attributes 
pre-shared-key VPNSHAREDKEYPW
end
!Configure Crypto Maps (You can only have 1 crypto map nam and each VPN needs an unique Map # to differentiate from each other)
!MAP: SiteA=1, SiteB=2, SiteC=3
config t
crypto map crypto-map 1 match address ACL_SiteA
crypto map crypto-map 1 set peer 1.1.1.1
crypto map crypto-map 1 set transform-set ESP-3DES-SHA
end
config t
crypto map crypto-map 3 match address ACL_SiteC
crypto map crypto-map 3 set peer 1.1.1.3
crypto map crypto-map 3 set transform-set ESP-3DES-SHA
end
!Attach Crypto Map to Interface
config t
crypto map crypto-map interface outside
end

Configure SiteC

!-----------------
!CONFIGURE OBJECTS
!-----------------
!Create Object for Local LAN SiteC
config t
object-group network obj-local
network-object 10.0.3.0 255.255.255.0
end
!Create Object for Remote LAN SiteA
config t
object-group network obj-SiteA
network-object 10.0.1.0 255.255.255.0
end
!Create Object for Remote LAN SiteB
config t
object-group network obj-SiteB
network-object 10.0.2.0 255.255.255.0
end
!
!---------------
!CONFIGURE ACL's
!---------------
!Configure VPN ACL SiteA
config t
access-list ACL_SiteA extended permit ip object-group obj-local object-group obj-SiteA
end
!Configure VPN ACL SiteB
config t
access-list ACL_SiteB extended permit ip object-group obj-local object-group obj-SiteB
end
!
!------------------------
!CONFIGURE NAT EXEMPTIONS
!------------------------
!Configure NAT Exemption SiteA
config t
nat (inside,outside) 1 source static obj-local obj-local destination static obj-SiteA obj-SiteA no-proxy-arp route-lookup
end
!Configure NAT Exemption SiteB
config t
nat (inside,outside) 1 source static obj-local obj-local destination static obj-SiteB obj-SiteB no-proxy-arp route-lookup
end
!
!-----------------------------------------------------
!CONFIGURE PHASE1 PROPOSAL (pre-g2-3des-md5 SA:84600s)
!-----------------------------------------------------
config t
crypto ikev1 policy 10
authentication pre-share
encryption 3des
hash md5
group 2
lifetime 86400
crypto ikev1 enable outside
end
!
!-------------------------------------------------------------------
!CONFIGURE PHASE 2 PROPOSALS 
!-------------------------------------------------------------------
!Configure Transform Set(g2-esp-3des-sha SA:84600s PFS:enabled)
config t
crypto ipsec ikev1 transform-set ESP-3DES-SHA esp-3des esp-sha-hmac
end
!Configure Transform Set (g2-esp-3des-md5 SA:84600s PFS:enabled)
config t
crypto ipsec ikev1 transform-set ESP-3DES-MD5 esp-3des esp-md5-hmac
end
!Configure Tunnel Group Remote WAN IP SiteA(Replace VPNSHAREDKEYPW)
config t
tunnel-group 1.1.1.1 type ipsec-l2l
tunnel-group 1.1.1.1 ipsec-attributes 
pre-shared-key VPNSHAREDKEYPW
end
!Configure Tunnel Group Remote WAN IP SiteB(Replace VPNSHAREDKEYPW)
config t
tunnel-group 1.1.1.2 type ipsec-l2l
tunnel-group 1.1.1.2 ipsec-attributes 
pre-shared-key VPNSHAREDKEYPW
end
!Configure Crypto Maps (You can only have 1 crypto map nam and each VPN needs an unique Map # to differentiate from each other)
!MAP: SiteA=1, SiteB=2, SiteC=3
config t
crypto map crypto-map 1 match address ACL_SiteA
crypto map crypto-map 1 set peer 1.1.1.1
crypto map crypto-map 1 set transform-set ESP-3DES-SHA
end
config t
crypto map crypto-map 2 match address ACL_SiteB
crypto map crypto-map 2 set peer 1.1.1.2
crypto map crypto-map 2 set transform-set ESP-3DES-SHA
end
!Attach Crypto Map to Interface
config t
crypto map crypto-map interface outside
end

 

Further Info

I would suggest using IKEV2 for a Site-to-Site VPN, and I’ll outline the steps a in future article once I get time to write it up…. Stay tuned!

 

May 21

Convert a WLC LDPE Image to Non-LDPE

If you ever purchase a used Cisco Wireless LAN Controller or receive one on RMA, you may run into an issue when you attempt to upgrade the image and receive the following error:

ERROR: Incompatible SW image.ERROR: Please install the Data Payload Encryption licensed image

This issue occurs because the Controller has an LDPE image installed, which is only needed in Russia where Data DTLS Payload Encryption is regulated by the Government.  Cisco only recommends using this image if you reside in Russia.  To resolve this issue and put the standard image on, follow these steps:

Step 1 – Confirm you have an LDPE image installed

From the console, enter the show sysinfo command and confirm the build type is DATA + WPS + LDPE

Build Type Data + WPS + LDPE

Step 2 – Upgrade to LDPE Image version 7.0.230.0

LDPE Image version 7.0.230.0 (ex: AIR-CT5500-LDPE-K9-7-0-230-0.aes for a 5508) introduced the ability to move to a normal image once a DTLS license is installed (Resolved Caveat CSCtw78061).  If the product version is not already on that image, download it, and install it.

Step 3a – Confirm a DTLS License is Installed

From the console, enter the show license summary and ensure under the Feature: data encryption section it shows License State: Active, In Use.  If you see this, then continue to step 4, otherwise you must download a free DTLS license and install it.

Data Encryption License State

Step 3b – Download a DTLS License

  1. Go to https://tools.cisco.com/SWIFT/LicensingUI/Quickstart
  2. Click Get New->IPS, Crypto, Other Licenses
    Get New->IPS,Crypto,Other Licenses
  3. Click Wireless, then click Cisco Wireless Controllers (2500/5500/7500/WISM2) DTLS License
    Cisco Wireless Controllers (2500/5500/7500/WISM2) DTLS License
  4. Choose the Controller Platform, enter the Product ID, enter the Serial Number, and click Next
    1. You can retrieve the PID and SN by running show license UDI at the console

    Specify Target and Options

  5. Select I agree with the Terms of the License, confirm your email address, and click Get License

Step 3c – Install the DTLS License

  1. Copy the DTLS license to the root of your TFTP server
  2. At the console, run the following command to install your license
    1. license install tftp://<TFTP_IP>/XXXX.lic
    2. Replace <TFTP_IP> with the IP address of your TFTP servver
    3. Replace XXXX.lic with the name of your license
  3. Save your configuration and reboot the WLC
    1. save config
    2. reset system

Step 4 – Install the Non-LDPE Image

You can now install any Non-LDPE Image as needed!

Apr 22

DeleteOldUsers.vbs

'=========================================================================
' DeleteOldUsers.vbs
' VERSION: 1.0
' AUTHOR: Brian Steinmeyer
' EMAIL: sigkill@sigkillit.com
' WEB: http://sigkillit.com
' DATE: 4/22/2014
' USER TERMINATION POLICY:
' - Reset PW
' - Set description to termination date
' - Optionally forward email
' - Move terminated users to specified OU
' COMMENTS: This script works in conjunction with the above user termination
' policy. Specify the OU containing old users and set the number of retention
' days to keep an AD user account after the termination date. You can schedule
' the script to run daily to delete any old users.  to permanently delete
' those users after X amount of days.
'=========================================================================
Option Explicit

' ------ SCRIPT CONFIGURATION ------
Dim oldUserOU: oldUserOU = "OU=Old Users,OU=User,DC=domain,DC=local"
Dim oldUserRetentionDays: oldUserRetentionDays = 30
Dim logResults: logResults = Replace(WScript.ScriptFullName,".vbs","_logs\") & Replace(WScript.ScriptName,".vbs","_") & FixDate(Date()) & Replace(FormatDateTime(Time,4), ":", "") & ".txt"
Dim logRetentionDays: logRetentionDays = 30
Dim emailBlatExe: emailBlatExe = "c:\blat\blat.exe" 'Custom Path to Blat.exe Default is Same Folder as Script
Dim emailProfile: emailProfile = "alert" 'Blat Profile Name See (http://www.blat.net) For Info
Dim emailRecipient: emailRecipient = "alert.admin@domain.com" 'Email to Receive Backup Result
Dim blnEmailOnlyOnDelete: blnEmailOnlyOnDelete = true 'True = Only email results when at least 1 user is deleted
' ------ END CONFIGURATION ------

'MAIN CALLS
Call TerminateOldUsers(oldUserRetentionDays,oldUserOU,logResults)
Call SendResults(logResults,blnEmailOnlyOnDelete,emailBlatExe,emailProfile,emailRecipient)
Call PurgeLogs(Replace(WScript.ScriptFullName,".vbs","_logs\"),logRetentionDays,".txt")

' ***************************************************************************************************
' Sub TerminateOldUsers - Parse Old Users and Delete after X days
' ***************************************************************************************************
Private Sub TerminateOldUsers(intDays,strOU,logName)

    On Error Resume Next    'Start Error Handling

    'Create Log File
    Call Logger(logName, "DATE:" & Now() & vbCrLf & "USER_OU:" & strOU & vbCrLf & "RETENTION_DAYS:" & intDays, True)

    'Search OU For Users
    Dim objConnection: Set objConnection = CreateObject("ADODB.Connection")
    Dim objCommand: Set objCommand =   CreateObject("ADODB.Command")
    objConnection.Provider = "ADsDSOObject"
    objConnection.Open "Active Directory Provider"
    Set objCommand.ActiveConnection = objConnection
    objCommand.Properties("Page Size") = 1000   'Override the Return 1000 Results Default
    Const ADS_SCOPE_SUBTREE = 2
    objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE    'Include Sub OU's
    objCommand.CommandText = "SELECT ADsPath, cn FROM 'LDAP://" & strOU & "' WHERE objectCategory='person' AND objectClass='user'"
    Dim objRecordSet: Set objRecordSet = objCommand.Execute
    'Parse Users
    If objRecordSet.RecordCount > 0 Then
        objRecordSet.MoveFirst
        Dim objUser, objParent, strDescription
        Dim strResult: strResult = ""
        Do Until objRecordSet.EOF
            Set objUser = GetObject(objRecordSet.Fields("ADsPath").Value)
            strDescription = objUser.description
            If strDescription = "" Then
                strDescription = "BLANK"
            End If
            'Only Evaluate Users with Date for Description
            If IsDate(objUser.Description) Then
                If DateDiff("d",objUser.Description,Date) > intDays Then
                    'Delete User Past Retention Date
                    strResult = "DELETE"
                    Set objParent = GetObject(objUser.Parent)
                    objParent.Delete "user", "CN=" & objRecordSet.Fields("cn").Value                   
                Else
                    strResult = "IGNORE"
                End If
            Else
                strResult = "IGNORE"
            End If         
            'Log Results
            If Err.Number <> 0 Then
                Err.Clear
                strResult = "!~ERROR!~"
            End If
            Call Logger(logName, strResult & ":(" & strDescription & "):" & objRecordSet.Fields("ADsPath").Value, False)
            objRecordSet.MoveNext
        Loop
    Else
        Call Logger(logName, "NOUSERS:" & strOU, False)
    End If

    On Error Goto 0 'End Error Handling

End Sub

' *****************************************************************
' Sub SendResults - Parse Log File and Send Results
' *****************************************************************
Private Sub SendResults(logName, blnEmail, emailBlatExe, emailProfile, emailRecipient)

    On Error Resume Next

    ' Create Objects
    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")

    'Parse Log File
    Dim intDelete: intDelete = 0
    Dim intTotal: intTotal = 0
    Dim intError: intError = 0
    Dim strLine
    Dim logFile: Set logFile = objFSO.OpenTextFile(logName, 1)
    Do Until logFile.AtEndOfStream
        strLine = logFile.ReadLine
        If InStr(1, strLine, "IGNORE:", 1) Then
            intTotal = intTotal + 1
        End If
        If InStr(1, strLine, "DELETE:", 1) Then
            intTotal = intTotal + 1
            intDelete = intDelete + 1
        End If
        If InStr(1, strLine, "!~ERROR~!:", 1) Then
            intTotal = intTotal + 1
            intError = intError + 1
        End If
        If InStr(1, strLine, "NOUSERS:", 1) Then
            'No Users
        End If
    Loop
    logFile.Close

    'Set Email Subject
    Dim emailSubject: emailSubject = Replace(WScript.ScriptName,".vbs","")
    If intError > 0 Then
        emailSubject =  emailSubject & " - Error"
    Else
        emailSubject = emailSubject & " - Deleted "  & intDelete & " Users"
    End If

    'Set Email Body
    Dim emailBody: emailBody = "TOTAL USERS: " & intTotal & vbCrLf & _
        "ERRORS: " & intError & vbCrLf & _
        "DELETED: " & intDelete

    'Email Results
    If blnEmail = true Then
        If intDelete > 0 Then
            Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
        End If
    Else
        Call SendBlatEmail(emailBlatExe, emailProfile, emailRecipient, emailSubject, emailBody, logName)
    End If

    'Cleanup Objects
    Set objFSO = Nothing

    On Error Goto 0

End Sub

' ***************************************************************************************************
' Function SendBlatEmail - Sends Email Using Blat
' ***************************************************************************************************
Private Sub SendBlatEmail(blatPath, blatProfile, strRecipients, strSubject, strBody, strAttachment)

    'Need blat.exe, blat.dll, blat.lib
   On Error Resume Next

    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim objShell: Set objShell = WScript.CreateObject("WScript.Shell")
    Dim scriptPath: scriptPath = Left(WScript.ScriptFullName,InstrRev(WScript.ScriptFullName,"\"))

    'Ensure Blat Exists
   If Not objFSO.FileExists(blatPath) Then
        If Not objFSO.FileExists(scriptPath & "blat.exe") Then
            Exit Sub
        Else
            blatPath = scriptPath & "blat.exe"
        End If
    End If

    'Set Blat Email Command
   Dim commandText: commandText = chr(34) & blatPath & chr(34)
    commandText = commandText & " -p " & chr(34) & blatProfile & chr(34)
    commandText = commandText & " -to " & strRecipients
    commandText = commandText & " -subject " & chr(34) & strSubject & " " & chr(34) 'Keep Space to Prevent Escaping the Quote
   commandText = commandText & " -body " & chr(34) & strBody & " " & chr(34) 'Keep Space to Prevent Escaping the Quote

    'Append Attachment(s)
   If objFSO.FileExists(strAttachment) Then
        commandText = commandText & " -attach " & chr(34) & strAttachment & chr(34)
    Else
        If objFSO.FileExists(scriptPath & strAttachment) Then
            commandText = commandText & " -attach " & chr(34) & scriptPath & strAttachment & chr(34)
        End If
    End If

    'Send Blat Email
   objShell.run commandText, True

    Set objFSO = Nothing
    Set objShell = Nothing

    If Err.Number <> 0 Then
        Err.Clear
    End If

    On Error Goto 0

End Sub

' ***************************************************************************************************
' Sub PurgeLogs - Deletes Old Log FIles
' ***************************************************************************************************
Private Sub PurgeLogs(logFolder,intDays,strExtension)

    On Error Resume Next

    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim objFolder: Set objFolder = objFSO.GetFolder(logFolder)
    Dim file
    For Each file In objFolder.Files
        If StrComp(Right(file.Name, Len(strExtension)),strExtension) = 0 Then
            If DateDiff("d", file.DateLastModified, Now) > intDays Then
                objFSO.DeleteFile(file), True
            End If
        End If
    Next

    Set objFSO = Nothing

    If Err.Number <> 0 Then
        Err.Clear
    End If

    On Error Goto 0

End Sub

' ***************************************************************************************************
' Function FixDate - Ensures Single Digit Numbers Have 0 In Front
' ***************************************************************************************************
Function FixDate(strDate)
    Dim arrTemp
    Dim M, D, Y
    arrTemp = Split(strDate, "/")
    M = arrTemp(0)
    D = arrTemp(1)
    Y = arrTemp(2)
    If (M>=0) And (M<10) Then M = "0" & M
    If (D>=0) And (D<10) Then D = "0" & D
    'If (Y>=0) And (Y<10) Then Y = "0" & Y
    FixDate = M & D & Y
End Function

' ***************************************************************************************************
' Function Logger
' ***************************************************************************************************
Private Sub Logger(fileName, logMessage, blnNewLog)

    On Error Resume Next

    Const ForReading = 1, ForWriting = 2, ForAppending = 8
    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim scriptPath: scriptPath = Left(WScript.ScriptFullName,InstrRev(WScript.ScriptFullName,"\"))
    Dim logName
    If InStr(1,fileName,"\",1) > 0 Then
        logName = fileName
        If objFSO.DriveExists(objFSO.GetDriveName(logName)) Then
            If StrComp(objFSO.GetExtensionName(logName), "", 1) = 0 Then
                If Not objFSO.FolderExists(logName) Then
                    If objFSO.FolderExists(objFSO.GetParentFolderName(logName)) Then
                        objFSO.CreateFolder logName 'Create Folder In Current Path
                       Exit Sub
                    Else
                        Call Logger(objFSO.GetParentFolderName(logName), logMessage, blnNewLog) 'Recurse Creating Parent Folder
                       Call Logger(logName, logMessage, blnNewLog) 'Recurse Creating Current Folder
                       Exit Sub
                    End If
                End If
            Else
                If Not objFSO.FileExists(logName) Then
                    If Not objFSO.FolderExists(objFSO.GetParentFolderName(logName)) Then
                        Call Logger(objFSO.GetParentFolderName(logName), logMessage, blnNewLog)  'Recurse Creating Parent Folder
                       Call Logger(logName, logMessage, blnNewLog)  'Recurse Creating Current Folder
                   End If
                End If
            End If
        End If
    Else
        logName = scriptPath & fileName
    End If
    Dim logFile
    If blnNewLog = True Then
        Set logFile = objFSO.CreateTextFile(logName, True)
    Else
        If objFSO.FileExists(logName) Then
            Set logFile = objFSO.OpenTextFile(logName, ForAppending, True)
        Else
            Set logFile = objFSO.CreateTextFile(logName, True)
        End If
    End If
    logFile.WriteLine logMessage
    logFile.Close
    Set objFSO = Nothing

    On Error Goto 0

End Sub

 

Apr 08

Matching Credit Card Numbers

Overview

Using regular expressions, you can easily match a credit card number.  You may wish to validate legit CC numbers, block financial information in emails, or audit security by finding financial information in documents.  There is no perfect algorithm or regex for detecting potential CCN’s, and there will always be false positives, etc.  Although regular expressions can match a CCN, they cannot confirm incorrect digits.  If you require a more robust solution, you will need to also implement the Luhn algorithm.  Moving forward, I’ll focus on detecting CCN’s in documents or emails.

Credit Card Info

The first 6 digits of a CCN are known as the Issuer Identification Number (IIN), which are used to identify the card issuer; the remaining digits can vary in length and are up to the issuer. Using the IIN and the CCN pre-defined length, we can identify blocks of numbers that belong to each issuer.  The credit card numbers are typically grouped with spaces or dashes in order to make them more readable.  We will need to keep this in mind when matching CCN’s.  I have listed US issuers, their IIN, and the CCN lengths below. For a full list including international issuers see http://en.wikipedia.org/wiki/Bank_card_number.

ISSUER IIN STARTING PATTERN LENGTH
American Express 34, 37 15
Diners Club 300-305, 309, 36, 38-39 15
Discover 6011, 622126-622925, 644-649, 65 16
JCB 3528-3589 16
MasterCard 50-55 16
Visa 4 16 or 13 on old cards

The Basics Validating Credit Cards

If we wanted to validate a credit card, you would first want to remove any spaces or dashes from the input.  Once the input is clean we can use a typical regular expression to match potential valid CCN’s. The below regex’s were originally taken from http://www.regular-expressions.info/creditcard.html, but I’ve updated the out of date expressions in accordance to the latest IIN changes as per the Wikipedia article listed above:

American Express

^3[47][0-9]{13}$

 

Diners Club

^3(?:0[0-5]|09|[68][0-9])[0-9]{11}$

 

Discover

^6(?:011|22[0-9]|5[0-9]{2})[0-9]{12}$

 

JCB

^35(?:2[89]|[3-8][0-9])[0-9]{12}$

 

Mastercard

^5[0-5][0-9]{14}$

 

Visa

^4[0-9]{12}(?:[0-9]{3})?$

 

However, if you are unable to strip the spaces and dashes out prior to validating the CCN, you’ll quickly find many shortcomings.  The above regex’s will not account for spaces or dashes as printed on the front of the card and will only detect a CCN when it’s the only thing on a line.  Obviously, this will not give us the results we desire for finding CCN’s in a document or email.  Instead, we will want to use \b to match on a word boundary instead of the carrot(^) and the dollar($).  In addition, we also want to add exceptions before and after the CCN we’re checking, which will help reduce false positives; this will allow us to eliminate items such as hyperlinks, order #’s, etc.  We can now use the below regex to surround each CC issuer’s rules that we want to detect:

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)CCN(?!\/)\b

 

  • Starting Position is a word boundary
  • Previous Character is not:
    • period(.)
    • left angle bracket(<)
    • right angle bracket(>)
    • dash()
    • plus(+)
    • forward slash(/) – Ignore false positives like http://www.domain.com/################/
    • Open parenthesis – Ignore false positives like (################)
    • Equal (=) – Ignores false positives like http://www.domain.com?si=################
    • Pound, Colon, Space(#:  ) – Ignore false positives like Order#: ################
    • dash, space(– )
    • ID, colon, space(ID: )
  • CCN – Represents the regex used to represent each issuers credit card number
  • Next character is not:
    • forward slash(/) – Ignore false positives like http://www.domain.com/################/
  • Ending position is a word boundary

Matching Credit Cards in a Document or Email

By doing a slight re-write of CC regex’s and combining it with our above wrapper, we can easily detect a CCN’s in a document or email. However, the oneliner for Discover CCN’s is quite long, and some systems limit the length of regex’s. Due to this, I’ve provided the oneliner plus shorter versions split up by the IIN.

American Express

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)3[47](?:\d{13}|\d{2}\s\d{6}\s\d{5}|\d{2}\-\d{6}\-\d{5})(?!\/)\b

 

Diners Club

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)3(?:0[0-5]|09|[689]\d)\d(?:\d{10}|\s\d{6}\s\d{4}|\-\d{6}\-\d{4})(?!\/)\b

 

Discover (Oneliner)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)(?:6011\d{12}|6011(\s\d{4}){3}|6011(\-\d{4}){3}|64[4-9]\d{13}|64[4-9]\d(\s\d{4}){3}|64[4-9]\d(\-\d{4}){3}|65\d{14}|65\d{2}(\s\d{4}){3}|65\d{2}(\-\d{4}){3}|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5])\d{10}|622(1\s2[6-9]|1\s[3-9][0-9]|[2-8]\s[0-9]{2}|9\s[0-1][0-9]|9\s2[0-5])\d{2}(\s\d{4}){2}|622(1\-2[6-9]|1\-[3-9][0-9]|[2-8]\-[0-9]{2}|9\-[0-1][0-9]|9\-2[0-5])\d{2}(\-\d{4}){2})(?!\/)\b

 

Discover (6011,644-649,65 IIN’s)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)6(?:011|4[4-9]\d|5\d{2})(?:\d{12}|(\s\d{4}){3}|(\-\d{4}){3})(?!\/)\b

 

Discover (622 IIN No Delimiter)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5])\d{10}(?!\/)\b

 

Discover (622 IIN Space Delimiter)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s)622(1\s2[6-9]|1\s[3-9][0-9]|[2-8]\s[0-9]{2}|9\s[0-1][0-9]|9\s2[0-5])\d{2}(\s\d{4}){2}(?!\/)\b

Note: Office 365 has a 128 character limit for the regex expression.  I have modified the default wrapper by removing a few items to keep this at 128 characters

 

Discover (622 IIN Dash Delimiter)

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s)622(1\-2[6-9]|1\-[3-9][0-9]|[2-8]\-[0-9]{2}|9\-[0-1][0-9]|9\-2[0-5])\d{2}(\-\d{4}){2}(?!\/)\b

Note: Office 365 has a 128 character limit for the regex expression.  I have modified the default wrapper by removing a few items to keep this at 128 characters

 

JCB

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)35(?:2[89]|[3-8]\d)(?:\d{12}|(\s\d{4}){3}|(\-\d{4}){3})(?!\/)\b

 

MasterCard

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)5[0-5](?:\d{14}|\d{2}(\s\d{4}){3}|\d{2}(\-\d{4}){3})(?!\/)\b

 

Visa

\b(?<![\.\<\>\-\+\/\(\=]|#:\s|\-\s|ID:\s)4(?:\d{11}|\d{3}\s(\d{4}\s){2}|\d{3}\-(\d{4}\-){2})\d(\d{3})?(?!\/)\b

If we test the above regex in an online tester, we can verify it’s working as expected. As you can see, our regex is capturing our test Visa CCN’s and missing a lot of false positives:

Visa Regex Test

Real World Ex: Blocking Emails with Credit Card Numbers in Office 365

Now that we’ve established our regex’s, let’s apply it to a real world example. For our example, we’ll block inbound/outbound email in Office 365. Please note, Office 365 transport rules only allow 128 characters in a regex, and due to this we’ll need to use multiple regex’s for matching Discover.  Also note, two of the Discover Regex’s I used above have a modified wrapper to keep them at the 128 character limit.

  1. Login to the Office 365 Admin Portal (https://portal.microsoftonline.com)
  2. Click Admin then click Exchange
  3. Under the Exchange Admin Center, click Mail Flow
  4. Click the Rules tab
  5. Click the + to create a new rule
    1. Name: Block Emails with Credit Card Numbers
    2. Apply this rule if: The subject or body matches:
      1. Paste each CCN Regex
    3. Do the following:  Reject the message with the explanation
      1. Rejection Reason: Your message was blocked due to the detection of a Credit Card Number
  6. Click Save
    1. Note: Mail flow rules normally take 30-35 minutes to replicate in Office 365
Apr 04

Block Outbound Email for Specific Users

Overview

There are a few situations where you may need to restrict certain users from sending email to external users.  For example, you may have part time employees that only need to send email to internal users OR you might have an employee who’s about to get terminated and don’t want them emailing clients.  Fortunately, in Office 365 Exchange you can create a Mail Flow Rule to accomplish this.

Create Distribution Group to Define Users to Block Outbound Email

In order for the mail flow rule to see the group, it must be a distribution group.  However, you can easily hide it from the GAL so your users don’t see it.  Many organizations use CustomAttribute15 to define what displays in there GAL.  If that’s your case, simply do not define CustomAttribute15 or define it to a value so it does not show in your GAL; otherwise, set the attribute to Hide group from Exchange Address Lists.

  1. Create a new distribution group
    1. Name: Block Outbound Email
    2. Email: blockoutboundemail@<company>.onmicrosoft.com
    3. Members: Add any user you want to block from sending outbound emails to external recipients (They will only be able to send to internal recipients)
  2. If you are using Office 365 in a Hybrid Deployment, make sure you use dirsync to synchronizes your new group

Create Mail Flow Rule

In this example, we will prevent a user from sending emails to any external recipients, but they will still be able to send to internal recipients.

  1. Login to the Office 365 Admin Portal https://portal.microsoftonline.com
  2. Click Admin then click Exchange to open the Exchange Admin CenterOpen Exchange Admin Center
  3. Click mail flow then click on the Rules tab
  4. Click the + symbol and click Create a new rule       Create New Rule
  5. Name the rule Block Outbound Emails to External Recipients
  6. Under Apply this rule if, click the recipient is located
    1. Select Outside the organization and click OK
  7. Click More Options to add another condition
  8. Click Add Condition
  9. On the new condition, select the sender is a member of this group
    1. Search and select the group Block Outbound Emails and click OK
    2. Note: Despite the wording stating “member of this group”, you can select a user instead of a group.  However, it’s easier to manage and you do not need to wait for the mail flow rule to propagate on 365, which can take up to an hour in my testing.
  10. Under Do the following, select Block the message then click delete the message without notifying anyone, and click OK
  11. Click Save

IMPORTANT NOTE:  It can take up to 45 minutes for Microsoft’s back end to fully synchronize rules!  This means any new or modified rules can take up to 45 minutes to take effect!

Block Outbound Email

 

 

 

Apr 04

Delivery Report in Outlook or Outlook Web App

Overview

When using Outlook or Outlook Web App (OWA) in an Office 365 or Exchange environment, you can track the message from the client side.  Both Outlook and OWA allow you to view a delivery report in order to confirm a message was delivered when the recipient claims they have not received it or if it’s taking a long time to deliver.  Delivery reports work for both internal and external recipients.

View a Delivery Report in Outlook

  1. In Outlook, go to your Sent Items folder
  2. Locate the message you want to track and open it
  3. Click File, click Info, and click Open Delivery Report

Outlook Message Delivery Report

View a Delivery Report in Outlook Web App (OWA)

If you are using any other email client than Outlook (mobile device, OWA, etc), you can use OWA to view a delivery report.

  1. Login to OWA at https://portal.microsoftonline.com
  2. Click the Gear Icon, then click Options
  3. Click Organize Email then click Delivery Reports
  4. Enter your search criteria, click Search
  5. Select the email you want to track and click the Pencil Icon to view the delivery report

OWA Delivery Report

Review Delivery Report

Internal delivery reports will show Delivered upon success delivering.  Also note, Office 365 Exchange only keeps message tracking data for 14 days.

Delivery Report Internal

External delivery reports will only show Transferred which means it successfully sent out from your mail server.  However, this does not guarantee the recipient received the email because there can be issues on the recipients email server.

Delivery Report External

Apr 04

365Licenses.ps1

#=========================================================================
# 365Licenses.ps1
# VERSION: 1.0
# AUTHOR: Brian Steinmeyer
# EMAIL: sigkill@sigkillit.com
# WEB: http://sigkillit.com
# DATE: 4/4/20114
# REQUIREMENTS:
# 1) Microsoft Online Services Sign-In Assistant for IT Professionals
# -(http://www.microsoft.com/en-gb/download/details.aspx?id=28177)
# 2) Windows Azure Active Directory Module for Windows PowerShell
# -(http://technet.microsoft.com/en-us/library/jj151815.aspx#bkmk_installmodule)
# COMMENTS: This script is intended to retrieve Office 365 licensing information
# by accepted domains. It will provide the active license count by domain,
# estimate the cost per domain, and provide the number of unused licenses.
#=========================================================================

# ------ SCRIPT CONFIGURATION ------
# Define License Unit Cost
$intCost = 3.88
# ------ END CONFIGURATION ------

# Connect to Microsoft Online
write-host "Connecting to Office 365..."
Import-Module MSOnline
Try {
Connect-MsolService -ErrorAction Stop
} Catch {
Write-Host $error[0].Exception -ForegroundColor Red -BackgroundColor Black
Write-Host "Error Connecting to Office 365... Quitting Script!" -ForegroundColor Red -BackgroundColor Black
Break
}

# Get Office 365 Accepted Domains and Active User Licenses
Try {
$arrDomains = @(Get-MsolDomain)
} Catch {
Write-Host "Error Retrieving Office 365 Accepted Domains... Quitting Script!" -ForegroundColor Red -BackgroundColor Black
Break
}
$arrCompany = @()
$TotalLicenses = 0
$TotalCost = 0
foreach ($d in $arrDomains){
$domain = $d.Name
write-host ("PROCESSING: " + $domain.ToUpper())
$users = Get-MsolUser -All | where {$_.isLicensed -eq "True" -and $_.UserPrincipalName.Contains($domain)} | Select DisplayName, UserPrincipalName -ExpandProperty Licenses | Select DisplayName, UserPrincipalName, AccountSkuID
If ($users.count -ne $null){
$i = $users.count
$users | format-table
$users | Export-Csv ("365_Licenses_" + $domain.replace(".","_") + ".csv")
}
Else{
$i = 0
Write-Host "0 Licenses<code>n</code>n"
}
$objCompany = New-Object -TypeName PSObject
$objCompany | Add-Member -Name 'Domain' -MemberType Noteproperty -Value $domain
$objCompany | Add-Member -Name 'Licenses' -MemberType Noteproperty -Value $i
$objCompany | Add-Member -Name 'Cost' -MemberType Noteproperty -Value ("{0:C2}" -f ($i * $intCost))
$arrCompany += $objCompany
$TotalLicenses += $i
$TotalCost += ($i * $intCost)

}

# Get Company Licensing Info
Try {
$companyLicenses = Get-MsolAccountSku | Select AccountSkuId, ActiveUnits, WarningUnits, ConsumedUnits
} Catch {
Write-Host $error[0].Exception -ForegroundColor Red -BackgroundColor Black
Write-Host "Error Retrieving Office 365 Account Info... Quitting Script!" -ForegroundColor Red -BackgroundColor Black
Break
}
$objCompany = New-Object -TypeName PSObject
$objCompany | Add-Member -Name 'Domain' -MemberType Noteproperty -Value "TOTAL ACTIVE LICENSES"
$objCompany | Add-Member -Name 'Licenses' -MemberType Noteproperty -Value $TotalLicenses
$objCompany | Add-Member -Name 'Cost' -MemberType Noteproperty -Value ("{0:C2}" -f $TotalCost)
$arrCompany += $objCompany

$unusedLicenses = ($companyLicenses.ActiveUnits - $companyLicenses.ConsumedUnits)
$unusedCost = ($unusedLicenses * $intCost)
$objCompany = New-Object -TypeName PSObject
$objCompany | Add-Member -Name 'Domain' -MemberType Noteproperty -Value "TOTAL UNUSED LICENSES"
$objCompany | Add-Member -Name 'Licenses' -MemberType Noteproperty -Value $unusedLicenses
$objCompany | Add-Member -Name 'Cost' -MemberType Noteproperty -Value ("{0:C2}" -f $unusedCost)
$arrCompany += $objCompany

$objCompany = New-Object -TypeName PSObject
$objCompany | Add-Member -Name 'Domain' -MemberType Noteproperty -Value "GRAND TOTAL LICENSES"
$objCompany | Add-Member -Name 'Licenses' -MemberType Noteproperty -Value $companyLicenses.ActiveUnits
$objCompany | Add-Member -Name 'Cost' -MemberType Noteproperty -Value ("{0:C2}" -f ($companyLicenses.ActiveUnits * $intCost))
$arrCompany += $objCompany

# Display Statistics
$companyLicenses | Format-Table -Auto
$arrCompany | Format-Table -Auto