署名付き URL (presigned URL) のときだけ IAM ポリシーで条件を制御してみた

2023.06.29

いわさです。

Amazon S3 には署名付き URL という概念があります。
制限時間が設けられている発行された URL を使うことで IAM 認証情報なしで Amazon S3 オブジェクトへアクセスすることが出来るようになります。

次の記事がわかりやすいのですが、署名付き URL 自体には実際は何の認可情報も含まれておらず、署名をしたユーザーの認証情報が一時的に付与されるようなイメージで、実際にはアクセスした時点で署名したユーザーに紐づいたポリシーが評価されて、アクセスが許可 / 拒否される仕組みになっています。

そのため、次のように対象ユーザーのポリシーで IP アドレス制限を行うことで、署名付き URL を使ったアクセスの場合でも IP アドレスによる制限を行うことが出来ます。

ただし、上記は署名付き URL を使わずに、IAM ユーザーが S3 オブジェクトにアクセスしようとした場合でも IP アドレスによる制限がされます。
今回、署名付き URL を使う場合と使わない場合で IAM ポリシーの制御を分ける方法を調べてみました。

条件キーを探せ

IAM ポリシーで何かしら条件で制御したい場合は条件キーで制御することになりますのでこのあたりをまずは眺めてみます。
サービスに関わらないグローバル条件キーと、サービス毎に使用可能なサービスレベルの条件キーがあります。

グローバル条件キーを眺めてみましたが、署名付き URL かどうかという条件はありませんでした。

一方で、サービスレベルの条件キーを眺めてみると、s3:authTypeで認証方法について言及されていることに気が付きました。

公式ドキュメントの例では、「s3:authTypeREST-HEADER以外の場合を Deny することで POST と署名付き URL のリクエストを拒否することが出来る」と解説されています。

これだ...

使ってみた

特にコントロールせずに署名付き URL を発行

まずは条件を設定せずに署名付き URL で普通にアクセス出来ることを確認してみましょう。
適当な IAM ユーザーを作成し、次の IAM ポリシーをアタッチさせます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "*"
        }
    ]
}

この IAM ユーザーのアクセスキーを使って、AWS CLI から S3 オブジェクトへアクセスするのと、署名付き URL を発行してその URL からアクセスしてみます。

# AWS CLI からアクセス
% aws s3 cp s3://hoge0629presign/hoge.txt hoge1.txt --region ap-northeast-1 --profile hoge0629  
download: s3://hoge0629presign/hoge.txt to ./hoge1.txt         
% cat hoge1.txt                                                                                 
aaaa

# 署名付き URL
% aws s3 presign s3://hoge0629presign/hoge.txt --region ap-northeast-1 --profile hoge0629       
https://hoge0629presign.s3.ap-northeast-1.amazonaws.com/hoge.txt?hogehogehogehoge
% curl "https://hoge0629presign.s3.ap-northeast-1.amazonaws.com/hoge.txt?hogehogehogehoge"
aaaa

どちらの方法もアクセス出来ました。
では、ここからポリシーを変更して遊んでみましょう。

署名付き URL のみ許可する

上記の認証方法に関するドキュメントのAuthentication Methods - Query string parametersで次のように言及されています。

this type of URL is often referred to as a presigned URL

この認証方法 = 我々が普段「署名付き URL」と呼んでいるものと同じだと解釈して良さそうな印象です。
この認証方法は値にREST-QUERY-STRINGを指定することで判定が出来ます。

では、ここでは署名付き URL の時だけs3:GetObjectを許可するように変更してみます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "s3:authType": "REST-QUERY-STRING"
                }
            }
        }
    ]
}
# AWS CLI からアクセス
% aws s3 cp s3://hoge0629presign/hoge.txt hoge2.txt --region ap-northeast-1 --profile hoge0629
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

# 署名付き URL
% aws s3 presign s3://hoge0629presign/hoge.txt --region ap-northeast-1 --profile hoge0629
https://hoge0629presign.s3.ap-northeast-1.amazonaws.com/hoge.txt?hogehogehogehoge
% curl "https://hoge0629presign.s3.ap-northeast-1.amazonaws.com/hoge.txt?hogehogehogehoge"
aaaa

署名付き URL のみアクセスすることが出来ました。
良いですね。

署名付き URL を使わない時は IP アドレス制限する

あとは如何様にもという感じだと思いますが、署名付き URL の場合はどこからでも利用が出来て、通常のアクセスの場合は特定の IP アドレスからのみ許可するようなポリシーを作ってみたいと思います。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Deny",
            "Action": "s3:GetObject",
            "Resource": "*",
            "Condition": {
                "StringNotEqualsIfExists": {
                    "s3:authType": "REST-QUERY-STRING"
                },
                "NotIpAddress": {
                    "aws:SourceIp": "203.0.113.1/32"
                }
            }
        }
    ]
}

許可された IP アドレス (203.0.113.1) から先程と同じようにアクセスしてみます。

# AWS CLI からアクセス
% aws s3 cp s3://hoge0629presign/hoge.txt hoge.txt --region ap-northeast-1 --profile hoge0629
download: s3://hoge0629presign/hoge.txt to ./hoge.txt    

# 署名付き URL      
% aws s3 presign s3://hoge0629presign/hoge.txt --region ap-northeast-1 --profile hoge0629
https://hoge0629presign.s3.ap-northeast-1.amazonaws.com/hoge.txt?hogehogehogehoge
% curl "https://hoge0629presign.s3.ap-northeast-1.amazonaws.com/hoge.txt?hogehogehogehoge"
aaaa

AWS CLI からも署名付き URL からもアクセスが出来ますね。

続いて、許可されてない IP アドレスから先程と同じ手順でアクセスしてみます。

# AWS CLI からアクセス
% aws s3 cp s3://hoge0629presign/hoge.txt hoge.txt --region ap-northeast-1 --profile hoge0629
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

# 署名付き URL
% aws s3 presign s3://hoge0629presign/hoge.txt --region ap-northeast-1 --profile hoge0629
https://hoge0629presign.s3.ap-northeast-1.amazonaws.com/hoge.txt?hogehogehogehoge
% curl "https://hoge0629presign.s3.ap-northeast-1.amazonaws.com/hoge.txt?hogehogehogehoge"
aaaa

今度は署名付き URL からのみアクセスが可能になりました。期待どおりです。

さいごに

本日は IAM ポリシーで署名付き URL (presigned URL) を使うときと、AWS CLI から GetObject する時で挙動が変わるようなポリシーの設定方法を行ってみました。

s3:authTypeは他にもパラメータがあるのでもう少し検証したほうが良さげですが、概ね期待した要件を満たせそうです。
署名付き URL の発行はローカル環境で署名するだけなので制御出来ないと思いますが、署名付き URL からのアクセスは許可しないといった使い方も出来そうなので、なかなかs3:authTypeおもしろいのではないでしょうか。

今回はとりあえず実験してみた感じなので、副作用が他にないか気になるのでもうちょっと調べてみたいところです。