Managing Infrastructure with RapidIdentity Part 3: Managing Windows Hosts

     

Recently, one of our RapidIdentity customers ran into a conundrum. While the customer heavily uses RapidConnect to synchronize their various application and authentication identity sources, they awoke to a mass Email stating that during their morning provisioning the displayName attribute on every user in their environment was changing!

The customer was confident this problem was not caused by our action set, but was unsure where to look to diagnose the issue. In a scramble, I was asked to investigate to see if I could tell them what had occurred.

Windows Security Event logs are an excellent resource to help determine what goes on in Active Directory, provided the proper log levels are enabled. In the case of our customer, I wanted to query the Security Event logs for the eventID 4738 - “A user account was changed.” Microsoft Windows 7 and 2008 Event ID’s can be found here

At the time, this was no easy task, as change events are logged on the Domain Controller (DC) on which they are processed. The customer had many DC’s, each with multiple days worth of archived Security Event logs, so it was no simple task for their staff. Eventually, the source of the changes was uncovered, and the customer was able to remediate and ensure that the responsible party was informed not to do this again. Realizing the time and effort this had required, I decided to use RapidConnect to automate the query tasks and minimize any future efforts.

Solution

This solution uses a single RapidConnect action, incorporating the RapidConnect Active Directory and CLI (command line interface) adapters to accomplish our goal. This action set accepts mandatory inputs of eventID and sAMAccountName and optional start and end dates (formatted in YYYYMMDD, such as 20160810) for which to query.

First, we’ll import our global variables, format any input dates, provide an output file path and establish a connection to Active Directory, and enumerate all Domain Controllers in the Domain.

# This action accepts a user's sAMAccountName and an eventID number from the windows security logs (Windows 2008R2, as some versions 'may' change their ID's - see https://support.microsoft.com/en-us/kb/977519 for ID's for 2008) and parses Security and Archive-Security logs on all DC's in the domain, reporting back with those events for the user
# This is useful to troubleshoot user attribute and password changes (eventID 4738), logons (eventID 4624) and others that may occur outside of RapidIdentity, and allow administrators to audit who performed a given action, within the date range of their security log and archives.
# Load our global variables
Globals()
# Open Output file
currentTime = now()
runTime = formatDate(currentTime, "yyyy-MM-dd-hh:mm")
fileTimestamp = toString(runTime)
outFile = openTextOutput("/adOut/" + fileTimestamp + "-" + sAMAccountName + "-" + eventID)
# Open PDC AD Connection and CLI
sessionPDC = openADConnection(Global.adHost, 636, true, Global.adUser,)
# Query for DCs to check for pwfilter
adDCs = getADRecords(sessionPDC, Global.adBaseDN, "sub", "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", "@dn,name,dNSHostName")

Next, the action iterates through each Domain Controller, opening a command line interface and passing a PowerShell command to query the Security Event log and any archived backup logs for our eventID and sAMAccountName (of the target user or object) and filters by date, if the optional start and end dates were provided in input. We output any results to the output file for manual user review, then close the CLI session with each Domain Controller.

Be advised, the PowerShell commands called within the if(startYYYYMMDD && endYYYYMMDD) sections, used to define date-filtered results below, required extra character escaping. This is both to send a quoted subcommand within the CLI command to call PowerShell, as well as the secondary, quoted FilterXPath filter info within that subcommand. It is important for those escape characters to be input precisely or else the date filters won’t work properly, and no results will be returned for date-specific runs.

# Iterate DC's
forEach(adDC, adDCs) {
    log("Retrieving Security log data from server: " + adDC['dnsHostName'], "blue")
    log("-----------------------------------------", "black")
    winCLI = openRemoteWindowsCLI(adDC['dnsHostName'], "Administrator", Global.adDNSDomain,)
    if(winCLI) {
        putTextOutputLine(outFile, "AD Domain Controller: " + adDC['dnsHostName'])
        putTextOutputLine(outFile, "-----------------------------------------")
        # previous logs
        archiveList = runRemoteCLICommand(winCLI, "powershell -executionpolicy unlimited \"dir c:\\windows\\system32\\winevt\\logs\\archive-security* | select name\"")
        archiveFiles = splitString(archiveList['output'], "\r")
        forEach(archiveFile, archiveFiles) {
            archiveFile = stringReplaceAll(archiveFile, " ", "")
            archiveFile = stringReplaceAll(archiveFile, "\n", "")
            foundArchive = stringContains(archiveFile, "Archive-Security", true)
            if(foundArchive) {
                if(startYYYYMMDD && endYYYYMMDD) {
                    startDate = startYYYYMMDD.substr(0,4) + "-" + startYYYYMMDD.substr(4,2) + "-" + startYYYYMMDD.substr(6,2) + "T00:00:00.000Z"
                    endDate = endYYYYMMDD.substr(0,4) + "-" + endYYYYMMDD.substr(4,2) + "-" + endYYYYMMDD.substr(6,2) + "T23:59:59.999Z"
                    cliCommand = "powershell -executionpolicy unrestricted \"try {Get-WinEvent -path 'c:\\windows\\system32\\winevt\\logs\\" + archiveFile + "' -filterxpath \\\"\"*[System[TimeCreated[@SystemTime > '" + startDate + "']]] and *[System[TimeCreated[@SystemTime < '" + endDate + "']]] and *[System[(EventID=" + eventID + ")]] and *[EventData[Data[@Name='TargetUserName']='" + sAMAccountName + "']]\\\"\" -ErrorAction Stop | Format-Table timecreated,message -autosize -wrap } catch [Exception] { if ($_.Exception -match 'No events were found that match the specified selection criteria.' ) { write-host ' '; }}\""
                } else {
                    cliCommand = "powershell -executionpolicy unrestricted \"try {Get-WinEvent -path 'c:\\windows\\system32\\winevt\\logs\\" + archiveFile + "' -filterxpath \\\"*[System[(EventID=" + eventID + ")]] and *[EventData[Data[@Name='TargetUserName']='" + sAMAccountName + "']]\\\" -ErrorAction Stop | Format-Table timecreated,message -autosize -wrap } catch [Exception] { if ($_.Exception -match 'No events were found that match the specified selection criteria.' ) { write-host \" \"; }}\""
                }
                queryInfo = runRemoteCLICommand(winCLI, cliCommand)
                foundData = stringContains(queryInfo['output'], "Time")
                if(foundData) {
                    (false) {
                        putTextOutputLine(outFile, queryInfo['output'])
                    }
                } else {
                }
            } else {
            }
        }
        # current log
        if(startYYYYMMDD && endYYYYMMDD) {
            startDate = startYYYYMMDD.substr(0,4) + "-" + startYYYYMMDD.substr(4,2) + "-" + startYYYYMMDD.substr(6,2) + "T00:00:00.000000000Z"
            endDate = endYYYYMMDD.substr(0,4) + "-" + endYYYYMMDD.substr(4,2) + "-" + endYYYYMMDD.substr(6,2) + "T23:59:59.999Z"
            cliCommand = "powershell -executionpolicy unrestricted \"try {Get-WinEvent -path 'c:\\windows\\system32\\winevt\\logs\\Security.evtx' -filterxpath \\\"\"*[System[TimeCreated[@SystemTime > " + startDate + "]]] and *[System[TimeCreated[@SystemTime < " + endDate + "]]] and *[System[(EventID=" + eventID + ")]] and *[EventData[Data[@Name='TargetUserName']='" + sAMAccountName + "']]\\\"\" -ErrorAction Stop | Format-Table timecreated,message -autosize -wrap } catch [Exception] { if ($_.Exception -match 'No events were found that match the specified selection criteria.' ) { write-host \" \"; }}\""
        } else {
            cliCommand = "powershell -executionpolicy unrestricted \"try {Get-WinEvent -path 'c:\\windows\\system32\\winevt\\logs\\Security.evtx' -filterxpath \\\"*[System[(EventID=" + eventID + ")]] and *[EventData[Data[@Name='TargetUserName']='" + sAMAccountName + "']]\\\" -ErrorAction Stop | Format-Table timecreated,message -autosize -wrap } catch [Exception] { if ($_.Exception -match 'No events were found that match the specified selection criteria.' ) { write-host \" \"; }}\""
        }
        queryInfo = runRemoteCLICommand(winCLI, cliCommand)
        foundData = stringContains(queryInfo['output'], "Time")
        if(foundData) {
            (false) {
                putTextOutputLine(outFile, queryInfo['output'])
            }
        } else {
        }
        putTextOutputLine(outFile, "-----------------------------------------")
        close(winCLI)
    } else {
    }
}

Finally, with our log file generated and our iteration through our Domain Controllers completed, we close our output file and AD session.

# Close Output File and AD session
close(outFile)
close(sessionPDC)

Technical Note

Depending on the event type and the size and quantity of the primary Security Event log and the archived logs, certain queries might take an excessive amount of time to return. This might be the case, for instance, if you query for account logons (eventID 4624) for an account used to run ‘once-per-minute’ RapidConnect actions against your Active Directory environment. Should a result set take too long to return, the job might error out on logs where this return didn’t come back in a timely fashion. If these mass results are a necessity to return, customers will need to contact Identity Automation’s support team to make adjustments to the JCIFS timeouts in the Tomcat application server configuration (specifically the jcifs.smb.client.responseTimeout parameter, which defaults to 30 seconds). The majority of queries return quickly for average users and typical events, provided the log file quantity and sizes are maintained.

Conclusion

This blog post provides one example of the usefulness of RapidConnect to manage your Windows infrastructure. In the next post, I’ll provide a second useful Windows example to demonstrate how to look for the presence of the RapidIdentity Password Filter on all of the Domain Controllers in an Active Directory environment.

Files

RetrieveADSecurityLogRecords (Input Properties:  eventID:string, sAMAccountName:string, startYYYYMMDD?:string, endYYYYMMDD?:string)


# This action accepts a user's sAMAccountName and an eventID number from the windows security logs (Windows 2008R2, as some versions 'may' change their ID's - see https://support.microsoft.com/en-us/kb/977519 for ID's for 2008) and parses Security and Archive-Security logs on all DC's in the domain, reporting back with those events for the user
# This is useful to troubleshoot user attribute and password changes (eventID 4738), logons (eventID 4624) and others that may occur outside of RapidIdentity, and allow administrators to audit who performed a given action, within the date range of their security log and archives.
# Load our global variables
Globals()
# Open Output file
currentTime = now()
runTime = formatDate(currentTime, "yyyy-MM-dd-hh:mm")
fileTimestamp = toString(runTime)
outFile = openTextOutput("/adOut/" + fileTimestamp + "-" + sAMAccountName + "-" + eventID)
# Open PDC AD Connection and CLI
sessionPDC = openADConnection(Global.adHost, 636, true, Global.adUser,)
# Query for DCs to check for pwfilter
adDCs = getADRecords(sessionPDC, Global.adBaseDN, "sub", "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", "@dn,name,dNSHostName")
# Iterate DC's
forEach(adDC, adDCs) {
    log("Retrieving Security log data from server: " + adDC['dnsHostName'], "blue")
    log("-----------------------------------------", "black")
    winCLI = openRemoteWindowsCLI(adDC['dnsHostName'], "Administrator", Global.adDNSDomain,)
    if(winCLI) {
        putTextOutputLine(outFile, "AD Domain Controller: " + adDC['dnsHostName'])
        putTextOutputLine(outFile, "-----------------------------------------")
        # previous logs
        archiveList = runRemoteCLICommand(winCLI, "powershell -executionpolicy unlimited \"dir c:\\windows\\system32\\winevt\\logs\\archive-security* | select name\"")
        archiveFiles = splitString(archiveList['output'], "\r")
        forEach(archiveFile, archiveFiles) {
            archiveFile = stringReplaceAll(archiveFile, " ", "")
            archiveFile = stringReplaceAll(archiveFile, "\n", "")
            foundArchive = stringContains(archiveFile, "Archive-Security", true)
            if(foundArchive) {
                if(startYYYYMMDD && endYYYYMMDD) {
                    startDate = startYYYYMMDD.substr(0,4) + "-" + startYYYYMMDD.substr(4,2) + "-" + startYYYYMMDD.substr(6,2) + "T00:00:00.000Z"
                    endDate = endYYYYMMDD.substr(0,4) + "-" + endYYYYMMDD.substr(4,2) + "-" + endYYYYMMDD.substr(6,2) + "T23:59:59.999Z"
                    cliCommand = "powershell -executionpolicy unrestricted \"try {Get-WinEvent -path 'c:\\windows\\system32\\winevt\\logs\\" + archiveFile + "' -filterxpath \\\"\"*[System[TimeCreated[@SystemTime > '" + startDate + "']]] and *[System[TimeCreated[@SystemTime < '" + endDate + "']]] and *[System[(EventID=" + eventID + ")]] and *[EventData[Data[@Name='TargetUserName']='" + sAMAccountName + "']]\\\"\" -ErrorAction Stop | Format-Table timecreated,message -autosize -wrap } catch [Exception] { if ($_.Exception -match 'No events were found that match the specified selection criteria.' ) { write-host ' '; }}\""
                } else {
                    cliCommand = "powershell -executionpolicy unrestricted \"try {Get-WinEvent -path 'c:\\windows\\system32\\winevt\\logs\\" + archiveFile + "' -filterxpath \\\"*[System[(EventID=" + eventID + ")]] and *[EventData[Data[@Name='TargetUserName']='" + sAMAccountName + "']]\\\" -ErrorAction Stop | Format-Table timecreated,message -autosize -wrap } catch [Exception] { if ($_.Exception -match 'No events were found that match the specified selection criteria.' ) { write-host \" \"; }}\""
                }
                queryInfo = runRemoteCLICommand(winCLI, cliCommand)
                foundData = stringContains(queryInfo['output'], "Time")
                if(foundData) {
                    (false) {
                        putTextOutputLine(outFile, queryInfo['output'])
                    }
                } else {
                }
            } else {
            }
        }
        # current log
        if(startYYYYMMDD && endYYYYMMDD) {
            startDate = startYYYYMMDD.substr(0,4) + "-" + startYYYYMMDD.substr(4,2) + "-" + startYYYYMMDD.substr(6,2) + "T00:00:00.000000000Z"
            endDate = endYYYYMMDD.substr(0,4) + "-" + endYYYYMMDD.substr(4,2) + "-" + endYYYYMMDD.substr(6,2) + "T23:59:59.999Z"
            cliCommand = "powershell -executionpolicy unrestricted \"try {Get-WinEvent -path 'c:\\windows\\system32\\winevt\\logs\\Security.evtx' -filterxpath \\\"\"*[System[TimeCreated[@SystemTime > " + startDate + "]]] and *[System[TimeCreated[@SystemTime < " + endDate + "]]] and *[System[(EventID=" + eventID + ")]] and *[EventData[Data[@Name='TargetUserName']='" + sAMAccountName + "']]\\\"\" -ErrorAction Stop | Format-Table timecreated,message -autosize -wrap } catch [Exception] { if ($_.Exception -match 'No events were found that match the specified selection criteria.' ) { write-host \" \"; }}\""
        } else {
            cliCommand = "powershell -executionpolicy unrestricted \"try {Get-WinEvent -path 'c:\\windows\\system32\\winevt\\logs\\Security.evtx' -filterxpath \\\"*[System[(EventID=" + eventID + ")]] and *[EventData[Data[@Name='TargetUserName']='" + sAMAccountName + "']]\\\" -ErrorAction Stop | Format-Table timecreated,message -autosize -wrap } catch [Exception] { if ($_.Exception -match 'No events were found that match the specified selection criteria.' ) { write-host \" \"; }}\""
        }
        queryInfo = runRemoteCLICommand(winCLI, cliCommand)
        foundData = stringContains(queryInfo['output'], "Time")
        if(foundData) {
            (false) {
                putTextOutputLine(outFile, queryInfo['output'])
            }
        } else {
        }
        putTextOutputLine(outFile, "-----------------------------------------")
        close(winCLI)
    } else {
    }
}
# Close Output File and AD session
close(outFile)
close(sessionPDC)
is-your-legacy-iam-system-doing-more-harm-than-good

Comments

Subscribe Here!