Skip to content

Create a Service User

ALTR connects to Snowflake using a dedicated service user; creating it runs ALTR’s setup stored procedure as ACCOUNTADMIN, which creates the service user’s role and warehouse and grants the role the Snowflake privileges it needs to apply policy, classify data, and monitor activity — see Service User Privilege Requirements for the full list.

You can use ALTR’s default service user objects, or custom names to match your Snowflake naming conventions. Custom names require a different version of the setup stored procedure (below).

To create a service user with default object names:

  1. Run the following SQL to create the service user, ALTR_SERVICE_USER, with a TYPE of SERVICE. The setup stored procedure creates its default role (ALTR_SERVICE_ROLE) and warehouse (ALTR_SERVICE_WH) if they do not already exist.

    CREATE USER IF NOT EXISTS ALTR_SERVICE_USER TYPE = SERVICE;
  2. Create a database to house ALTR’s setup stored procedure. ALTR recommends PC_ALTR_DB.

    CREATE DATABASE IF NOT EXISTS PC_ALTR_DB;
  3. Run the following SQL in that database to create the SETUP_ALTR_SERVICE_ACCOUNT stored procedure.

    Show Setup Service User Stored Procedure (SQL)
    CREATE OR REPLACE PROCEDURE SETUP_ALTR_SERVICE_ACCOUNT(IS_PARTNER_CONNECT BOOLEAN)
    RETURNS STRING
    LANGUAGE JAVASCRIPT
    VOLATILE
    EXECUTE AS CALLER
    AS $$
    // ********************************************
    // Stored Procedure Caller Report
    // ********************************************
    function AltrServiceAccountException(message) {
    this.message = message;
    }
    function StoredProcedureReport() {
    this.isSuccess = true;
    this.successMessages = [];
    this.failMessages = [];
    this.unknownDBList = [];
    this.skippedDBList = [];
    this.fail = function(message) {
    this.isSuccess = false;
    this.failMessages.push(message);
    }
    this.success = function(message) {
    this.successMessages.push(message);
    }
    this.unknownDB = function(message) {
    this.unknownDBList.push(message);
    }
    this.skipped = function(message) {
    this.skippedDBList.push(message);
    }
    this.callerReport = function() {
    let report = '';
    function appendMessages(prefix, messages) {
    const totalPrefix = '\n[' + prefix + ']: ';
    for (const message of messages) {
    report += totalPrefix + message;
    }
    }
    if (this.isSuccess) {
    report = 'SUCCEEDED!';
    } else {
    report = 'FAILED!';
    appendMessages('FAILURE', this.failMessages);
    }
    appendMessages('SUCCESS', this.successMessages);
    appendMessages('SKIPPED', this.skippedDBList);
    appendMessages('UKNOWN OBJECT FOUND', this.unknownDBList);
    return report;
    }
    }
    var RUN_AS_ALTR = false;
    const storedProcedureReport = new StoredProcedureReport();
    function isNullOrEmpty(str) {
    return typeof('string') !== typeof(str) || str.length < 1;
    }
    function tryGetColumnValueAsString(resultSet, columnName) {
    try {
    return resultSet.getColumnValueAsString(columnName);
    } catch (err) {
    return null;
    }
    }
    function execQuery(queryString) {
    return snowflake.execute({sqlText:queryString});
    }
    function execStatement(queryString, handleError) {
    try {
    const resultSet = execQuery(queryString);
    storedProcedureReport.success(queryString);
    return true;
    } catch (error) {
    let reportFailure = true;
    if ('function' === typeof(handleError)) {
    try {
    reportFailure = handleError(queryString, error);
    } catch (error) {
    reportFailure = true;
    }
    }
    if (reportFailure) {
    storedProcedureReport.fail(queryString + ": " + error);
    }
    }
    return false;
    }
    function delimitIdentifier(identifier) {
    return '"' + identifier.replace(/\"/g, '""') + '"';
    }
    function getDelimitedComment(objectType) {
    return "'This " + objectType + " is used by ALTR to help simplify governance and control over data in Snowflake. Please do not modify without speaking with ALTR Support.'";
    }
    function isUknownDB(queryString, error) {
    const errorString = '' + error;
    let idx = -1;
    idx = errorString.indexOf('Database');
    if (idx !== -1) {
    if (errorString.includes(' does not exist or not authorized.')) {
    storedProcedureReport.unknownDB(errorString.substring(idx));
    return false;
    }
    }
    return true;
    }
    function permissionRole(delimitedRoleName) {
    let targetRoleExists = true;
    execStatement('GRANT CREATE DATABASE ON ACCOUNT TO ROLE ' + delimitedRoleName, function(queryString, error) {
    const errorString = '' + error;
    if (errorString.includes('Role ') && errorString.includes(' does not exist or not authorized.')) {
    targetRoleExists = false;
    storedProcedureReport.fail('You called this stored procedure with a role that does not exist.');
    if (RUN_AS_ALTR) {
    throw new AltrServiceAccountException(storedProcedureReport.callerReport());
    }
    return false;
    } else {
    return true;
    }
    });
    if (!targetRoleExists) return;
    execStatement('GRANT APPLY MASKING POLICY ON ACCOUNT TO ROLE ' + delimitedRoleName, function(queryString, error) {
    return !('' + error).includes('Unsupported feature');
    });
    execStatement('GRANT CREATE INTEGRATION ON ACCOUNT TO ROLE ' + delimitedRoleName);
    execStatement('GRANT APPLY TAG ON ACCOUNT TO ROLE ' + delimitedRoleName);
    execStatement('GRANT APPLY ROW ACCESS POLICY ON ACCOUNT TO ROLE ' + delimitedRoleName);
    const warehouseNames = [];
    let resultSet = execQuery('SHOW WAREHOUSES');
    while (resultSet.next()) {
    warehouseNames.push(resultSet.getColumnValueAsString('name'));
    }
    for (const warehouseName of warehouseNames) {
    execStatement('GRANT MONITOR ON WAREHOUSE ' + delimitIdentifier(warehouseName) + ' TO ROLE ' + delimitedRoleName);
    }
    const databaseNames = [];
    resultSet = execQuery('SHOW DATABASES');
    while (resultSet.next()) {
    const name = resultSet.getColumnValueAsString('name');
    const options = tryGetColumnValueAsString(resultSet, 'options');
    if (!isNullOrEmpty(options)) {
    const optionsUpper = options.toUpperCase();
    if (optionsUpper.includes('TRANSIENT') || optionsUpper.includes('TEMPORARY')) {
    storedProcedureReport.skipped('Database ' + name + ' because it has options: ' + options);
    continue;
    }
    }
    const kind = tryGetColumnValueAsString(resultSet, 'kind');
    if (!isNullOrEmpty(kind) && !kind.toUpperCase().includes('STANDARD')) {
    storedProcedureReport.skipped('Database ' + name + ' because it is type: ' + kind);
    continue;
    }
    const origin = tryGetColumnValueAsString(resultSet, 'origin');
    if (!isNullOrEmpty(origin)) {
    storedProcedureReport.skipped('Database ' + name + ' because it has origin: ' + origin);
    continue;
    }
    if (!isNullOrEmpty(name)) {
    databaseNames.push(name);
    } else {
    storedProcedureReport.skipped('Database has null or empty name');
    }
    }
    for (const databaseName of databaseNames) {
    const delimitedDatabaseName = delimitIdentifier(databaseName);
    let querySuccess = execStatement('GRANT USAGE ON DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    if (querySuccess === false)
    continue;
    execStatement('GRANT CREATE SCHEMA ON DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT USAGE ON FUTURE SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT USAGE ON ALL SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON FUTURE TABLES IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON ALL TABLES IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON FUTURE VIEWS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON ALL VIEWS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON FUTURE MATERIALIZED VIEWS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON ALL MATERIALIZED VIEWS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT CREATE TAG ON FUTURE SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT CREATE TAG ON ALL SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT CREATE ROW ACCESS POLICY ON FUTURE SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT CREATE ROW ACCESS POLICY ON ALL SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    }
    execStatement('CREATE DATABASE IF NOT EXISTS ALTR_DSAAS_DB');
    execStatement('GRANT ALL ON FUTURE SCHEMAS IN DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT ALL ON ALL SCHEMAS IN DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT ALL ON FUTURE FUNCTIONS IN DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT ALL ON ALL FUNCTIONS IN DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT ALL ON DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT MANAGE GRANTS ON ACCOUNT TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE TO ROLE ' + delimitedRoleName, isUknownDB);
    }
    function permissionUser(delimitedRoleName, delimitedUserName, delimitedWarehouseName) {
    execStatement('CREATE ROLE IF NOT EXISTS ' + delimitedRoleName);
    execStatement('ALTER ROLE ' + delimitedRoleName + ' SET COMMENT = ' + getDelimitedComment('role'));
    execStatement('GRANT ROLE ' + delimitedRoleName + ' TO ROLE SYSADMIN');
    execStatement('CREATE WAREHOUSE IF NOT EXISTS ' + delimitedWarehouseName
    + ' WITH WAREHOUSE_SIZE = XSMALL SCALING_POLICY = ECONOMY AUTO_RESUME = TRUE INITIALLY_SUSPENDED = TRUE');
    execStatement('ALTER WAREHOUSE ' + delimitedWarehouseName + ' SET AUTO_SUSPEND = 30 COMMENT = ' + getDelimitedComment('warehouse'));
    execStatement('GRANT USAGE ON WAREHOUSE ' + delimitedWarehouseName + ' TO ROLE ' + delimitedRoleName);
    execStatement('GRANT OWNERSHIP ON WAREHOUSE ' + delimitedWarehouseName + ' TO ROLE SYSADMIN COPY CURRENT GRANTS');
    permissionRole(delimitedRoleName);
    execStatement('GRANT ROLE ' + delimitedRoleName + ' TO USER ' + delimitedUserName);
    execStatement('ALTER USER ' + delimitedUserName + ' SET DEFAULT_ROLE = ' + delimitedRoleName);
    execStatement('ALTER USER ' + delimitedUserName + ' SET DEFAULT_WAREHOUSE = ' + delimitedWarehouseName);
    execStatement('ALTER USER ' + delimitedUserName + ' SET COMMENT = ' + getDelimitedComment('user'));
    execStatement('GRANT MODIFY PROGRAMMATIC AUTHENTICATION METHODS ON USER ' + delimitedUserName + ' TO USER ' + delimitedUserName);
    }
    try {
    execQuery("USE ROLE ACCOUNTADMIN");
    } catch (error) {
    storedProcedureReport.fail('You must call this stored procedure with a user and role that can assume role ACCOUNTADMIN.');
    return storedProcedureReport.callerReport();
    }
    if (IS_PARTNER_CONNECT) {
    execStatement('ALTER WAREHOUSE PC_ALTR_WH SET AUTO_SUSPEND = 30');
    permissionRole('"PC_ALTR_ROLE"');
    } else {
    permissionUser('"ALTR_SERVICE_ROLE"', '"ALTR_SERVICE_USER"', '"ALTR_SERVICE_WH"');
    }
    return storedProcedureReport.callerReport();
    $$;
  4. Run the stored procedure. This can take several minutes on Snowflake accounts with many databases, schemas, and tables.

    CALL SETUP_ALTR_SERVICE_ACCOUNT(FALSE);

    This creates the ALTR_SERVICE_ROLE role and ALTR_SERVICE_WH warehouse (if they don’t already exist), grants the service user’s role the account, warehouse, and database privileges ALTR needs, and assigns the role to the service user.

Use a custom service user, role, or warehouse name to match your Snowflake naming conventions.

To create a service user with custom object names:

  1. Create a service user.

  2. (Optional) Create a role. The stored procedure creates the role if it doesn’t already exist — pre-create it only if you need specific role settings.

  3. (Optional) Create a warehouse. The stored procedure creates the warehouse if it doesn’t already exist — pre-create it only if you need specific warehouse settings.

  4. Determine which databases the role should access, for classification and column/tag connections, and build that list for the stored procedure’s DATABASE_NAMES array below. Leave the list blank to grant access to every database in the account.

  5. Run the following SQL to create the SETUP_ALTR_SERVICE_ACCOUNT stored procedure, which accepts the custom object names as parameters.

    Show Setup Service User Stored Procedure (custom object names) (SQL)
    CREATE OR REPLACE PROCEDURE SETUP_ALTR_SERVICE_ACCOUNT(
    SERVICE_USER STRING DEFAULT 'ALTR_SERVICE_USER',
    DATABASE_NAMES ARRAY DEFAULT [],
    SERVICE_ROLE STRING DEFAULT NULL,
    SERVICE_WAREHOUSE STRING DEFAULT NULL
    ) RETURNS STRING
    LANGUAGE JAVASCRIPT
    VOLATILE
    EXECUTE AS CALLER
    AS $$
    function AltrServiceAccountException(message) {
    this.message = message;
    }
    function StoredProcedureReport() {
    this.currentRole = "";
    this.isSuccess = true;
    this.successMessages = [];
    this.failMessages = [];
    this.unknownDBList = [];
    this.skippedDBList = [];
    this.fail = function(message) {
    this.isSuccess = false;
    this.failMessages.push(message);
    }
    this.success = function(message) {
    this.successMessages.push(message);
    }
    this.unknownDB = function(message) {
    this.unknownDBList.push(message);
    }
    this.skipped = function(message) {
    this.skippedDBList.push(message);
    }
    this.callerReport = function() {
    let report = 'ROLE EXECUTED AS: ' + this.currentRole + "\n\n";
    function appendMessages(prefix, messages) {
    const totalPrefix = '\n[' + prefix + ']: ';
    for (const message of messages) {
    report += totalPrefix + message;
    }
    }
    if (this.isSuccess) {
    report += 'SUCCEEDED!';
    } else {
    report += 'FAILED!';
    appendMessages('FAILURE', this.failMessages);
    }
    appendMessages('SUCCESS', this.successMessages);
    appendMessages('SKIPPED', this.skippedDBList);
    appendMessages('UNKNOWN OBJECT FOUND', this.unknownDBList);
    return report;
    }
    }
    var RUN_AS_ALTR = false;
    const storedProcedureReport = new StoredProcedureReport();
    function isNullOrEmpty(str) {
    return typeof('string') !== typeof(str) || str.length < 1;
    }
    function tryGetColumnValueAsString(resultSet, columnName) {
    try {
    return resultSet.getColumnValueAsString(columnName);
    } catch (err) {
    return null;
    }
    }
    function execQuery(queryString) {
    return snowflake.execute({sqlText:queryString});
    }
    function execStatement(queryString, handleError) {
    try {
    const resultSet = execQuery(queryString);
    storedProcedureReport.success(queryString);
    return true;
    } catch (error) {
    let reportFailure = true;
    if ('function' === typeof(handleError)) {
    try {
    reportFailure = handleError(queryString, error);
    } catch (error) {
    reportFailure = true;
    }
    }
    if (reportFailure) {
    storedProcedureReport.fail(queryString + ": " + error);
    }
    }
    return false;
    }
    function delimitIdentifier(identifier) {
    return '"' + identifier.replace(/\"/g, '""') + '"';
    }
    function getDelimitedComment(objectType) {
    return "'This " + objectType + " is used by ALTR to help simplify governance and control over data in Snowflake. Please do not modify without speaking with ALTR Support.'";
    }
    function isUknownDB(queryString, error) {
    const errorString = '' + error;
    let idx = -1;
    idx = errorString.indexOf('Database');
    if (idx !== -1) {
    if (errorString.includes(' does not exist or not authorized.')) {
    storedProcedureReport.unknownDB(errorString.substring(idx));
    return false;
    }
    }
    return true;
    }
    function isDatabaseInList(databaseName) {
    return DATABASE_NAMES.find(function(element) { return element === databaseName });
    }
    function permissionRole(delimitedRoleName) {
    let targetRoleExists = true;
    execStatement('GRANT CREATE DATABASE ON ACCOUNT TO ROLE ' + delimitedRoleName, function(queryString, error) {
    const errorString = '' + error;
    if (errorString.includes('Role ') && errorString.includes(' does not exist or not authorized.')) {
    targetRoleExists = false;
    storedProcedureReport.fail('You called this stored procedure with a role that does not exist.');
    if (RUN_AS_ALTR) {
    throw new AltrServiceAccountException(storedProcedureReport.callerReport());
    }
    return false;
    } else {
    return true;
    }
    });
    if (!targetRoleExists) return;
    execStatement('GRANT APPLY MASKING POLICY ON ACCOUNT TO ROLE ' + delimitedRoleName, function(queryString, error) {
    return !('' + error).includes('Unsupported feature');
    });
    execStatement('GRANT CREATE INTEGRATION ON ACCOUNT TO ROLE ' + delimitedRoleName);
    execStatement('GRANT APPLY TAG ON ACCOUNT TO ROLE ' + delimitedRoleName);
    execStatement('GRANT APPLY ROW ACCESS POLICY ON ACCOUNT TO ROLE ' + delimitedRoleName);
    const warehouseNames = [];
    let resultSet = execQuery('SHOW WAREHOUSES');
    while (resultSet.next()) {
    warehouseNames.push(resultSet.getColumnValueAsString('name'));
    }
    for (const warehouseName of warehouseNames) {
    execStatement('GRANT MONITOR ON WAREHOUSE ' + delimitIdentifier(warehouseName) + ' TO ROLE ' + delimitedRoleName);
    }
    const databaseNames = [];
    resultSet = execQuery('SHOW DATABASES');
    while (resultSet.next()) {
    const name = resultSet.getColumnValueAsString('name');
    if (DATABASE_NAMES.length !== 0) {
    const database = DATABASE_NAMES.find(function(element) { return element === name });
    if (!database) {
    continue;
    }
    }
    const options = tryGetColumnValueAsString(resultSet, 'options');
    if (!isNullOrEmpty(options)) {
    const optionsUpper = options.toUpperCase();
    if (optionsUpper.includes('TRANSIENT') || optionsUpper.includes('TEMPORARY')) {
    storedProcedureReport.skipped('Database ' + name + ' because it has options: ' + options);
    continue;
    }
    }
    const kind = tryGetColumnValueAsString(resultSet, 'kind');
    if (!isNullOrEmpty(kind) && !kind.toUpperCase().includes('STANDARD')) {
    storedProcedureReport.skipped('Database ' + name + ' because it is type: ' + kind);
    continue;
    }
    const origin = tryGetColumnValueAsString(resultSet, 'origin');
    if (!isNullOrEmpty(origin)) {
    storedProcedureReport.skipped('Database ' + name + ' because it has origin: ' + origin);
    continue;
    }
    if (!isNullOrEmpty(name)) {
    databaseNames.push(name);
    } else {
    storedProcedureReport.skipped('Database has null or empty name');
    }
    }
    for (const databaseName of databaseNames) {
    const delimitedDatabaseName = delimitIdentifier(databaseName);
    let querySuccess = execStatement('GRANT USAGE ON DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    if (querySuccess === false)
    continue;
    execStatement('GRANT CREATE SCHEMA ON DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT USAGE ON FUTURE SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT USAGE ON ALL SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON FUTURE TABLES IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON ALL TABLES IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON FUTURE VIEWS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON ALL VIEWS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON FUTURE MATERIALIZED VIEWS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT SELECT ON ALL MATERIALIZED VIEWS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT CREATE TAG ON FUTURE SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT CREATE TAG ON ALL SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT CREATE ROW ACCESS POLICY ON FUTURE SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT CREATE ROW ACCESS POLICY ON ALL SCHEMAS IN DATABASE ' + delimitedDatabaseName + ' TO ROLE ' + delimitedRoleName, isUknownDB);
    }
    execStatement('GRANT ALL ON FUTURE SCHEMAS IN DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT ALL ON ALL SCHEMAS IN DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT ALL ON FUTURE FUNCTIONS IN DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT ALL ON ALL FUNCTIONS IN DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT ALL ON DATABASE ALTR_DSAAS_DB TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT MANAGE GRANTS ON ACCOUNT TO ROLE ' + delimitedRoleName, isUknownDB);
    execStatement('GRANT IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE TO ROLE ' + delimitedRoleName, isUknownDB);
    }
    function permissionUser(delimitedRoleName, delimitedUserName, delimitedWarehouseName) {
    execStatement('CREATE ROLE IF NOT EXISTS ' + delimitedRoleName);
    execStatement('ALTER ROLE ' + delimitedRoleName + ' SET COMMENT = ' + getDelimitedComment('role'));
    execStatement('GRANT ROLE ' + delimitedRoleName + ' TO ROLE SYSADMIN');
    execStatement('CREATE WAREHOUSE IF NOT EXISTS ' + delimitedWarehouseName
    + ' WITH WAREHOUSE_SIZE = XSMALL SCALING_POLICY = ECONOMY AUTO_RESUME = TRUE INITIALLY_SUSPENDED = TRUE');
    execStatement('ALTER WAREHOUSE ' + delimitedWarehouseName + ' SET AUTO_SUSPEND = 30 COMMENT = ' + getDelimitedComment('warehouse'));
    execStatement('GRANT USAGE ON WAREHOUSE ' + delimitedWarehouseName + ' TO ROLE ' + delimitedRoleName);
    execStatement('GRANT OWNERSHIP ON WAREHOUSE ' + delimitedWarehouseName + ' TO ROLE SYSADMIN COPY CURRENT GRANTS');
    permissionRole(delimitedRoleName);
    execStatement('GRANT ROLE ' + delimitedRoleName + ' TO USER ' + delimitedUserName);
    execStatement('ALTER USER ' + delimitedUserName + ' SET DEFAULT_ROLE = ' + delimitedRoleName);
    execStatement('ALTER USER ' + delimitedUserName + ' SET DEFAULT_WAREHOUSE = ' + delimitedWarehouseName);
    execStatement('ALTER USER ' + delimitedUserName + ' SET COMMENT = ' + getDelimitedComment('user'));
    execStatement('GRANT MODIFY PROGRAMMATIC AUTHENTICATION METHODS ON USER ' + delimitedUserName + ' TO USER ' + delimitedUserName);
    }
    let paramsResultSet = execQuery("SHOW PARAMETERS LIKE '%QUERY_TAG%' IN SESSION");
    while (paramsResultSet.next()) {
    let queryTagStr = '';
    queryTagStr = paramsResultSet.getColumnValueAsString('value');
    if (!isNullOrEmpty(queryTagStr) && queryTagStr === "ALTR") {
    RUN_AS_ALTR = true;
    break;
    }
    }
    let currentRoles = [];
    let resultSet = execQuery("SELECT CURRENT_ROLE() AS CURRENT_ROLE;");
    while (resultSet.next()) {
    currentRoles.push(tryGetColumnValueAsString(resultSet, "CURRENT_ROLE"));
    }
    storedProcedureReport.currentRole = currentRoles[0];
    try {
    execQuery("USE ROLE ACCOUNTADMIN");
    } catch (error) {
    storedProcedureReport.fail('You must call this stored procedure with a user and role that can assume role ACCOUNTADMIN.');
    return storedProcedureReport.callerReport();
    }
    execStatement('CREATE DATABASE IF NOT EXISTS ALTR_DSAAS_DB');
    if (SERVICE_USER === "PC_ALTR_USER") {
    let pcRole = !SERVICE_ROLE ? '"PC_ALTR_ROLE"' : SERVICE_ROLE;
    let pcWarehouse = !SERVICE_WAREHOUSE ? '"PC_ALTR_WH"' : SERVICE_WAREHOUSE;
    execStatement('ALTER WAREHOUSE ' + pcWarehouse +' SET AUTO_SUSPEND = 30');
    permissionRole(pcRole);
    } else {
    let serviceUser = !SERVICE_USER ? '"ALTR_SERVICE_USER"' : SERVICE_USER;
    let serviceRole = !SERVICE_ROLE ? '"ALTR_SERVICE_ROLE"' : SERVICE_ROLE;
    let serviceWarehouse = !SERVICE_WAREHOUSE ? '"ALTR_SERVICE_WH"' : SERVICE_WAREHOUSE;
    permissionUser(serviceRole, serviceUser, serviceWarehouse);
    }
    return storedProcedureReport.callerReport();
    $$;
  6. Run the stored procedure, setting the parameters you created above. Add single quotes around string parameters. This can take several minutes; limiting DATABASE_NAMES reduces run time.

    CALL SETUP_ALTR_SERVICE_ACCOUNT('<SERVICE_USER>',['<DATABASE_NAME1>','<DATABASE_NAME2>'],'<SERVICE_ROLE>','<SERVICE_WAREHOUSE>');

Run the Setup Stored Procedure on a Schedule

Section titled “Run the Setup Stored Procedure on a Schedule”

Run SETUP_ALTR_SERVICE_ACCOUNT again any time you add a database or warehouse in Snowflake, so ALTR recognizes and can apply policy to the new object. ALTR recommends running the stored procedure as a scheduled task — for example, nightly — rather than relying on manually re-running it.

To set up a scheduled task, update the following in the SQL below:

  • <YOUR_WAREHOUSE>, <PROCEDURE_DB>, and <PROCEDURE_SCHEMA> to your respective values. You can identify the database and schema where SETUP_ALTR_SERVICE_ACCOUNT is stored by running SHOW PROCEDURES; and reading the catalog_name and schema_name columns for the SETUP_ALTR_SERVICE_ACCOUNT row.
  • SCHEDULE if you want the task to run at a time other than daily at midnight UTC.
  • <IS_PARTNER_CONNECT_BOOLEAN> depending on your service user: TRUE if you used PC_ALTR_USER/PC_ALTR_ROLE (Snowflake Marketplace), FALSE if you used ALTR_SERVICE_USER/ALTR_SERVICE_ROLE (default manual setup).

This scheduled-task SQL grants and calls the Boolean-signature procedure (SETUP_ALTR_SERVICE_ACCOUNT(BOOLEAN)) used by the default manual and Snowflake Marketplace paths. If you used the Custom Object Names procedure instead, its signature takes named parameters, not a Boolean — adjust the GRANT USAGE ON PROCEDURE and CALL statements below to match the four-argument signature shown in Custom Object Names above, rather than using the Boolean form.

Show Scheduled Task Setup (SQL)
--PREREQUISITE: SETUP_ALTR_SERVICE_ACCOUNT must already exist as a stored procedure in your environment.
CREATE DATABASE SASA_TASK_DB;
CREATE SCHEMA SASA_TASK_SCHEMA;
USE SCHEMA SASA_TASK_DB.SASA_TASK_SCHEMA;
--It is not required to make a new database and schema. However, if you use an existing database and/or schema,
--make sure to replace all instances of SASA_TASK_DB and SASA_TASK_SCHEMA in this script with your desired values.
USE ROLE securityadmin;
CREATE ROLE sasataskrole;
CREATE ROLE taskadmin;
--To ensure your user has access to sasataskrole, or whichever role you use in its place,
--go to Admin > Users & Roles, or run: GRANT ROLE SASATASKROLE TO USER <YOUR_USER>;
USE ROLE accountadmin;
GRANT EXECUTE TASK, EXECUTE MANAGED TASK ON ACCOUNT TO ROLE taskadmin;
USE ROLE securityadmin;
GRANT ROLE taskadmin TO ROLE sasataskrole;
GRANT USAGE ON DATABASE SASA_TASK_DB TO ROLE sasataskrole;
GRANT ALL ON SCHEMA SASA_TASK_DB.SASA_TASK_SCHEMA TO ROLE sasataskrole;
GRANT USAGE ON DATABASE <PROCEDURE_DB> TO ROLE sasataskrole;
GRANT USAGE ON SCHEMA <PROCEDURE_DB>.<PROCEDURE_SCHEMA> TO ROLE sasataskrole;
GRANT USAGE ON PROCEDURE <PROCEDURE_DB>.<PROCEDURE_SCHEMA>.SETUP_ALTR_SERVICE_ACCOUNT(BOOLEAN) TO ROLE sasataskrole;
USE ROLE sasataskrole;
CREATE TASK SASA_TASK_DB.SASA_TASK_SCHEMA.SASA_TASK
WAREHOUSE = <YOUR_WAREHOUSE> --replace with your desired warehouse
SCHEDULE = 'USING CRON 0 0 * * * UTC' --currently set up to run daily at midnight UTC
--see Snowflake's task scheduling documentation for CRON syntax
AS
CALL <PROCEDURE_DB>.<PROCEDURE_SCHEMA>.SETUP_ALTR_SERVICE_ACCOUNT(<IS_PARTNER_CONNECT_BOOLEAN>);
ALTER TASK SASA_TASK_DB.SASA_TASK_SCHEMA.SASA_TASK RESUME; --all tasks are suspended immediately after creation;
--this command resumes the task so it runs at the specified schedule.
EXECUTE TASK SASA_TASK_DB.SASA_TASK_SCHEMA.SASA_TASK; --test the task to confirm it was set up correctly