As a data engineer who’s spent the last few years implementing enterprise-scale security in Snowflake, I’ve witnessed firsthand how challenging it can be to architect a robust security framework that balances protection with accessibility. Whether you’re dealing with regulatory compliance requirements, implementing multi-tenant data solutions, or simply trying to ensure proper data access controls, the journey can seem daunting at first.
I remember sitting in a meeting where our CISO (Chief Information Security Officer) asked for row-level security implementation across our data warehouse, while simultaneously requiring dynamic data masking for PII, and maintaining high-performance query execution. The wealth of Snowflake security features available was both a blessing and a challenge — having so many options meant we could solve any security requirement, but it also meant we needed to carefully architect our solution.
In this practical guide, we’ll explore techniques to help you secure your use of Snowflake:
- Foundational Security Setup
- Secure Views and Their Critical Role
- Row-Level Security Implementation Methods
- Dynamic Data Masking Strategies
- Encryption and Data Protection
- Best Practices and Common Pitfalls
I’ll walk you through battle-tested approaches to implementing Snowflake security features, sharing real implementation patterns that worked (and some that didn’t) in production environments. We’ll focus on concrete examples and scenarios you’re likely to encounter in your day-to-day work as a data engineer.
Foundational Security Setup: Setting up access to your Snowflake database
Before diving into advanced security features, let’s establish a solid foundation. Proper user and role management is crucial for maintaining security at scale.
In Snowflake, all access control is managed through roles, not direct user permissions. Users cannot log in directly to database objects – they must be assigned to roles that have been granted the necessary privileges. Here’s how to structure your roles effectively:
Step 1, Create standard roles:
1 2 3 4 5 6 |
-- Create functional roles CREATE ROLE data_scientist; CREATE ROLE analyst; CREATE ROLE data_engineer; |
Step 2, Give standard rights to your roles:
1 2 3 4 |
-- Grant access to specific schemas GRANT USAGE ON SCHEMA analytics.public TO ROLE analyst; GRANT SELECT ON ALL TABLES IN SCHEMA analytics.public TO ROLE analyst; |
All database object access should be granted to these functional roles, which can then be assigned to users. This approach allows you to version control your security model and maintain consistency across environments. The user-to-role assignments can be managed separately for each environment:
Step 3, Put users into these roles:
1 2 3 4 5 |
-- Environment-specific user management --(separate script per environment) CREATE USER bob_smith; GRANT ROLE analyst TO USER bob_smith; |
Pro Tip: When testing security implementations, always use roles instead of specific users. This allows your security patterns to remain consistent across environments. For example, instead of testing with “bob@company.com
“, use a role like “ACCOUNTING_ANALYST
” that can be assigned to different users in different environments.
Secure Views: Your First Line of Defense
Before we dive into specific security implementations, let’s understand Secure Views – a useful, fundamental building block of building a secure Snowflake environment. Secure views differ from regular views in several crucial ways:
- They hide the underlying query logic from users who only have access to the view
- They prevent unauthorized access to the source data, unlike regular views where users might need permissions on underlying tables
- They help optimize security policy enforcement by hiding implementation details you may not want to share
Example of a basic secure view
1 2 3 4 5 6 7 8 |
CREATE OR REPLACE SECURE VIEW vw_customer_basic AS SELECT customer_id, customer_name, city, country FROM customer_data; |
This view will give the user access to only these columns of the customer_data
table. And you can specify a WHERE
clause to partition the data to only a subset of the rows too. Most any SQL Query can be used in a view.
For a normal view, users that can query the view, can also use the DESCRIBE
view to see the underlying query. For a secure view, they cannot get the definition and will get this error when trying to view the definition:
1 |
DESCRIBE VIEW vw_customer_basic; |
Executing this statement will result in an error such as:
Error: Insufficient privileges to operate on view 'VW_CUSTOMER_BASIC'
The SECURE
keyword is crucial here when you wish to keep the implementation details hidden from the typical user.
Row-Level Security (RLS): Multiple Approaches
Row-Level Security allows you to control which rows each user can see in a table based on their role or other attributes. This is crucial for implementing data segregation in multi-tenant environments or enforcing data access boundaries between departments.
Approach 1: View-Based RLS
View-based RLS is a security pattern that restricts which rows a user can see in a table based on their assigned role. Let’s break down the implementation step by step with examples and explain why each component is important.
This is the traditional approach using secure views and mapping tables. First, let’s create sample data. This table contains sales data from different regions. Without RLS, any user with SELECT permissions could see all records.
1 2 3 4 5 6 |
CREATE TABLE sales_data ( sale_id INTEGER, region STRING, amount FLOAT, customer_email STRING ); |
Next, I will create a role-based mapping table. This table will map roles that can see each of the different region’s data.
1 2 3 4 5 6 7 8 9 |
CREATE TABLE role_region_mapping ( role_name STRING, region STRING ); -- Define access rules INSERT INTO role_region_mapping VALUES ('NORTH_AMERICA_SALES', 'NA'), ('EUROPE_SALES', 'EU'); |
Why role-based instead of user-based?
- Better scalability – you don’t need to update mappings when employees join/leave
- Easier maintenance – roles typically change less frequently than user assignments
- Consistent across environments – same roles work in dev/test/prod
While it is better to user roles than users to make management easier, but the technique can be used with many attributes of a user if necessary.
Next I will create the view for the users to use instead of the sales_data
table.
1 2 3 4 5 6 7 8 9 10 |
-- Create the secure view with RLS CREATE OR REPLACE SECURE VIEW vw_sales_data AS SELECT s.* FROM sales_data s WHERE s.region IN ( SELECT region FROM role_region_mapping WHERE role_name = CURRENT_ROLE() ); |
Finally, you need to grant the users that will view the data, rights to use the vw_sales_data
view, but not the sales_data
table. This leaves their only view of the data to be what they can see in the view.
Important considerations for CURRENT_ROLE()
:
- A user can be a member of multiple roles but can only have one active role at a time
- Users can switch their active role using
USE ROLE role_name
- For users needing access to multiple regions, consider creating composite roles or using RBAC hierarchies
- System administrators (
SYSADMIN
role) typically have access to all data unless explicitly restricted. However, it is something you need to be careful with, because if their defaultROLE
isSYSADMIN
, no data would be returned from theSECURE
VIEW
as written.
Let’s break down how this works:
- The
SECURE
keyword prevents users from seeing the view definition CURRENT_ROLE()
returns the current user’s active role- The subquery
SELECT region FROM role_region_mapping WHERE role_name = CURRENT_ROLE()
gets the allowed regions for the current role - The
WHERE s.region IN (...)
clause filters the data to only show rows from allowed regions
Approach 2: Row Access Policy
Row Access Policy is Snowflake’s native method for implementing row-level security directly at the table level, without requiring secure views. It’s more maintainable and can be reused across multiple tables.
Note: It does tend to be so hidden that it can be a bit confusing to users who think they are querying a full table but are being filtered in a hidden policy.
Here is the code you could use to duplicate the sort of filtering that we implemented in the View Based RLS section that preceded this one:
Create a row access policy
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CREATE OR REPLACE ROW ACCESS POLICY sales_region_policy AS (region STRING) RETURNS BOOLEAN -> EXISTS ( SELECT 1 FROM role_region_mapping WHERE role_name = CURRENT_ROLE() AND region = sales_data.region ); -- Apply the policy to the table ALTER TABLE sales_data ADD ROW ACCESS POLICY sales_region_policy ON (region); |
Let’s break down this policy:
AS (region STRING)
– Specifies the input parameter and its type. This must match the column type in your tableRETURNS BOOLEAN
– The policy must return true/false to determine if a row should be visibleEXISTS (...)
– Checks if there’s a mapping between the current role and the regionsales_data.region
– References the region column from the table where the policy is applied
Key differences between approaches:
- View-based RLS offers more flexibility for complex logic and can combine multiple security features
- Row Access Policy is easier to maintain and apply consistently
- Row Access Policies can be applied to views as well as tables
- Row Access Policies don’t hide table structure or prevent schema modifications
- Views allow you to implement custom aggregations and transformations alongside security
Dynamic Data Masking: Advanced Data Protection for Sensitive Information
This powerful Snowflake security feature is essential for implementing data privacy controls – imagine you’re working with sensitive customer data, and different teams need varying levels of access to the same information.
Some need to see full credit card numbers, others just the last four digits, and some shouldn’t see them at all. Here’s a real example from when I implemented PII protection for our customer service application:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
-- Create a sophisticated masking policy CREATE OR REPLACE MASKING POLICY credit_card_mask AS (val STRING) RETURNS STRING -> CASE WHEN CURRENT_ROLE() = 'FINANCIAL_ADMIN' THEN val WHEN CURRENT_ROLE() = 'CUSTOMER_SERVICE' THEN CONCAT('XXXX-XXXX-XXXX-', RIGHT(val, 4)) ELSE 'XXXX-XXXX-XXXX-XXXX' END; -- Apply it to your table ALTER TABLE customer_data MODIFY COLUMN credit_card_number SET MASKING POLICY credit_card_mask; |
Important considerations for masking policies:
- Masked values affect both display AND queries
- Queries using the actual value (e.g.,
WHERE credit_card = '1111-1111-1111-1111'
) will still match masked records if the user has appropriate permissions to query the underlying data
A lesson learned in data privacy: Always test your masking policies with different roles before pushing to production. I once accidentally masked data for our fraud detection team, and well… let’s just say it was an interesting day in our data security journey. Apart from testing the masking policies, it’s essential to understand some of the limitations listed here to design robust masking policies.
Encryption: The Foundation of Cloud Data Security
While Snowflake handles most encryption automatically, there are still some crucial aspects of data protection you need to know. Here’s what I wish someone had told me about implementing encryption in Snowflake:
Data in Transit Security:
Data in Transit Security protects information as it moves between systems. While Snowflake automatically encrypts network traffic using TLS 1.2, you can add additional security through Network Policies. Here’s how to implement comprehensive network security for:
- Restricting access to corporate networks only
- Implementing zero-trust architecture
- Ensuring VPN usage for remote workers
- Monitoring and auditing access attempts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-- Create a network access policy CREATE OR REPLACE NETWORK POLICY restricted_access -- Internal networks ALLOWED_IP_LIST = ('10.0.0.0/8', '192.168.1.0/24') BLOCKED_IP_LIST = ('172.16.0.0/12') -- Blocked ranges COMMENT = 'Network policy for production environment'; -- Apply to specific account objects ALTER ACCOUNT SET NETWORK_POLICY = restricted_access; -- Monitor network usage and policy violations SELECT * FROM snowflake.account_usage.network_policy_events WHERE event_timestamp >= dateadd(day, -7, current_timestamp()) ORDER BY event_timestamp DESC; |
This policy ensures that:
- Only approved IP ranges can access your Snowflake account
- Suspicious IP ranges are explicitly blocked
- All access attempts are logged for audit purposes
Data at Rest Protection:
Data at Rest Protection ensures your stored data remains secure through encryption. While Snowflake provides default encryption, you can implement Customer Managed Keys (CMK) for additional control:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
-- Step 1: Create the key CREATE OR REPLACE MASTER KEY encryption_key TYPE = 'AWS_KMS' CREDENTIAL = (AWS_KEY_ARN = 'arn:aws:kms:region:account:key/key-id') COMMENT = 'Master key for PII data encryption'; -- Step 2: Apply to specific objects ALTER DATABASE sensitive_data SET MASTER KEY = encryption_key; -- Step 3: Monitor key usage SELECT * FROM snowflake.account_usage.key_usage_history WHERE key_name = 'encryption_key' AND usage_date >= dateadd(day, -30, current_date) ORDER BY usage_date DESC; |
This setup provides:
- Complete control over encryption keys
- Ability to rotate keys as needed
- Audit trail of key usage
- Compliance with regulations requiring customer-managed encryption
Real-world Security Best Practices
In this section I will cover some security best practices you should understand.Start with Security Fundamentals
Begin with these essential security measures before implementing more advanced features:
- Role-Based Access Control (RBAC)
- Define clear role hierarchies
- Implement principle of least privilege
- Regular access reviews
- Data Classification
- Identify sensitive data
- Apply appropriate security controls
- Document protection requirements
- Monitoring and Auditing
- Set up basic access logging
- Monitor failed login attempts
- Track sensitive data access
Without setting up proper access to objects and data, understanding the data you have that should not be seen by just anyone, you get most of what you need to lock down your access. But making sure to keep an eye on users’ activity is also crucial, just in case someone is doing something they shouldn’t be. (Which is often because they don’t realize the boundaries that have been configured, but is sometime for nefarious reasons.)
Document Your Security Implementation:
Of course, no one likes to do documentation. But whether we like it or not, documentation is crucial for so many things:
- Maintaining knowledge across team transitions
- Audit compliance
- Troubleshooting
- Change management
- Security reviews
And as much as we complain about writing it, we also tend to complain about NOT having it when we need it even more.
In-Database Documentation:
A nice place to have some documentation is stored right with the objects. When you need to know details quickly, having them where you can use the COMMENT ON
command. For example:
1 2 3 4 5 6 7 8 9 |
-- Basic view documentation COMMENT ON VIEW vw_customer_360 IS 'PURPOSE: Secure view for customer data with PII masking ACCESS: Restricted to Customer Service and Account Management roles MASKING: - Email addresses masked for non-CS roles - SSN visible only to compliance team LAST UPDATED: 2024-01-15 OWNER: Data Security Team CONTACT: security@company.com'; |
You can then view the comments when you use the DESC command, such as:
1 |
DESC VIEW vw_customer_360; |
And you will see details about that view, including the comment that would be added by the previous command. You can also see the values when using the SHOW command. For more examples see this documentation on the Snowflake site.
General Documentation
Of course, you need more documentation than just what you can apply to your objects. A few types of documentation might include the following:
- Security architecture diagrams
- Role hierarchy documentation
- Data classification guidelines
- Access request procedures
- Incident response procedures
Change Management
Perhaps the most important documentation is capturing what security should look like, and then keeping up with the difference as time changes so you know what changes were made within policy, and which are made outside of policy.
- Security policy changes
- Role modifications
- Access control updates
Implement Security Monitoring and Auditing:
Once you have security configures and working, it is important to audit your server regular for changes and proper utilization of resources. In this section, I will present queries that have has saved me many times in security audits
Monitor failed access attempts:
1 2 3 4 5 6 7 8 9 10 |
SELECT query_text, user_name, role_name, error_code, error_message FROM snowflake.account_usage.query_history WHERE error_code IS NOT NULL AND query_start_time >= dateadd(hour, -24, current_timestamp()) ORDER BY query_start_time DESC; |
Track role membership changes:
1 2 3 4 5 6 7 8 9 |
SELECT user_name, role_name, granted_by, created_on, deleted_on FROM snowflake.account_usage.grants_to_users<br>--Use if you just want new membership changes --WHERE deleted_on IS NULL ORDER BY created_on DESC; |
These queries are valuable for Security Monitoring by identifying failed and unauthorized access attempts, permission issues, and potential breaches. It’s also useful for Troubleshooting by showing which queries are failing, which users are affected and providing error messages. Additionally, it serves as an Audit Trail by creating a record of failed attempts, which is helpful during security audits and for tracking suspicious activity.
Another query that can help you monitor sensitive data access:
1 2 3 4 5 6 7 8 9 10 |
SELECT query_text, user_name, role_name, database_name, schema_name, table_name FROM snowflake.account_usage.access_history WHERE table_name IN ('CUSTOMER_PII', 'FINANCIAL_DATA') AND query_start_time >= dateadd(day, -7, current_timestamp()); |
Common Data Security Pitfalls to Avoid
As I wrap up this article, here are some brief tips to guide you along the way as you set up and maintain your Snowflake security:
Role Complexity
- Keep role hierarchies simple and documented
- Avoid creating roles for every possible permission combination
- Example of overcomplexity: Creating separate roles for read/write access to each table instead of using schema-level roles with appropriate grants
Security Function Usage
- When building row level security functions, understand the differences between
CURRENT_USER(),
CURRENT_ROLE()
, andCURRENT_ACCOUNT()
, We only usedCURRENT_ROLE()
in this article, but these are a few of the functions that can be employed. - Test security policies with multiple roles and scenarios
- Document which security functions are used where and why
Monitoring Gaps
- Don’t rely solely on access logs
- Monitor both successful and failed access attempts
- Track changes to security configurations
Documentation Deficits
- Keep both in-database and external documentation current
- Document security decisions and their rationale
- Maintain clear ownership and contact information
Conclusion
Implementing enterprise-grade security in Snowflake is a journey, not a destination. Start with the fundamentals of data protection, test your security controls thoroughly, and gradually add more sophisticated measures as needed. Remember, the goal is to protect sensitive data while ensuring people can still do their jobs effectively.
I’m constantly learning new things about Snowflake security features and compliance requirements, and I update my implementations accordingly. The most important thing is to stay curious and keep testing your assumptions about how your security measures work.
Keep an eye on Snowflake’s security documentation and release notes – they’re constantly adding new data governance features that might enhance your security architecture!
Load comments