Grouper ABAC

Grouper does a great job with group relationships and group math.  Basis groups can be loaded into Grouper but that is a single relationship from a user to an attribute (group).  For instance, you can load groups that represent every affiliation with the users who have that affiliation.  You can load groups that represent organizations with the users who are in those organizations.  But you cannot take those groups and use group math to calculate which users have a specific affiliation in a specific organization (both are many to many relationships).  You would need to load the cross product of the data which is not scalable as the number of attributes increases. 

ABAC allows you to model rows of data for a user, and then make an ABAC script to specify criteria in that row of data.  You could instantly make a group for users who have certain affiliations in certain org in their primary job in a full time capacity.  Previously you needed to make a loader job to load a group with a SQL query that can join various data elements from a data warehouse.  At Penn over 15 years we now have 700 loader jobs.  Only Grouper sysadmins can manage these loader jobs for security reasons.  It takes tickets to create the job, update the job, and troubleshoot the data.  This valuable staff time is greatly reduced with ABAC.  These loader daemons generally do not have real time updates since that is difficult to configure for every job, so hourly full syncs are scheduled which waste resources.  There is no way to do grace periods on the source data unless the source database keeps data history (which likely is not the case). 

Enter Grouper ABAC, with a few data providers (identity data, student data, payroll data, training data, etc), we can replace 2/3 of our loader jobs with ABAC.  The data will flow to grouper in real time (with fewer data feeds by two orders of magnitude it is feasible to configure real time updates).  Grouper keeps history on all the data so grace periods at the row level are available.  Each data provider has its own security policies at at the column level (with three access levels) which can be securely delegated to eligible power users.  Users can use ABAC scripts to configure, troubleshoot, and update their own groups.  A dynamic data dictionary informs users which attributes are available, what the values mean, and how to use them.  The analysis screen shows the numbers of all the parts of the script, and if test users have each attribute or the result.  This troubleshooting in SQL is very time consuming.  The groups will be updated in real time and the attribute values change.  A single ABAC script can use data from any data provider and even Grouper memberships (which cannot be done with loaders without supporting ETL jobs).  Loaders (to build multiple groups at once) can point to Grouper's attribute repository to have consistent data and real time updates.  The reduction in loader jobs from external data sources will reduce the network query traffic and database loads.  This ABAC data can be used to replace subject sources, provision rich object representations, manage users' lifecycles, and populate reports.  

The ability to manage groups by rows of data has been requested for years and will revolutionize access management.  It further differentiates Grouper from other IAM products.


Attribute based access control (ABAC) Overview

To implement access policies, it has often been necessary to set up intermediate groups, include/exclude, requirement groups, and allow/deny manual groups. Grouper has features to help in this area including: rules, hooks, templates, move/copy, import/export, and GSH scripts.

The ABAC with scripted groups feature is designed to offer increased efficiency in implementing access policies.  It's important for the common groups and policy language to be well documented and people to be properly trained.

Syntax


TypeConceptExampleDescription
Entity attributemember of

'ref:mfaEnrolled'

entity.memberOf('ref:mfaEnrolled')

Users that are members of this group (by system name / ID path)
Entity attributerecent member of

entity.recentMemberOf('ref:staff', '30 days')

entity.recentMemberOf('ref:staff', '1 hour')

Users that were recently (but not currently) members of this group (by system name / ID path)
Entity attributehas attribute

org

'org'

entity.hasAttribute(org)

entity.hasAttribute('org')

User has this attribute assigned or true for boolean attribute or has the attribute with any value for other types
Entity attributehas attribute string

org==abc

org=='01234'

'org'=='012#$%45'

entity.hasAttribute(org, abc)

entity.hasAttribute(org, 'abc')

entity.hasAttribute('org', 'abc')

Users that have this string attribute assigned to them
Entity attributehas attribute integer

org==123

'org'==123

entity.hasAttribute(org, 123)

Users that have this integer attribute assigned to them
Entity attributehas any attribute string in list

jobCode =~ [abc, def]

jobCode =~ ['abc', 'def']

entity.hasAttributeAny('jobCode', ['abc', 'def'])

Users that have any of these values for this attribute
Entity attributehas any attribute integer in list

jobCode =~ [123, 234]

entity.hasAttributeAny('jobNumber', [123, 234])

Users that have any of these values for this attribute
Entity attributehas attribute value likeentity.hasAttributeLike(org, '%\\_2%')

Users that have an attribute value like the SQL likeString.  Note: "like" expressions are more efficient than regex
% (percent) matches any zero or more any characters
_ (underscore) matches exactly one any character
\\ (double backslash) escapes the next percent, underscore, or backslash
\\\\ (quadruple backslash) literal backslash
\' (backslash single quote) literal single quote in single quoted string
\" (backslash double quote) literal double quote in double quoted string

Entity attributehas attribute with value matching regex

org =~ '^.*2.*$'

entity.hasAttributeRegex(org, '^.*2.*$')

Users that have an attribute value that matches the regex.  Recommended regex site to build and test a regex.  Escape quotes and slashes in jexl with backslash.  Less efficient than SQL like string.
Entity rowhas row with attribute assignmententity.hasRow('affiliation', 'active')Users that have an affiliation row with an attribute assigned or true for boolean, or any value for other types
Entity rowhas row with attribute valueentity.hasRow('affiliation', 'affiliationCode==staff')Users with a row of affiliation with a column value of attributeCode or value staff.  "staff" is a string that doesnt start with an integer or have special characters in it.
Entity rowhas row with attribute valueentity.hasRow('affiliation', "affiliationCode=='01234'  ")Users with a row of affiliation with a column value of attributeCode or value 01234.  "01234" has quotes around it since it has special chars or starts with an integer
Entity rowhas row with attribute string in listentity.hasRow('affiliation', 'affiliationCode =~ [staff, fac, alum]')Users with row of affiliation and has column affiliation code in staff, fac, alum
Entity rowhas row with attribute value SQL like stringentity.hasRow('affiliation', "hasAttributeLike(affiliationCode, '%f%') ")Users with row affiliation where a value for column affiliationCode is has an f in it.  This is more efficient than regex.
Entity rowhas row with attribute value that matches a regexentity.hasRow('affiliation', "hasAttributeRegex(affiliationCode, '^.*f.*$' )" )Users with row affiliation where a value for column affiliationCode has an f in it.  Users that have an attribute value that matches the regex.  Recommended regex site to build and test a regex.  Escape quotes and slashes in jexl with backslash.  Less efficient than SQL like string.

JEXL loaded groups

In Grouper v2.6.6+ there is a first pass at JEXL loaded groups using memberships of groups only.  In v5+ scripted groups can also be based on entity data fields.  It is basic and can be built on.  Note: this is subject to change as we see a working solution and discuss the optimal path forward.

See the blog!

For more info, see the February 2022 blog on Attribute Based Access Control with Grouper.


Video

Expression language (JEXL) scripts facilitate implementing the part of ABAC that defines who is included in a policy based on attributes of those users.  Other parts of ABAC such as resource attributes or environment attributes can be taken into consideration with Grouper permissions or by the service which has protected resources.

We want to be able to craft policies by an expression instead of creating loaders or tons of reference groups based on cartesian products of basis/ref groups.

Individual groups can be configured to automatically have their membership managed with individual subjects (or in future groups as members)

Why do we need this feature?

  • Reduces pre-loaded rollups that might not be used
  • You don't need a loader job for each one of these groups
  • Any Grouper user could edit the policies if they can READ underlying groups.  The expressions are secure (future state)
  • The memberships of the ABAC groups are near real time based on an intelligent change log consumer (future state)
  • You can have a UI to help build it and give good error messages
  • Could visualize the policies.  Perhaps could be integrated into existing visualization (future state)
  • This solves the issue of composites with any number of factors


UI to configure



Daemon screen

Note in Grouper v2.6.6 you need to wait an hour after changing a script, or run the JEXL script loader full job.  In v5+ an incremental job will adjust the members quicker.  Note: there is one full daemon and one incremental daemon that handles all of the JEXL script ABAC groups.  You do not add this, it is built-in


Scripts

The script can only be written by people who can READ groups in the script and UPDATE the owner group.  Since this is actually a JEXL script (not a JEXL expression), so you could have multiple lines, variables, conditionals, etc

In an entity script, the variable 'entity' is an instance of class: edu.internet2.middleware.grouper.abac.GrouperAbacEntity

You can use entity.memberOf('full:group:id:path') exactly like that to see if user is in a group or not.

ExpressionDescription
${ entity.memberOf('ref:staff') && entity.memberOf('ref:payroll:fullTime') && entity.memberOf('ref:mfaEnrolled') }

Three part intersection.  

Full time staff in MFA

${ ( entity.memberOf('ref:employee')
 || entity.memberOf('ref:student')  // employees or students
  || (entity.memberOf('ref:guests')
     && entity.memberOf('app:vpn:vpnManualOverrides'))) // or guests who are in manual allow
  && !entity.memberOf('ref:globalLockout')
  && !entity.memberOf('app:vpn:vpnManualLockout') }  // and not in either lockout group

Example policy

That means users who are not in globalLockout and not in vpnManualLockout
and in an eligible population which is faculty, students, or guests who are in the manual app override group

${ entity.memberOf('app:vpn:users') != entity.memberOf('ref:mfaEnrolled') }

Exclusive OR

This is VPN users not in MFA and MFA users not in VPN:


How it works in v5+

The script is parsed and converted to SQL.  The results represent the members of the group.  The diffs will be added or removed from the group.

Analyze policy

To confirm a policy is correct, a long form translation of the policy can be displayed along with group names and group counts


Policy patterns

Your institution can make a GSH template that will help users setup policies

TODO document this


See Also

Access Management Features Overview

  • No labels