At a recent client engagement involving SailPoint IdentityIQ, I was asked to provide an enhancement feature which would allow them to reverse the termination of an identity that was done by mistake. The client had a highly customized environment where the identities could be any of several account types and could potentially hold dozens of entitlements and roles across many different applications of varying type: Active Directory, SAP, RACF, ORACLE, AS400. The problem was complicated by the fact that the birthright provisioning for each application and account type was specialized so that there was a lot of variability of configuration from identity to identity.
The Key to the Solution
The key to the solution was a decision made early on in the project to use a standardized sub-process for the provisioning of all changes, including terminations. This process was something akin to the “LCM Provisioning” and “Identity Request Provision” sub-processes that comes out of the box with IdentityIQ, but it was tailored to the client’s highly customized environment. Any time a workflow needed to provision any changes, it would call this sub-process, passing the ProvisioningPlan it had prepared to it. The sub-process would handle everything from that point on.
The principle benefit of this structure was to leave a detailed record of the termination procedure behind in a series of one or more IdentityRequest objects, the exact number depending on the specific sequences of steps the workflow’s designer decided on. A second benefit that simplified the problem, was that all terminations followed the same pattern of steps, even though the details differed by account type and application. Though this isn’t crucial to solving the problem, it does simplify its implementation.
How it Works
The operating principle of the solution is simply the recognition that the actual compiled ProvisioningProject (note: not Plan, but Project) is recorded in the IdentityRequest. This project contains the MasterPlan as well as all of the details produced during the compilation process, including the partitioned plans and expansion items. By iterating through the ProvisioningPlans, AccountRequests, and AttributeRequests in the ProvisioningProject, it is possible to create a new ProvisioningPlan that reverses everything done during the termination. Often, this is as simple as changing “Removes” to “Adds”, and “Disables” to “Enables”. There are other complexities of course, but these can be dealt with on a case by case basis.
Below is an example of a ProvisioningProject found in an IdentityRequest from an un-customized IIQ installation. Though it is rather simple in comparison to the projects encountered in customized version, it serves as an illustration.
<Attributes> <Map> <entry key="provisionedProject"> <value> <ProvisioningProject identity="A051272"> <Attributes> <Map> <entry key="disableRetryRequest"> <value> <Boolean>true</Boolean> </value> </entry> <entry key="identityRequestId" value="0000000004"/> <entry key="optimisticProvisioning" value="false"/> <entry key="requester" value="spadmin"/> <entry key="source" value="LCM"/> </Map> </Attributes> <MasterPlan> <ProvisioningPlan> <AccountRequest application="IIQ" nativeIdentity="A051272" op="Modify"> <AttributeRequest name="email" op="Set" value="[email protected]"> <Attributes> <Map> <entry key="previousValue" value="[email protected]"/> </Map> </Attributes> </AttributeRequest> </AccountRequest> <Attributes> <Map> <entry key="identityRequestId" value="0000000004"/> <entry key="requester" value="spadmin"/> <entry key="source" value="LCM"/> </Map> </Attributes> </ProvisioningPlan> </MasterPlan> <ProvisioningPlan targetIntegration="IIQ"> <AccountRequest application="IIQ" nativeIdentity="A051272" targetIntegration="IIQ"> <AttributeRequest name="email" op="Set" value="[email protected]"> <Attributes> <Map> <entry key="previousValue" value="[email protected]"/> </Map> </Attributes> </AttributeRequest> </AccountRequest> <Attributes> <Map> <entry key="identityRequestId" value="0000000004"/> <entry key="requester" value="spadmin"/> <entry key="source" value="LCM"/> </Map> </Attributes> </ProvisioningPlan> </ProvisioningProject> </value> </entry> <entry key="taskResultId" value="8a8080884ebc9ec6014ebcae18ef001a"/> </Map> </Attributes>
Some Complexities Encountered
Of course, an actual solution will have to deal with complexities. One principle behavior that must be confronted up front is entitlement and role expansion. During the compilation of a ProvisioningPlan into a project, the compiler will perform an expansion based on roles. Taking the straightforward approach outlined above, may not produce the desired results because the expansion items may conflict or duplicate items created by the reversal algorithm. Connectors may or may not handle this in a desirable fashion.
So, what’s the way to best handle this? The answer I chose in my design was to avoid the circumstance altogether – and I found two ways to accomplish this. The first involves allowing the expansion to proceed but constructs a reversal plan that only includes items not covered in the expansion. Though the coding is relatively straight forward, it is a bit involved. Essentially, one must first identify the roles that were terminated and place them into their own plan, compile it, and run a difference algorithm against the termination project in the IdentityRequest. There is nothing out-of-the-box to do this for you, but the algorithm is fairly easy to develop in beanshell.
The second approach to avoid this expansion conflict is simply to turn of the expansion. This is accomplished by setting the “noRoleExpansion” and “noApplicationTemplates” compiler options to “true”. Both of these options are sent to the compiler via an <Arg> variable in the workflow step which calls the compiler. Once this is done, the simple-minded algorithm can be used to produce the reversal plan, as nothing is generated by the compiler to produce a conflict. The down side of this approach is that the expansion may produce needed items that were implicitly rather than explicitly removed during the termination process, and therefore cannot be restored by the simple-minded strategy.
Sometimes an expansion done by the compiler may be erroneous. In this case, the only approach would be to turn off the expansion. Although rare, it can happen under certain circumstances. An example which I encountered, is when the role expansion is supposed to assign an entitlement to an application account on an identity, but the identity has multiple versions of that accounts and insufficient information to determine which account to assign it to.
Another complexity that is likely to be encountered if there are specialty connectors involved are implicit deletions. This occurs when elements are removed as a side-effect of what is in the ProvisioningProject, rather than because it was explicitly deleted. The best way to handle this is to allow role and entitlement expansion to handle it automatically. If that isn’t possible, then these items must be handled on a cases-by-case basis by having the workflow or supporting script recognize the condition and then add the element explicitly to the reversal plan.
Another circumstance likely to be encountered is that the termination project made a lossy modification. Typically, this entails changing an attribute’s value. For example, one might see a “status” attribute changed to “TERMINATED”.
This can cause an issue if the attribute can have more than two possible values, because the previous value isn’t a part of the ProvisioningProject. When this happens, one of several approaches can be used to address it. The easiest answer is to set the attribute to a standard default value. This may not be viable if the prior value is really important to a proper continued functioning. Another option may be to record the prior value in the IdentityRequest someplace during termination, and then look it up when building the reversal plan. In some installations where field value rule libraries are being used, entitlement expansion may provide an adequate solution if the field value rule can correctly compute the value.
The ability to reverse a termination made by mistake is an achievable feature. The basic idea is rather straight forward to implement, but can become involved as various complexities are encountered along the way. In the majority of the cases, a viable solution suggests itself and can be implemented, and this article addresses some of these.
Expect to spend more time in testing and identifying these complexities than in the actual design and coding of the solution. The amount of time spent in the testing cycle predictably increases with the greater variety in your system. If your implementation has a half dozen or more application or identity types, expect more time testing and identifying complexities than if it had only one or two.