❄️

【Snowflake】STAGE利用による意図せぬ情報漏洩を防げ! PRESIGNED_URLの制限

2024/05/15に公開

本記事で参考になるケース

  • 内部STAGE, 外部STAGEの利用により発行可能になってしまうGET_PRESIGNED_URLに何らかの制限したい

記事の概要

  • 前提として内部STAGEのGET_PRESIGNED_URLを制限する方法は なさそう
      追記: 追加の設定によりNWポリシーを適用させることができる!私の見落としです!(記事末尾に記載)
  • 外部STAGEにおいてGCS(GC)を選択した場合も制限する方法はない
  • 外部STAGEにおいてS3(AWS)を選択した場合詳細な制御が可能である
    ※Azureは未調査..

今回の課題

  • 今回の問題はSnowflake上でユーザに対してNetwork Policyを設定した場合でも、そのユーザが実行したSELECT GET_PRESIGNED_URLにより発行される署名付きURLにはデフォルトではNetwork PolicyのIP制限がかからないということ
    • これは

      • ユーザはSTAGEに大量の機密データを出力及びそのファイルに対する署名付きURLを発行することが可能である
      • 上記の署名付きURLを知っている人間は「誰でも(発行したユーザ以外でも)どこからでも認証不要で」そのURLを用いデータをダウンロード/持ち出しできてしまう

      ということである.
      ※署名付きURLに有効期限があるとしても場合によっては危険である.

AWS S3を外部STAGEに用いたGET_PRESIGNED_URLの制御

Snowflake側では制御ができないように見える. したがってAWS側で制御を行う.
行うことは非常にシンプルであり、AWSのポリシーを用い制御する.

  • SnowflakeのGET_PRESIGNED_URLによって発行されたURLを常時無効にする方法

外部STAGE作成の際に作成したSTORAGE INTEGRATIONに設定したAWS IAM ROLEのポリシーに署名付きURL発行を制限するDeny Statementを追記する

Snowflake内
CREATE STORAGE INTEGRATION test_snowflake_stage
  TYPE = EXTERNAL_STAGE
  STORAGE_PROVIDER = S3
  ENABLED = true
  STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::XXXXXXXX:role/ROLE_NAME'
  STORAGE_ALLOWED_LOCATIONS = ('s3://BUCKET_NAME/');

上記のAWS IAM ROLEはarn:aws:iam::XXXXXXXX:role/ROLE_NAMEである.
このRoleのPolicyに下記のようにDeny...not equel s3:authType": "REST-HEADERを追記する.

AWS内IAM Role
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:GetObjectVersion",
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:DeleteObjectVersion",
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME",
                "arn:aws:s3:::BUCKET_NAME/*"
            ]
        },
        {
            "Effect": "Deny",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::BUCKET_NAME/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:authType": "REST-HEADER"
                }
            }
        }
    ]
}

検証

CREATE STAGE test
  STORAGE_INTEGRATION = test_snowflake_stage
  URL = 's3://BUCKET_NAME/';

COPY INTO @test/test
  from (select 1)
  file_format = (TYPE = CSV COMPRESSION = GZIP FIELD_DELIMITER = '\t' SKIP_HEADER = 1 );

SELECT GET_PRESIGNED_URL(@test,'test_0_0_0.csv.gz');

発行されたURLを利用した場合

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>XXXXXXXXXXXXXXXX</RequestId>
<HostId>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</HostId>
</Error>

無事拒否される

発展: SnowflakeのGET_PRESIGNED_URLによって発行された署名付きURLを特定のIPから利用した時のみ有効(ダウンロード可能)にする方法

下記はAWS S3を外部STAGEにした場合を示す.

引き続きSnowflakeのSTORAGE INTEGRATIONに設定したAWSのIAM Rolearn:aws:iam::XXXXXXXX:role/ROLE_NAMEのPolicyを下記のように編集する.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:GetObjectVersion",
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:DeleteObjectVersion",
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::BUCKET_NAME",
                "arn:aws:s3:::BUCKET_NAME/*"
            ]
        },
        {
            "Effect": "Deny",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::BUCKET_NAME/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:authType": "REST-HEADER"
                },
                "NotIpAddress": {
                    "aws:SourceIp": [
                        "署名付きURLを用いてダウンロードできる接続元IP"  // "123.456.789.101/32"
                    ]
                },
            }
        }
    ]
}

まとめ

SnowflakeにおいてSTAGEは非常に便利でありよく使うものになっている. 特にSnowflake in Streamlitなどでは必須で利用されるものであるが、利用の仕方によっては基盤の要件に合わないセキュリティ上の問題を作り出してしまう可能性がある.
この点は注意しておく必要があるかと思われます.

追記 内部STAGEのGET_PRESIGNED_URLのIP制限

ネットワークルールを使用するネットワークポリシーでAWS内部ステージへのアクセスを制限するかどうかを設定

alter account set enforce_network_rules_for_internal_stages = true;

Snowflakeドキュメント参考

これで適応されます.

ただし、注意事項がいくつか

・ネットワークルールを使用しないネットワークポリシーには影響しない(ネットワークルールをちゃんと作ってね!)
・外部ステージでAWS S3を利用した時のようなGET_PRESIGNED_URL経由のみ利用できるIPの範囲を広げる/狭めるという細かな制御は難しそう?

Discussion