❄️

SnowflakeのAWS PrivateLink接続つまずきポイント

に公開

はじめに

こんにちは。ナウキャストでデータエンジニアをしている島尻です。
普段はSnowflakeを中心としたデータ基盤構築をお客様向けに構築する仕事をしています。

Snowflakeを導入する際、セキュリティ要件が厳しい企業では、インターネットを経由しないプライベートな接続が求められるケースがあります。例えばAWS上でそのような接続を実現するためには、SnowflakeへのPrivateLink設定を適切に行う必要がありますが、公式ドキュメントを見ても意外とつまずくポイントが多く存在します。
https://docs.snowflake.com/ja/user-guide/admin-security-privatelink


AWS PrivateLink構成の一部抜粋イメージ

本記事では、実際の導入過程で遭遇した課題や解決方法を整理し、これからPrivateLink接続を検討している方にとって少しでも参考になる情報をご提供できればと思っています!

以下のような内容を紹介していきます。

  • PrivateLinkの必要性の判断
  • PrivateLink URL接続の仕組み
  • CNAMEレコード登録の詳細
  • つまずきポイント①ネットワークポリシーの適用
  • つまずきポイント②Duoインターフェース変更の影響
  • つまずきポイント③Snowsightのリージョンレス URL
  • つまずきポイント④Streamlit in Snowflakeの名前解決
  • つまずきポイント⑤DNSレコードの管理場所
  • つまずきポイント⑥内部ステージ向けPrivateLinkの設定

PrivateLink接続がそもそも必要か?

Snowflakeの標準的なセキュリティ機能

PrivateLink構成を検討する前に、Snowflakeが標準で提供しているセキュリティ機能を理解しておく必要があります。
https://docs.snowflake.com/en/guides-overview-secure

カテゴリに分けていくつか例を挙げると以下のような機能がサポートされています

1. 暗号化

  • データは転送時・保存時ともにAES-256で暗号化
  • 自動的な暗号化キーのローテーション

2. ネットワークポリシー

  • IP許可リスト/ブロックリストによるアクセス制御

3. 認証・認可

  • SAML SSO、キーペア、多要素認証(MFA)、PATなど多様な認証方式のサポート
  • ロールベースアクセス制御(RBAC)
  • 行レベルセキュリティ、動的データマスキング

4. 監査とコンプライアンス

詳細は割愛しますが、SnowflakeはSaaSとしてこうした広範なセキュリティ機能をネイティブにサポートしているため、オンプレミスでのDB構築よりも容易にセキュアな環境を構築することができます。

PrivateLinkが必要になる特殊なケース

これらの標準セキュリティ機能がある一方で、通信要件としてPrivateLinkでの接続が必要となることもあります。
PrivateLinkが必要なケースは、例えば企業のセキュリティポリシーなどで「顧客データを含むすべての通信は、インターネットを経由せずプライベートネットワーク経由で行うこと」のようなポリシーが明言されているケースです。

これはどちらかといえば特殊なケースと言うことができ、PrivateLinkはSnowflake利用にあたって必須の機能ではなく、特定のセキュリティポリシー等がある場合の選択肢ということを理解しておくことが重要です。

少し前段が長くなりましたが、ここからPrivateLinkの話に入っていきます。
本記事では具体的な手順の解説は行いませんが、以下の記事などが参考になるかと思います。
https://dev.classmethod.jp/articles/snowflake-aws-privatelink-snowflakedb/

標準接続とPrivateLink接続の違い

標準のSnowflake接続(インターネット経由)と、PrivateLink経由での接続ではそもそもの接続URLが異なります。具体的には以下のようなイメージです。

標準接続の場合:

アカウントURL: https://{myorg}-{myaccount}.snowflakecomputing.com

PrivateLink接続の場合:

アカウントURL: https://{myorg}-{myaccount}.privatelink.snowflakecomputing.com

※ {myorg}: 組織名、{myaccount}: アカウント名

つまり、.privatelinkがアカウント名の後ろに付くか否かが違いとなります。これは他のツールから接続する際も同様です。

dbtでのPrivateLink接続設定例

profiles.ymlでの設定例:

my_snowflake_db:
  target: dev
  outputs:
    dev:
      type: snowflake
      account: myorg-myaccount.privatelink  # .privateLinkを追加
      user: dbt_user
      private_key: "{{ env_var('SNOWFLAKE_PRIVATE_KEY') }}"
      role: transformer
      database: analytics
      warehouse: transforming
      schema: dbt_hoge
      threads: 8

TerraformでのPrivateLink接続設定例

provider "snowflake" {

  organization_name = "myorg"
  account_name      = "myaccount.privatelink"  # .privateLinkを追加
  role              = "ACCOUNTADMIN"
  user              = "TERRAFORM_USER"

  authenticator = "SNOWFLAKE_JWT"
...
}

SnowSQLでのPrivateLink接続例

snowsql -a myorg-myaccount.privatelink -u hoge_user


PrivateLink(VPCエンドポイント)までのネットワーク経路は別途確保する必要がありますが、ツール側の設定ではアカウント名の末尾に.privatelinkを追加するだけで、PrivateLink接続に切り替わります。

CNAMEレコード登録の詳細

必要なCNAMEレコードの種類

Snowflakeに対してPrivateLink接続を行う場合、DNSサーバにCNAMEレコードを登録し、各ホスト名をVPCエンドポイントのDNS名に名前解決させる必要があります。
https://docs.snowflake.com/ja/user-guide/admin-security-privatelink#configure-your-vpc-network

SYSTEM$GET_PRIVATELINK_CONFIG関数を実行することで、登録すべきホスト名の一覧を確認することができます。2025年8月時点では以下のホスト名がどのケースでも必要なホスト名になります。
※関数の返却値については、必ず最新のドキュメントを確認してください。

1. アカウント接続用

{orgname}-{accountname}.privatelink.snowflakecomputing.com #regionless-privatelink-account-url
{locator}.{region}.privatelink.snowflakecomputing.com #privatelink-account-url

2. Snowsight接続用 ※regionlessの必要性については後述

app.{region}.privatelink.snowflakecomputing.com #snowsight-privatelink-url
app-{orgname}-{accountname}.privatelink.snowflakecomputing.com #regionless-snowsight-privatelink-url

3. OCSP検証用

ocsp.{orgname}-{accountname}.privatelink.snowflakecomputing.com #regionless-privatelink-ocsp-url
ocsp.{locator}.{region}.privatelink.snowflakecomputing.com #privatelink_ocsp-url

その他、Streamlit in Snowflake(SiS)、Openflow、Snowpark Container Service(SPCS)などを使う時に利用するホスト名もありますので、用途に応じて追加が必要です。

参考までに、Route53でCNAMEレコードを設定する際のTerraformリソースのイメージを以下に記載します。

Route53でのCNAME設定例(Terraform)

# プライベートホストゾーン(PHZ)の作成
resource "aws_route53_zone" "snowflake_privatelink_main" {
  name = "privatelink.snowflakecomputing.com"

  vpc {
    vpc_id = aws_vpc.{VPCリソース名}.id
  }

  tags = {
    Name = "PHZ for Snowflake PrivateLink Main"
  }
}

# アカウント接続用CNAMEレコード
resource "aws_route53_record" "snowflake_private_zone_record_main_regionless_account_url" {
  zone_id = aws_route53_zone.snowflake_privatelink_main.zone_id
  name    = "myorg-myaccount"
  type    = "CNAME"
  ttl     = 300
  records = [aws_vpc_endpoint.{VPCエンドポイントリソース名}.dns_entry[0].dns_name]
}

resource "aws_route53_record" "snowflake_private_zone_record_main_account_url" {
  zone_id = aws_route53_zone.snowflake_privatelink_main.zone_id
  name    = "${var.locator}.ap-northeast-1" # locatorは具体的なロケーター名、リージョンは環境に合わせる
  type    = "CNAME"
  ttl     = 300
  records = [aws_vpc_endpoint.{VPCエンドポイントリソース名}.dns_entry[0].dns_name]
}

# Snowsight接続用CNAMEレコード(共通URLのみ)
resource "aws_route53_record" "snowflake_private_zone_record_main_snowsight_url" {
  zone_id = aws_route53_zone.snowflake_privatelink_main.zone_id
  name    = "app.ap-northeast-1" # locatorは具体的なロケーター名、リージョンは環境に合わせる
  type    = "CNAME"
  ttl     = 300
  records = [aws_vpc_endpoint.{VPCエンドポイントリソース名}.dns_entry[0].dns_name]
}

# OCSP検証用CNAMEレコード
resource "aws_route53_record" "snowflake_private_zone_record_main_ocsp_url" {
  zone_id = aws_route53_zone.snowflake_privatelink_main.zone_id
  name    = "ocsp.${var.locator}.ap-northeast-1" # locatorは具体的なロケーター名、リージョンは環境に合わせる
  type    = "CNAME"
  ttl     = 300
  records = [aws_vpc_endpoint.{VPCエンドポイントリソース名}.dns_entry[0].dns_name]
}

resource "aws_route53_record" "snowflake_private_zone_record_main_regionless_ocsp_url" {
  zone_id = aws_route53_zone.snowflake_privatelink_main.zone_id
  name    = "ocsp.myorg-myaccount"
  type    = "CNAME"
  ttl     = 300
  records = [aws_vpc_endpoint.{VPCエンドポイントリソース名}.dns_entry[0].dns_name]
}


以降、PrivateLink構成を構築する際につまずきそうなポイントをいくつか挙げていきます!

つまずきポイント①ネットワークポリシーの適用

「PrivateLinkを設定すれば自動的にパブリックアクセスがブロックされる」

これは誤解です。PrivateLinkを設定しただけでは接続経路は制御できておらず、従来のインターネット経由のSnowflakeアクセスも並行して可能な状態が続きます。

PrivateLink経由の接続のみを許可しパブリックアクセスをブロックするためには、Snowflake側でネットワークポリシーを明示的に設定し、以下を実装する必要があります:

  1. AWSVPCEID による許可: 特定のVPCエンドポイント経由のアクセスのみ許可
  2. IPv4による全ブロック: 0.0.0.0/0を明示的にブロック

https://docs.snowflake.com/ja/user-guide/network-policies#interaction-between-allowed-lists-and-blocked-lists

ネットワークポリシーの設定例(Terraform)

# VPCエンドポイント経由のアクセスを許可するネットワークルール
resource "snowflake_network_rule" "network_rule_private_link_main" {
  name       = "NETWORK_RULE_PRIVATELINK_MAIN"
  database   = snowflake_database.${var.database_name}.name # ネットワークルール管理用のDBリソース名
  schema     = snowflake_schema.${var.schema_name}.name # ネットワークルール管理用のschemaリソース名
  comment    = "network rule for private link"
  type       = "AWSVPCEID"
  mode       = "INGRESS"
  value_list = var.snowflake_privatelink_vpc_endpoint_ids # PrivateLinkで利用するVPCエンドポイントのエンドポイントID
}

# VPCエンドポイント経由のアクセスを許可するネットワークルール(内部ステージ用)
resource "snowflake_network_rule" "network_rule_private_link_internal_stage" {
  name       = "NETWORK_RULE_PRIVATELINK_INTERNAL_STAGE"
  database   = snowflake_database.${var.database_name}.name # ネットワークルール管理用のDBリソース名
  schema     = snowflake_schema.${var.schema_name}.name # ネットワークルール管理用のschemaリソース名
  comment    = "network rule for private link internal stage"
  type       = "AWSVPCEID"
  mode       = "INTERNAL_STAGE"
  value_list = var.s3_vpc_endpoint_ids # 内部ステージ用PrivateLinkで利用するVPCエンドポイントのエンドポイントID
}

# パブリックアクセスをブロックするネットワークルール
resource "snowflake_network_rule" "network_rule_block_ingress" {
  name       = "NETWORK_RULE_BLOCK_INGRESS"
  database   = snowflake_database.${var.database_name}.name # ネットワークルール管理用のDBリソース名
  schema     = snowflake_schema.${var.schema_name}.name # ネットワークルール管理用のschemaリソース名
  comment    = "network rule for block ip address ingress"
  type       = "IPV4"
  mode       = "INGRESS"
  value_list = ["0.0.0.0/0"]
}

# ネットワークポリシーの作成
resource "snowflake_network_policy" "network_policy_account" {
  name = "NETWORK_POLICY_ACCOUNT"
  allowed_network_rule_list = [
    snowflake_network_rule.network_rule_private_link_main.fully_qualified_name,
    snowflake_network_rule.network_rule_private_link_internal_stage.fully_qualified_name
  ]
  blocked_network_rule_list = [
    snowflake_network_rule.network_rule_block_ingress.fully_qualified_name
  ]
  comment = "network policy for account"
}

# アカウントレベルでネットワークポリシーを適用
resource "snowflake_network_policy_attachment" "network_policy_attachment_account" {
  network_policy_name = snowflake_network_policy.network_policy_account.name
  set_for_account     = true
}

なお、上記はパブリックアクセスを完全にブロックする際の設定イメージですが、パブリックIPからの接続を一部許可させる場合にはtype = "IPV4"で明示的に許可するルールを追加する必要があります。

つまずきポイント②Duoインターフェース変更の影響

2025_01バンドルでの変更内容とその意外な影響

2025年1月のbehavior changeで、MFAのDuoインターフェースが従来のDuo PromptからDuo Universal Promptに変更されました。
https://docs.snowflake.com/en/release-notes/bcr-bundles/2025_01/bcr-1875

実は上記リンクのドキュメントにも小さく書いてあるのですが、
この変更により、アカウント名にアンダースコアが含まれ、かつPrivateLink接続を使用している場合に追加のDNS設定が必要になりました。
具体的にはアンダースコア(_)をハイフン(-)に置き換えたホスト名が追加で使用されます。

If your account name contains an underscore and you use private connectivity, you need to add a DNS entry that includes a dash instead of an underscore in the account URL.
For example, if your account name is account_dev and your organization is myorg, then add an entry like myorg-account-dev.privatelink.snowflakecomputing.com.

例:

  • アカウント名: myaccount_dev
  • 組織名: myorg
  • 必要なDNSエントリ: myorg-myaccount-dev.privatelink.snowflakecomputing.com

対応が必要なDNS設定

従来のアンダースコア形式に加えて、ハイフン形式のCNAMEレコードを追加する必要があります:

# 従来のアンダースコア形式(既存、こちらも残します)
resource "aws_route53_record" "snowflake_private_zone_record_main_regionless_account_url" {
  zone_id = aws_route53_zone.snowflake_privatelink_main.zone_id
  name    = "myorg-myaccount_dev"
  type    = "CNAME"
  ttl     = 300
  records = [aws_vpc_endpoint.{VPCエンドポイントリソース名}.dns_entry[0].dns_name]
}

# 新しいハイフン形式(追加)
resource "aws_route53_record" "snowflake_private_zone_record_main_regionless_account_url_for_duo_mfa" {
  zone_id = aws_route53_zone.snowflake_privatelink_main.zone_id
  name    = "myorg-myaccount-dev"
  type    = "CNAME"
  ttl     = 300
  records = [aws_vpc_endpoint.{VPCエンドポイントリソース名}.dns_entry[0].dns_name]
}

実際に使用されるホスト名については、SYSTEM$GET_PRIVATELINK_CONFIG関数privatelink-dashed-urls-for-duoで対応する値を確認してください。

つまずきポイント③Snowsightのリージョンレス URL

複数アカウント環境での課題

devアカウントとprodアカウント等、同一リージョンで複数のSnowflakeアカウントを運用するケースは多くあると思います。
この際、SYSTEM$GET_PRIVATELINK_CONFIG関数で取得されるsnowsight-privatelink-urlが同一の値になってしまいます。

東京リージョンの例:

app.ap-northeast-1.privatelink.snowflakecomputing.com

リージョンレス URLによる解決

Snowflakeサポートに依頼することで、regionless-snowsight-privatelink-urlを有効化できます。これにより、アカウント固有のURLが利用可能になります:

app-{組織名}-{アカウント名}.privatelink.snowflakecomputing.com

こちらについては以前私が検証した際の記事が残っていますので、詳細については以下の記事をご参照ください。
https://zenn.dev/rshimajiri/articles/5acf9f1a0ae220

つまずきポイント④Streamlit in Snowflakeの名前解決

PrivateLink環境下でのStreamlit in Snowflake(SiS)利用はサポートされていますが、
2025年8月時点で、SiSはアカウント固有のURL設定をサポートしていません

SnowsightのようにリージョンレスURLが利用できないため、以下の条件が揃う場合に影響があります。

  • 複数アカウントでPrivateLink環境が必要
  • 該当複数アカウントは同一リージョンに存在する
  • SiSをPrivateLink経由で利用
  • CNAMEの名前解決を行うDNSサーバを1箇所に集約している

特に最後の4つ目が注目ポイントで、
AWSアカウント側も各環境ごとに独立していてそれぞれのRoute 53で名前解決を行う場合などは問題ありませんが、DNSサーバをオンプレミス側で管理するなど、CNAMEレコードを1箇所に集約している場合に影響が出ます

これはCNAMEが同一のホスト名のレコードを登録できないためであり、DNS自体の仕様であるため現状回避策はありません。

Snowflakeサポートに、アカウント固有のSiS用ホスト名がサポートしているかも問い合わせましたが、現状未対応との回答でした。(2025年8月時点)

つまずきポイント⑤DNSレコードの管理場所

AWSでPrivateLink接続を構築する場合、Route53での名前解決が最も汎用的な選択肢となります。

利点:

  • VPCとの連携が容易
  • プライベートホストゾーンの管理が容易
  • Terraform等のIaCツールでの管理が可能
    • VPCエンドポイントのDNS名が変更された場合(再作成など)、リソース間の参照関係により自動的にCNAMEレコードも更新される

一方で、オンプレミスからDirectConnect経由でVPCに接続する要件があるようなケースでは、オンプレミス側のDNSサーバで管理したいということがあります。その場合には、以下の考慮事項も踏まえて選択する必要があります。

オンプレミスDNSでの考慮事項

  • 複数アカウントに対するSiS用CNAMEレコードが登録できない問題
    • PrivateLink経由でのSiS利用はいずれかのアカウントに限定する必要あり
  • VPCエンドポイントのDNS名が変更された場合には手動で変更する必要あり


オンプレミスからの接続を含む構成の一部抜粋イメージ

つまずきポイント⑥内部ステージ向けPrivateLinkの設定

初めに結論を述べておくと、ユーザーが明示的にSnowflakeの内部ステージを作成していない場合でも、内部ステージ向けPrivateLinkは必須です。これに関わる仕様と具体的な設定内容について簡単に説明します。

大きなクエリ結果セット取得に関する仕様

これは、Snowflakeの裏側の仕様としてクエリ結果セットが100KB以上の場合、結果データが内部ステージ経由でダウンロードされるということが関係しています。

そのため、内部ステージ向けのPrivateLink設定が不足している場合、以下のような現象が発生する可能性があります:

  • 小さなクエリ結果(100KB未満)は正常に取得できる
  • 大きなクエリ結果(100KB以上)でタイムアウトやSSL証明書等のエラーが発生

公式ドキュメントには明記されていませんが、以下のドキュメントはこの仕様のヒントになります。
https://docs.snowflake.com/ja/user-guide/client-connectivity-troubleshooting/common-issues#fetching-large-query-result-sets-failures

場合によっては、クライアントは小さなクエリ結果を取得できても、大きな結果を取得するのに苦労することがあります。これは大きな結果(100KB超)を取得するには、クライアントがネットワークにフルアクセスして、すべての STAGE エンドポイントに対して証明書のパススルーを行う必要があるためです。

https://docs.snowflake.com/ja/user-guide/querying-persisted-results

大規模な 保存済みのクエリ結果(つまり、サイズが100 KB以上)にアクセスするために使用するセキュリティトークンは、6時間後に期限切れになります。結果がまだキャッシュにある間に、結果にアクセスするための新しいトークンを取得できます。より小さい保存済みのクエリ結果は、アクセストークンを使用しません。

※1つ目のドキュメントの記載から分かるように、PrivateLink構成に限らず、社内プロキシ等で社外への通信を制限している場合は同じエラーが発生する可能性があります。この場合、ドキュメントに従って内部ステージが使用するホスト名を許可することで解決できます。

ここで紹介した内部ステージの動作については、以下の記事が分かりやすく、参考になります
https://qiita.com/abe_masanori/items/4c092a1383b248266c53

必要な設定

内部ステージ向けのPrivateLink設定は、以下のドキュメントに従って実施します。
https://docs.snowflake.com/ja/user-guide/private-internal-stages-aws#label-aws-privatelink-internal-stage-network-isolation

ドキュメントにもあるように、実装戦略としては以下の2パターンがありますが、「2.」の方が簡潔に実装できます。CNAMEレコードの登録が不要で、Snowflakeのアカウントパラメータで制御可能です。

  1. インターフェイスエンドポイントによる内部ステージへのアクセス
  2. 専用インターフェイスエンドポイントによる内部ステージへのアクセス

「2.」パターンのアカウントパラメータの設定例(Terraform)

resource "snowflake_account_parameter" "enable_internal_stages_privatelink" {
  key   = "ENABLE_INTERNAL_STAGES_PRIVATELINK"
  value = "true"
}

resource "snowflake_account_parameter" "s3_stage_vpce_dns_name" {
  key   = "S3_STAGE_VPCE_DNS_NAME"
  value = "*.vpce-xxxx-yyyy.s3.ap-northeast-1.vpce.amazonaws.com" #内部ステージS3向けのVPCエンドポイントDNS名
}

まとめ

SnowflakeのPrivateLink接続は、適切に構成することで強固なセキュリティを実現できますが、多くのつまずきポイントが存在します。セキュリティ要件としてPrivateLink接続が必要なケースでは、本記事で紹介した設定例なども参考にしながら、環境に適した構成を検討してください。

Finatext Tech Blog

Discussion