❄️

外部ステージS3向けのOutbound PrivateLinkを試してみた

2025/02/28に公開

はじめに

Snowflakeの2025年1月リリースのVer.9.1にて、SnowflakeからPrivateLinkを利用してS3外部ステージに接続できるようになりました(以下この機能を「Outbound PrivateLink」と呼びます)。
https://docs.snowflake.com/en/user-guide/data-load-aws-private
※利用にはBusiness Criticalエディションが必要です

構成図として示すと、以下の赤枠のところが今回新たにできるようになっています。


今回はOutbound PrivateLinkを検証し、以下の2点を確認することにしました。

  • 公式ドキュメント上はアンロード時の経路をメインで書いているが、Snowflakeへのロード時に外部ステージを参照する際の経路もきちんとPrivateLinkにできるか

  • ストレージ統合とOutbound PrivateLinkを組み合わせて、以下2点を両立させるようなIAMポリシーでの制御が可能になるか(公式ドキュメントではバケットポリシーでの制御)
    • ストレージ統合によってSnowflake側のIAMユーザーからのアクセスのみを許可
    • Outbound PrivateLinkで使用するVPCエンドポイントIDからのアクセスのみ許可

検証前提

今回の検証構成では、Snowflakeに対するPrivateLinkの方は構成していません。これはSnowflakeに対するPrivateLinkがなくても、SnowflakeからのOutbound PrivateLinkは独立して構成可能であることを明確にするためです。

ただ、Outbound PrivateLinkはBusiness Criticalエディションでないと使用できないため、その点は注意が必要です。

Snowflakeに対するPrivateLinkが構成されていないことは、SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS関数を実行することで確認できます。

承認済みのVPCエンドポイントがないことが分かる

検証準備(ストレージ統合)

まずは検証の準備としてストレージ統合を構成します。詳細手順は省略しますので、適宜以下のドキュメントをご確認ください。
https://docs.snowflake.com/ja/user-guide/data-load-s3-config-storage-integration

以下に、ストレージ統合を行う上で作成した各オブジェクトの内容を記載します。

外部ステージとして利用するS3バケットとプレフィックス:


IAMポリシー:

IAMポリシーのJSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:GetObjectVersion",
                "s3:DeleteObject",
                "s3:DeleteObjectVersion"
            ],
            "Resource": "arn:aws:s3:::{バケット名}/{プレフィックス}/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": "arn:aws:s3:::{バケット名}",
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "{プレフィックス}/*"
                    ]
                }
            }
        }
    ]
}


IAMロール:

IAMロールのJSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "{Snowflake側のIAMユーザーのARN}"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "sts:ExternalId": "Snowflake側で取得した外部ID"
                }
            }
        }
    ]
}


Snowflakeのストレージ統合オブジェクト

ストレージ統合DDL
CREATE STORAGE INTEGRATION s3_privatetest
  TYPE = EXTERNAL_STAGE
  STORAGE_PROVIDER = 'S3'
  ENABLED = TRUE
  STORAGE_AWS_ROLE_ARN = 'ストレージ統合で使用するIAMロールのARN'
  STORAGE_ALLOWED_LOCATIONS = ('s3://snowflake-rshimajiri/PrivateTest/')
;


Outbound PrivateLink構成

ここからは以下のドキュメントに従ってOutbound PrivateLinkを構成していきます。
https://docs.snowflake.com/en/user-guide/data-load-aws-private

まずはSYSTEM$PROVISION_PRIVATELINK_ENDPOINT関数を実行し、Snowflake側のVPC上にVPCエンドポイントをプロビジョニングします。
※以下はAWS東京リージョンのSnowflakeを利用している例です

USE ROLE ACCOUNTADMIN;
SELECT SYSTEM$PROVISION_PRIVATELINK_ENDPOINT(
    'com.amazonaws.ap-northeast-1.s3',
    '*.s3.ap-northeast-1.amazonaws.com');

ワイルドカードを使用していますが、公式ドキュメントによれば全てのS3バケットがアクセス対象になってしまうわけではなく、Outbound PrivateLinkでの接続が有効な外部ステージから参照されるバケットのみが、VPCエンドポイント経由でアクセスできるとのことです。
https://docs.snowflake.com/en/user-guide/private-manage-endpoints-aws#provision-private-connectivity-endpoints

次に、SYSTEM$GET_PRIVATELINK_ENDPOINTS_INFO関数を実行します。この関数によって、VPCエンドポイントのプロビジョニングのステータスを確認できます。

SELECT SYSTEM$GET_PRIVATELINK_ENDPOINTS_INFO();


上のキャプチャでは見切れてしまっていますが、返却値の最後が"status\":\"available\"となっていれば、無事にVPCエンドポイントがプロビジョニングされているということになります。

続いて、Snowflake側でプロビジョニングしたVPCエンドポインからのみバケットアクセスを許可するようなIAMポリシーを作成します。公式ドキュメント上ではバケットポリシー上で制限をかけていますが、今回の検証ではIAMポリシー上で制御する形を取ります。
すでにストレージ統合の手順でIAMポリシーは作成済みであるため、既存のIAMポリシーにステートメントを追加します。

ステートメント追加後のIAMポリシーのJSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:GetObjectVersion",
                "s3:DeleteObject",
                "s3:DeleteObjectVersion"
            ],
            "Resource": "arn:aws:s3:::{バケット名}/{プレフィックス}/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": "arn:aws:s3:::{バケット名}",
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "{プレフィックス}/*"
                    ]
                }
            }
        },
        {
            "Effect": "Deny",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::{バケット名}",
                "arn:aws:s3:::{バケット名}/{プレフィックス}/*"
            ],
            "Condition": {
                "StringNotEquals": {
                    "aws:SourceVpce": "{Snowflake側でプロビジョニングしたVPCエンドポイントのID}"
                }
            }
        }
    ]
}


次に、ストレージ統合オブジェクトでOutbound PrivateLinkの使用を有効化します。今回、ストレージ統合オブジェクトは作成済みのため、以下のALTER文を実行します。

ALTER STORAGE INTEGRATION s3_privatetest
  SET USE_PRIVATELINK_ENDPOINT = true;



最後に、ストレージ統合を使用した外部ステージを作成します。これで、Outbound PrivateLink経由での外部ステージが作成できたことになります。

CREATE OR REPLACE STAGE my_storage_private_stage
  URL = 's3://snowflake-rshimajiri/PrivateTest/'
  STORAGE_INTEGRATION = s3_privatetest;



Outbound PrivateLinkを利用したアンロード・ロード

IAMポリシーの権限としては、アンロードもロードも可能になっているため、両方試してみます。

まずはアンロードを試します。
Snowflakeで最初から利用できるTPCHのサンプルデータから、1000行の簡単なcustomerテーブルを作成しておきます。


このcustomerテーブルのデータを、先ほど作成した外部ステージに対してアンロードしてみます。

COPY INTO @my_storage_private_stage
  FROM customer;

問題なくアンロードできました。
もちろん、外部ステージで指定したS3バケットのプレフィックス上にファイルが置かれていることも確認できます。


続いて、S3からSnowflakeにロードしてみます。
先ほどのcustomerテーブルに以下のCSVファイルで1行分のデータを追加することを試します。

C_CUSTKEY,C_NAME,C_ADDRESS,C_NATIONKEY,C_PHONE,C_ACCTBAL,C_MKTSEGMENT,C_COMMENT
61001,Customer#000061001,zwkHSjRJYn9yMpk3gWuXkULpteHpSoXXCWXiFOT,3,13-146-110-4664,9684.47,FURNITURE,e of the regular deposits. carefully pending packages boost blithely about the pending ideas. ironic packages 


先ほどのアンロードしたデータは削除済みです

以下のSQL文でSnowflakeにデータをロードします。

COPY INTO customer FROM @my_storage_private_stage
  FILE_FORMAT = (TYPE = CSV FIELD_DELIMITER = ',' SKIP_HEADER = 1);

無事にロードも通りました!
ちゃんとテーブルにデータが格納されていることもわかります。



以上より、アンロード・ロードともに、Outbound PrivateLinkを利用してファイルの書き込みや参照を行えていることが分かりました。

Outbound PrivateLinkの費用

Outbound PrivateLinkでは、Snowflake側でVPCエンドポイントをプロビジョニングすることになり、そのための費用が発生します。
費用の要素としては以下の2点です。

  • プロビジョニングしている時間による課金
    • 例:AWS東京リージョンでは1000時間(≒41.7日)ごとに$14.00
  • 処理データ量による課金
    • 例:AWS東京リージョンでは1TBごとに$10.24

参考:https://www.snowflake.com/legal-files/CreditConsumptionTable.pdf

また、費用の確認は2025/02/26現在、ORGANIZATION_USAGEでのみ確認できるようです。
例えば、以下のようにクエリを実行することで、プロビジョニング時間による課金額を確認することができました。

SELECT
    *
FROM
    snowflake.organization_usage.usage_in_currency_daily
WHERE
    service_type = 'OUTBOUND_PRIVATELINK_ENDPOINT';

service_type = 'OUTBOUND_PRIVATELINK_DATA_PROCESSED'では結果が返らず、確認できませんでした、、、

なお、2025/02/26現在、以下のドキュメントにはACCOUNT_USAGEでも費用確認ができるような記載になっていますが、サポートにも問い合わせしたところ現時点ではできないようです。
開発チームへのエンハンスはしていただいたので、いつかACCOUNT_USAGEでの確認もできるようになるかもしれません(いつもいつもご対応ありがとうございます!)。
https://docs.snowflake.com/en/user-guide/data-load-aws-private#outbound-private-connectivity-costs

まとめ

今回は2025年1月にGAとなった、外部ステージ向けのOutbound PrivateLinkの機能を試してみました。

会社のセキュリティポリシーなどで、外部ステージとして利用したいS3等へのアクセス方式が制限されている場合に、ぜひこちらの機能も検討してみてください!

Discussion