{"id":105435,"date":"2025-03-04T20:17:05","date_gmt":"2025-03-04T20:17:05","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=105435"},"modified":"2025-03-04T20:17:06","modified_gmt":"2025-03-04T20:17:06","slug":"implementing-enterprise-data-security-in-snowflake-practical-concepts-from-the-trenches","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/snowflake\/implementing-enterprise-data-security-in-snowflake-practical-concepts-from-the-trenches\/","title":{"rendered":"Implementing Enterprise Data Security in Snowflake: Practical Concepts"},"content":{"rendered":"\n<p>As a data engineer who&#8217;s spent the last few years implementing enterprise-scale security in Snowflake, I&#8217;ve witnessed firsthand how challenging it can be to architect a robust security framework that balances protection with accessibility. Whether you&#8217;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.<\/p>\n\n\n\n<p>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 &#8212; having so many options meant we could solve any security requirement, but it also meant we needed to carefully architect our solution.<\/p>\n\n\n\n<p>In this practical guide, we&#8217;ll explore techniques to help you secure your use of Snowflake:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Foundational Security Setup<\/li>\n\n\n\n<li>Secure Views and Their Critical Role<\/li>\n\n\n\n<li>Row-Level Security Implementation Methods<\/li>\n\n\n\n<li>Dynamic Data Masking Strategies<\/li>\n\n\n\n<li>Encryption and Data Protection<\/li>\n\n\n\n<li>Best Practices and Common Pitfalls<\/li>\n<\/ul>\n<\/div>\n\n\n<p>I&#8217;ll walk you through battle-tested approaches to implementing Snowflake security features, sharing real implementation patterns that worked (and some that didn&#8217;t) in production environments. We&#8217;ll focus on concrete examples and scenarios you&#8217;re likely to encounter in your day-to-day work as a data engineer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-foundational-security-setup-setting-up-access-to-your-snowflake-database\">Foundational Security Setup: Setting up access to your Snowflake database<\/h2>\n\n\n\n<p>Before diving into advanced security features, let&#8217;s establish a solid foundation. Proper user and role management is crucial for maintaining security at scale.<\/p>\n\n\n\n<p>In Snowflake, all access control is managed through roles, not direct user permissions. Users cannot log in directly to database objects &#8211; they must be assigned to roles that have been granted the necessary privileges. Here&#8217;s how to structure your roles effectively:<\/p>\n\n\n\n<p><strong>Step 1, Create standard roles: <\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Create functional roles\nCREATE ROLE data_scientist;\n\nCREATE ROLE analyst;\n\nCREATE ROLE data_engineer;<\/pre>\n\n\n\n<p><strong>Step 2, Give standard rights to your roles:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Grant access to specific schemas\nGRANT USAGE ON SCHEMA analytics.public TO ROLE analyst;\n\nGRANT SELECT ON ALL TABLES IN SCHEMA analytics.public TO ROLE analyst;<\/pre>\n\n\n\n<p>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:<\/p>\n\n\n\n<p><strong>Step 3, Put users into these roles:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Environment-specific user management \n--(separate script per environment)\nCREATE USER bob_smith;\n\nGRANT ROLE analyst TO USER bob_smith;<\/pre>\n\n\n\n<p>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 &#8220;<code>bob@company.com<\/code>&#8220;, use a role like &#8220;<code>ACCOUNTING_ANALYST<\/code>&#8221; that can be assigned to different users in different environments.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-secure-views-your-first-line-of-defense\">Secure Views: Your First Line of Defense<\/h2>\n\n\n\n<p>Before we dive into specific security implementations, let&#8217;s understand <a href=\"https:\/\/docs.snowflake.com\/en\/user-guide\/views-secure\">Secure Views<\/a> &#8211; a useful, fundamental building block of building a secure Snowflake environment. Secure views differ from <a href=\"https:\/\/docs.snowflake.com\/en\/user-guide\/views-introduction\">regular views<\/a> in several crucial ways:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>They hide the underlying query logic from users who only have access to the view<\/li>\n\n\n\n<li>They prevent unauthorized access to the source data, unlike regular views where users might need permissions on underlying tables<\/li>\n\n\n\n<li>They help optimize security policy enforcement by hiding implementation details you may not want to share<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Example of a basic secure view<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CREATE OR REPLACE SECURE VIEW vw_customer_basic \nAS \nSELECT \n   customer_id, \n   customer_name, \n   city, \n   country \nFROM customer_data; <\/pre>\n\n\n\n<p>This view will give the user access to only these columns of the <code>customer_data<\/code> table. And you can specify a <code>WHERE<\/code> clause to partition the data to only a subset of the rows too. Most any SQL Query can be used in a view.<\/p>\n\n\n\n<p>For a normal view, users that can query the view, can also use the <code>DESCRIBE<\/code> 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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DESCRIBE VIEW vw_customer_basic; <\/code><\/pre>\n\n\n\n<p>Executing this statement will result in an error such as:<\/p>\n\n\n\n<p><code>Error: Insufficient privileges to operate on view 'VW_CUSTOMER_BASIC'<\/code><\/p>\n\n\n\n<p>The <code>SECURE<\/code> keyword is crucial here when you wish to keep the implementation details hidden from the typical user.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-row-level-security-rls-multiple-approaches\">Row-Level Security (RLS): Multiple Approaches<\/h2>\n\n\n\n<p>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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-approach-1-view-based-rls\">Approach 1: View-Based RLS<\/h3>\n\n\n\n<p>View-based RLS is a security pattern that restricts which rows a user can see in a table based on their assigned role. Let&#8217;s break down the implementation step by step with examples and explain why each component is important.<\/p>\n\n\n\n<p>This is the traditional approach using secure views and mapping tables. First, let&#8217;s create sample data. This table contains sales data from different regions. Without RLS, any user with SELECT permissions could see all records.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CREATE TABLE sales_data (\n    sale_id INTEGER,\n    region STRING,\n    amount FLOAT,\n    customer_email STRING\n);<\/pre>\n\n\n\n<p>Next, I will create a role-based mapping table. This table will map roles that can see each of the different region\u2019s data.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CREATE TABLE role_region_mapping (\n    role_name STRING,\n    region STRING\n);\n\n-- Define access rules\nINSERT INTO role_region_mapping VALUES\n    ('NORTH_AMERICA_SALES', 'NA'),\n    ('EUROPE_SALES', 'EU');<\/pre>\n\n\n\n<p>Why role-based instead of user-based?<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Better scalability &#8211; you don&#8217;t need to update mappings when employees join\/leave<\/li>\n\n\n\n<li>Easier maintenance &#8211; roles typically change less frequently than user assignments<\/li>\n\n\n\n<li>Consistent across environments &#8211; same roles work in dev\/test\/prod<\/li>\n<\/ul>\n<\/div>\n\n\n<p>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.<\/p>\n\n\n\n<p>Next I will create the view for the users to use instead of the <code>sales_data<\/code> table.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Create the secure view with RLS\nCREATE OR REPLACE SECURE VIEW vw_sales_data \nAS\nSELECT s.*\nFROM sales_data s\nWHERE s.region IN (\n    SELECT region \n    FROM role_region_mapping \n    WHERE role_name = CURRENT_ROLE()\n);<\/pre>\n\n\n\n<p>Finally, you need to grant the users that will view the data, rights to use the <code>vw_sales_data <\/code>view, but not the <code>sales_data<\/code> table. This leaves their only view of the data to be what they can see in the view.<\/p>\n\n\n\n<p>Important considerations for <code>CURRENT_ROLE()<\/code>:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>A user can be a member of multiple roles but can only have one active role at a time<\/li>\n\n\n\n<li>Users can switch their active role using <code>USE ROLE role_name<\/code><\/li>\n\n\n\n<li>For users needing access to multiple regions, consider creating composite roles or using RBAC hierarchies<\/li>\n\n\n\n<li>System administrators (<code>SYSADMIN<\/code> role) typically have access to all data unless explicitly restricted. However, it is something you need to be careful with, because if their default <code>ROLE<\/code> is <code>SYSADMIN<\/code>, no data would be returned from the <code>SECURE<\/code> <code>VIEW<\/code> as written.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Let&#8217;s break down how this works:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The <code>SECURE<\/code> keyword prevents users from seeing the view definition<\/li>\n\n\n\n<li><code>CURRENT_ROLE()<\/code> returns the current user&#8217;s active role<\/li>\n\n\n\n<li>The subquery <code>SELECT region FROM role_region_mapping WHERE role_name = CURRENT_ROLE()<\/code> gets the allowed regions for the current role<\/li>\n\n\n\n<li>The <code>WHERE s.region IN (...)<\/code> clause filters the data to only show rows from allowed regions<\/li>\n<\/ul>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-approach-2-row-access-policy\">Approach 2: Row Access Policy<\/h3>\n\n\n\n<p>Row Access Policy is Snowflake&#8217;s native method for implementing row-level security directly at the table level, without requiring secure views. It&#8217;s more maintainable and can be reused across multiple tables.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>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:<\/p>\n\n\n\n<p>Create a row access policy<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CREATE OR REPLACE ROW ACCESS POLICY sales_region_policy \nAS (region STRING) \n    RETURNS BOOLEAN -&gt;\n    EXISTS (\n        SELECT 1 \n        FROM role_region_mapping \n        WHERE role_name = CURRENT_ROLE()\n        AND region = sales_data.region\n    );\n\n-- Apply the policy to the table\nALTER TABLE sales_data \n  ADD ROW ACCESS POLICY sales_region_policy ON (region);<\/pre>\n\n\n\n<p>Let&#8217;s break down this policy:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><code>AS (region STRING)<\/code> &#8211; Specifies the input parameter and its type. This must match the column type in your table<\/li>\n\n\n\n<li><code>RETURNS BOOLEAN<\/code> &#8211; The policy must return true\/false to determine if a row should be visible<\/li>\n\n\n\n<li><code>EXISTS (...)<\/code> &#8211; Checks if there&#8217;s a mapping between the current role and the region<\/li>\n\n\n\n<li><code>sales_data.region<\/code> &#8211; References the region column from the table where the policy is applied<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Key differences between approaches:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>View-based RLS offers more flexibility for complex logic and can combine multiple security features<\/li>\n\n\n\n<li>Row Access Policy is easier to maintain and apply consistently<\/li>\n\n\n\n<li>Row Access Policies can be applied to views as well as tables<\/li>\n\n\n\n<li>Row Access Policies don&#8217;t hide table structure or prevent schema modifications<\/li>\n\n\n\n<li>Views allow you to implement custom aggregations and transformations alongside security<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-dynamic-data-masking-advanced-data-protection-for-sensitive-information\">Dynamic Data Masking: Advanced Data Protection for Sensitive Information<\/h2>\n\n\n\n<p>This powerful Snowflake security feature is essential for implementing data privacy controls \u2013 imagine you&#8217;re working with sensitive customer data, and different teams need varying levels of access to the same information.<\/p>\n\n\n\n<p>Some need to see full credit card numbers, others just the last four digits, and some shouldn&#8217;t see them at all. Here&#8217;s a real example from when I implemented PII protection for our customer service application:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Create a sophisticated masking policy\nCREATE OR REPLACE MASKING POLICY credit_card_mask \nAS (val STRING) RETURNS STRING -&gt;\n    CASE\n        WHEN CURRENT_ROLE() = 'FINANCIAL_ADMIN' THEN val\n        WHEN CURRENT_ROLE() = 'CUSTOMER_SERVICE' \n            THEN CONCAT('XXXX-XXXX-XXXX-', RIGHT(val, 4))\n        ELSE 'XXXX-XXXX-XXXX-XXXX'\n    END;\n\n-- Apply it to your table\nALTER TABLE customer_data MODIFY COLUMN credit_card_number \n    SET MASKING POLICY credit_card_mask;<\/pre>\n\n\n\n<p>Important considerations for masking policies:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Masked values affect both display AND queries<\/li>\n\n\n\n<li>Queries using the actual value (e.g., <code>WHERE credit_card = '1111-1111-1111-1111'<\/code>) will still match masked records if the user has appropriate permissions to query the underlying data<\/li>\n<\/ul>\n<\/div>\n\n\n<p>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&#8230; let&#8217;s just say it was an interesting day in our data security journey. Apart from testing the masking policies, it\u2019s essential to understand some of the <a href=\"https:\/\/docs.snowflake.com\/en\/user-guide\/security-column-intro#label-security-column-limitations\">limitations listed here<\/a> to design robust masking policies.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-encryption-the-foundation-of-cloud-data-security\">Encryption: The Foundation of Cloud Data Security<\/h2>\n\n\n\n<p>While Snowflake handles most encryption automatically, there are still some crucial aspects of data protection you need to know. Here&#8217;s what I wish someone had told me about implementing encryption in Snowflake:<\/p>\n\n\n\n<p><strong>Data in Transit Security:<\/strong><\/p>\n\n\n\n<p>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&#8217;s how to implement comprehensive network security for:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Restricting access to corporate networks only<\/li>\n\n\n\n<li>Implementing zero-trust architecture<\/li>\n\n\n\n<li>Ensuring VPN usage for remote workers<\/li>\n\n\n\n<li>Monitoring and auditing access attempts<\/li>\n<\/ul>\n<\/div>\n\n\n<pre class=\"wp-block-preformatted\">-- Create a network access policy\nCREATE OR REPLACE NETWORK POLICY restricted_access\n    -- Internal networks\n    ALLOWED_IP_LIST = ('10.0.0.0\/8', '192.168.1.0\/24')  \n    BLOCKED_IP_LIST = ('172.16.0.0\/12') -- Blocked ranges\n    COMMENT = 'Network policy for production environment';\n\n-- Apply to specific account objects\nALTER ACCOUNT SET NETWORK_POLICY = restricted_access;\n\n-- Monitor network usage and policy violations\nSELECT * \nFROM snowflake.account_usage.network_policy_events\nWHERE event_timestamp &gt;= dateadd(day, -7, current_timestamp())\nORDER BY event_timestamp DESC;<\/pre>\n\n\n\n<p>This policy ensures that:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Only approved IP ranges can access your Snowflake account<\/li>\n\n\n\n<li>Suspicious IP ranges are explicitly blocked<\/li>\n\n\n\n<li>All access attempts are logged for audit purposes<\/li>\n<\/ul>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-data-at-rest-protection\">Data at Rest Protection:<\/h3>\n\n\n\n<p>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:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Step 1: Create the key\nCREATE OR REPLACE MASTER KEY encryption_key\n    TYPE = 'AWS_KMS'\n    CREDENTIAL = (AWS_KEY_ARN = 'arn:aws:kms:region:account:key\/key-id')\n    COMMENT = 'Master key for PII data encryption';\n\n-- Step 2: Apply to specific objects\nALTER DATABASE sensitive_data \n    SET MASTER KEY = encryption_key;\n\n-- Step 3: Monitor key usage\nSELECT * \nFROM snowflake.account_usage.key_usage_history\nWHERE key_name = 'encryption_key'\nAND usage_date &gt;= dateadd(day, -30, current_date)\nORDER BY usage_date DESC;<\/pre>\n\n\n\n<p>This setup provides:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Complete control over encryption keys<\/li>\n\n\n\n<li>Ability to rotate keys as needed<\/li>\n\n\n\n<li>Audit trail of key usage<\/li>\n\n\n\n<li>Compliance with regulations requiring customer-managed encryption<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-real-world-security-best-practices\">Real-world Security Best Practices<\/h2>\n\n\n\n<p>In this section I will cover some security best practices you should understand.<a id=\"post-105435-_heading=h.2th1c6sc2vxd\"><\/a>Start with Security Fundamentals<\/p>\n\n\n\n<p>Begin with these essential <strong>security<\/strong> measures before implementing more advanced features:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Role-Based Access Control (RBAC)<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Define clear role hierarchies<\/li>\n\n\n\n<li>Implement principle of least privilege<\/li>\n\n\n\n<li>Regular access reviews<\/li>\n<\/ul>\n<\/div><\/li>\n\n\n\n<li>Data Classification<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Identify sensitive data<\/li>\n\n\n\n<li>Apply appropriate security controls<\/li>\n\n\n\n<li>Document protection requirements<\/li>\n<\/ul>\n<\/div><\/li>\n\n\n\n<li>Monitoring and Auditing<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Set up basic access logging<\/li>\n\n\n\n<li>Monitor failed login attempts<\/li>\n\n\n\n<li>Track sensitive data access<\/li>\n<\/ul>\n<\/div><\/li>\n<\/ul>\n<\/div>\n\n\n<p>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\u2019 activity is also crucial, just in case someone is doing something they shouldn\u2019t be. (Which is often because they don\u2019t realize the boundaries that have been configured, but is sometime for nefarious reasons.)<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-document-your-security-implementation\">Document Your Security Implementation:<\/h3>\n\n\n\n<p>Of course, no one likes to do documentation. But whether we like it or not, documentation is crucial for so many things:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Maintaining knowledge across team transitions<\/li>\n\n\n\n<li>Audit compliance<\/li>\n\n\n\n<li>Troubleshooting<\/li>\n\n\n\n<li>Change management<\/li>\n\n\n\n<li>Security reviews<\/li>\n<\/ul>\n<\/div>\n\n\n<p>And as much as we complain about writing it, we also tend to complain about NOT having it when we need it even more.<\/p>\n\n\n\n<p><strong>In-Database Documentation:<\/strong><\/p>\n\n\n\n<p>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 <code>COMMENT ON<\/code> command. For example:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">     -- Basic view documentation \nCOMMENT ON VIEW vw_customer_360 IS \n'PURPOSE: Secure view for customer data with PII masking \nACCESS: Restricted to Customer Service and Account Management roles MASKING: \n- Email addresses masked for non-CS roles \n- SSN visible only to compliance team \nLAST UPDATED: 2024-01-15 \nOWNER: Data Security Team \nCONTACT: security@company.com';<\/pre>\n\n\n\n<p>You can then view the comments when you use the DESC command, such as:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DESC VIEW vw_customer_360;<\/pre>\n\n\n\n<p>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 <a href=\"https:\/\/docs.snowflake.com\/en\/sql-reference\/sql\/comment\">this documentation<\/a> on the Snowflake site.<\/p>\n\n\n\n<p><strong>General Documentation<\/strong><\/p>\n\n\n\n<p>Of course, you need more documentation than just what you can apply to your objects. A few types of documentation might include the following:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Security architecture diagrams<\/li>\n\n\n\n<li>Role hierarchy documentation<\/li>\n\n\n\n<li>Data classification guidelines<\/li>\n\n\n\n<li>Access request procedures<\/li>\n\n\n\n<li>Incident response procedures<\/li>\n<\/ul>\n<\/div>\n\n\n<p><strong>Change Management <\/strong><\/p>\n\n\n\n<p>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.<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Security policy changes<\/li>\n\n\n\n<li>Role modifications<\/li>\n\n\n\n<li>Access control updates<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-implement-security-monitoring-and-auditing\"><span style=\"color: revert; font-family: 'Roboto Slab', serif; font-size: 1.875rem;\">Implement Security Monitoring and Auditing:<\/span><\/h2>\n\n\n\n<p>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<\/p>\n\n\n\n<p>Monitor failed access attempts:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT \n    query_text,\n    user_name,\n    role_name,\n    error_code,\n    error_message\nFROM snowflake.account_usage.query_history\nWHERE error_code IS NOT NULL\nAND query_start_time &gt;= dateadd(hour, -24, current_timestamp())\nORDER BY query_start_time DESC;<\/pre>\n\n\n\n<p>Track role membership changes:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT \n    user_name,\n    role_name,\n    granted_by,\n    created_on,\n    deleted_on\nFROM snowflake.account_usage.grants_to_users<br>--Use if you just want new membership changes\n--WHERE deleted_on IS NULL\nORDER BY created_on DESC;<\/pre>\n\n\n\n<p>These queries are valuable for Security Monitoring by identifying failed and unauthorized access attempts, permission issues, and potential breaches. It&#8217;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.<\/p>\n\n\n\n<p>Another query that can help you monitor sensitive data access:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT \n    query_text,\n    user_name,\n    role_name,\n    database_name,\n    schema_name,\n    table_name\nFROM snowflake.account_usage.access_history\nWHERE table_name IN ('CUSTOMER_PII', 'FINANCIAL_DATA')\nAND query_start_time &gt;= dateadd(day, -7, current_timestamp());<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-common-data-security-pitfalls-to-avoid\">Common Data Security Pitfalls to Avoid<\/h2>\n\n\n\n<p>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:<\/p>\n\n\n\n<p><strong>Role Complexity<\/strong><\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Keep role hierarchies simple and documented<\/li>\n\n\n\n<li>Avoid creating roles for every possible permission combination<\/li>\n\n\n\n<li>Example of overcomplexity: Creating separate roles for read\/write access to each table instead of using schema-level roles with appropriate grants<\/li>\n<\/ul>\n<\/div>\n\n\n<p><strong>Security Function Usage<\/strong><\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>When building row level security functions, understand the differences between <code>CURRENT_USER(),<\/code> <code>CURRENT_ROLE()<\/code>, and <code>CURRENT_ACCOUNT()<\/code>, We only used <code>CURRENT_ROLE()<\/code> in this article, but these are a few of the functions that can be employed.<\/li>\n\n\n\n<li>Test security policies with multiple roles and scenarios<\/li>\n\n\n\n<li>Document which security functions are used where and why<\/li>\n<\/ul>\n<\/div>\n\n\n<p><strong>Monitoring Gaps<\/strong><\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Don&#8217;t rely solely on access logs<\/li>\n\n\n\n<li>Monitor both successful and failed access attempts<\/li>\n\n\n\n<li>Track changes to security configurations<\/li>\n<\/ul>\n<\/div>\n\n\n<p><strong>Documentation Deficits<\/strong><\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Keep both in-database and external documentation current<\/li>\n\n\n\n<li>Document security decisions and their rationale<\/li>\n\n\n\n<li>Maintain clear ownership and contact information<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion\">Conclusion<\/h2>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>I&#8217;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.<\/p>\n\n\n\n<p>Keep an eye on Snowflake&#8217;s security documentation and release notes \u2013 they&#8217;re constantly adding new data governance features that might enhance your security architecture!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As a data engineer who&#8217;s spent the last few years implementing enterprise-scale security in Snowflake, I&#8217;ve witnessed firsthand how challenging it can be to architect a robust security framework that balances protection with accessibility. Whether you&#8217;re dealing with regulatory compliance requirements, implementing multi-tenant data solutions, or simply trying to ensure proper data access controls, the&#8230;&hellip;<\/p>\n","protected":false},"author":345259,"featured_media":105436,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[53,159001],"tags":[159123,159262],"coauthors":[159232],"class_list":["post-105435","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-featured","category-snowflake","tag-snowflake","tag-snowflake-security"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/105435","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/345259"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=105435"}],"version-history":[{"count":9,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/105435\/revisions"}],"predecessor-version":[{"id":105961,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/105435\/revisions\/105961"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/105436"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=105435"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=105435"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=105435"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=105435"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}