Best Practices: AD Account Moves

Best Practices: AD Account Moves

Overview

There are many reasons for a company to need to move AD accounts to different OUs.

On termination: Move to a "Disabled Users" OU

Many clients will have a designated OU to hold terminated users. One reason to do this would be to set specific AD policies on that OU. For example, accounts will be in this OU for 90 days before they are automatically deleted. Alternatively, some clients who have integrations with AD (such as Azure) will sync only users in a particular branch and will exclude the "Disabled Users" OU. This gives the flexibility to continue to manage the user accounts, but remove them from the scope of other applications.

On mover: Move to the designated OU

In prior years, it was a good policy to have separate OUs for logical distribution of users. For example, some clients have specific department OUs to logically group users in a single department together. Others clients who have many subsidiaries might have company specific OUs. By separating users, it's easy to give granular AD policies to users based on an OU. Microsoft has given other tools to accomplish this and it is now best practice to have a flat (single OU) structure.

This is still common to see, and many older customers have not already migrated their directories to match Microsoft’s guidance, so this is still a valid use case.

Technical Complexities

The OU that an account resides in makes up the latter part of a user's distinguishedName. The first part is usually the CN. For example, if the user's CN or common name is "John Smith" and the parent OU is "Services" in the "Users" OU of acme.com, then the distinguishedName will be

CN=John Smith,OU=Services,OU=Users,DC=acme,DC=com

A user's distinguishedName MUST be unique in an Active Directory domain. That is to say if there is another John Smith in the Services OU, it will need a separate CN. A typical approach is to use serialization and add a number, e.g.

CN=John Smith 2,OU=Services,OU=Users,DC=acme,DC=com

Any time an account needs to be moved, it will require a uniqueness check. Otherwise, there may already exist a user in that other OU. There can be a "John Smith" in the "Services" OU and one in the "Support" OU. If you move the one in the "Support" OU to "Services", then there will be a clash on the CN and the moved account will require a different name, e.g CN=John Smith 2.

IdentityNow specifics

In IdentityNow, the Active Directory source uses the distinguishedName as the Account Id (unique identifier). As such, moving users in AD will look like a delete and add of separate accounts. In addition, IdentityNow cannot simply update the distinguishedName since it is an AD identifier.

The connector does have the ability to move AD accounts using 2 key attributes.

  • AC_NewParent: This is the new parent OU that the account will reside in. Example from above: "OU=Services,OU=Users,DC=acme,DC=com"

  • AC_NewName: This is the new CN portion of the distinguishedName that the account will have. This value is not usually set which means to maintain the same CN. Example from above: "CN=John Smith 2"

These attributes need to be added to the provisioning plan during a Modify/Enable/Disable operation in order to move the AD account.

After Moving Active Directory Account

When the Active Directory account is moved to a different OU, it is unlinked from the Identity. IdentityNow utilizes the distinguished name to execute provisioning requests and the distinguished name has changed. A Delta aggregation will not suffice to read/update the account; it is necessary to fully aggregate Active Directory to re-link the account to the Identity. Any modifications (Attribute Sync, Access Request) executed after the account move will fail until a full aggregation is run and completed. Until the connector seamlessly updates the distinguished name in IdentityNow, ensure your automated processes account for the delay. 

If any attribute synchronizations need to occur in addition to moving the account, utilize the before provisioning rule to set the attributes at the same time as the account move. That way additional attribute synchronizations will not be necessary on the account and no provisioning attempts will fail.

The BeforeProvisioning rule sample below is one way in which attribute sync requests can be included within the same plan as the OU move operation:

import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AccountRequest.Operation;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
import sailpoint.object.Identity;
import sailpoint.tools.Util;
import sailpoint.object.Link;
import sailpoint.object.Application;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import sailpoint.rule.Account;
import sailpoint.rule.ManagedAttributeDetails;
import sailpoint.rule.RuleObjectFactory;

if (plan != null) {

    Identity identity = plan.getIdentity();
	String destinationOU = "OU=Disabled,OU=Users,OU=Corporate,DC=company,DC=com";
    
    List accountRequests = plan.getAccountRequests();
    if (accountRequests != null) {
        for (AccountRequest accountRequest : accountRequests) {
            String accountNativeIdentity = accountRequest.getNativeIdentity();
            if(accountRequest.getOperation(ProvisioningPlan.AccountRequest.Operation.Disable).equals()) {
                log.debug( "Operation is a Disable. We want to move OUs." );
                accountRequest.add(new AttributeRequest("AC_NewParent", ProvisioningPlan.Operation.Set, destinationOU));

                Account link = idn.getAccountByNativeIdentity("Active Directory [source]", accountNativeIdentity);
                Map acctAttrs = link.getAttributes();

                //Adding an attribute request for extensionAttribute5, which is being kept in sync with identity attribute departmentCode, if the two values aren't equal
                if(!acctAttrs.get("extensionAttribute5").toString().equals(identity.getAttribute("departmentCode").toString())) {
                    accountRequest.add(new AttributeRequest("extensionAttribute5", ProvisioningPlan.Operation.Set, identity.getAttribute("departmentCode").toString()));
                }
            }
        }
    }
}

Recommendations

SailPoint recommends the following when dealing with Account Moves through IdentityNow:

Use a unique value for CN

When dealing with unique values in IdentityNow, the best place is via an attribute generator rule. These are found in the AccountCreate Profile. Since this is not available during Updates (Modify/Enable/Disable), it's best to use a unique value for the CN attribute. That way account moves won't possibly clash. Possible unique values include:

  • sAMAccountName. This is already guaranteed to be unique in the domain.

  • HR Unique Identifier (like employeeId). This should be unique per person. However if users have multiple accounts, this could be difficult. They could have a format such as employeeId-p for privileged accounts.

  • Other unique values could be generated via a string randomization process, UUID generation, etc.

For Movers, utilize Attribute Sync triggers

It's best to only move accounts when necessary. The simplest way to do that is to trigger off an AttributeSync event when one of the attributes used in OU calculation changes.

Use Identity Attributes for Parent OUs

The best way to calculate where a user should be is in an Identity Attribute rule. That way you can manage the parent OU via transforms. One suggestion would be to use 2 attributes

  • activeParentOU: This is the OU for when the user is active. You can use this in the Create profile and the Before Provisioning Rule when handling active user moves and rehires.

  • disabledParentOU: This is the OU to place terminated users. You can use this in the Before Provisioning Rule when handling terminations.

Use the Before Provisioning Rule instead of Account Profiles

In order to add AC_NewName and AC_NewParent to the provisioning plan, there are 2 avenues that IdentityNow will allow from a technical standpoint: AccountProfiles (Modify, Enable, or Disable) and the Before Provisioning Rule. The AccountProfiles can be updated through the APIs. Before Provisioning Rules can only be updated through SailPoint Professional Services.

It is not recommended to go through the account profiles because attributes set via AccountProfiles go through a verification stage after provisioning. Since AC_NewParent and AC_NewName are not read via the connector, they can never be verified. This results in “stranded” account activity entries within the IdentityNow tenant.

Take Lifecycle State into account

Account moves are handled by specific use cases. Make sure to think through other use cases that could trigger provisioning to ensure the account move occurs appropriately.

Example Configurations

Example Identity Attribute Transform for OU

When handling OU moves, it's easiest to handle via an Identity Attribute. That way you can reference the Identity Attribute rather than calculate in the Rule. This allows updates to be handled via transform updates which can solely be handled by administrators rather than Rule updates which rely on SailPoint assistance.

The below transform is a simple example of setting the OU based on the user's department. This can be tied to the Identity Attribute "Active Parent OU" ("activeParentOu" would be the technical name).

 

 

{
    "attributes": {
        "input": {
            "attributes": {
                "attributeName": "Department",
                "sourceName": "Employees"
            },
            "type": "accountAttribute"
        },
        "table": {
            "Services": "OU=Services,OU=Users,DC=example,DC=com",
            "Human Resources": "OU=HR,OU=Users,DC=example,DC=com",
            "Information Technology": "OU=IT,OU=Users,DC=example,DC=com",
            "Engineering": "OU=Engineering,OU=Users,DC=example,DC=com",
            "default": "OU=Default,OU=Users,DC=example,DC=com"
        }
    },
    "id": "Calculate AD OU",
    "type": "lookup"
}

 

 

Example Before Provisioning Rule

The following represents how to use the above suggestions in a single Before Provisioning Rule to handle account moves. This rule handles

  • Rehire use case

  • Move use case in case of a department change

  • Termination use case

Note: This Rule does NOT account for uniqueness checks when moving the account. It assumes the CN is unique across the domain per above recommendations.

 

 

 

import sailpoint.object.Identity;
import sailpoint.object.ProvisioningPlan;
import sailpoint.object.ProvisioningPlan.AccountRequest;
import sailpoint.object.ProvisioningPlan.AttributeRequest;
​
List accountRequests = plan.getAccountRequests();
​
if (accountRequests != null) {
    for (AccountRequest accountRequest: accountRequests) {
        AccountRequest.Operation op = accountRequest.getOperation();
        if(op == null) continue;
        String nativeIdentity = accountRequest.getNativeIdentity();
        Identity identity = plan.getIdentity();
        String currentLCS = null;
        String activeOU = null;
        String disabledOU = null;
        if(identity != null){
            currentLCS = identity.getAttribute("cloudLifecycleState");
            activeOU = identity.getAttribute("activeParentOu");
            disabledOU = identity.getAttribute("disabledParentOu");
        }
        
        boolean departmentChanged = false;
        if(op.equals(AccountRequest.Operation.Modify)){
            List dAttrReqs = accountRequest.getAttributeRequests("department");
            if(dAttrReqs != null && !dAttrReqs.isEmpty()){
                departmentChanged = true;
            }
        }
        String newOU = null;
        /*
            Get a new OU only if we are in 
                Rehire Use Case (Enable, user LCS is active and user was in the disabled OU)
                Mover Use Case (Modify and user LCS is active)
                Termination Use Case (Disable and user LCS is inactive)
        */
        if ("active".equals(currentLCS) && op.equals(AccountRequest.Operation.Enable)
            && disabledOU != null && activeOU != null && nativeIdentity.endsWith(disabledOU)){
            //Rehire Use Case
            newOU = activeOU;
        }
        else if ("active".equals(currentLCS) && departmentChanged && activeOU != null){
            //Mover Use Case
            newOU = activeOU;
        }
        else if("inactive".equals(currentLCS) && op.equals(AccountRequest.Operation.Disable)){
            //Termination Use Case
            newOU = disabledOU;
        }
        if(newOU != null && !nativeIdentity.endsWith(newOU)){
            //Account not currently in the proper OU. Put them there
            accountRequest.add(new AttributeRequest("AC_NewParent", ProvisioningPlan.Operation.Set, newOU));
        }
    }
}

 

 

 

Frequently Asked Questions

  • Can we handle account moves outside of IdentityNow?

    • Yes. Account moves can be handled outside of IdentityNow. However a full Aggregation must be ran before any provisioning is processed through IdentityNow. Otherwise IdentityNow will think that the account is in one place and while it is really in another. This is true for both account moves from IdentityNow and outside of IdentityNow.

  • Can't we use the attribute generator transform on an Identity Attribute to synchronize distinguishedName?

    • It is not recommended to use attribute generators in Identity Attributes. It would be a big performance hit, and due to the multi-threaded nature of IdentityNow's architecture, the uniqueness check at the identity attribute level is not guaranteed to return a unique value. Additionally, attribute generators use a source context which Identity Attributes do not have.

Labels (1)
Comments

Is there a reason for why we can't change the Account ID to another identifier, such as 'objectguid'? I believe this would resolve a lot of the complexities with handling account moves and will allow for delta aggregations.

@bostelmannper the July 2021 release notes:

The default native identifier for Active Directory applications has changed back to
distinguishedName. All new Active Directory applications created will have
distinguishedName as the native identifier. The use of objectGUID as the native
identifier introduced in previous patches introduced some usability and functional
limitations that will be corrected in a future release. The current recommended best
practice is to continue to use distinguishedName as the native identifier for Active
Directory applications. 

Version history
Revision #:
7 of 7
Last update:
Monday
Updated by: