Implementation of Separation of Duties controls is often an Identity Governance requirement. Whilst SoD controls will find their way into the Okta Identity Governance product at some point, they can be implemented today using the Okta Identity Cloud data model and Okta Workflows. This article provides a sample implementation.
Article contents:
Introduction
A common requirement for Identity Governance is for Separation of Duties (aka Segregation of Duties, or just SoD) controls. SoD controls are intended to stop overlaying IT access that would allow compromising activities. The classic example is you wouldn’t want someone to be able to both raise a purchase order and also approve the same purchase order.
SoD controls may be based on user groups, roles, jobroles, business activities or other identity objects that can represent a set of IT accesses. Often these need to be fine-grained accesses tied to application functions (think purchase order create and purchase order approve – both are likely functions or transactions within the one application). The definition of these roles may come from audit findings or local analysis, and defining them and the policies may be a considerable exercise (the cost of the consulting can outweigh the cost of the product to implement the controls).
Controls may be proactive or reactive. It is common to implement a mechanism to check for SoD policy violations whilst access is being assigned, but also to run periodic SoD violation reporting. The example here is looking at the former – detecting violations during the access request process.
Okta and Coarse- and Fine-Grained Entitlements with Groups
The standard Okta Identity Cloud data model concerns users, groups and applications. Users can be members of multiple groups. Users and groups can be assigned to applications (although group assignment is considered best practice). Depending on the application, the user or group assignment can also set application profile attributes, such as a role, profile or permission sets. So users in Group A may inherit a specific application role, and users in Group B may inherit a different application role.

Thus groups in Okta could represent sets of access. From the coarse-grained perspective a group represents a set of applications and may be granular enough for SoD policies. However group assignments, where assignment applications represent access, may also provide the granularity needed for SoD policies.
Let’s use the Salesforce application as an example. The Salesforce.com (SFDC) application profile contains a number of access attributes that could be associated with group assignments, such as profile, role, permission sets and feature licenses. In this example we have defined four groups that are assigned to SFDC and represent a specific SFDC role, as shown below.

Within each group assignment, a specific role has been selected.

Thus anyone in the DO.SFDC.Role.Channel-Sales
group will be assigned to the Channel Sales Team role automatically in SFDC. The other three groups shown are assigned to the corresponding roles in SFDC. This means each group represents a fine-grained permission in the application. We will use these groups in the example below.
This model relies on the applications exposing that fine-grained access information and the integration with Okta consuming it and making it available for group assignment.
Implementing an Example
This section will explore the implementation of the example, with an overview, the mechanics in the different Okta components and finally what it looks like.
Overview
The basic premise in this example is that there are some SoD policies based on groups (representing fine-grained application permissions) and that adding a user to one of these groups may trigger a SoD policy violation.
The components and flow are summarised in the following figure.

The flow is initiated by a user being added to a group (1.) in Okta Identity Cloud (Okta). This could be from an administrator assigning the user to the group, an API call, a user Access Request in Okta Identity Governance or any other mechanism.
The “user assigned to group” event in Okta will trigger a flow in Okta Workflows (2.). The flow will get the details of the user and new group, check if the group is subject to any SoD policies and if so evaluate the policies.
It may be that the group change does not violate any policies. If it does, the policies are checked for enforcement. If enforcement is flagged, the flow will back out the group assignment (i.e. remove the user from the new group) (3.). The details of the violation are emailed to the user and their manager, and a summary is sent to a Slack channel (4.).
There are various sub-flows and utility flows supporting this main flow.
The component are detailed in the following sections.
Okta Events
Earlier I described how we can use groups and the group to application mapping to represent roles. In this example, I have four groups mapped to Salesforce.com (SFDC) with each setting a specific SFDC role. For example group DO.SFDC.Role.Channel-Sales
will map to SFDC role “Channel Sales Team” (as shown above).
The only event in Okta we are concerned with is when a user is added to one of these groups.

Workflows Table for SoD Flows
The Okta Workflows configuration consists of two tables and a set of flows.
The main table holds the SoD Policies. It has a row for each policy with a policy name, description, whether to enforce the policy or not (i.e. whether to back out the group membership), scope (the allowed number of groups in the set, more than this number will be a violation), the groups and the severity (not actually used).

This table could be managed externally in a CSV file, and wiped and re-imported from the CSV file as needed.
The other table is an environment variables table. I tend to use this as a best practice for all my flow sets to centrally store and manage variables I use in the flows, with a single sub flow to access specific values. The only value I use in this set of flows is called “SoD_group” and it stores a list of groups that appear anywhere in the flows. This is used as a first pass to determine if the new group could trigger SoD policy violation. If the new group isn’t in this list, the complex SoD checking isn’t done. I have a flow that will go through all the SoD rules (in the SoD Policy table) and re-create this list.
Main Okta Workflow for User Added to Group
There are many flows in this set.

But I will focus on the main flow – M00 – User Add to Group Check SoD. The other flows are sub flows (S**) for specific functions called from the main flow, or utility (U**) flows to setup data.
The main flow is shown below:





The flow (as shown above) will:
- Be triggered by the user being added to the group and check to see if the new group is in the list of SoD groups, if so it will continue
- Check the SoD table to see if the new group and the users existing groups cause any policy violations
- If SoD violations are found it will continue on (it is possible that the new group is the first group in a SoD policy and thus not cause a violation), and format a email body standard text
- Check to see if there are any enforce=yes policies that have been violated, and if so go remove the user from the group in Okta and construct the email/Slack message bodies, otherwise just construct the email/Slack message bodies (it also checks to see if the group removal was successful or not and format the email content appropriately)
- Read the user to get their emails and manager ID, and if there is a manager ID it reads that manager user to get their email (or sets the manager email to be the system admin) then sends the email to users and manager, and a slack message to a channel in Slack (i.e. the channel that the compliance team would monitor).
The following paragraphs will drill into each of these sections of the flow, but you can skip ahead if you aren’t interested in the flow details.
Section 1 – Trigger and Pre-Check
The flow is triggered by a User Added to Group event in a standard Okta Connector card.

A sub flow (S90 – Is SoD Group) is called with the new group name. This sub flow will check if the new group is in the list of groups in SoD policies and return a true/false. If the response (is_sod) is true, the flow will continue.
This is done for scalability/performance reasons. There could be a lot of group membership activity in an Okta org, but you don't want to check SoD rules for each one.
Section 2 – Check for Violations
The next section will retrieve all groups the user belongs to (note that this will include the new group as Okta has already added the user to it).

It strips the group names out from the returned group object list and puts it into its own text list. It then sets an empty list that will be used to store the violations that are found.
Next the SoD Policies table is searched for each policy rule (row) and each row is processed against the current group list by the S00 – Compare Curr to SoD Group Lists sub flow. The key to this sub flow is that it compares the two group lists with a List Intersection and List Length cards (i.e. how many groups are common to both), and compares this to the scope from the policy table (section of sub flow shown below).

For example, if two groups are in the current users group list and also in a SoD policy, and the scope was 1 (i.e. you could only have one group) then the policy is in violation.
As the main flow iterates through the policy rules, any violations are written to that (initially empty) violation list. This is why a List Reduce card use used.
Section 3 – If Violations, Format Standard Message Content
Once all the policy table rows have been evaluated, the main flow check to see if any violations have been detected and if not will stop executing.
Before processing the violations, it will setup some standard content for the emails (i.e. this text will be used no matter what state of violation).

It is formatting the message that has come out of the violations (i.e. a HTML formatted bit of text for each violation) and putting together a standard email body prefix and suffix.
Section 4 – Format Specific Message Content (and Remove Group)
The next section looks for any violations that have been flagged as enforceable (enforce=yes). It does this by finding the first instance of “Enforce = Yes” in the violations list (not found will return a -1).

If there are any enforceable violations, it will attempt to remove the user from the group and format email and slack messages to suit. If this fails, or there are no enforceable violations, then different messages are formatted. Note that the email bodies are using the standard prefix and suffix from earlier.
Section 5 – Send Emails and Slack
The last section sends the email and Slack messages.

It reads the user from Okta to get emails, and checks for a ManagerID field. If found it reads the manager to get their email, otherwise it sets it to an admin account. The last card (not shown) will send a slack message to a standard channel and flag it as coming from SoDBot.
This is how it’s constructed. The last section shows how it runs.
The Outcome
For this example, we manually added the user (Waylon Smithers) to the SFDC Marketing role group.

The user appears in the group straight away.

But refreshing the group shows that Waylon has been removed.

Checking the Okta System Logs shows the user was added to the group and almost immediately removed from the group.

So the flow has executed and as the user was removed from the group they triggered an enforceable SoD Policy. We can see this in the email sent to Waylon.

There was also a Slack message sent showing a summary of the event.

This shows the flow has run as expected.
Conclusion
This article has shown how Separation of Duties (SoD) policies can be implemented using standard Okta data objects, events and Okta workflows and tables. It is a sample implementation to show how it could be built, but could be used as the basis for a custom deployment.
Sample Code
The workflows example described above can be downloaded from https://github.com/iamse-blog/workflows-templates/tree/main/dae-iga00-SoD