In previous articles, we have discussed how to set up private connectivity with Snowflake for both Azure and AWS:
On its own, private connectivity is an excellent way to secure traffic between your network and Snowflake. However, this does not take you all the way down the path to having a secure data environment. Once private connectivity has been configured for your Snowflake account, it is important to then create network rules that ensure this connectivity is used and to apply these to the account or to specific users by applying network policies.
If you do not take this step, your efforts to establish private connectivity may be wasted, as users can still avoid this configuration and access Snowflake through the standard public internet.
What Are Network Rules?
In short, network rules are objects that store information on a type of network traffic for Snowflake. This traffic can be one of two categories:
- Ingress – Traffic from outside of Snowflake that is coming in to Snowflake
- Egress – Traffic that is leaving Snowflake and travelling outside
This article specifically focuses on the ingress category and how we can leverage this to restrict inbound traffic to Snowflake to specific private endpoints. Note that in Snowflake’s terminology, the category (ingress/egress) is referred to as the “mode,” and this terminology will be used for the rest of this article.
Ingress Mode – Traffic Types
There are three types of inbound traffic that Snowflake can leverage for network rules:
IPV4
– The standard internetAWSVPCEID
– Private endpoints established via AWS PrivateLinkAZURELINKID
– Private endpoints established via Azure Private Link
Since private connectivity can only be established for Snowflake platforms in the same cloud provider, we only need to worry about two of these types at any time. Either the Snowflake account is hosted in Azure, and the only possible types are IPV4 and AZURELINKID; or the Snowflake account is hosted in AWS, and the only possible types are IPV4 and AWSVPCEID.
When creating a network rule, the type is paired with a list of values. For example, if the type is IPV4
then each value in the list must either be an IP address or a range of addresses provided as a CIDR block. When creating a network rule for private endpoints via AWS or Azure, then each value in the values list must be Snowflake’s internal ID for that private endpoint.
An Important Note
An important piece of information to note is that network rules will only ever act on traffic within their specific traffic type. So if a network rule is created for IPv4 that matches a specific IP range, then traffic coming through a different type but in that same IP range will not be matched.
For example, consider a network rule of type IPV4
that matches the IP address 10.10.10.10
. Any inbound traffic that travels via the public internet using IPv4 from the IP address 10.10.10.10
will be matched. However, this IP address may belong to a user that is connecting to Snowflake using private connectivity. In that case, the type of their connection would be AZURELINKID
or AWSVPCEID
, not IPV4
. In this scenario, even though the connection comes from the IP address of 10.10.10.10
, the traffic does not match against the the IPV4
rule as the traffic is not of that type.
Example Network Rule – IPv4
Here is a simple example for creating an IPV4
network rule:
create network rule if not exists "ALL_IPV4" type = IPV4 mode = INGRESS value_list = ('0.0.0.0/0') comment = 'Network rule that can match against the full range of IPv4 addresses, i.e. all public network traffic. This can be included in blocked lists for private connectivity policies' ;
This network rule uses CIDR notation to span the full range of IP addresses. This means that any connection to Snowflake that comes across the public internet would match against this rule.
Retrieving Snowflake’s Identifier for a Private Endpoint
When creating a network rule for private endpoints via AWS or Azure, then each value in the values list must be Snowflake’s internal ID for that private endpoint. Once you have established private connectivity to Snowflake (see the two articles linked at the top of this page), you can then execute the following command to see Snowflake’s metadata for the endpoints:
select SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS();
This will provide an output similar to the following, containing details for each private endpoint that has been authorised:
[ { "endpointId": "/subscriptions/a123bc45-67de-89f1-2345-ab6789cd1ef2/resourcegroups/my-resource-group-rg/providers/microsoft.network/privateEndpoints/pep-snowflake-private-link-domain-a", "endpointIdType": "Azure Endpoint Connection Id", "linkIdentifier": "123456789" }, { "endpointId": "/subscriptions/a123bc45-67de-89f1-2345-ab6789cd1ef2/resourcegroups/my-resource-group-rg/providers/microsoft.network/privateEndpoints/pep-snowflake-private-link-domain-b", "endpointIdType": "Azure Endpoint Connection Id", "linkIdentifier": "678912345" }, { "endpointId": "/subscriptions/a123bc45-67de-89f1-2345-ab6789cd1ef2/resourcegroups/my-resource-group-rg/providers/microsoft.network/privateEndpoints/pep-snowflake-private-link-matillion", "endpointIdType": "Azure Endpoint Connection Id", "linkIdentifier": "987654321" } ]
In our example, we have three private endpoints in this list. Each one has an “endpointId,” an “endpointIdType” and a “linkIdentifier.” This example is using an Azure-hosted Snowflake account, but the output will be similar for AWS-hosted Snowflake accounts.
The “linkIdentifier” in the output above is the value that must be provided to the values list in the network rule.
Example Network Rule – Azure Link ID
Here is a simple example for creating an AZURELINKID
network rule using the first “linkIdentifier” in the example output above:
create network rule if not exists "MY_PRIVATE_ENDPOINT_FOR_GENERIC_USERS" type = AZURELINKID value_list = ('123456789') comment = 'Matches against the specific private endpoint my-private-endpoint-for-generic-users' ;
This network rule includes Snowflake’s identifier for a specific Azure private endpoint within its value list. This means that any connection to Snowflake that comes via that private endpoint would match against this rule.
What Are Network Policies?
Network policies are mechanisms in Snowflake that can be used to control which IP whitelisting and network rules are applied to the account or to specific users.
It is important to note that Snowflake stores network policies as account-level objects. This is different to network rules, which are stored as database objects, meaning they sit inside schemas in databases.
There are two main components of a network policy:
- allowed_network_rule_list – The list of network rules where the policy will allow access for matching traffic
- blocked_network_rule_list – The list of network rules where the policy will block access for matching traffic
Here are some important details to note:
- The above options can be used to attach combinations of network rules to a network policy, resulting in a final policy that only allows traffic that meets the requirements of both allowed and blocked lists.
- If the same value is provided in both the blocked and allowed lists, the blocked value will take precedence and access for that value will be blocked.
- In general, the list of allowed network rules is intended to be restrictive where possible. That is to say, if a specific list of allowed network rules is provided, then any traffic of that mode that does not meet one of the allowed rules is automatically blocked without requiring a specific value in the blocked list. A blocked list is therefore only required in two scenarios:
- You wish to block traffic from another mode. For example, if your allowed network rule is of the mode
AZURELINKID
then you must explicitly block all traffic of the modeIPV4
using the blocked list. - You wish to block traffic in a subgroup of the allowed values, such as blocking a smaller range of IP addresses inside a larger allowed range of IP addresses.
- You wish to block traffic from another mode. For example, if your allowed network rule is of the mode
In addition to the above, it is also possible to provide IPv4 ranges directly in allowed/blocked lists. This can be used both instead of or in addition to network rules.
- allowed_ip_list – The list of IPv4 ranges where the policy will allow access for matching traffic
- blocked_ip_list – The list of IPv4 ranges where the policy will block access for matching traffic
How to Apply Network Policies
Network policies can be applied either to the entire account or to specific users. The approach for each is the same, leveraging an alter
statement.
It is important to note that user-level network policies take precedence over account-level network policies. If a network policy has been applied to a specific user, that user must only meet the requirements of their specific access policy, and the account-level access policy is ignored.
The following example creates a simple network policy and applies it to a specific user:
-- Create the network policy create network policy "NETPOL__USER_A" allowed_ip_list = ( '10.10.10.10' -- Example IP address for the specific user ) allowed_network_rule_list = ('MY_PRIVATE_ENDPOINT_FOR_GENERIC_USERS') comment = 'Allows access through the private endpoint for generic users, further restricting traffic to the specific IP address of user A.' ; -- Apply the network policy alter user "USER_A" set network_policy = 'NETPOL__USER_A' ;
Be careful when applying account-wide policies as these also apply to your own user. Our best practice advice when applying account-wide policies is to first apply the policy for a specific test user and make sure everything is working correctly, to avoid accidentally locking yourself out when you apply the global policy. If you want to be more cautious, you could also create a temporary “backdoor” user with a policy that does not restrict access at all, that you can then disable again once you are happy with the performance of the account-wide policy.
How to Restrict Snowflake Access to Specific Private Endpoints Using Network Rules and Policies
As indicated in point (3.1) in the important details for network policies above, if you wish to block all access that does not come via private endpoint, it is not sufficient to only place a private link network rule in the allowed list. You must also either add the full range of IPs to an IPV4 network rule in the blocked list of your network policy, or you must create an IPV4 network rule that only allows specific IP ranges and add this to the allowed list of your network policy.
In other words, three components are required:
- Network rule(s) that align with the private endpoint(s) for which access should be granted
- A network rule that aligns with the full range of public IPv4 addresses, spanning the entire public internet
- A network policy that is configured to allow access using the private endpoint network rules and block access using the IPv4 network rule
As luck would have it, we have already created some example network rules earlier in this article that directly apply to this scenario!
A Simple Example for an Account-Wide Network Policy
This simple example combines the elements that we have already constructed earlier in the article, to create a network policy that only allows access through specific private endpoints, and blocks any traffic that does not come via private endpoint:
-------------------------------------------------------- -- Environment use role "ACCOUNTADMIN"; create database if not exists "ACCOUNT_ADMIN"; create schema if not exists "ACCOUNT_ADMIN"."NETWORK_RULES"; use schema "ACCOUNT_ADMIN"."NETWORK_RULES"; -------------------------------------------------------- -- Network Rules -- View all network rules show network rules; -- View all authorized private link endpoints (does not include managed endpoints) select SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS(); -- Specific Azure private endpoint - Generic users in domain A create network rule if not exists "MY_PRIVATE_ENDPOINT_FOR_GENERIC_USERS" type = AZURELINKID value_list = ('123456789') comment = 'Restricts access to the private endpoint my-private-endpoint-for-generic-users' ; -- Block all IPV4 create network rule if not exists "ALL_IPV4" type = IPV4 mode = INGRESS value_list = ('0.0.0.0/0') comment = 'Network rule that can match against the full range of IPv4 addresses, i.e. all public network traffic. This can be included in blocked lists for private connectivity policies' ; -------------------------------------------------------- -- Network Policies -- View all network policies show network policies; -- Generic user access create network policy "NETPOL__GENERIC_USER" allowed_ip_list = ( '<internal subnet>' -- Subnet for users in internal network ) allowed_network_rule_list = ('MY_PRIVATE_ENDPOINT_FOR_GENERIC_USERS') blocked_network_rule_list = ('ALL_IPV4') comment = 'Allows general user access through the private endpoint for generic users, further restricting traffic to the subnet for users in the internal network. Blocks any access that comes through the public internet.' ; desc network policy "NETPOL__GENERIC_USER"; -------------------------------------------------------- -- Network Policy Assignment -- Generic user access alter account set network_policy = 'NETPOL__GENERIC_USER' ;
A More Complex Example Demonstrating User-Specific Exceptions
To wrap up, here is a more complex example that demonstrates applying an account-wide network policy for standard users with a user-specific policy to further restrict traffic for a specific service principal that is being used for Matillion:
-------------------------------------------------------- -- Environment use role "ACCOUNTADMIN"; create database if not exists "ACCOUNT_ADMIN"; create schema if not exists "ACCOUNT_ADMIN"."NETWORK_RULES"; use schema "ACCOUNT_ADMIN"."NETWORK_RULES"; -------------------------------------------------------- -- Network Rules -- View all network rules show network rules; -- View all authorized private link endpoints (does not include managed endpoints) select SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS(); -- Specific Azure private endpoint - Generic users in domain A create network rule if not exists "MY_PRIVATE_ENDPOINT_FOR_GENERIC_USERS_A" type = AZURELINKID value_list = ('123456789') comment = 'Restricts access to the private endpoint my-private-endpoint-for-generic-users-a' ; -- Specific Azure private endpoint - Generic users in domain B create network rule if not exists "MY_PRIVATE_ENDPOINT_FOR_GENERIC_USERS_B" type = AZURELINKID value_list = ('678912345') comment = 'Restricts access to the private endpoint my-private-endpoint-for-generic-users-b' ; -- Specific Azure private endpoint - Matillion create network rule if not exists "MY_PRIVATE_ENDPOINT_FOR_MATILLION" type = AZURELINKID value_list = ('987654321') comment = 'Restricts access to the private endpoint my-private-endpoint-for-matillion' ; -- Block all IPV4 create network rule if not exists "ALL_IPV4" type = IPV4 mode = INGRESS value_list = ('0.0.0.0/0') comment = 'Network rule that can match against the full range of IPv4 addresses, i.e. all public network traffic. This can be included in blocked lists for private connectivity policies' ; -------------------------------------------------------- -- Network Policies -- View all network policies show network policies; -- Matillion VM create network policy "NETPOL__MATILLION_ETL" allowed_ip_list = ('<internal Matillion ETL IP>') allowed_network_rule_list = ('MY_PRIVATE_ENDPOINT_FOR_MATILLION') blocked_network_rule_list = ('ALL_IPV4') comment = 'Limits access to the internal VM running Matillion ETL via private connectivity, restricting traffic to the Matillion ETL virtual machine itself. Blocks any access that comes through the public internet.' ; desc network policy "NETPOL__MATILLION_VM"; -- Generic user access create network policy "NETPOL__GENERIC_USER" allowed_ip_list = ( '<internal subnet A>' -- Subnet for users in domain A , '<internal subnet B>' -- Subnet for users in domain B ) allowed_network_rule_list = ( 'MY_PRIVATE_ENDPOINT_FOR_GENERIC_USERS_A' , 'MY_PRIVATE_ENDPOINT_FOR_GENERIC_USERS_B' ) blocked_network_rule_list = ('ALL_IPV4') comment = 'Allows general user access through the private endpoints for generic users in domains A and B, further restricting traffic to the internal subnets for domains A and B. Blocks any access that comes through the public internet.' ; desc network policy "NETPOL__GENERIC_USER"; -------------------------------------------------------- -- Network Policy Assignment -- Matillion ETL service principal alter user "SVC__MATILLION_ETL" set network_policy = 'NETPOL__MATILLION_ETL' ; -- Generic user access alter account set network_policy = 'NETPOL__GENERIC_USER' ;