+1 (415) 997-4269

Locking Down the Snowflake Network Perimeter: Network Policies, PrivateLink, and Egress Control

Most Snowflake hardening work starts and stops at authentication: enforce MFA, move service accounts to key pairs or PATs, ban password logins. That is the right first move, and we covered it in the authentication hardening tutorial. But a stolen credential is only useful if it can be used from somewhere, and a governed account is one where credentials only work from networks you recognise, private traffic never crosses the public internet, and outbound calls from inside Snowflake go to an allow-list you wrote.

This tutorial walks the network perimeter end to end: account- and user-level network policies, network rules as reusable building blocks, PrivateLink for inbound and outbound private connectivity, egress control for external access integrations, and the Trust Center scanners that tell you whether any of it is actually switched on.

The four layers

LayerQuestion it answersObjects
Inbound allow-listWhich IPs/VPCs may authenticate?NETWORK POLICY, NETWORK RULE
Private inboundCan we take the public endpoint off the table?AWS PrivateLink / Azure Private Link / GCP Private Service Connect
Private outboundCan Snowflake reach our systems without public IPs?Outbound private connectivity, external volumes/stages over private endpoints
Egress allow-listWhere may code running inside Snowflake call out to?EXTERNAL ACCESS INTEGRATION + egress NETWORK RULE

They are independent. Teams commonly do layer 1 and assume the rest followed. It did not.

Layer 1: network rules and network policies

A network rule is a named list of identifiers. A network policy combines rules into allowed and blocked sets and is attached to an account, a user, or a security integration.

USE ROLE securityadmin;

-- Corporate egress IPs for humans on VPN
CREATE OR REPLACE NETWORK RULE corp_vpn_ipv4
  MODE = INGRESS
  TYPE = IPV4
  VALUE_LIST = ('203.0.113.0/24', '198.51.100.17');

-- The VPC that our ETL runners live in (AWS, same region as the account)
CREATE OR REPLACE NETWORK RULE etl_vpce
  MODE = INGRESS
  TYPE = AWSVPCEID
  VALUE_LIST = ('vpce-0abc123def4567890');

CREATE OR REPLACE NETWORK POLICY acme_account_policy
  ALLOWED_NETWORK_RULE_LIST = ('corp_vpn_ipv4', 'etl_vpce')
  COMMENT = 'Account default: corp VPN + ETL VPC endpoint';

Before you attach anything, test it against reality. Locking yourself out of an account is the classic Friday-evening incident.

-- Where have logins actually come from in the last 30 days?
SELECT client_ip, user_name, COUNT(*) AS attempts, MAX(event_timestamp) AS last_seen
FROM snowflake.account_usage.login_history
WHERE event_timestamp > DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP BY 1, 2
ORDER BY attempts DESC;

-- Dry-run the policy against an identifier
SELECT SYSTEM$VALIDATE_NETWORK_POLICIES('ACME_ACCOUNT_POLICY', '203.0.113.44');

Then attach:

ALTER ACCOUNT SET NETWORK_POLICY = acme_account_policy;

-- Tighter policy for a single high-value service user
CREATE OR REPLACE NETWORK POLICY svc_airflow_policy
  ALLOWED_NETWORK_RULE_LIST = ('etl_vpce');
ALTER USER svc_airflow SET NETWORK_POLICY = svc_airflow_policy;

Rules that keep this survivable:

  • User-level policies override the account policy for that user — they are not additive. A user policy that omits your VPN will lock that user out even if the account policy allows it.
  • Keep one break-glass ACCOUNTADMIN user whose policy includes a stable, documented management IP, and rehearse the recovery path. Snowflake Support can lift a policy, but that is a ticket, not a plan.
  • Blocked lists are evaluated first: BLOCKED_NETWORK_RULE_LIST wins over allowed.
  • If you federate through Okta/Entra, also set a policy on the security integration so SAML/OAuth logins are constrained too.

Watch for a subtlety: with PrivateLink, the client_ip Snowflake sees is the private address from your VPC, so IPv4 CIDRs written for public egress will not match. Use AWSVPCEID / AZURELINKID rules for private traffic and IP rules for public traffic, in the same policy.

Layer 2: private inbound connectivity

PrivateLink (AWS), Private Link (Azure), and Private Service Connect (GCP) put the Snowflake account endpoint inside your own network. Traffic from your VPC to Snowflake never traverses the public internet, and combined with a network policy you can make the public endpoint effectively unusable.

The Snowflake-side steps are SQL; the cloud-side steps belong to your network team.

USE ROLE accountadmin;

-- AWS: the values your network team needs to create the VPC endpoint
SELECT SYSTEM$GET_PRIVATELINK_CONFIG();

-- After the endpoint exists and DNS is in place, verify from a host in the VPC
SELECT SYSTEM$AUTHORIZE_PRIVATELINK('<federated-token>', '<aws-account-or-subscription>');

Two things teams miss:

  1. DNS. The private URL (<account>.privatelink.snowflakecomputing.com) must resolve inside the VPC via a private hosted zone. Without it, clients silently fall back to the public endpoint and you think PrivateLink is working when it is not. Confirm with SELECT CURRENT_ACCOUNT(), CURRENT_REGION(); plus a dig from the runner host, and cross-check login_history.client_ip.
  2. Internal stages and the Snowsight UI have their own private endpoints. Data loading over a public path while logins are private is a common half-finished state.

Once private access is verified for every client, restrict the account policy to VPC-endpoint rules only and re-run SYSTEM$VALIDATE_NETWORK_POLICIES for each remaining public user.

Layer 3: private outbound connectivity

Snowflake also needs to reach your systems: an API behind a load balancer, a Kafka broker, an on-prem database via a proxy, object storage. Outbound private connectivity lets external access integrations and external stages use a private endpoint instead of public egress.

USE ROLE accountadmin;

-- Register a private endpoint from Snowflake's VPC to your internal service
SELECT SYSTEM$PROVISION_PRIVATELINK_ENDPOINT(
  'com.amazonaws.vpce.us-east-1.vpce-svc-0123456789abcdef0',
  'api.internal.acme.com'
);

SELECT SYSTEM$GET_PRIVATELINK_ENDPOINTS_INFO();

Then reference the private hostname in your network rule instead of a public one. From the UDF's point of view nothing changes; the packets just stop leaving your cloud.

Layer 4: egress control for code running inside Snowflake

The blast radius most often overlooked: a Python UDF, a stored procedure, or a Snowpark Container Services service can make outbound HTTP calls. Snowflake blocks this by default and requires an external access integration built from egress network rules — this is your outbound allow-list, and it is per-host, not per-network.

CREATE OR REPLACE NETWORK RULE billing_api_egress
  MODE = EGRESS
  TYPE = HOST_PORT
  VALUE_LIST = ('api.stripe.com:443');

CREATE OR REPLACE SECRET stripe_key
  TYPE = GENERIC_STRING
  SECRET_STRING = '<rotate-me>';

CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION billing_api_eai
  ALLOWED_NETWORK_RULES = (billing_api_egress)
  ALLOWED_AUTHENTICATION_SECRETS = (stripe_key)
  ENABLED = TRUE;

GRANT USAGE ON INTEGRATION billing_api_eai TO ROLE app_developer;

Audit what exists today, because integrations accumulate:

SHOW EXTERNAL ACCESS INTEGRATIONS;

-- Which functions and procedures are allowed to call out, and through what?
SELECT function_name, function_owner, external_access_integrations
FROM snowflake.account_usage.functions
WHERE external_access_integrations IS NOT NULL
  AND deleted IS NULL;

Red flags worth a review ticket: a rule with a wildcard or a broad CIDR, an integration granted to PUBLIC, a secret shared across unrelated integrations, or an integration whose owning role nobody recognises. The mechanics of writing the UDF side are in Calling External APIs from Snowflake; this section is about keeping the allow-list small.

Verifying the whole perimeter

Snowflake ships scanner packages in the Trust Center that check exactly these controls. Turn them on and treat findings as a backlog, not a dashboard.

USE ROLE accountadmin;
GRANT APPLICATION ROLE snowflake.trust_center_admin TO ROLE secops;

-- Findings, highest severity first
SELECT scanner_package, scanner_name, severity, at_risk_entities, suggested_action
FROM snowflake.local.trust_center_findings
ORDER BY DECODE(severity, 'CRITICAL', 1, 'HIGH', 2, 'MEDIUM', 3, 4);

Add three of your own queries to a weekly review:

-- 1. Users with no network policy at all (they inherit the account one — is that OK for service users?)
SELECT name, type, has_password, has_rsa_public_key
FROM snowflake.account_usage.users
WHERE deleted_on IS NULL AND type = 'SERVICE';

-- 2. Successful logins from outside the expected ranges
SELECT user_name, client_ip, reported_client_type, COUNT(*) 
FROM snowflake.account_usage.login_history
WHERE is_success = 'YES'
  AND event_timestamp > DATEADD('day', -7, CURRENT_TIMESTAMP())
  AND NOT (client_ip LIKE '203.0.113.%' OR client_ip LIKE '10.%')
GROUP BY 1, 2, 3 ORDER BY 4 DESC;

-- 3. Policy churn — who changed the perimeter?
SELECT query_text, user_name, role_name, start_time
FROM snowflake.account_usage.query_history
WHERE start_time > DATEADD('day', -30, CURRENT_TIMESTAMP())
  AND (query_text ILIKE '%network policy%' OR query_text ILIKE '%network rule%'
       OR query_text ILIKE '%external access integration%')
ORDER BY start_time DESC;

Rollout order that does not cause an outage

  1. Inventory 30 days of login_history by user, IP, and client type; classify every source.
  2. Create network rules per source class; validate with SYSTEM$VALIDATE_NETWORK_POLICIES.
  3. Attach a permissive account policy (everything you found), confirm nothing breaks for a week.
  4. Tighten per-user policies for service accounts first — they have known, stable sources.
  5. Stand up PrivateLink, verify DNS from every client host, then remove public IP rules class by class.
  6. Register outbound private endpoints; move external access integrations onto private hostnames.
  7. Audit and prune egress rules and integrations; enable Trust Center scanners and assign the findings.
  8. Codify all of it — network rules, policies, and integrations are first-class Terraform resources, so the perimeter should live in Git alongside your RBAC as code.

Done in that order, each step is reversible and no step depends on a change nobody tested.

PowderInsights designs and implements Snowflake security perimeters — network policy rollouts, PrivateLink migrations, egress reviews, and Trust Center remediation — alongside the RBAC and governance work they depend on. Get in touch with your account layout and we will sketch the sequence.