Using Identity Manager to Detect New AD Domain Controllers



By: rrawson

November 17, 2010 5:26 pm

Reads: 449

Comments:1

Rating:5.0

My company provides outsourcing of Identity Management solutions to our clients, where the client continues to operate their own Active Directory environment. One of the challenges of this set of operational boundaries is when our client updates their AD configuration, such as adding a new domain controller, they seldom let us know, nor do they install the mandated password filters.

To help solve this problem, we have crafted a little solution which will notify us by eMail whenever a new domain controller appears on the network.

The solution assumes an already functioning AD driver. This solution provides additional policies that would be added to an existing driver.

Filter

The filter has been enhanced to allow two additional classes with one attribute each. More could have been allowed but it was not necessary for this use case. The schema being allowed through is as follows:

  • Class: domain (representing the domain name in AD)
    • Attribute: Description (contain the domain short name)
  • Class: Computer
    • Attribute: L (containing the DNS name of the domain controller)
<filter-class class-name="Computer" publisher="sync" publisher-create-homedir="true" publisher-track-template-member="true" subscriber="ignore">
		<filter-attr attr-name="L" merge-authority="default" publisher="sync" publisher-optimize-modify="true" subscriber="ignore"/>
	</filter-class>
	<filter-class class-name="domain" publisher="sync" publisher-create-homedir="true" publisher-track-template-member="true" subscriber="ignore">
		<filter-attr attr-name="Description" merge-authority="default" publisher="sync" publisher-optimize-modify="true" subscriber="ignore"/>
	</filter-class>
	

Schema Map

The mapping is as follows:

  • Class domain in eDirectory maps to domainDNS in AD
    • Attribute Description maps to name in AD
  • Class Computer in eDirectory maps to computer in AD
    • Attribute L maps in eDirectory maps to dNSHostName in AD
<class-name>
		<nds-name>domain</nds-name>
		<app-name>domainDNS</app-name>
	</class-name>
	<attr-name class-name="domain">
		<nds-name>Description</nds-name>
		<app-name>name</app-name>
	</attr-name>
	<class-name>
		<nds-name>Computer</nds-name>
		<app-name>computer</app-name>
	</class-name>
	<attr-name class-name="Computer">
		<nds-name>L</nds-name>
		<app-name>dNSHostName</app-name>
	</attr-name>
	

GCVs

I usually place my GCV on the driver set but it could be placed on the driver object itself. However a subsequent article will show some other ways to use the data created by this function which would be dependent on this being assigned to the driver set.

	<definition critical-change="true" display-name="Container to place objects representing Domain Controllers" dn-space="dirxml" dn-type="slash" name="cis-dc-landing-container" type="dn">
			<description/>
			<value>lab\idm\Domains</value>
		</definition>
	Additionally, in a multi-domain forest, a GCV is required on each driver which indicates what the root DN for the forest is. This is because despite which domain controller you connect to, the root domain's DN is used to find the configuration container.
		<definition critical-change="true" display-name="Domain in LDAP format which is the root of this domain's forest" dn-space="application" dn-type="ldap" name="AD-Forest-Root" type="dn">
			<description/>
			<value>dc=wwt,dc=corp</value>
		</definition>
		

Publisher Matching Policy

A matching policy is not strictly required for this driver since it’s mapping over some fairly static data. However in some configurations the driver will veto in the match rule, so by having this matching policy we can assure that this solution can bolt easily into an existing driver.

<?xml version="1.0" encoding="UTF-8"?><policy>
	<rule>
		<description>[CIS] Domain Matching</description>
		<conditions>
			<and>
				<if-class-name mode="nocase" op="equal">domain</if-class-name>
			</and>
		</conditions>
		<actions>
			<do-find-matching-object scope="entry">
				<arg-dn>
					<token-global-variable name="cis-dc-landing-container"/>
					<token-text xml:space="preserve">\</token-text>
					<token-parse-dn dest-dn-format="slash" src-dn-format="ldap">
						<token-src-dn/>
					</token-parse-dn>
				</arg-dn>
				<arg-match-attr name="Description"/>
			</do-find-matching-object>
			<do-break/>
		</actions>
	</rule>
	<rule>
		<description>[CIS] Domain Contrller Matching</description>
		<conditions>
			<and>
				<if-class-name mode="nocase" op="equal">Computer</if-class-name>
			</and>
		</conditions>
		<actions>
			<do-find-matching-object scope="subtree">
				<arg-dn>
					<token-global-variable name="cis-dc-landing-container"/>
				</arg-dn>
				<arg-match-attr name="l"/>
			</do-find-matching-object>
			<do-break/>
		</actions>
	</rule>
</policy>

Publisher Create Policy

One of the key elements of this is the Publisher Create Policy, This policy will allow only computer objects which are domain controllers. To determine if a computer is a domain controller, the policy will query AD for any objects of class “nTDSDSA”. It iterates through those looking at the parent server object comparing the computer object to the “serverReference” attribute value. It will break out of the loop and continue on a match, otherwise, the create is vetoed as it is not a domain controller.

While there might have been more elegant and scalable ways to do this, computers do not join a domain that frequently, and unless your domain is huge, this should not place a huge burden on the infrastructure.

<?xml version="1.0" encoding="UTF-8"?><policy>
	<rule>
		<description>[CIS] Verify Computer is a DC</description>
		<conditions>
			<and>
				<if-class-name mode="nocase" op="equal">Computer</if-class-name>
			</and>
		</conditions>
		<actions>
			<do-for-each>
				<arg-node-set>
					<token-query class-name="nTDSDSA" datastore="src" scope="subtree">
						<arg-dn>
							<token-text xml:space="preserve">CN=Configuration,</token-text>
							<token-global-variable name="AD-Forest-Root"/>
						</arg-dn>
					</token-query>
				</arg-node-set>
				<arg-actions>
					<do-set-local-variable name="DC" scope="policy">
						<arg-string>
							<token-src-attr class-name="server" name="serverReference">
								<arg-dn>
									<token-parse-dn dest-dn-format="ldap" length="-2" src-dn-format="qualified-slash" start="0">
										<token-parse-dn dest-dn-format="qualified-slash" src-dn-format="ldap">
											<token-xpath expression="$current-node/@src-dn"/>
										</token-parse-dn>
									</token-parse-dn>
								</arg-dn>
							</token-src-attr>
						</arg-string>
					</do-set-local-variable>
					<do-if>
						<arg-conditions>
							<and>
								<if-src-dn op="equal">$DC$</if-src-dn>
							</and>
						</arg-conditions>
						<arg-actions>
							<do-break/>
						</arg-actions>
						<arg-actions/>
					</do-if>
				</arg-actions>
			</do-for-each>
			<do-veto/>
		</actions>
	</rule>
</policy>

Publisher Placement Policy

The placement policy handles two different classes, the domain objects (of which each AD domain has one and only one) and the computer objects.

A domain is represented in eDirectory as individual objects for each level of the hierarchy. In AD, the LDAP name looks like it would be represented that way, but the domainDNS object is really akin to the Tree object in eDirectory, the root of the name space.

When syncing a domain, the driver will split up the domain into levels, then iterate from the top to 1 level above the bottom to create (if necessary) placeholder objects to store the domain in. Then the actual domain object (which gets a DirXML-Association) is represented by it’s bottom level.

Domain controllers are represented as Computer objects, named for the server name, and placed directly under the domain (again it was structured like this for a future project)

<?xml version="1.0" encoding="UTF-8"?><policy>
	<rule>
		<description>[CIS] Create Domain Structure</description>
		<comment xml:space="preserve">Breaks down the dpmain into separate objects for each level</comment>
		<conditions>
			<and>
				<if-class-name mode="nocase" op="equal">domain</if-class-name>
			</and>
		</conditions>
		<actions>
			<do-set-local-variable name="LEVELS" scope="policy">
				<arg-node-set>
					<token-split delimiter=",">
						<token-src-dn/>
					</token-split>
				</arg-node-set>
			</do-set-local-variable>
			<do-set-local-variable name="DEX" scope="policy">
				<arg-string>
					<token-xpath expression="string(count($LEVELS))"/>
				</arg-string>
			</do-set-local-variable>
			<do-while>
				<arg-conditions>
					<and>
						<if-local-variable mode="nocase" name="DEX" op="not-equal">1</if-local-variable>
					</and>
				</arg-conditions>
				<arg-actions>
					<do-set-local-variable name="LDAP-NAME" scope="policy">
						<arg-string>
							<token-xpath expression="$LEVELS[number($DEX)]"/>
							<token-text xml:space="preserve">,</token-text>
							<token-local-variable name="LDAP-NAME"/>
						</arg-string>
					</do-set-local-variable>
					<do-set-local-variable name="NDAP-NAME" scope="policy">
						<arg-string>
							<token-global-variable name="cis-dc-landing-container"/>
							<token-text xml:space="preserve">\</token-text>
							<token-parse-dn dest-dn-format="slash" src-dn-format="ldap">
								<token-xpath expression="substring($LDAP-NAME,1,string-length($LDAP-NAME)-1)"/>
							</token-parse-dn>
						</arg-string>
					</do-set-local-variable>
					<do-set-local-variable name="OBJ" scope="policy">
						<arg-node-set>
							<token-query class-name="domain" scope="entry">
								<arg-dn>
									<token-local-variable name="NDAP-NAME"/>
								</arg-dn>
							</token-query>
						</arg-node-set>
					</do-set-local-variable>
					<do-if>
						<arg-conditions>
							<and>
								<if-xpath op="true">count($OBJ)=0</if-xpath>
							</and>
						</arg-conditions>
						<arg-actions>
							<do-add-dest-object class-name="domain" direct="true">
								<arg-dn>
									<token-local-variable name="NDAP-NAME"/>
								</arg-dn>
							</do-add-dest-object>
						</arg-actions>
						<arg-actions/>
					</do-if>
					<do-set-local-variable name="DEX" scope="policy">
						<arg-string>
							<token-xpath expression="string(number($DEX)-1)"/>
						</arg-string>
					</do-set-local-variable>
				</arg-actions>
			</do-while>
		</actions>
	</rule>
	<rule>
		<description>[CIS] DC Placement</description>
		<conditions>
			<and>
				<if-class-name mode="regex" op="equal">Computer|domain</if-class-name>
			</and>
		</conditions>
		<actions>
			<do-set-local-variable name="DOMAIN-PART" scope="policy">
				<arg-string>
					<token-lower-case>
						<token-src-dn/>
					</token-lower-case>
				</arg-string>
			</do-set-local-variable>
			<do-set-local-variable name="DOMAIN-PART" scope="policy">
				<arg-string>
					<token-text xml:space="preserve">dc=</token-text>
					<token-xpath expression='substring-after($DOMAIN-PART,"dc=")'/>
				</arg-string>
			</do-set-local-variable>
			<do-if>
				<arg-conditions>
					<and>
						<if-class-name mode="nocase" op="equal">Computer</if-class-name>
					</and>
				</arg-conditions>
				<arg-actions>
					<do-set-op-dest-dn>
						<arg-dn>
							<token-global-variable name="cis-dc-landing-container"/>
							<token-text xml:space="preserve">\</token-text>
							<token-parse-dn dest-dn-format="slash" src-dn-format="ldap">
								<token-local-variable name="DOMAIN-PART"/>
							</token-parse-dn>
							<token-text xml:space="preserve">\</token-text>
							<token-src-name/>
						</arg-dn>
					</do-set-op-dest-dn>
				</arg-actions>
				<arg-actions>
					<do-set-op-dest-dn>
						<arg-dn>
							<token-global-variable name="cis-dc-landing-container"/>
							<token-text xml:space="preserve">\</token-text>
							<token-parse-dn dest-dn-format="slash" src-dn-format="ldap">
								<token-local-variable name="DOMAIN-PART"/>
							</token-parse-dn>
						</arg-dn>
					</do-set-op-dest-dn>
				</arg-actions>
			</do-if>
			<do-break/>
		</actions>
	</rule>
</policy>

Input Transform

The serverReference attribute is a stored in a DN reference in AD, which would map to a DN reference in eDir except we don’t have one. So this policy changes the reference into a string so we can synchronize it irrespective of whether or not we have a related object in eDirectory.

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE policy PUBLIC "policy-builder-dtd" "/home/rrawson/designer/plugins/com.novell.idm.policybuilder_3.5.0.201003011501/DTD/dirxmlscript3.6.1.dtd"><policy>
	<rule>
		<description>[CIS] Make serverReference a string (test)</description>
		<comment xml:space="preserve">This allows queries of the serverReference attribute through on a server object, it server objects are not synced, is entirely to verify that a computer object is in fact a doman controller</comment>
		<comment name="author" xml:space="preserve">RR</comment>
		<conditions>
			<and>
				<if-operation mode="case" op="equal">instance</if-operation>
				<if-op-attr name="serverReference" op="available"/>
			</and>
		</conditions>
		<actions>
			<do-strip-xpath expression='attr[@attr-name="serverReference"]/value/@type'/>
			<do-strip-xpath expression='attr[@attr-name="serverReference"]/value/@naming'/>
			<do-strip-xpath expression='attr[@attr-name="serverReference"]/value/@association-ref'/>
		</actions>
	</rule>
</policy>

Command Transform

The command transform has a simple policy – if a computer object is added, we will send an eMail to an administrator reminding them to install the password filter.

<?xml version="1.0" encoding="UTF-8"?><policy>
	<rule>
		<description>[CIS] Domain Controller Add</description>
		<conditions>
			<and>
				<if-class-name mode="nocase" op="equal">Computer</if-class-name>
				<if-operation mode="case" op="equal">add</if-operation>
			</and>
		</conditions>
		<actions>
			<do-send-email-from-template notification-dn="Security\Default Notification Collection" template-dn="Security\Default Notification Collection\[CIS] Domain Controller Notification">
				<arg-string name="to">
					<token-text xml:space="preserve">wattssupport@ciscony.com</token-text>
				</arg-string>
				<arg-string name="DCAddress">
					<token-attr name="L"/>
				</arg-string>
				<arg-string name="DomainName">
					<token-dest-dn start="2"/>
					<token-dest-attr name="Description">
						<arg-dn>
							<token-dest-dn start="2"/>
						</arg-dn>
					</token-dest-attr>
				</arg-string>
			</do-send-email-from-template>
		</actions>
	</rule>
</policy>

In order to support this, a simple eMail template is necessary:

<template name="[CIS] Domain Controller Notification" subject="New Domain Controller Detected in Domain $DomainName$">
	<data>
		<form:text xmlns:form="http://www.novell.com/dirxml/workflow/form">
			<form:token-descriptions>
				<form:token-description description="" item-name="DomainName"/>
				<form:token-description description="" item-name="DCAddress"/>
			</form:token-descriptions>To Whom It May Concern:

A new domain controller has been added to Active Directory domain $DomainName$ at address $DCAddress$.

Please check to make certain that the password filter has been installed and the domain controller has been rebooted.

Thank you
CIS 
</form:text>
	</data>
</template>

Implementation

The recommended sequence for testing this solution once in place is as follows:

  1. Migrate the domain objects. You should see the domain structure built out in the identified container.
  2. Migrate a single domain controller as a computer object by name. This should appear under the domain as a computer.
  3. During a “quiet” time, migrate all computers. There will be a fair amount of activity during the migration which could cause other synchronization to queue up behind this, so this should be done during off hours if possible.

Conclusion

This solution provides a way to get operational data from Active Directory into your identity vault. In addition to simple notification, other envisioned applications include:

  • Synchronizing accurate AD Login Times from all domain controllers
  • Automating password filter installation

Future articles may discuss these solutions and more.

VN:D [1.9.22_1171]
Rating: 5.0/5 (1 vote cast)
Using Identity Manager to Detect New AD Domain Controllers, 5.0 out of 5 based on 1 rating

Tags: , ,
Categories: Uncategorized

Disclaimer: As with everything else at NetIQ Cool Solutions, this content is definitely not supported by NetIQ, so Customer Support will not be able to help you if it has any adverse effect on your environment.  It just worked for at least one person, and perhaps it will be useful for you too.  Be sure to test in a non-production environment.

1 Comment

  1. By:joakim_ganse

    I will surely implement this for some customers in the future.

Comment