Skip to content

NickAkincilar/Snowflake_SelfService_Sandbox_Config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 

Repository files navigation

Snowflake SelfService Sandbox Configuration.

alt text

Following Script creates a Self service Sandbox enviroment within Snowflake where users gain access to unique schemas where they have full rights to upload, create, modify & delete their data objects but restricted in terms of sharing their content with others. Part of this setup is also to grant them read-only access to other governed datasets within the account for them to join their own data.

-- ******* 2/16/2022 by Nick Akincilar ****
-- SCRIPT TO CREATE A SELF SERVICE ENVIRONMENT FOR USERS
-- THIS SCRIPT CREATES THE FOLLOWING:
--
-- 1. A dedicated virtual warehouse : SELF_SERVICE_WH 
-- 2. Monthly cost monitoring for the SELF_SERVICE_WH Warehouse
-- 3. A database named SANDBOX_DB to be shared by all self-service users
-- 4. STORED PROCS: 
--    * - ADD_SANDBOX_USER(user_name varchar)               --> Enrolls an existing user to sandbox environment
--    * - ADD_SANDBOX_ROLE(new_role varchar, uid varchar)   --> Created a role per each user to limit them to single schema 
-- 5. Generic role (SANDBOX_ROLE) which will be used to grant read rights to governed datasets. 
-- 
-- NOTES: 
-- Sandbox environment is a separate database using managed schemas per user. 
-- Each user gets access to their own schema where they will have full rights to add/create/drop tables but they won't be
-- able to grant rights to other users to their content. Any grants to content created in these schemas will have to be done by
-- a higher level admin. 
-- For managing & removing unused user created content, tables & views that have not been accessed in the last X days can be 
-- identified via SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY (Enterprise edition & above) and be automatically removed.
-- 

-- ************ RUN THIS TO CREATE THE FRAMEWORK *******

USE ROLE SYSADMIN;

CREATE OR REPLACE DATABASE SANDBOX_DB;

-- CREATE A DEDICATED COMPUTE CLUSTER FOR SELF-SERVICE SANDBOX.

CREATE OR REPLACE WAREHOUSE SELF_SERVICE_WH 
WITH 
  WAREHOUSE_SIZE = 'SMALL' 
  WAREHOUSE_TYPE = 'STANDARD' 
  AUTO_SUSPEND = 600 
  AUTO_RESUME = TRUE 
  MIN_CLUSTER_COUNT = 1 
  MAX_CLUSTER_COUNT = 3 
  SCALING_POLICY = 'STANDARD';



-- CREATE A CREDIT MONITOR FOR VIRTUAL WAREHOUSE (500 credits per month)

USE ROLE ACCOUNTADMIN;

CREATE OR REPLACE RESOURCE MONITOR "SANDBOX_COST_MONITOR" WITH CREDIT_QUOTA = 500 
 TRIGGERS 
 ON 100 PERCENT DO SUSPEND 
 ON 75 PERCENT DO NOTIFY;
 
ALTER WAREHOUSE SELF_SERVICE_WH SET RESOURCE_MONITOR = "SANDBOX_COST_MONITOR";


USE ROLE SECURITYADMIN;
CREATE OR REPLACE ROLE SANDBOX_ROLE;
GRANT USAGE ON DATABASE SANDBOX_DB TO ROLE SANDBOX_ROLE;


USE ROLE SYSADMIN;

GRANT USAGE ON WAREHOUSE SELF_SERVICE_WH TO ROLE SANDBOX_ROLE;
GRANT USAGE  ON DATABASE SANDBOX_DB TO ROLE SECURITYADMIN;
GRANT ALL  ON WAREHOUSE SELF_SERVICE_WH TO ROLE SECURITYADMIN;
GRANT ALL ON SCHEMA SANDBOX_DB.PUBLIC TO ROLE SECURITYADMIN;



USE ROLE SECURITYADMIN;
USE SCHEMA SANDBOX_DB.PUBLIC;
  
  create or replace procedure ADD_SANDBOX_ROLE(new_role varchar, uid varchar)
  returns varchar
  language sql
  as
  $$

    begin

      LET nuid string := '"' || :uid || '"';
      LET UC integer := 1;
     
      
      if (UC > 0) then
        CREATE OR REPLACE ROLE identifier(:new_role);
        GRANT ROLE identifier(:new_role) TO USER identifier(:nuid) ;   
        GRANT ROLE "SANDBOX_ROLE" TO ROLE identifier(:new_role) ;
        return :new_role || ' role is created';
      else
        return 'No user found';
      end if;  
    end;
  $$  ;
  
  
GRANT USAGE ON PROCEDURE IDENTIFIER('SANDBOX_DB.PUBLIC.ADD_SANDBOX_ROLE') (VARCHAR, VARCHAR) TO ROLE IDENTIFIER('SYSADMIN');



USE ROLE SYSADMIN;
USE SCHEMA SANDBOX_DB.PUBLIC;

create or replace procedure ADD_SANDBOX_USER(user_name varchar)
  returns varchar
  language sql
  as
  $$

    begin
      LET uid := :user_name;
      user_name :=  REPLACE(:user_name, '@', '_');
      user_name :=  REPLACE(:user_name, '.', '_');
      user_name :=  REPLACE(:user_name, '#', '_');    
 
      LET new_schema := 'SANDBOX_DB.' || user_name || '_dev'; 
      LET new_role := 'SANDBOX_' || user_name;

      CREATE OR REPLACE SCHEMA identifier(:new_schema)  WITH MANAGED ACCESS;
      
      call ADD_SANDBOX_ROLE(:new_role, :uid);
      
      GRANT USAGE ON DATABASE SANDBOX_DB TO ROLE identifier(:new_role);
      GRANT ALL ON SCHEMA identifier(:new_schema) TO ROLE identifier(:new_role);
      GRANT USAGE ON WAREHOUSE SELF_SERVICE_WH TO ROLE identifier(:new_role);
      
      return :new_schema || ' sandbox is created';
    end;
  $$  
  ;
  
  
 
 
 -- ************ ONCE THE FRAMEWORK IS CONFIGURED, USE THE FOLLOWING TO ENROLL USERS & REMOVE UNUSED DATA *******
 
 --- EXECUTE FOLLOWING TO ENROLL A USER INTO THE SANDBOX ENVIRONMENT ( Creates a dedicated schema & a role for this user) 
 --- USER ID IS CASE SENSITIVE
 
USE ROLE SYSADMIN;
USE SCHEMA SANDBOX_DB.PUBLIC;

call ADD_SANDBOX_USER('JOHN');


-- Following Query allows to identify tables & views that have not been accessed in the last 90 days and creates the neessary 
-- SQL commands to drop them in order to remove unused content from the sandbox environment
-- This requires Enterprise edition or higher

use role accountadmin;
use schema snowflake.account_usage;

-- Tables not being queries in last 90
with ObjectsCurrent as
(
select 

  f1.value:"objectName"::string as BaseObjectName,
  f1.value:"objectDomain"::string as BaseObjectType,
  f2.value:"objectName"::string as ObjectName,
  f2.value:"objectDomain"::string as ObjectType  

from access_history
, lateral flatten(base_objects_accessed) f1
,lateral flatten(direct_objects_accessed) f2  

where
(
 f1.value:"objectDomain"::string='Table'
OR 
f2.value:"objectDomain"::string='View'
 )
 
and query_start_time >= dateadd('day', -90, current_timestamp())
group by 1,2,3,4),

ObjectsOld as
(
select 

  f1.value:"objectName"::string as BaseObjectName,
  f1.value:"objectDomain"::string as BaseObjectType,
  f2.value:"objectName"::string as ObjectName,
  f2.value:"objectDomain"::string as ObjectType

from access_history
, lateral flatten(base_objects_accessed) f1
,lateral flatten(direct_objects_accessed) f2  
where  
(
 f1.value:"objectDomain"::string='Table'
OR 
f2.value:"objectDomain"::string='View'
  
 )

and query_start_time <= dateadd('day', -90, current_timestamp())
group by 1,2,3,4)

SELECT OBJECTNAME as Name, OBJECTTYPE, 'DROP ' || OBJECTTYPE || ' ' || OBJECTNAME || ';' as SQL FROM ObjectsOld WHERE 
BaseObjectName NOT IN (SELECT BaseObjectName FROM ObjectsCurrent)
AND BaseObjectName LIKE 'SANDBOX_DB.%'
AND OBJECTTYPE = 'View'

UNION

SELECT BaseObjectName as Name, OBJECTTYPE , 'DROP ' || OBJECTTYPE || ' ' || BaseObjectName || ';' as SQL FROM ObjectsOld WHERE 
BaseObjectName NOT IN (SELECT BaseObjectName FROM ObjectsCurrent)
AND BaseObjectName LIKE 'SANDBOX_DB.%'
AND OBJECTTYPE = 'Table'

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors