Skip to content

Author Policy

In the Automate data access control decisions use case, we covered subscription policies at length. Subscription policies control table level access.

In this use case, we are focused one level deeper: columns and rows within a table that can be protected in a more granular manner with global data policies.

Global data policy: column masking

With Immuta, you are able to author global masking policies, or even mask cells within a given column per row using cell-level masking (also termed conditional masking).

This is important for this use case to granularly mask or grant unmasked access to specific columns to specific users. This granularity of policy allows more access to data because you no longer have to roll up coarse access decisions to the table level and can instead make them at the individual column, row, or cell level.

Using tags

The concept of masking columns has been mentioned a few times already, but as you already know per the Managing data metadata guide, your masking policies will actually target tags instead of physical columns. This abstraction is powerful, because it allows you to build a single policy that may apply to many different columns across many different tables (or even computes).

Masking conflicts

If you build policy using tags, isn't there a good chance that multiple masking policies could land on the same column? Yes.

This can be avoided using tag-depth. Immuta supports tag hierarchy, so if the depth of a tag targeted by a policy is deeper than the conflicting policy, the deepest (the more specific one) wins. As an example, mask by making null columns tagged PII is less specific (depth of 1) than the policy mask using hasing columns tagged Discovered.name (depth of 2), so the hashing masking policy would apply to columns tagged Discovered.name and PII rather than the null one.

Principle of least privilege

Immuta meets principle of least privilege by following an exception based policy authoring practice. What this means is that if you mask a column, that mask will apply to everyone except [some list of exceptions]. This uni-directional approach avoids policy conflicts, makes change management easier, authoring policy less complex, and (most importantly) avoids data leaks.

Masking techniques

There are many different approaches you can take to masking a column. Some masks render the column completely useless to the querying user, such as nulling it, while other masking techniques can provide some level of utility from the column while at the same time maintaining a level of privacy and security. These advanced masking techniques are sometimes termed privacy enhancing technologies (PETs). Immuta provides a vast array of masking techniques that allow for privacy-vs-utility trade-off decisions when authoring masking policies.

Dealing with data types

If you were to build masking policies natively in your data platform, they require that you build a masking policy per data type it could mask. This makes sense, because a varchar column type can't display numerics, or vice versa, for example. Furthermore, when building masking policies that target tags instead of physical columns, it is possible that policy may target many differing data types, or even target new unforeseen data types in the future when new columns appear.

This adds a large deal of effort to policy authoring. With Immuta, rather than explicitly requiring you to cover all possible data types when building a masking policy, Immuta has intelligent fallbacks. This allows Immuta to apply the masking policy by changing, as minimally as possible, the masking type to something that is possible against that data type, while still maintaining the required privacy level provided by the original masking type.

Global data policy: row-level

You may hear this policy called row filter or row access policy. The idea is to redact rows at query time based on the user running the query.

Without this capability, you would need a transform process that segments your data across different tables and then manage access to those tables. This introduces extra compute costs and at some point, when dealing with large tables and many differing permutations of access, it may be impossible to maintain as those tables grow.

Immuta allows you to author policies that redact rows using a global scope by leveraging tags just like with masking policies.

Using tags

Unlike masking policies, the use of tags for redacting rows not only impacts which tables are targeted with the policy, but also the logic of the policy. When authoring the policy, you must choose the column to "compare against" when making a decision on if the row should be visible to the user or not at query time. The tag can drive this decision. This allows you to author a single row-level policy that targets many different tables (or even computes).

Advanced functions

Sometimes customization is required for scenarios that require more complex logic. When in the policy builder, these are called only show rows WHERE policies. Within your custom WHERE, Immuta also provides several different functions you can leverage for powerful and scalable policies:

  • @groupsContains('SQL Column or Expression') allows you to compare the value of a column to see if the user possesses any groups with a matching name (case sensitive).
  • @attributeValuesContains('Attribute Name', 'SQL Column or Expression') allows you to compare the value of a column to see if the user possesses any attribute values under a specific key with a matching name (case sensitive).
  • @purposesContains('SQL Column or Expression') allows you to compare the value of a column to see if the user is acting under a matching purpose (case sensitive).
  • @columnTagged('Tag Name') allows you to target tags instead of physical columns when using one of the above functions.

Here's a simple example that targets a physical column COUNTRY comparing it to the querying user's country attribute key's values:

@attributeValuesContains('country', 'COUNTRY')

This could also be written to instead use the @columnTagged function instead of the physical column name:

@attributeValuesContains('country', @columnTagged('Discovered.Country'))

Allowing this policy to be reused across many different tables that may have the COUNTRY column name spelled differently.

Data policy temporary overrides

You may want to override and grant access to unmasked data to an individual for a very specific reason. Our recommendation is to use purposes to create exceptions to global data policies.

There is some up front work that needs to occur to make this possible. A user with GOVERNANCE permission would need to create legitimate purposes for access to different data types unmasked. As part of creating the purposes, they may want to alter the acknowledgement statement the user must agree to when acting under that purpose.

Then, the masking or row-level policies would need to be updated to include that purpose and those purposes as exceptions to the policy, for example:

...for everyone except users acting under purpose [some legitimate purpose(s)]

Once that is done, users can create a project and add to the project both of the following:

  • Data sources with the policies they want to be excluded from and
  • Purposes

However, that project does nothing until approved by a user with PROJECT_MANAGEMENT permission. Once that approval is complete, the user wanting the exception must acknowledge they will only use the data for that purpose and then, using the Immuta UI, switch to that purpose (the project). Once switched to that purpose, the approved exception(s) will occur for the user.

This can be made temporary by deleting the project once access is no longer needed or revoking approval for the project after the need for access is gone.

Subscription policies

After you've created global data policies as described above, how do you actually give access to the tables?

This is a good question, and it really depends if you already have a table-access process you are already happy with or not. If you do, keep using it. If you don't, we recommend you create a subscription policy that opens all tables up to anyone, as the point of this use case is not to focus on coarse grained table-level access but instead fine-grained access using global data policies.

When creating new tables, you should follow the best practices to ensure there's data downtime while you wait for policy uptime through Immuta schema monitoring.