Cybersecurity
DevOps Cloud (ADM)
IT Operations Cloud
Our client asked us to automatically produce a monthly report which would allow them to see all users and what their last logon time was. This is an audit requirement, and it needs to be done by someone other than the system administrators, making it a perfect candidate for automation.
This solution is an example of how the XML tokens can be used to create new HTML to be inserted into an eMail template. While this is a relatively simple task, it did offer an opportunity to utilize this new, unheralded feature of IDM 3.6.
The components of this solution include:
The logic of this solution is contained within a Null Driver, and all the processing is in a single policy set. The configuration of the solution is done primarily through GCVs.
The following GCVs were defined on this driver
The lists are defined as lists of strings; the Groups are DN references to a group object. The driver will use these objects to create a list of who to send the eMail to.
<?xml version="1.0" encoding="UTF-8"?><configuration-values>
<definitions>
<definition critical-change="true" display-name="List of Attributes to Report On" name="attr-names" type="list">
<description/>
<value>
<item>company</item>
<item>Description</item>
<item>Full Name</item>
<item>Given Name</item>
<item>Initials</item>
<item>Surname</item>
<item>Group Membership</item>
<item>Internet Email Address</item>
<item>L</item>
<item>Login Disabled</item>
<item>Login Time</item>
<item>manager</item>
<item>OU</item>
<item>Password Expiration Time</item>
<item>Telephone</item>
<item>Title</item>
<item>uniqueID</item>
<item>workforceID</item>
<item>homeDirectory</item>
<item>uidNumber</item>
</value>
</definition>
<definition critical-change="true" display-name="Attributes that are date format" name="Date-Attributes" type="list">
<description/>
<value>
<item>Login Time</item>
<item>Password Expiration Time</item>
</value>
</definition>
<definition critical-change="true" display-name="Group to send reports to" dn-space="dirxml" dn-type="slash" name="to-group" type="dn">
<description/>
<value>Watts\Groups\AUDIT-REPORT-TO</value>
</definition>
<definition critical-change="true" display-name="Group to CC reports to" dn-space="dirxml" dn-type="slash" name="cc-group" type="dn">
<description/>
<value>Watts\Groups\AUDIT-REPORT-CC</value>
</definition>
<definition critical-change="true" display-name="Group to bc reports to" dn-space="dirxml" dn-type="slash" name="bc-group" type="dn">
<description/>
<value>Watts\Groups\AUDIT-REPORT-BC</value>
</definition>
</definitions>
</configuration-values>
The entire solution is contained within a single policy set, the subscriber event transform.
The solution operates as follows:
<?xml version="1.0" encoding="UTF-8"?><policy>
<rule>
<description>[CIS] Report</description>
<conditions>
<and>
<if-operation mode="case" op="equal">trigger</if-operation>
</and>
</conditions>
<actions>
<do-set-local-variable name="TYPES" scope="policy">
<arg-string>
<token-global-variable name="Date-Attributes"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DOC" scope="policy">
<arg-node-set>
<token-xml-parse>
<token-text xml:space="preserve"><use-html/></token-text>
</token-xml-parse>
</arg-node-set>
</do-set-local-variable>
<do-append-xml-element expression="$DOC/use-html" name="table"/>
<do-append-xml-element expression="$DOC//table[last()]" name="tr"/>
<do-append-xml-element expression="$DOC//table/tr[last()]" name="td"/>
<do-set-xml-attr expression="$DOC//table/tr[last()]" name="class">
<arg-string>
<token-text xml:space="preserve">underline</token-text>
</arg-string>
</do-set-xml-attr>
<do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]">
<arg-string>
<token-text xml:space="preserve">Distinguished Name</token-text>
</arg-string>
</do-append-xml-text>
<do-for-each>
<arg-node-set>
<token-global-variable name="attr-names"/>
</arg-node-set>
<arg-actions>
<do-append-xml-element expression="$DOC//table/tr[last()]" name="td"/>
<do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]">
<arg-string>
<token-local-variable name="current-node"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
</do-for-each>
<do-for-each>
<arg-node-set>
<token-query class-name="User" datastore="src">
<arg-dn>
<token-src-dn/>
</arg-dn>
<arg-string>
<token-text xml:space="preserve">Object Class</token-text>
</arg-string>
</token-query>
</arg-node-set>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="nocase" name="BAR" op="equal">greenbar</if-local-variable>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="BAR" scope="policy">
<arg-string>
<token-text xml:space="preserve">whitebar</token-text>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
<do-set-local-variable name="BAR" scope="policy">
<arg-string>
<token-text xml:space="preserve">greenbar</token-text>
</arg-string>
</do-set-local-variable>
</arg-actions>
</do-if>
<do-append-xml-element expression="$DOC//table[last()]" name="tr"/>
<do-set-xml-attr expression="$DOC//table/tr[last()]" name="class">
<arg-string>
<token-local-variable name="BAR"/>
</arg-string>
</do-set-xml-attr>
<do-set-local-variable name="RECORD" scope="policy">
<arg-node-set>
<token-query class-name="User" datastore="src" scope="entry">
<arg-dn>
<token-xpath expression="$current-node/@src-dn"/>
</arg-dn>
<arg-string>
<token-text xml:space="preserve">company</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Description</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Full Name</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Given Name</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Initials</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Surname</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Group Membership</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Internet Email Address</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">L</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Login Disabled</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Login Time</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">manager</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">OU</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Password Expiration Time</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Telephone</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">Title</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">uniqueID</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">workforceID</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">homeDirectory</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">uidNumber</token-text>
</arg-string>
</token-query>
</arg-node-set>
</do-set-local-variable>
<do-append-xml-element expression="$DOC//table/tr[last()]" name="td"/>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">$RECORD/attr[@attr-name="Login Disabled"]/value/text()='true'</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-append-xml-element expression="$DOC//table/tr[last()]/td[last()]" name="strike"/>
<do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]/strike">
<arg-string>
<token-xpath expression="$current-node/@src-dn"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
<arg-actions>
<do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]">
<arg-string>
<token-xpath expression="$current-node/@src-dn"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
</do-if>
<do-for-each>
<arg-node-set>
<token-global-variable name="attr-names"/>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="ATTR-NAME" scope="policy">
<arg-string>
<token-local-variable name="current-node"/>
</arg-string>
</do-set-local-variable>
<do-append-xml-element expression="$DOC//table/tr[last()]" name="td"/>
<do-for-each>
<arg-node-set>
<token-xpath expression="$RECORD/attr[@attr-name=$ATTR-NAME]/value"/>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="VALUE" scope="policy">
<arg-string>
<token-xpath expression="$current-node/text()"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-xpath op="true">contains($TYPES,$ATTR-NAME)</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="VALUE" scope="policy">
<arg-string>
<token-convert-time dest-format="!LONG.DATETIME" dest-lang="en-US" dest-tz="US/Eastern" src-format="!CTIME" src-tz="UTC">
<token-local-variable name="VALUE"/>
</token-convert-time>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions/>
</do-if>
<do-append-xml-element expression="$DOC//table/tr[last()]/td[last()]" name="br"/>
<do-append-xml-text expression="$DOC//table/tr[last()]/td[last()]">
<arg-string>
<token-local-variable name="VALUE"/>
</arg-string>
</do-append-xml-text>
</arg-actions>
</do-for-each>
<do-strip-xpath expression="$DOC//table/tr[last()]/td[last()]/br[1]"/>
</arg-actions>
</do-for-each>
</arg-actions>
</do-for-each>
<do-for-each>
<arg-node-set>
<token-src-attr name="Member">
<arg-dn>
<token-global-variable name="to-group"/>
</arg-dn>
</token-src-attr>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="current-email" scope="policy">
<arg-string>
<token-src-attr name="Internet EMail Address">
<arg-dn>
<token-local-variable name="current-node"/>
</arg-dn>
</token-src-attr>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="nocase" name="current-email" op="not-equal"/>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="TO-EMAIL" scope="policy">
<arg-string>
<token-local-variable name="TO-EMAIL"/>
<token-text xml:space="preserve">,</token-text>
<token-local-variable name="current-email"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions/>
</do-if>
</arg-actions>
</do-for-each>
<do-for-each>
<arg-node-set>
<token-src-attr name="Member">
<arg-dn>
<token-global-variable name="cc-group"/>
</arg-dn>
</token-src-attr>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="current-email" scope="policy">
<arg-string>
<token-src-attr name="Internet EMail Address">
<arg-dn>
<token-local-variable name="current-node"/>
</arg-dn>
</token-src-attr>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="nocase" name="current-email" op="not-equal"/>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="CC-EMAIL" scope="policy">
<arg-string>
<token-local-variable name="CC-EMAIL"/>
<token-text xml:space="preserve">,</token-text>
<token-local-variable name="current-email"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions/>
</do-if>
</arg-actions>
</do-for-each>
<do-for-each>
<arg-node-set>
<token-src-attr name="Member">
<arg-dn>
<token-global-variable name="bc-group"/>
</arg-dn>
</token-src-attr>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="current-email" scope="policy">
<arg-string>
<token-src-attr name="Internet EMail Address">
<arg-dn>
<token-local-variable name="current-node"/>
</arg-dn>
</token-src-attr>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="nocase" name="current-email" op="not-equal"/>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="BC-EMAIL" scope="policy">
<arg-string>
<token-local-variable name="BC-EMAIL"/>
<token-text xml:space="preserve">,</token-text>
<token-local-variable name="current-email"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions/>
</do-if>
</arg-actions>
</do-for-each>
<do-send-email-from-template notification-dn="Security\Default Notification Collection" template-dn="Security\Default Notification Collection\[CIS] User Report">
<arg-string name="to">
<token-substring start="1">
<token-local-variable name="TO-EMAIL"/>
</token-substring>
</arg-string>
<arg-string name="WHEN">
<token-time format="!LONG.DATETIME" lang="en-US" tz="US/Eastern"/>
</arg-string>
<arg-string name="TABLE">
<token-xml-serialize>
<token-local-variable name="DOC"/>
</token-xml-serialize>
</arg-string>
<arg-string name="cc">
<token-substring start="1">
<token-local-variable name="CC-EMAIL"/>
</token-substring>
</arg-string>
<arg-string name="bcc">
<token-substring start="1">
<token-local-variable name="BC-EMAIL"/>
</token-substring>
</arg-string>
</do-send-email-from-template>
</actions>
</rule>
</policy>
eMail Template
The eMail template is very simple; it contains just variables which represent when the report was run as well as one to insert the HTML. It also contains style definitions which could be used to provide a nicer output, although in this case we just use them to simulate greenbar paper.
<template name="[CIS] User Report" subject="User Report: $WHEN$">
<data>
<html>
<form:tokendescriptions>
<form:tokendescription description="" itemname="WHEN"/>
<form:tokendescription description="" itemname="TABLE"/>
</form:tokendescriptions>
<head>
<title>User Report for $WHEN$</title>
<style>
<! body { fontfamily: Trebuchet MS } >
</style>
<style type="text/css">
tr.greenbar td {
backgroundcolor: #EBFFEB; color: black;}
tr.whitebar td {
backgroundcolor: #FFFFFF; color: black;}
tr.underline td {
textdecoration: underline; }
td.strikethrough td {
textdecoration: linethrough;}
</style>
</head>
<body BGCOLOR="#FFFFFF">
<H1>User Report</H1>
$TABLE$
</body>
</html>
</data>
</template>
Create a new Subscriber Channel Trigger on the Jobs tab of the driver in iManager.
The resulting job needs to be assigned to the appropriate server.
The subscriber channel trigger job can execute as often as you wish, we selected the first of every month at 1:00 am for the job to execute.
Select the root container where you wish this report to be effective.
Important: Select the job to apply only to the container. The policies will query for subordinate objects. Selecting a subtree could result in many separate very short reports being mailed rather than one large one.
Make certain that "Submit a trigger document for objects without a driver association" is set to true. Otherwise this will never execute since no objects have an association to this null driver.
Security
You must also grant the job object trustee rights to the driver object. This can be done in iManager.
Select the View Objects view from the top button bar of iManager, and browse down the tree to your new Null Driver object. Check the driver object and from the Actions menu select Modify Trustees.
Click the Add Trustee button.
Browse to the new Job object you just created
Click on Assigned Rights
Under "All Attribute Rights" click "Write" and click Done to save.
Note: You can assign a single additional right to write to "DirXML-AccessSubmitCommand" instead of modifying all attributes but this is fewer clicks.
You can now test the job by returning to the job, selecting it and clicking "Run Now".
This is a simple example of what can be done with an IDM policy to generate an automated report. It should be noted that this report will take a long time to run; our testing showed that it was processing approximately one object every three seconds. On our 4200 object tree, this is a 3½ hour report time. It also does use significant processing power to run.
This does not obviate the need for auditing tools such as those provided by Novell Access Goverance solution; in fact what it does is point up that need. The use of the IDM solution to audit itself is clever but really violates the principals of auditing; you should always audit with a different tool than you are administering with.
However, this does provide a starting point for auditing when you need a simple list generated monthly to review.