Skip to content

Configure Principal Directory

This property enables a connector-owned principal directory table in Snowflake. When enabled, masking and row-filter policies whose conditions reference the caller's group/role membership or attributes are translated into a constant-size sub-query against the directory table, instead of inlining the resolved list of users into the policy.

Applies to policy conditions only

The directory translation applies only to ABAC macros placed in a policy's condition (the policy-item access/expression condition). Macros used inside a custom masking value or inside a row-filter filter expression are not translated — for those, the generated SQL DDL still inlines the resolved user list (the existing behavior).

Overview

By default, when a masking or row-filter policy uses a principal-attribute condition (for example IS_IN_GROUP('sales') or USER.level > 5), the connector resolves the condition to the matching users and inlines that user list into the generated Snowflake policy (for example CURRENT_USER IN ('u1','u2', … )). For large groups/roles this produces very large policy bodies that must be regenerated on every membership change, and can exceed Snowflake's statement-size limits.

With the principal directory enabled, the connector instead:

  1. Creates a table PRIV_PRINCIPAL_DIRECTORY in the PUBLIC schema of its home database (default PRIVACERA_DB) and keeps it in sync with the Ranger principal (user, group, role) attributes and membership.
  2. Emits the policy condition as a constant-size EXISTS(...) sub-query against that directory, evaluated per CURRENT_USER() at query time.

Only principals with attributes or membership are stored

The directory holds a row only for principals that carry data a condition can match — a user that has one or more attributes, or a group/role that has members or attributes. A principal with no attributes and no membership contributes no rows, so the directory stays compact. A user's group and role memberships are recorded on the containing group or role, not on the user's own rows.

Benefits

  • Constant-size policy SQL — the generated masking/row-filter policy no longer grows with the number of users, and does not need to be rewritten when a principal's membership or attributes change (only the directory rows change).
  • Correct for transitive roles — role membership is resolved transitively (nested roles and group-in-role).
  • No regression — anything the directory cannot translate falls back to the existing inline path automatically.

Supported conditions

The directory translation applies to the following Ranger ABAC macros and expression variables:

  • IS_IN_GROUP, IS_IN_ANY_GROUP, IS_NOT_IN_ANY_GROUP, HAS_UG_ATTR
  • IS_IN_ROLE, IS_IN_ANY_ROLE, IS_NOT_IN_ANY_ROLE, HAS_UR_ATTR
  • HAS_USER_ATTR
  • USER.<attr> typed comparisons (==, !=, >, <, >=, <=)

Fallback to the inline path

Conditions outside the supported grammar are not translated and automatically fall back to the inline path, so behavior is unchanged for them. This includes the Ranger built-in user attributes _name and _emailAddress (these are computed by Ranger and are not stored in the directory), and dynamic GET_* expressions.

Prerequisites

The connector creates and maintains the directory table under the PUBLIC schema of its home database — the CONNECTOR_SNOWFLAKE_JDBC_DB property, default PRIVACERA_DB (see Configuration). That database must already exist and the connector's Snowflake role must be able to create a table in it.

  1. Ensure the home database exists. The default PRIVACERA_DB is created as part of the standard Snowflake prerequisites. If you configure the connector to use a custom database, create that database first and apply the same grants described in the standard Snowflake prerequisites.

  2. Grant the CREATE TABLE privilege on the home database's PUBLIC schema. This is in addition to the USAGE + CREATE FUNCTION grants that the standard prerequisites already apply to PRIVACERA_DB — the standard setup does not grant CREATE TABLE:

    SQL
    1
    2
    3
    GRANT USAGE ON DATABASE "<home-db>" TO ROLE <connector-role>;
    GRANT USAGE ON SCHEMA "<home-db>"."PUBLIC" TO ROLE <connector-role>;
    GRANT CREATE TABLE ON SCHEMA "<home-db>"."PUBLIC" TO ROLE <connector-role>;
    

    (Replace <home-db> with PRIVACERA_DB or your custom database name, and <connector-role> with the connector's Snowflake role, e.g. PRIVACERA_POLICYSYNC_ROLE.)

    USAGE is required in addition to CREATE TABLE

    Without USAGE on both the database and the schema, Snowflake reports Database '<db>' does not exist or not authorized at startup even when CREATE TABLE has been granted — this is Snowflake hiding object existence behind authorization, not a missing table.

End users need no grants on the directory table

The masking/row-filter policies evaluate the directory sub-query with the policy owner's privileges, so the querying users do not need any grant on PRIV_PRINCIPAL_DIRECTORY or its database.

Configuration

  1. SSH to the instance where Privacera Manager is installed.

  2. Run the following command to open the .yml file to be edited.

    If you have multiple connectors, then replace instance1 with the appropriate connector instance name.

    Bash
    vi ~/privacera/privacera-manager/config/custom-vars/connectors/snowflake/instance1/vars.connector.snowflake.yml
    

    Home database (CONNECTOR_SNOWFLAKE_JDBC_DB)

    The directory table is created in the PUBLIC schema of the connector's home database, set by the CONNECTOR_SNOWFLAKE_JDBC_DB property. It defaults to PRIVACERA_DB; to place the directory in a different database, set it to your custom database name:

    YAML
    CONNECTOR_SNOWFLAKE_JDBC_DB: "MY_CUSTOM_DB"
    
    A custom database must be pre-created and granted the required privileges — see Prerequisites.

  3. Add the following property:

    YAML
    CONNECTOR_SNOWFLAKE_ENABLE_PRINCIPAL_DIRECTORY: "true"
    

  4. Once the property is configured, run the following commands to update your Privacera Manager platform instance:

    Step 1 - Setup which generates the helm charts. This step usually takes few minutes.

    Bash
    cd ~/privacera/privacera-manager
    ./privacera-manager.sh setup
    
    Step 2 - Apply the Privacera Manager helm charts.
    Bash
    cd ~/privacera/privacera-manager
    ./pm_with_helm.sh upgrade
    
    Step 3 - (Optional) Post-installation step which generates Plugin tar ball, updates Route 53 DNS and so on. This step is not required if you are updating only connector properties.

    Bash
    cd ~/privacera/privacera-manager
    ./privacera-manager.sh post-install
    

Default

This property is disabled by default. Existing connectors are unaffected until you opt in; enabling it does not change the outcome of policy evaluation — only the form of the generated policy SQL.

Behavior on fresh install, upgrade, and rollback

Enabling or disabling the principal directory is a configuration change applied through Privacera Manager; it takes effect after the connector restarts. Once the connector restarts, it automatically converts the already-applied policies into the required form — you do not need to edit or re-publish any Ranger policy. In every case the outcome of policy evaluation is unchanged; only the form of the generated Snowflake SQL (a directory sub-query versus an inline user list) differs.

Fresh install — directory enabled from the start

On the first startup with the property enabled, the connector creates the directory table (and a companion metadata table that records a bootstrap-complete marker) and runs a full sync to populate it from the connector's principal store. As policies are synchronized, every masking or row-filter policy whose condition is translatable is applied directly in the directory (EXISTS) form; conditions outside the supported grammar are applied inline. There are no earlier inline policies to convert.

Upgrade — enabling on a connector that already has policies

When you enable the property on a connector whose policies were previously applied in the inline form:

  1. On restart, the connector creates and fully syncs the directory table, the same as a fresh install.
  2. As part of the same restart, each already-applied masking or row-filter policy whose condition is translatable is converted once into the directory form. Policies that cannot be translated stay inline.

This is a one-time conversion per affected policy; after it completes there is no further churn. While the feature stays enabled, a principal's membership or attribute change updates only the directory rows — the policy SQL is not regenerated.

Rollback — disabling after it was enabled

When you disable the property on a connector that has directory-form policies applied:

  1. On restart, the connector stops bootstrapping and syncing the directory. The directory table is left in place but is no longer maintained; it is not dropped.
  2. As part of the same restart, each policy currently deployed in the directory form is converted once back into the inline form, so no policy is left evaluating against a directory table that is no longer kept current.

This automatic downgrade is what keeps policy evaluation correct after a rollback. If you do not plan to re-enable the feature and want to remove the directory objects, drop the PRIV_PRINCIPAL_DIRECTORY table together with its companion metadata table; if you enable the feature again later, the connector re-creates and re-syncs both.

Examples

Example 1: Condition using HAS_USER_ATTR

Policy condition: HAS_USER_ATTR('dept')

Generated (directory) form:

SQL
1
2
3
EXISTS (SELECT 1 FROM "PRIVACERA_DB"."PUBLIC"."PRIV_PRINCIPAL_DIRECTORY"
        WHERE PRINCIPAL_TYPE = 'USER' AND PRINCIPAL_ID = CURRENT_USER()
        AND ATTR_NAME = 'dept')

The predicate matches when the current user has a dept attribute in the directory. It is the same size regardless of how many users hold the attribute, and does not need to be regenerated when users gain or lose it.

Example 2: Masking using a user attribute

Policy condition: USER.level > 5

Generated (directory) form:

SQL
1
2
3
EXISTS (SELECT 1 FROM "PRIVACERA_DB"."PUBLIC"."PRIV_PRINCIPAL_DIRECTORY"
        WHERE PRINCIPAL_TYPE = 'USER' AND PRINCIPAL_ID = CURRENT_USER()
        AND ATTR_NAME = 'level' AND TRY_CAST(ATTR_VALUE AS DOUBLE) > 5)

The mask applies when the current user's level attribute is greater than 5. Ordering operators (>, <, >=, <=) compare ATTR_VALUE numerically via TRY_CAST(... AS DOUBLE).