Chained Workflows

**A really happy new year 🎄 !!**

Introduction

We were recently tasked with solving a simple use case. As is often the case, the solution, though, was not.

Statement

The use case can be stated by this simple sentence :

Out of a group of accesses (Roles or Access Profile), any identity is to only have one at any given time.

Example

In order to better understand, here is an example of what we want :

Say we have the following group of Access Profiles : {AP1, AP2, AP3, AP4}

Upon receiving any of those, the system has to remove all the other possibly existing ones from the identity , so that it remains with only the requested one. Events should unfold like this :

  1. Identity id1 has {AP2, AP4}
  2. id1 is given the Access Profile AP1 through an Access Request.
  3. The mechanism kicks in (:arrow_left: this is what we have to implement)
  4. id1 is left with only {AP1}

The analysis

What objects are we dealing with here ?

  • Identities
  • Accesses (Roles or Access Profile)
  • Access Requests

Given with what we are dealing with, we need :

  1. Something to detect the Access Request decision
  2. Something able to manage Identities accesses
  3. Something acceptable for the customer ( obviously :slight_smile: )

Possible technical solutions

Let’s review all the technical ways we can think of for solving this use case and evaluate how it satisfies our needed elements.

Separation of Duties

  • :white_check_mark: Something to detect the Access Request decision :

At first, the use case inevitably makes us think about the Separation of Duties feature. Although, it would allow for Access Request being given to an identity. This would not be in real-time. Moreover …

  • :x: Something able to manage Identities accesses :

This would not allow us to automatically remove accesses from the Identity.

External System

  • :white_check_mark: Something to detect the Access Request decision :

An external script could poll IdentityNow regularly for conflicting accesses and remediate. It could even be possible to use the External Trigger feature to trigger it.

  • :white_check_mark: Something able to manage Identities accesses :

An external system would be able to manage Identity access via the API

  • :x: Something acceptable for the customer :

While all this would be technically possible and even though we would get everything we need the customer often wants to avoid engaging additional systems and resources. Especially when one sense it is possible to achieve with IdentityNow alone.

Workflows

  • :white_check_mark: Something to detect the Access Request decision

Yes ! Workflows can be triggered when an Access Decision is made via its dedicated Access Request Decision trigger block :
image

  • :white_check_mark: Something able to manage Identities accesses

There is a dedicated action just for this in the Workflows actions library
image

  • :white_check_mark: Something acceptable for the customer

Being native and integrated feature, Workflows should be an ideal solution for the customer.

:tools: The analysis leads us to try and see what the Workflow feature has to offer !
Let’s dive into finding a technical solution to the use case, leveraging the Workflow feature.

Implementation

Workflows being chosen as the technical solution, it was time to explore the feature’s possibilities when it came to the elements we had listed as needed :

:one: Needed element 1 : Access Request decision

Workflows come with built-in blocks called triggers. They represent the IdentityNow event that will set the workflow in motion. As such, there can be only one per Workflow obviously but they still can be filtered down with JSONPath in order to be valid in the eyes of the Workflow engine.

:spiral_notepad: Being able to filter here is an important requirement : because we will be removing accesses in some steps of the workflow, we don’t want to create a loop where our workflow would be triggered by the accesses removal from its own actions.

image

Here the Workflow will be triggered only if an identity is being Added the ACCESS_PROFILE named AP1

:warning: JSONPath filters in triggers blocks are not the same as those in the action blocks :
They are a more extensive implementation of the Goessner syntax and allow for more complex filters.

Here is the JSON data passed to the Workflow Engine when an Access Request Decision is made in Identity Now :

{
  "requestedFor": {
                    "id": "2c918086837ffea50183a2abd10c4963",
                    "name": "Doe1",
                    "type": "IDENTITY"
                },
                "requestedBy": {
                    "type": "IDENTITY",
                    "id": "2c918086833c721a01833cd251140dcf",
                    "name": "janette.doe"
                },
              
                "accessRequestId": "42eb6814c5a14252874c9b8163f37b24",
                "requestedItemsStatus": [
                    {
                        "id": "26d6294246e647aa8f71c1e90c828700",
                        "name": "AP1",
                        "type": "ACCESS_PROFILE",
                        "operation": "Add",
                        "comment": "AP1",
                        "clientMetadata": null,
                        "approvalInfo": []
                    }
                ]
}

This data will be passed on to subsequent Workflow actions.

With this, we have everything we need to initiate our removal actions :

  • The requestedFor object points at the identity we want to manage the accesses from with all the relevant elements :

    • id : the internal ID of the identity
    • name : the name of the identity
  • The requestedItemsStatus object provides us with an array of the accesses that have been requested with the relevant info for each :

    • name : The name of the access
    • id : the internal id
    • type : the type of access (ROLE, ACCESS_PROFILE, …)
    • operation : Added or Revoked

:two: Needed element 2 : Manage Identity accesses

At that point of designing the Workflow, we know these info :

  • We know the identity we want to remove access from
  • We know the accesses we want to be removed because they are a pre-defined set.

But we don’t know these ones:

  • What accesses the identity already has.

    • The Workflow engine has a very relevant action for what we want to achieve : removing accesses. It is the Get Access action. It allows to add the pointed identity accesses to the json data passed along the Workflow actions.

    • Knowing what accesses we want to remove, we need a Workflow action to do just that.
      The Workflow library has this : the Manage Access action

      image

Let’s deep dive into what we can do with these 2 bricks.

:trophy: The solution : chaining Workflows

The presented solution is the destination of a very interesting trial and error journey. Other technical solutions that were attempted are available in the Solution Appendix section of this article.

:bulb: Idea

As often the case when exploring technical solutions we are at some point faced with additional technical limitations on top of the functional ones.

In our case, the main problem that we faced was to be able to deal with error handling inside Workflows. Indeed : it is not possible to request the removal of an access the Identity does not have.

:spiral_notepad: Additionally, this limitation also applies when the removal request is for a group of accesses, if the identity does not have one of the access of the requested group.

For example asking for the removal of {AP1, AP2} if the identity only has AP1 will fail altogether and not remove anything. This limitation is actually related to the Access Request API that is ultimately used by the Workflow engine.

It’s time to have a look at what SaaS Workflows offer in terms of actions and out of the box solutions :slight_smile: Let’s keep in mind we need action blocks that would not stop the workflow execution by failing.

After glancing at the SaaS Workflows action blocks , we notice an interesting one that allows for making HTTP Requests in JSON format. It even allows for several kind of authentication scheme : headers, Basic and OAuth.

image

This is a non blocking action : as long as the request is sent and taken into account by the service we are connecting to, the Workflow will continue to the next step.

With the help of the new inline variable feature we should be able to craft a JSON request to an external system that would handle all the access removal work. As long as the Response code to our request is a successful one (typically a 2xx), the workflow should not stop and keep on its attempts at removing accesses.

This way we are kind of brute-forcing by trying to remove all the accesses from the group but never stopped because the identity does not have one of them.

There is one caveat remaining though : we are calling an external system and this does not comply with one of the customer needs : be acceptable, the mechanism has to be purely IdentityNow based.

image

Let’s go back to our SaaS Workflows blocks and here is a very interesting one that perfectly fits the need.

As stated by Workflow documentation for triggers and steps :

External Trigger

A third-party system triggered a workflow based on configurations made on that system and within your SaaS platform.

Because the input provided to the workflow by the external trigger varies depending on the external site and API, it’s not possible to use the variable selector in future steps to choose variables from this trigger.

However, you can still select variables using JSONPath for use in future steps by adding the trigger field to your JSONPath expression using the Goessner implementation.

This is our holy grail : Not only will we be able to call an external system with whatever payload we want but on top of that, said external system will be IdentityNow and we are free to use the payload as we please !

With only 2 workflows :

  • Workflow 1 triggered by the Access Request
  • Workflow 2 triggered by “external” HTTP requests performed from Workflow 1. It will try to remove the access provided in the request’s payload by Workflow 1 call. If it fails, only this instance of Workflow 2 execution will fail and stop, but, it won’t affect Workflow 1. This is what we want.

We should have all our needed elements checked :

  1. :white_check_mark: Something to detect the Access Request decision : We have our Access Request trigger for Workflow 1
  2. :white_check_mark: Something able to manage Identities accesses : Workflow 2 instances will take care of those removal requests.
  3. :white_check_mark: Something acceptable for the customer : Only 2 chained workflows and completely internal to IdentityNow. This meets all the customer’s needs.

… on to try and implement that.

:computer: Implementation

Data model

Because we are going to have 2 workflows communicating via an HTTP request, just like an API call, we need to design some kind of data model for the called workflow (2) to be able to use the data passed on by the calling workflow (1).

Here is a suggested data model :

{
 "triggeringAccess": "<ACCESS_THAT_TRIGGERED_THIS>",
 "requestedFor": {
   "id": "<IDENTITY_ID>",
   "name": "<IDENTITY_NAME>"
 },
 "accessToRemove": {
   "id": "<ACCESS_ID>",
   "name": "<ACCESS_NAME>",
  "type": "<ACCESS_TYPE>"
 }
}
  • triggeringAccess : Allows to test if we are not removing the access that was just being given.
  • requestedFor : The identity object that we will remove the access from
  • accessToRemove : the full access object we will try to remove from the identity

Workflow 1 : Access Request triggered.

Because we know precisely what the group of access we need to handle, we can define precisely all the calls to Workflow 2 : upon being triggered, Workflow 1 will need to perform a request to Workflow 2 for every access from the group {AP1, AP2, AP3, AP4}.

Each request will follow the data model. Here is an example of what the JSON body of each request defined as :

Note that the hard coded part is the accessToRemove object that we have control over. The rest is dynamic because it is triggered by an event.

{
  "triggeringAccess": "{{$.trigger.requestedItemsStatus[0].name}}",
  "requestedFor": {
    "id": "{{$.trigger.requestedFor.id}}",
    "name": "{{$.trigger.requestedFor.name}}"
  },
  "accessToRemove": {
    "id": "a9bd51c3f99744d9b9069407d63c7f2b",
    "name": "AP1",
    "type": "ACCESS_PROFILE"
  }
}

will for example translate to :

{
  "triggeringAccess": "AP2",
  "requestedFor": {
    "id": "2c918086837ffea50183a2abd10c4963",
    "name": "Doe1"
  },
  "accessToRemove": {
    "id": "2c94fb82c2294217b29b70c23c3dd64d",
    "name": "AP1",
    "type": "ACCESS_PROFILE"
  }
}

Workflow 2 : Called by Workflow 1

On the receiving end here is how the Workflow is set up :

image

This is how the data is used :

  • External trigger is triggered by the HTTP Request from Workflow 1
  • Compare Strings : compares that we are not trying to remove the access that has just been given

  • Remove the provided Access (originally Manage Access) : uses the data from the trigger to remove the access if need be :

Putting it all together

Flow


Here is a high level diagram of how we have succeed in connecting 2 workflows in order to solve this use case

Enpoint and Authentication

A note on communication between the 2 workflows :

  • Workflow 2 Endpoint : When the External trigger trigger is used in the context of a Workflow. IdentityNow creates a unique endpoint that is provided within the action.

  • Workflow 2 Authentication : Upon creating the endpoint, the Workflow engine does not create credentials for accessing this endpoint. Credentials have to be created by using the + New Access Token button. Upon creation, you will be presented with a client id and a client secret that you will then be able to use in order to receive a short lived access token via the client credentials OAuth flow.

  • Workflow 1 : Endpoint & Authentication : Those values can then be used in the HTTP Request action from Worfklow 1 in order to properly contact and authenticate towards Worfklow 2’s endpoint.

:man_student: Appendix : Study, possible solutions

While exploring the technical solutions that would lead us to what we want, we came across several possibilities. Here are 3 of them with their associated limitations. They are presented in the order that tries to respect the path our train of thoughts traveled for the analysis.

:goal_net: Solution 1 : Filter accesses

:bulb: Idea

image

Because we know exactly the roles we want to remove, the idea is to filter on those. Either with their id or with their names or event with a part of their name if we are able to follow a naming convention.

The Get Access Action only allows us to get access from an Identity or a Search Query. Here we really need to handle the accesses for the identity that triggered the Workflow so we can not filter on accesses at that stage.

The managed access on the other hand allows for choosing a variable. We could try and point the variable provided by the Get Access action and filter it with JSONPath, this way we could target only the accesses from the group to be removed.

:expressionless: Caveat

Actions filters do not support the full Goessner syntax yet. As a consequence this is not possible to use the displayed JSONPath filter : $.getAccess.accessItems[?(@.name=='AP2' || @.name=='AP3' || @.name=='AP4' )] in order to remove only the accesses we need to remove.

As a consequence, this idea is not possible to implement.

:information_source: The dev team in charge of Workflows is aware of this limitation and it is taken into account for next updates.

:question: Solution 2 : Test all the possibilities

:bulb: Idea

Try to test all combinations of roles from the group our identity might have and remove them.

Because we cannot filter out only those roles from the group from the identity accesses, we can still leverage the use of the Compare String operator that allows for exact comparison with simple JSONPath syntax supported by this operator :

This idea was actually a really clever one that we received from one of our architect teammate and that was completely usable on the customer’s sandbox environment with a group of 4 Access Profiles :

We would have 4 Workflows triggered by the 4 Access Profiles granted through an Access Request and the Workflow would then test all the possible combinations of accesses the identity would have thus constructing a test tree of 2^3 = 8 possible combinations. Each time a Compare String operator would return true, the access would be removed and the Workflow would move on to the next possibility.

The overall test tree would then end up with 8 leaves leading down to the last Success Step as seen below

:expressionless: Caveat

While this solution is technically elegant as well as feasible with a group of 4 accesses, it becomes a different story when considering a group of 9 accesses on the production tenant as requested by the customer.

With a group of 9 accesses, this would lead us to coding 9 workflows each with 256 possible paths to the End Step in order to test all the possible combinations as showcased in this quickly coded scriptlet : Access Profile group possible combinations .

Spoiler : that is 9 * 256 ~ 2300 combinations.

In that case, with that amount of possibilities, this technical solution is neither elegant anymore nor acceptable for the customer.

On to the next idea …

:muscle: Solution 3 : Brute-force removal

:bulb: Idea

Because we are technically limited and can neither filter our identity accesses nor test out all the possible accesses combination it might have, let’s try it the brute-force way : no tests, just try and remove all the accesses from the group without checking if the identity has the access or not.

In the builder, this looks like the image on the left.

:expressionless: Caveat

The issue here is obvious : Workflows are designed to be a chain of successful actions.

As soon as the workflow will reach a removal action for an access profile the Identity does not have, it will fail and the worfklow will exit without attempting to remove the other ones. No need to investigate this one further.

:repeat_one: Solution 4 : Loop over accesses

:bulb: Idea

Loops are a very promising recent operator available in the Workflow library. The idea is to be able to loop over array data provided in steps above the loop. For instance it is possible to loop over an identity accesses.

This would be ideal and combine the ideas of some of the solutions shown earlier in this post.

As shown here, with a little bit of naming convention, we would be able to revoke an access based on how it is named :

Thus allowing is to only remove the accesses the identity has and that corresponds to a certain criteria.

:expressionless: Caveat

At this point, loops operators are recent and do not have access to the data provided by the steps above them. The only data it can manipulate is the one it is looping over.

In other word : we are able to see the accesses the identity has but not the identity itself. In this context, it is not possible to use the Manage Access action.

:information_source: The dev team in charge of Workflows is aware of this limitation and it is taken into account for next updates.

Notes

Improvements

Of course there is always room for improvement in those situations.

Some improvement will come from the updates brought to the features that we are using, some are inherent to what we have setup. On the top of our heads, here is what could be improved in the future.

  • Better test handling, maybe move the test in the calling workflow : this would have to be investigated.
  • More dynamical data modeling : This would be interesting ways to make this dynamic : instead of having to configure a fixed set of access from within the calling Workflow, be able to dynamically retrieve them via a technical mechanism like :
    • tags
    • naming convention
    • search

Tools and pointers

We have used some tools during this technical investigation, here are two very interesting technical tools when it comes to working with workflows

Visual Studio Code IDN extension

Yannick Béot’s Visual Code Extension is a must have when dealing with IdentityNow workflows, the features, among others are :

  • Ability to edit the workflow json directly from vscode
  • Ability to enable and disable Workflows from within vscode
  • Ability to read workflows’ executions directly in JSON inside vscode : very useful to follow the data passed from step to step
  • Ability to test the workflows (although we have not tested this one)

Get it from : SailPoint IdentityNow - Visual Studio Marketplace or directly from within vscode. It’s actively being updated and is really good ! Check it out on github

webhook.site website

https://webhook.site website is also a must have as it allows, by leveraging the HTTP Request action from the workflow library, to call an endpoint with the data being passed around the steps.

Upon connection, https://webhook.site website will automatically generate and endpoint that is usable within your workflow. The “trick” is to call this endpoint with the following parameters in your HTTP Request action to be able to read all the data the workflow is working with. ‘$’ corresponds to the root of the JSON object being passer around the workflow steps. This way, it allows for easy, direct and visual debugging of the data.

image

Conclusion

This was a really fun use case to work on. It allowed us to learn more about Workflows and how they can be used to model technically advanced solutions to customers’ use cases.

When looking back at the technical solution, the HTTP external trigger is so flexible that it is kind of like modeling a whole API but with no coding. This design pattern could be extended to numerous use cases.

Also, thanks to the whole Workflow team for their help !

15 Likes

@chazeauc Thank you a lot for your very detailed explanation of proposed solutions for that quite standard account management case.

3 Likes

Hi @chazeauc

We wanted to understand how you are calling second workflow which uses external trigger from first workflow Http Request step.
I too have generated client URL, client id and secret from the external trigger. Trying to put those in first workflow HTTP request step but we are getting the error saying

“reached max cap for secrets: 100”

If possible can you share the workflow script for our reference.

Hello,
I don’t think the error you are witnessing has anything to do with the Workflow(s) themselves.

I think it has to do with the limitations on how many secrets Workflows can hold overall.

I also see there was an issue with this limit yesterday (that is to say an incident was raised on this) so either :

  • Check your other Workflows to see if you have more than 100 and delete some if possible.
  • Try again as it appears the issue has been mitigated.

Keep us posted !
Thanks.
Christophe.

You are right @chazeauc . It seems to be a limitation in one of our lower tenant. In the other tenant it is working and I am able to call the second workflow now. Thank you for the suggestion will check that helps.