Manually remove and clean up Immuta policy objects in Snowflake

Identify and gather the following:

  • Immuta integration database name created in Snowflake

  • Immuta system role created to manage the integration(e.g., <db name>.SYSTEM)

  • Immuta system account user created to manage the integration (e.g., <db name>.SYSTEM_ACOUNT)

Below is the list of Snowflake SQLs can perform clean up of Immuta created policy objects in Snowflake:

  • Review and understand the Snowflake script thoroughly; review the in-line comments about the overview of the script and the comments in each section.

  • Update parameters in the SQL script/statements and make necessary changes to the database in referenced, i.e. <IMMUTA_DB> should be Immuta integration database in Snowflake.

  • Call and execute both functions to iterate and disassociate policy-objects from tables.

  • Thereafter, the Immuta database, system role, and system_account user can be dropped before re-integration.

--Manually Remove and clean up Immuta created Snowflake Policies--
-- OVERVIEW:
-- 1. grant the role needed to create clean up functions
-- 2. define and implement remove masking policy function
-- 3. define and implement remove row-access policy function
-- 4. run/execute functions to detach/disassociate policy objects
-- 5. drop database, drop system role, drop system_account user

--STEPS:
--1. Grant permissions to create Immuta functions
GRANT ROLE "<SYSTEM_ACCOUNT_ROLE>" TO ROLE "<CURRENT_ROLE>";

--2. Define function to remove masking policies
CREATE OR REPLACE PROCEDURE <IMMUTA_DB>.IMMUTA_PROCEDURES.DETACH_MASKING_POLICIES()
            RETURNS boolean
            LANGUAGE JAVASCRIPT
            EXECUTE AS CALLER
            AS
            $$
                    -- Get all masking policies in the Immuta database.
                    const maskingPolicies = snowflake.execute({
                        sqlText: `SHOW MASKING POLICIES IN <IMMUTA_DB>.IMMUTA_POLICIES`
                    });
                    const tableMap = {};
                    while (maskingPolicies.next())  {
                        const policyName = maskingPolicies.getColumnValue(2);
                        -- This query returns the database, schema, table, target type, and referenced column for current policy.
                        -- Policy names may contain single quotes. So need to escape them at the end.
                        const policyRecord = snowflake.execute({ sqlText: `SELECT REF_DATABASE_NAME, REF_SCHEMA_NAME, REF_ENTITY_NAME, REF_ENTITY_DOMAIN, REF_COLUMN_NAME FROM TABLE(<IMMUTA_DB>.INFORMATION_SCHEMA.POLICY_REFERENCES(policy_name => '<IMMUTA_DB>."IMMUTA_POLICIES"."${policyName.replace(/'/g, `\'`)}"'));` });
                        -- If previous query returns a result set, then call next() to get the row.
                        -- This should only run once in most cases, but we loop in order to handle detaching the policy from cloned tables.
                        while (policyRecord.getRowCount() > 0 && policyRecord.next()) {
                            const qualifiedTableName = `"${policyRecord.getColumnValue('REF_DATABASE_NAME')}"."${policyRecord.getColumnValue('REF_SCHEMA_NAME')}"."${policyRecord.getColumnValue('REF_ENTITY_NAME')}"`;
                            const targetType = policyRecord.getColumnValue('REF_ENTITY_DOMAIN');
                            const referencedColumn = policyRecord.getColumnValue('REF_COLUMN_NAME');
                            -- If the object contains the qualified table name as a key, that means a policy is applied to multiple columns in the table. Therefore, just add the column to its list.
                            -- Else, create a new key with the qualified table name and add the target type and referenced column to the object.
                            if (tableMap[qualifiedTableName]) {
                                tableMap[qualifiedTableName]['columns'].push(referencedColumn);
                            } else {
                                tableMap[qualifiedTableName] = { targetType, columns: [referencedColumn] };
                            }
                        }
                    }
                    -- Loop over the object, create a column clause for each column in the table, and execute an alter query to unset the masking policies from the columns.
                    for (const table in tableMap) {
                        const columnClauses = tableMap[table]['columns'].map((column) => {
                            return `COLUMN "${column}" UNSET MASKING POLICY`;
                        });
                        snowflake.execute({ sqlText: `ALTER ${tableMap[table]['targetType']} ${table} MODIFY ${columnClauses.join(',')}` });
                    }
                    return true;
            $$;

--3. Define function to remove row access policies
CREATE OR REPLACE PROCEDURE <IMMUTA_DB>.IMMUTA_PROCEDURES.DETACH_ROW_ACCESS_POLICIES()
            RETURNS boolean
            LANGUAGE JAVASCRIPT
            EXECUTE AS CALLER
            AS
            $$
                    -- Get all row access policies in the Immuta database.
                    const rowAccessPolicies = snowflake.execute({
                        sqlText: `SHOW ROW ACCESS POLICIES IN <IMMUTA_DB>."IMMUTA_POLICIES"`
                    });
                    while (rowAccessPolicies.next())  {
                        const policyName = rowAccessPolicies.getColumnValue(2);
                        -- This query returns the concatenated qualified table name and target type for current policy.
                        -- Policy names may contain single quotes. So need to escape them at the end.
                        const policyRecord = snowflake.execute({ sqlText: `SELECT REF_DATABASE_NAME, REF_SCHEMA_NAME, REF_ENTITY_NAME, REF_ENTITY_DOMAIN FROM TABLE(<IMMUTA_DB>.INFORMATION_SCHEMA.POLICY_REFERENCES(policy_name => '<IMMUTA_DB>."IMMUTA_POLICIES"."${policyName.replace(/'/g, `\'`)}"'))` });
                        -- If previous query returns a result set, then call next() to get the row.
                        -- This should only run once in most cases, but we loop in order to handle detaching the policy from cloned tables.
                        while(policyRecord.getRowCount() > 0 && policyRecord.next()) {
                            const qualifiedTableName = `"${policyRecord.getColumnValue('REF_DATABASE_NAME')}"."${policyRecord.getColumnValue('REF_SCHEMA_NAME')}"."${policyRecord.getColumnValue('REF_ENTITY_NAME')}"`;
                            const targetType = policyRecord.getColumnValue('REF_ENTITY_DOMAIN');
                            try {
                                -- Drop row access policy references on a table or view.
                                snowflake.execute({ sqlText: `ALTER ${targetType} ${qualifiedTableName} DROP ROW ACCESS POLICY <IMMUTA_DB>.IMMUTA_POLICIES."${policyName}"` });
                            } catch (err) {
                                -- If we failed to remove the RLS that probably means there was no policy.
                            }
                        }
                    }
                    return true;
                $$;

--4. Call and invoke functions and cleanup Immuta policy objects (same as cleanup script in Immuta UI)
-- Execute procedures to clean up masking and row access policies.
--
EXECUTE IMMEDIATE
$$
DECLARE 
  CURRENT_ROLE VARCHAR;
BEGIN
    -- Grant Immuta system role to the current executing role to access the policy stored procedures.
    CURRENT_ROLE := '"' || (SELECT CURRENT_ROLE()) || '"';   
    GRANT ROLE <SYSTEM_ACOUNT_ROLE> TO ROLE IDENTIFIER(:CURRENT_ROLE);
    CALL <IMMUTA_DB>.IMMUTA_PROCEDURES.DETACH_MASKING_POLICIES();
    CALL <IMMUTA_DB>.IMMUTA_PROCEDURES.DETACH_ROW_ACCESS_POLICIES();
    RETURN TRUE;
END;
$$
;


--5. After both masking and row-access policies have been detach/disassocated from table/view objects, drop the immuta database which contains the secure views for data sources and workspaces
--
DROP DATABASE IF EXISTS "<IMMUTA_DB>";
--
-- Drop the immuta_system role used by the native snowflake integration
--
-- NOTE: If you changed the default role as part of a manual bootstrap process this
--       role may need to be manually set.
--
DROP ROLE IF EXISTS "<SYSTEM_ACCOUNT_ROLE>";
--
-- Drop the Immuta system account if it exists (may not exist if a manual account was provided).
-- In the event a manual account was provided (and granted the immuta_system role) that account
-- can potentially be removed as well.
--
DROP USER IF EXISTS "<SYSTEM_ACCOUNT>";

Last updated