Sticky entitlement problem. The Sailpoint ISC keep trying to create account in AD even if the Account exists

We are seeing the Sailpoint Identity refresh is trying to add AD entitlement to the user and it triggers the AD account create operation. The user has an account in AD so the operation fails because conflicting with existing Account in AD.

This is causing triggering of After Create rule and subsequent processes impacted.

Is there a way to clear all these sticky entitlements and avoid triggering the after create rule if the create event is failing ?

Hi @rahulks88 - is the user’s AD account properly correlated to their Identity?

Yes it is correlated

A few questions, if I may.

Why do you think the AD account is correlated? If it was correlated correctly then it would not trigger another account creation.

Why do you mention After Create Rule? Firstly, an After Create Rule will not be triggered if the account is not created. Secondly, the log entries you share are nothing to do with an After Create Rule.

Why are you concerned with these ā€œstickyā€ entitlements, as you call them? Surely your priority is to workout why it is trying to create another AD account?

Thanks for the response. I have all these questions myself and couldn’t figure out why it happens. I have a support case opened. But I thought to check if anyone facing the same issue.

Account is correlated for sure because we are fetching the display name and email from the AD account.

The after create rule part is really not making sense to me as well. It should not trigger for failed create events.

The reason why I am concerned about entitlement because Add entitlement operation is causing the create event.

If the AD account is correlated correctly, the only conclusion (assuming normal running) is that the Entitlement is associated with a source diifferent to the correlated account. Do you have more than one AD source?

What is adding the entitlement? Is it a workflow or is it part of a Role or Access Profile? If it is a role, do you have assignment criteria on the Role? Like Jeremy stated, adding an entitlement will only create an account on the source if it does not exist on the Identity.

How many authorative sources do you have ?

I have only one AD source. One thing I noted is the entitlement is being added to an account in different OU and original account is in different OU. This happens due to move ou operations as part of some use cases. But even in that case, the entitlement should not assign to the old CN

We have only one auth source for workers

Identity refresh task run by Identity security cloud is adding the entitlement

Hi @rahulks88 So the account was not correctly correlated. Review your account move processes so accounts remain correctly correlated.

Did SailPoint accept your support case?

Yes. I am looking into that. Also thinking to reset the source and re-aggregate if that makes any difference

Yes they did. But I feel like they are confused as well. Could be an odd case

HI @rahulks88

If accounts are getting moved natively in AD, you will face problems.
If accounts are getting moved via ISC without the use of AC_NewParent/AC_NewName, you will face problems.

Hi Rahul,

If the AD account is not being moved by SailPoint, this behaviour will occur. It’s a classic sticky entitlement scenario where SailPoint is trying to maintain the integrity of access being governed by the platform by ensuring if something was revoked outside of ISC it will re-provision to the original AD OU

My recommendations:

  1. Move these AD OU moves into SailPoint using a transform and the service standard before prov rule. That should resolve the issue
  2. If 1. is not possible:
    1. Set up native change detection on the DN attribute
    2. Set up a workflow that responds to this native change detection, compiles a list of access assigned to that AD account, and then un-assigns the access
    3. The access will then be ā€˜detected’

I’ve done 2. with an implementation before to get around the native OU moves because the organization was not ready to have SailPoint handle all OU moves.

Sample to get you started below…

{
  "id": "xxx",
  "name": "xxx",
  "description": "",
  "modifiedBy": {
    "type": "IDENTITY",
    "id": "xxx",
    "name": "xxx"
  },
  "definition": {
    "start": "Define Variable",
    "steps": {
      "Define Variable": {
        "attributes": {
          "id": "sp:define-variable",
          "variables": [
            {
              "description": "",
              "name": "oldDN",
              "transforms": [],
              "variableA.$": "$.trigger.singleValueAttributeChanges.[?(@.name == \"distinguishedName\")]"
            },
            {
              "description": "",
              "name": "oldDistinguishedName",
              "transforms": [],
              "variableA.$": "$.trigger.singleValueAttributeChanges.[?(@.name == \"distinguishedName\")].oldValue"
            },
            {
              "description": "",
              "name": "identityID",
              "variableA.$": "$.trigger.identity.id"
            }
          ]
        },
        "displayName": "",
        "nextStep": "HTTP Request",
        "type": "Mutation"
      },
      "Get Revocable Access": {
        "actionId": "sp:http",
        "attributes": {
          "authenticationType": "OAuth",
          "method": "post",
          "oAuthClientId": "xxxxx",
          "oAuthClientSecret": "",
          "oAuthCredentialLocation": "oAuthInHeader",
          "oAuthScope": null,
          "oAuthTokenUrl": "xxxxx",
          "requestContentType": "text",
          "requestHeaders": "Content-Type:application/json",
          "textRequestBody": "{\"indices\":[\"identities\"],\"query\":{\"innerHit\":{\"query\":\"revocable:true AND type:ACCESS_PROFILE\ - REVISE THIS AS NEEDED FOR YOUR IMPLEMENTAION",\"type\":\"access\"},\"query\":\"id:\\\"{{$.trigger.identity.id}}\\\"\"}}",
          "url": "xxxxx"
        },
        "description": "Get all Revocable Access",
        "nextStep": "Has Revocable Access?",
        "type": "action",
        "versionNumber": 2
      },
      "HTTP Request": {
        "actionId": "sp:http",
        "attributes": {
          "authenticationType": "OAuth",
          "method": "get",
          "oAuthClientId": "xxxxx",
          "oAuthClientSecret": "",
          "oAuthCredentialLocation": "oAuthInHeader",
          "oAuthTokenUrl": "xxxxx",
          "requestContentType": "json",
          "requestHeaders": {
            "Accept": "application/json"
          },
          "url": "xxxxxxxxx/beta/access-request-approvals/completed/?filters=requestedFor.id%20eq%20\"{{$.defineVariable.identityID}}\""
        },
        "description": "Get entitlements from the identity that were requested for the old distinguished name",
        "displayName": "",
        "nextStep": "Loop Standalone Entitlements",
        "type": "action",
        "versionNumber": 2
      },
      "Has Revocable Access?": {
        "choiceList": [
          {
            "comparator": "IsPresent",
            "nextStep": "Revoke Revocable Access",
            "variableA.$": "$.getRevocableAccess.body[0].id"
          }
        ],
        "defaultStep": "No Revocable Access",
        "type": "choice"
      },
      "Loop Standalone Entitlements": {
        "actionId": "sp:loop:iterator",
        "attributes": {
          "context.$": "$.trigger.identity.id",
          "input.$": "$.hTTPRequest.body[*].requestedObject.id",
          "start": "Has Entitlement Id?",
          "steps": {
            "Get Access": {
              "actionId": "sp:access:get",
              "attributes": {
                "accessprofiles": true,
                "entitlements": true,
                "getAccessBy": "searchQuery",
                "query": "id:$.loop.loopInput",
                "roles": true
              },
              "description": null,
              "displayName": "",
              "nextStep": "Verify Data Type",
              "type": "action",
              "versionNumber": 1
            },
            "Has Entitlement Id?": {
              "choiceList": [
                {
                  "comparator": "IsPresent",
                  "nextStep": "Get Access",
                  "variableA.$": "$.loopStandaloneEntitlements.loopInput"
                }
              ],
              "defaultStep": "No Entitlement to Revoke",
              "type": "choice"
            },
            "No Entitlement to Revoke": {
              "description": "No Entitlement to Revoke",
              "type": "success"
            },
            "Revoke Standalone Entitlement": {
              "actionId": "sp:access:manage",
              "attributes": {
                "comments": "Native Change - Handling Sticky Entitlements",
                "removeIdentity.$": "$.loop.context.identity.id",
                "requestType": "REVOKE_ACCESS",
                "requestedItems.$": "$.getAccess.accessItems[*]"
              },
              "nextStep": "Standalone Entitlement Revoked",
              "type": "action",
              "versionNumber": 1
            },
            "Standalone Entitlement Revoked": {
              "type": "success"
            },
            "Verify Data Type": {
              "choiceList": [
                {
                  "comparator": "IsPresent",
                  "nextStep": "Revoke Standalone Entitlement",
                  "variableA.$": "$.getAccess.accessItems"
                }
              ],
              "defaultStep": "No Entitlement to Revoke",
              "displayName": "",
              "type": "choice"
            }
          }
        },
        "nextStep": "Get Revocable Access",
        "type": "action",
        "versionNumber": 1
      },
      "No Revocable Access": {
        "description": "No standing entitlements to revoke",
        "type": "success"
      },
      "Revoke Revocable Access": {
        "actionId": "sp:access:manage",
        "attributes": {
          "comments": "Native Change Detection",
          "removeIdentity.$": "$.trigger.identity.id",
          "requestType": "REVOKE_ACCESS",
          "requestedItems.$": "$.getRevocableAccess.body[*]"
        },
        "nextStep": "Standing Access Revoked",
        "type": "action",
        "versionNumber": 1
      },
      "Standing Access Revoked": {
        "description": "Remediated Sticky Access for OU Move",
        "type": "success"
      }
    }
  },
  "enabled": false,
  "creator": {
    "type": "IDENTITY",
    "id": "xxxxxxx",
    "name": "xxxxxxxxx"
  },
  "owner": {
    "type": "IDENTITY",
    "id": "xxxxxx",
    "name": "xxxxxxxxxx"
  },
  "trigger": {
    "type": "EVENT",
    "attributes": {
      "filter.$": "$.singleValueAttributeChanges[?(@.name == \"distinguishedName\")]",
      "id": "idn:native-change-account-updated"
    }
  }
}

I agree with that. But all these move ou happens using Before Provisioning rule. We are not letting admins move the accounts in AD directly. My other thought is the way we do could be a problem in BPR.

Thanks for the suggestions I will look into this.

I totally agree with that. We are not moving accounts outside Sailpoint. Here’s one scenario the users are moved to disabled OU in AD after termination. CN=Lastname, FirstName,OU=disabledusers,dc=company,dc=com. This move is done using Before Prov rule ac_newparent option. But the entitlement is being added again and again to the old DN. That’s causing the problem

I think I have to look at the move OU process in Before Prov rule. Could be that’s the problem here.

Hi @rahulks88 I take it you are using AC_NewParent/AC_NewName in the BPR when you are moving accounts?

Also, check your correlation config on AD connector and scope (there’s no chance that the accounts could have been moved out of connector scope?)