Share:|

This has come up at a couple customers recently so I figured I'd post something about it.

 

Problem: You have a number of approved exceptions that should be set on a components associated w/ a component template.  As new servers are added and components created there is no mechanism to have exceptions applied to the new components so they are in place before the next compliance run.

 

Solution: While there is no built-in mechanism to do this in BSA currently this can be accomplished with some blcli and nsh scripting.

 

There's a couple parts to this - first is where to keep the exceptions.  A couple obvious places are the Property Dictionary or a text file.  So of course I'm going to use the Property Dictionary because BSA! but the text file is fine also.

 

So I have a custom property class called ApproveExceptions that contains properties that define the exception (exceptionName, exceptionDescription, etc).  I ended up writing a script to create this and populate it, more on that later. So in this class I created sub-classes for each Component Template that I need to set exceptions on.  Under each sub-class I create the instances for each exception I need to set.  That's a pretty simple setup since I'm only working w/ the out-of-the-box component templates.  You might need to make something with more depth, or add some properties to better identify the template.  The point here was to make this as automated and self-resolving as possible.  I also define the Templates workspace folder my templates are in - I'm assuming I'll loop through them all, then loop through the sub-class that list what templates should get some exceptions and match them up.  Once I know what template I'm looking at I grab all the exceptions I need to set.  Then I grab all the components for the template and loop through them.  I see what exceptions are already set, compare them to the exceptions that should be set, and if there is a mismatch, set the exception.  New exceptions are new property instances that can be added in the Property Dictionary manually through the gui or via the blcli.

 

A few ideas:

  • Around line 80 instead of getting all the components for the template, just get the components created in the last few days and only process those.  If the default exception list is static that should reduce the processing time, especially in larger environments.  This could be done w/ the creation of a smart component group based on the template and the DATE_CREATED property.
  • Read the exceptions out of a file instead of pulling the exceptions out of the Property Dictionary.  Might work as an integration point with another compliance tracking system or CMDB.
  • Parameterize the script so it can either process just the newly components or all and then you have a script that can be used to only handle newly created components or push out a new exception across the existing components.
  • Put this in a batch job that runs Discovery, this NSH Script and then Compliance so you know the Components always have the exceptions set before Compliance runs.

 

 

#!/bin/nsh

blcli_setjvmoption -Dcom.bladelogic.cli.execute.quietmode.enabled=true
blcli_setoption serviceProfileName defaultProfile
blcli_setoption roleName BLAdmins
blcli_connect

exceptionClass="Class://SystemObject/ApprovedExceptions"
exceptionFile="/tmp/exceptions.csv"
templateGroup="/CIS Compliance Content"


# get all the templates in the group
blcli_execute TemplateGroup groupNameToDBKey "${templateGroup}"
blcli_storeenv templateGroupKey
blcli_execute Template findAllByGroup ${templateGroupKey} true
blcli_execute Template getDBKey
blcli_execute Utility setTargetObject
blcli_execute Utility listPrint
blcli_execute Utility setTargetObject
blcli_storeenv templateKeys
templateKeys="$(awk 'NF' <<< "${templateKeys}")"

blcli_execute Utility getCurrentRole
blcli_storeenv rbacRole
blcli_execute Utility getCurrentUserName
blcli_storeenv rbacUser


# for each template, get the exception name and other info
while read templateKey
        do
        blcli_execute Template findByDBKey ${templateKey}
        blcli_execute Template getName
        blcli_storeenv templateName
        blcli_execute Template getGroupId
        blcli_storeenv templateGroupId
        blcli_execute Group getQualifiedGroupName 5008 ${templateGroupId}
        blcli_storeenv templateGroupPath
        echo "Processing ${templateGroupPath}/${templateName}..."
        blcli_execute PropertyClass isPropertyClassDefined "${exceptionClass}/${templateName}"
        blcli_storeenv classExists

        if [[ "${classExists}" = "false" ]]
                then
                echo "   Cannot find exception class ${exceptionClass}/${templateName}, will not set exceptions for ${templateName}..."
                break
        fi

        # get the instance (exception) names in the class (for the template)
        blcli_execute PropertyClass listAllInstanceNames "${exceptionClass}/${templateName}"
        blcli_storeenv instanceNames
        instanceNames="$(awk 'NF' <<< "${instanceNames}")"

        typeset -a exceptionNames
        typeset -A exceptionDescriptions
        typeset -A exceptionComments
        typeset -A exceptionRuleNames

        if [[ -n ${instanceNames} ]]
                then
                while read i
                        do
                        echo "   Getting exception information for: ${i}..."
                        blcli_execute PropertyInstance getPropertyValue "${i}" "exceptionName"
                        blcli_storeenv exceptionName
                        exceptionNames+=("${exceptionName}")
                        blcli_execute PropertyInstance getPropertyValue "${i}" "exceptionDescription"
                        blcli_storeenv exceptionDescription
                        exceptionDescriptions[${exceptionName}]="${exceptionDescription}"
                        blcli_execute PropertyInstance getPropertyValue "${i}" "exceptionComment"
                        blcli_storeenv exceptionComment
                        exceptionComments[${exceptionName}]="${exceptionComment}"
                        blcli_execute PropertyInstance getPropertyValue "${i}" "exceptionRuleName"
                        blcli_storeenv exceptionRuleName
                        exceptionRuleNames[${exceptionName}]="${exceptionRuleName}"
                done <<< "${instanceNames}"

                # get all the components for a template
                blcli_execute Component getAllComponentKeysByTemplateKey ${templateKey}
                blcli_storeenv componentKeys
                componentKeys="$(awk 'NF' <<< "${componentKeys}")"

                while read componentKey
                        do
                        blcli_execute Component componentKeyToName ${componentKey}
                        blcli_storeenv componentName
                        echo "   Processing exceptions on ${componentName}..."
                        blcli_execute Component findComponentExceptions ${componentKey}
                        blcli_execute ComponentException getName
                        blcli_execute Utility setTargetObject
                        blcli_execute Utility listPrint
                        blcli_storeenv componentExceptions
                        for i in {1..${#exceptionNames}}
                                do
                                exceptionName="${exceptionNames[${i}]}"
                                echo "    Checking ${componentName} for ${exceptionName}..."
                                hasException="false"
                                while read componentException
                                        do
                                        if [[ "${exceptionName}" = "${componentException}" ]]
                                                then
                                                hasException="true"
                                                echo "     ${componentName} has exception: ${exceptionName}..."
                                                break
                                        fi
                                done <<< "${componentExceptions}"
                        
                                if [[ "${hasException}" = "false" ]]
                                        then
                                        echo "     ${componentName} needs exception: ${exceptionName}, setting..."
                                        echo "      Adding exception: ${exceptionName} ${exceptionDescriptions[${exceptionName}]} ${exceptionComments[${exceptionName}]} on rule: ${exceptionRuleNames[${exceptionName}]}..."
                                        blcli_execute ComponentException createComponentExceptionWithOneRule ${componentKey} "${exceptionName}" "${exceptionDescriptions[${exceptionName}]}" "${rbacRole}" "${rbacUser}" "${exceptionComments[${exceptionName}]}" "${templateGroupPath}" "${templateName}" "${exceptionRuleNames[${exceptionName}]}"
                                        blcli_storeenv componentKey
                                fi
                        done 
                done <<< "${componentKeys}"

                unset exceptionNames exceptionDescriptions exceptionComments exceptionRuleNames
        else    
                echo "   No exceptions for ${templateName}..."
        fi
done <<< "${templateKeys}"

 

When I was building the script above I realized I wanted a way to create the Property Dictionary instances by processing a text file so I had something to work with in the other script, instead of creating the instances by hand.  So that script is below.  It reads a csv file (further below) and creates the instances.

 

#!/bin/nsh

blcli_setjvmoption -Dcom.bladelogic.cli.execute.quietmode.enabled=true
blcli_setoption serviceProfileName defaultProfile
blcli_setoption roleName BLAdmins
blcli_connect

exceptionClass="Class://SystemObject/ApprovedExceptions"
typeset -A exceptionProperties
exceptionProperties=(exceptionName Primitive:/String exceptionDescription Primitive:/String exceptionComment Primitive:/String exceptionRuleName Primitive:/String)
exceptionFile="/tmp/testExceptions.csv"
bulkPropertyFile="/tmp/bulkSetPSIProps.csv"
templateGroup="/CIS Compliance Content"

[[ -f "${bulkPropertyFile}" ]] && rm -f "${bulkPropertyFile}"

blcli_execute PropertyClass isPropertyClassDefined "${exceptionClass}"
blcli_storeenv classExists

if [[ "${classExists}" = "false" ]]
        then
        blcli_execute PropertyClass createSubClass "${exceptionClass%/*}" "${exceptionClass##*/}" ""
fi

for i in ${(k)exceptionProperties}
        do
        blcli_execute PropertyClass isPropertyDefined "${exceptionClass}" "${i}"
        blcli_storeenv propertyExists

        if [[ "${propertyExists}" = "false" ]]
                then
                blcli_execute PropertyClass addProperty "${exceptionClass}" "${i}" "${i}" "${exceptionProperties[${i}]}" true false ""
        fi
done

# get all templates in the group, will create a subclass for each one to hold the exceptions
blcli_execute TemplateGroup groupNameToDBKey "${templateGroup}"
blcli_storeenv templateGroupKey
blcli_execute Template findAllByGroup ${templateGroupKey} true
blcli_execute Template getDBKey
blcli_execute Utility setTargetObject
blcli_execute Utility listPrint
blcli_execute Utility setTargetObject
blcli_storeenv templateKeys
templateKeys="$(awk 'NF' <<< "${templateKeys}")"

while read templateKey
        do
        blcli_execute Template findByDBKey ${templateKey}
        blcli_execute Template getName
        blcli_storeenv templateName
        blcli_execute PropertyClass isPropertyClassDefined "${exceptionClass}/${templateName}"
        blcli_storeenv subClassExists
        if [[ "${subClassExists}" = "false" ]]
                then
                blcli_execute PropertyClass createSubClass "${exceptionClass}" "${templateName}" ""
        fi
done <<< "${templateKeys}"

# read the csv file that lists the env-wide exceptions for each template and create the PSI and set the property values for each one.
if [[ -f "${exceptionFile}" ]]
        then
        while IFS=, read templateName exceptionName exceptionDescription exceptionComment exceptionRuleName
                do
                blcli_execute PropertyClass isPropertyClassInstanceDefined "${exceptionClass}/${templateName}" "${exceptionName}"
                blcli_storeenv instanceExists

                if [[ "${instanceExists}" = "false" ]]
                        then
                        blcli_execute PropertyInstance createInstance "${exceptionClass}/${templateName}" "${exceptionName}" ""
                else
                        echo "\"${exceptionClass}/${templateName}/${exceptionName}\",\"exceptionName\",\"${exceptionName}\"" >> "${bulkPropertyFile}"
                        echo "\"${exceptionClass}/${templateName}/${exceptionName}\",\"exceptionDescription\",\"${exceptionDescription}\"" >> "${bulkPropertyFile}"
                        echo "\"${exceptionClass}/${templateName}/${exceptionName}\",\"exceptionRuleName\",\"${exceptionRuleName}\"" >> "${bulkPropertyFile}"
                        echo "\"${exceptionClass}/${templateName}/${exceptionName}\",\"exceptionComment\",\"${exceptionComment}\"" >> "${bulkPropertyFile}"
                fi
        done < "${exceptionFile}"
        if [[ -s "${exceptionFile}" ]]
                then
                blcli_execute PropertyInstance bulkSetPropertyValues "${bulkPropertyFile%/*}" "${bulkPropertyFile##*/}"
        fi
else
        echo "Cannot find ${exceptionFile}..."
        exit 1
fi

 

Sample input file for the Property Instance creation script:

Template Name, exception name, exception description,comment,rule path/name.

CIS - Windows Server 2008 R2,exception-1,description-1,comment-1,/1.1.1.1 System Services/1.1.1.1.3 Configure IIS Admin Service
CIS - Windows Server 2008 R2,exception-2,description-2,comment-2,/1.1.1.1 System Services/1.1.1.1.4 Configure Windows Installer
CIS - Windows Server 2012 R2,exception-1a,description-1a,comment-1a,/1 Account Policies/1.1.1 Set Enforce password history to 24 or more passwords
CIS - Windows Server 2012 R2,exception-2a,description-2a,comment-2a,/1 Account Policies/1.1.3 Set Minimum password age to 1 or more days

 

This was a couple hours of hacking at it.  There are likely some performance improvements and error handling to be added and it hasn't been tested at any large scale.