---
name: snowflake-policy-guardrails
description: 'Implement Snowflake governance guardrails with network rules, session
  policies,

  authentication policies, and automated compliance checks.

  Use when enforcing security policies, implementing data governance,

  or configuring automated compliance for Snowflake.

  Trigger with phrases like "snowflake policy", "snowflake guardrails",

  "snowflake governance", "snowflake compliance", "snowflake enforce".

  '
allowed-tools: Read, Write, Edit, Bash(npx:*)
version: 1.0.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- data-warehouse
- analytics
- snowflake
compatibility: Designed for Claude Code
---
# Snowflake Policy & Guardrails

## Overview

Automated policy enforcement and governance guardrails using Snowflake-native features: network rules, authentication policies, session policies, and object-level governance.

## Instructions

### Step 1: Network Rules and Policies

```sql
-- Network rules (more granular than legacy network policies)
CREATE OR REPLACE NETWORK RULE corp_vpn_rule
  TYPE = IPV4
  MODE = INGRESS
  VALUE_LIST = ('203.0.113.0/24', '198.51.100.0/24');

CREATE OR REPLACE NETWORK RULE cloud_services_rule
  TYPE = HOST_PORT
  MODE = EGRESS
  VALUE_LIST = ('api.company.com:443', 'events.company.com:443');

-- Create network policy using rules
CREATE OR REPLACE NETWORK POLICY prod_network_policy
  ALLOWED_NETWORK_RULE_LIST = (corp_vpn_rule)
  BLOCKED_NETWORK_RULE_LIST = ();

-- Apply at account level
ALTER ACCOUNT SET NETWORK_POLICY = prod_network_policy;

-- Or per-user (service accounts can have different rules)
ALTER USER svc_etl SET NETWORK_POLICY = prod_network_policy;
```

### Step 2: Authentication Policies

```sql
-- Require MFA for interactive users
CREATE OR REPLACE AUTHENTICATION POLICY interactive_auth
  MFA_AUTHENTICATION_METHODS = ('TOTP')
  CLIENT_TYPES = ('SNOWFLAKE_UI', 'SNOWSQL')
  SECURITY_INTEGRATIONS = ('saml_sso');

-- Service accounts: key pair only, no password
CREATE OR REPLACE AUTHENTICATION POLICY service_auth
  AUTHENTICATION_METHODS = ('KEYPAIR')
  CLIENT_TYPES = ('SNOWFLAKE_DRIVER')
  MFA_AUTHENTICATION_METHODS = ();

-- Apply policies
ALTER USER analyst_user SET AUTHENTICATION POLICY = interactive_auth;
ALTER USER svc_etl SET AUTHENTICATION POLICY = service_auth;
```

### Step 3: Session Policies

```sql
-- Enforce session timeout and idle limits
CREATE OR REPLACE SESSION POLICY prod_session_policy
  SESSION_IDLE_TIMEOUT_MINS = 30
  SESSION_UI_IDLE_TIMEOUT_MINS = 15;

-- Apply to account
ALTER ACCOUNT SET SESSION POLICY = prod_session_policy;
```

### Step 4: Statement-Level Guardrails

```sql
-- Prevent runaway queries
ALTER WAREHOUSE PROD_WH SET
  STATEMENT_TIMEOUT_IN_SECONDS = 3600,          -- 1 hour max
  STATEMENT_QUEUED_TIMEOUT_IN_SECONDS = 600;     -- 10 min max queue

-- Prevent accidental full table operations
-- Use row access policies + stored procedures instead of raw access

-- Example: Safe delete procedure with audit
CREATE OR REPLACE PROCEDURE safe_delete(
  table_name VARCHAR, where_clause VARCHAR, max_rows INTEGER DEFAULT 10000
)
  RETURNS VARCHAR
  LANGUAGE SQL
AS
$$
BEGIN
  -- Count affected rows first
  LET count_sql VARCHAR := 'SELECT COUNT(*) FROM ' || :table_name || ' WHERE ' || :where_clause;
  LET affected_rows INTEGER;
  EXECUTE IMMEDIATE :count_sql INTO :affected_rows;

  IF (:affected_rows > :max_rows) THEN
    RETURN 'BLOCKED: Would delete ' || :affected_rows || ' rows (max: ' || :max_rows || ')';
  END IF;

  -- Audit log
  INSERT INTO audit.delete_log (table_name, where_clause, row_count, executed_by, executed_at)
  VALUES (:table_name, :where_clause, :affected_rows, CURRENT_USER(), CURRENT_TIMESTAMP());

  -- Execute delete
  EXECUTE IMMEDIATE 'DELETE FROM ' || :table_name || ' WHERE ' || :where_clause;
  RETURN 'Deleted ' || :affected_rows || ' rows from ' || :table_name;
END;
$$;

-- Usage: CALL safe_delete('orders', 'order_date < ''2024-01-01''', 50000);
```

### Step 5: Data Governance Tags and Policies

```sql
-- Create governance taxonomy
CREATE TAG IF NOT EXISTS data_domain ALLOWED_VALUES 'finance', 'marketing', 'engineering', 'hr';
CREATE TAG IF NOT EXISTS data_owner;
CREATE TAG IF NOT EXISTS retention_days;

-- Apply tags to databases/schemas
ALTER DATABASE PROD_DW SET TAG data_domain = 'finance';
ALTER SCHEMA PROD_DW.GOLD SET TAG data_owner = 'analytics-team@company.com';
ALTER TABLE PROD_DW.GOLD.REVENUE SET TAG retention_days = '2555';  -- 7 years

-- Automated compliance report
SELECT
  tag_name, tag_value, object_database, object_schema, object_name, column_name
FROM TABLE(INFORMATION_SCHEMA.TAG_REFERENCES_ALL_COLUMNS(
  'PROD_DW.GOLD.REVENUE', 'TABLE'
));

-- Find untagged tables (governance gap)
SELECT t.table_catalog, t.table_schema, t.table_name, t.row_count
FROM INFORMATION_SCHEMA.TABLES t
LEFT JOIN TABLE(INFORMATION_SCHEMA.TAG_REFERENCES(
  t.table_catalog || '.' || t.table_schema || '.' || t.table_name, 'TABLE'
)) tr ON TRUE
WHERE tr.tag_name IS NULL
  AND t.table_schema NOT IN ('INFORMATION_SCHEMA')
ORDER BY t.row_count DESC NULLS LAST;
```

### Step 6: CI/CD Policy Checks

```yaml
# .github/workflows/snowflake-governance.yml
name: Snowflake Governance Check

on:
  pull_request:
    paths: ['sql/**', 'migrations/**']

jobs:
  policy-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check for dangerous SQL patterns
        run: |
          # No DROP DATABASE/SCHEMA without IF EXISTS
          if grep -rn "DROP DATABASE\|DROP SCHEMA" sql/ | grep -v "IF EXISTS"; then
            echo "ERROR: DROP without IF EXISTS detected"
            exit 1
          fi

          # No GRANT ... TO PUBLIC
          if grep -rn "TO ROLE PUBLIC\|TO PUBLIC" sql/; then
            echo "ERROR: Granting to PUBLIC role is not allowed"
            exit 1
          fi

          # No hardcoded passwords
          if grep -rn "PASSWORD = " sql/ | grep -v "PASSWORD = \$"; then
            echo "ERROR: Hardcoded password detected"
            exit 1
          fi

          # All tables must have DATA_RETENTION_TIME_IN_DAYS
          for f in $(grep -rl "CREATE TABLE\|CREATE OR REPLACE TABLE" sql/); do
            if ! grep -q "DATA_RETENTION_TIME_IN_DAYS" "$f"; then
              echo "WARNING: $f missing explicit retention policy"
            fi
          done

          echo "All governance checks passed"

      - name: Validate SchemaChange naming
        run: |
          # Ensure migration files follow V{version}__{description}.sql
          for f in migrations/V*.sql; do
            if ! echo "$f" | grep -qE 'V[0-9]+\.[0-9]+\.[0-9]+__[a-z_]+\.sql'; then
              echo "ERROR: Invalid migration filename: $f"
              echo "Expected: V{major}.{minor}.{patch}__{description}.sql"
              exit 1
            fi
          done
```

### Step 7: Automated Compliance Audit

```sql
-- Weekly compliance audit stored procedure
CREATE OR REPLACE PROCEDURE run_compliance_audit()
  RETURNS TABLE (check_name VARCHAR, status VARCHAR, details VARCHAR)
  LANGUAGE SQL
AS
$$
  -- Check 1: No users with ACCOUNTADMIN default role
  SELECT 'accountadmin_check' AS check_name,
    CASE WHEN COUNT(*) = 0 THEN 'PASS' ELSE 'FAIL' END AS status,
    COUNT(*) || ' users with ACCOUNTADMIN default' AS details
  FROM SNOWFLAKE.ACCOUNT_USAGE.USERS
  WHERE default_role = 'ACCOUNTADMIN' AND disabled = 'false'

  UNION ALL

  -- Check 2: Network policy active
  SELECT 'network_policy_check',
    CASE WHEN value != '' THEN 'PASS' ELSE 'FAIL' END,
    'Account network policy: ' || COALESCE(value, 'NONE')
  FROM TABLE(FLATTEN(INPUT => PARSE_JSON(
    SYSTEM$GET_SNOWFLAKE_PLATFORM_INFO()
  )))
  WHERE key = 'network_policy'

  UNION ALL

  -- Check 3: MFA adoption
  SELECT 'mfa_check',
    CASE WHEN COUNT_IF(has_mfa = 'true') * 100 / COUNT(*) >= 90 THEN 'PASS' ELSE 'WARN' END,
    COUNT_IF(has_mfa = 'true') || '/' || COUNT(*) || ' users have MFA'
  FROM SNOWFLAKE.ACCOUNT_USAGE.USERS
  WHERE disabled = 'false';
$$;
```

## Error Handling

| Issue | Cause | Solution |
|-------|-------|----------|
| Network policy blocks legitimate user | IP not in allowlist | Add IP range to network rule |
| Auth policy prevents login | Wrong client type in policy | Adjust CLIENT_TYPES |
| Session timeout too aggressive | Short idle timeout | Increase SESSION_IDLE_TIMEOUT_MINS |
| CI check false positive | SQL pattern too broad | Refine regex pattern |

## Resources

- [Network Policies](https://docs.snowflake.com/en/user-guide/network-policies)
- [Authentication Policies](https://docs.snowflake.com/en/user-guide/authentication-policies)
- [Data Governance](https://docs.snowflake.com/en/guides-overview-govern)
- [Object Tagging](https://docs.snowflake.com/en/user-guide/object-tagging)

## Next Steps

For architecture blueprints, see `snowflake-architecture-variants`.
