【Snowflake】S3 Storage Integration作成時にはS3バケットポリシーにも注意する

概要

SnowflakeのS3 Storage Integrationで設定する対象のS3バケットにおいて、S3バケットポリシーにaws:PrincipalOrgIDを条件にしたPut系の権限を設定している場合は注意しよう。

比較的ニッチな話題ではあるが、なぜ注意が必要かというと以下のことが理由である。

  • S3 Storage IntegrationでGet系の権限を設定したにも関わらず、SnowflakeからそのS3へUnload(Put)ができる(本来意図している挙動とは異なる)

aws:PrincipaOrgIDとは?

AWS IAMポリシーのConditionの条件キーの一つ。(参考)

この条件を使えば、AWS Organizationに属するIdentifierのみにアクションを制御することができる。

ケースとしてはS3バケットへのアクセスをAWS Organization内に限定する場合などが挙げられる。

 {
  "Version": "2012-10-17",
  "Statement": {
    "Sid": "AllowPutObject",
    "Effect": "Allow",
    "Principal": "*",
    "Action": "s3:PutObject",
    "Resource": "arn:aws:s3:::policy-ninja-dev/*",
    "Condition": {"StringEquals":
      {"aws:PrincipalOrgID":"o-xxxxxxxxxxx"}
    }
  }
}

指定したorganization以外からのS3バケットへのアクセスを拒否することができる。

問題になるケース

早速、問題となるケースを手順を示しながら見ていく。

1. [AWS] S3バケットバケットポリシーを設定

事前に作成したS3バケットにList系、Get系、Put系のアクションを許可するポリシーを設定する。

指定したPrincipalOrgIDであれば、誰でも許可されたアクションを実行できる。

2. [AWS] S3 Storage Integration用のIAMポリシーの作成

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:GetObjectVersion"
            ],
            "Resource": "arn:aws:s3:::XXXXXX/*" #自身のS3バケットパス
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": "arn:aws:s3:::XXXXXX", #自身のS3バケットパス
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "*"
                    ]
                }
            }
        }
    ]
}

このポリシーをアタッチしたIAMロールを作成する。

3. [Snowflake] S3 Storage Integrationを作成

CREATE OR REPLACE STORAGE INTEGRATION S3_INT
TYPE = EXTERNAL_STAGE
STORAGE_PROVIDER = 'S3'
ENABLED = TRUE
STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::XXXXXXXX' #自身のIAMロールのARN
STORAGE_ALLOWED_LOCATIONS = ('s3://XXXXXXX/') #自身のS3バケットパス
;

以下のSQLを実行し、返却された結果のSTORAGE_AWS_IAM_USER_ARNSTORAGE_AWS_EXTERNAL_IDをメモしておく。

DESC INTEGRATION S3_INT;

4. [AWS] IAMロールの信頼関係の設定

先程メモしたプロパティをIAMロールの信頼関係において以下のように設定する。

  • PrincipalのAWSSTORAGE_AWS_IAM_USER_ARN

  • ExternalId → STORAGE_AWS_EXTERNAL_ID

5. [Snowflake] S3バケット内のフォルダにUnloadしてみる

今回作成したIntegrationはGetやList系のみのアクションのみが可能なため、本来であればSnowflakeからS3に対してPutはできないはず。

サンプルテーブルを用意し、そのテーブルをS3バケットに対してUnloadをすると、、

USE DATABASE SAMPLE_DB;
USE SCHEMA SAMPLE_SCHEMA;

CREATE TABLE SAMPLE_TABLE (ID INT, NAME VARCHAR)
AS 
SELECT 1 AS ID, 'USER1' AS NAME 
UNION ALL 
SELECT 2 AS ID, 'USER2' AS NAME 
;

COPY INTO 's3://XXXXXXX/' #自身のS3バケットパス
FROM SAMPLE_TABLE 
STORAGE_INTEGRATION = S3_INT 
FILE_FORMAT = (TYPE = CSV)
;

なんとPutができてしまう!

Snowflake側のIAMユーザーに対してAssumeRoleする際にPrincipalOrgIDの属性も持つようになるため、S3バケットに書き込みが可能となる。

Snowflake側ではこの挙動を制限できないので、S3バケットポリシーの許可範囲を絞る方法で解決できる。

まとめ

S3のバケットポリシーによってはSnowflakeのGetやList系のみ許可されたS3 Storage Integrationを用いたとしても、S3バケットへのPutが可能になるケースがあるので注意しましょう。