AWSのS3署名付きURLを利用してハマったこと

# はじめに
外部クラウドストレージにアップロードしたファイルを、期限付きでダウンロード可能とする機能。これがAWSのS3だと簡単にできる。だが、いざやってみたらハマったのでここに備忘として残す。


# 公式の文献
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html


>デフォルトでは、すべての Amazon S3 オブジェクトはプライベートであり、オブジェクトの所有者のみがアクセスできます。ただし、オブジェクトの所有者は、署名付き URL を作成することで、他のユーザーとオブジェクトを共有できます。署名済み URL は、セキュリティ認証情報を使用してオブジェクトをダウンロードするアクセス許可を期限付きで付与します。署名付き URL をブラウザに入力するか、プログラムで使用してオブジェクトをダウンロードできます。署名付き URL で使用される認証情報は、URL を生成した AWS ユーザーのものです。

S3でパブリックアクセスなしのプライベートで作成しても、署名付きURLを生成することで、期限をつけて対象オブジェクト（ファイル）だけ公開できると言っている。
<br>

>S3 コンソールの使用
Amazon S3 コンソールで次の手順に従い、オブジェクトを共有するための署名付き URL を生成できます。コンソールを使用した場合、署名付き URL の最大有効期限は作成時点から 12 時間です


>AWS CLI を使用する場合
次の例では、AWS CLI コマンドにより、Amazon S3 バケットのオブジェクトを共有するための署名付き URL を生成します。AWS CLI を使用する場合、署名付き URL の最大有効期限は、作成時点から 7 日間です。

AWS CLIで作ると7日間の期限を設定できるが、マネジメントコンソールからだと12時間期限しか設定できない。であればAWSCLIで作る他ない。


CloudShellを開いて、公式サイトに記載されている以下コマンドを実行する。
```
aws s3 presign s3://my-bucket/testdir/mydoc.txt --expires-in 604800
```

しかしアクセスするとエラーとなり、そもそも共有できていない。
そこで公式の文献を改めて読むと以下記載が・・・。

>注記
2019 年 3 月 20 日より後に開設されたすべての AWS リージョンでは、リクエストで endpoint-url と AWS リージョンを指定する必要があります。すべての Amazon S3 のリージョンとエンドポイントのリストについては、「AWS 全般のリファレンス」の「リージョンとエンドポイント」を参照してください。
>```
>aws s3 presign s3://amzn-s3-demo-bucket/mydoc.txt --expires-in 604800 --region af-south-1 --endpoint-url https://s3.af-south-1.amazonaws.com

なんじゃそれ、というわけで、リージョン名を記載して再度以下コマンドを実行。

```
aws s3 presign s3://my-bucket/testdir/mydoc.txt --expires-in 604800 --region ap-northeast-1 --endpoint-url https://s3.ap-northeast-1.amazonaws.com
```

実行するとプロンプトが返り、同時に以下のようなURLが生成される。
```
https://s3.ap-northeast-1.amazonaws.com/my-bucket/testdir/mydoc.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXX%2F20250513%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=YYYYMMDDDHHSSZ&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=XXX
```

これが署名付きURLとなる。このURLにブラウザなどでアクセスすると、s3://my-bucket/testdir/mydoc.txtの内容を参照することができた。
だがしかし、ここでまた困ったことが。


# 5~10分で期限が切れて見れなくなる
ようやく署名付きURLでS3にアップロードしたファイルを見れるようになったと思いきや、数分で期限切れに。AWSCLIで署名付きURLを生成したら、7日間まで見れるんじゃないの？という期待はあっさり裏切られた。

そこで調べると以下ブログ記事を発見。

https://uchimanajet7.hatenablog.com/entry/2019/03/29/000000

>
>>・AWS Identity and Access Management (IAM) インスタンスプロファイル: 最大 6 時間有効
・AWS Security Token Service (STS): 最大 36 時間有効 (AWS アカウントユーザーや IAM ユーザーの認証情報など、永続的認証情報を使用して署名した場合)
・IAM ユーザー: 最大 7 日間有効 (AWS 署名バージョン 4 を使用した場合)

>という記述があることから最大 7 日間なのかーという部分しか確認せずにExpiresIn=604800を指定してみましたが、残念ながら有効期限より前に失効する状況は改善しませんでした。<br>
それもそのはずで、よく読めば利用している認証情報で有効期限の最大値が決まり、その最大値で有効期限が失効するということになります。<br>
AWS LambdaだとIAM role を利用しているので、AssumeRole アクションにてAWS STS から一時トークンを取得し、その一時トークンで認証を行なっていることになります。<br>
そのため、上記ドキュメントに記載のSTS 利用時に合致し指定している7日を待たずに失効する形になっていました。


なんとまあ難しい制約で、ブログ主の方のようにLambdaを使った場合や、私のようにCloudShellを使って署名付きURLを生成した場合、AWS Security Token Service (STS)の認証を使ったと判断され、7日間分である604800秒を指定しても、その指定時間は無視されてしまう。STSは最大 36 時間有効のURLを生成できるが、デフォルト値がおそらく5-10分でそれが適用されてしまう、というからくり。

```
IAM ユーザー: 最大 7 日間有効 (AWS 署名バージョン 4 を使用した場合)
```
上記の記載の通り、IAMユーザーかつAWS署名バージョン4.で署名付きURLを生成する必要がある。
<br>

# というわけで対応
## 自分のクライアント、端末にAWSCLIをインストールする。

https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html

## IAMで利用するIAMユーザーの作成（コンソールログインなし）
署名付きURL生成専用ユーザーを作る。自分の普段使っているユーザーでもよいが、強めの権限がついていると、漏れた時に特権を取られるので、署名付きURL専用ユーザーを別途作る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/56e3d4aa-e5c4-4a66-b326-1a8dc4dda336.png)


## S3読取IAMポリシー作成
先に作成した署名付きURL専用ユーザーの権限を作成する。ポリシーの作成をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/fcc42358-0547-45f2-be0a-ff1ec85276d8.png)
<br>
リソースをアップロードしたS3バケットのみにGetとListが操作できる権限を指定して、バケット配下をアスタリスクですべて許可しているが、ディレクトリもさらに限定して許可範囲を絞りたい場合、さらにResource部分のS3のARNを追記修正する。
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": [
                "arn:aws:s3:::my-bucket",
                "arn:aws:s3:::my-bucket/*"
            ]
        }
    ]
}
```
<br>

## IAMで利用するIAMユーザーにポリシー割り当て
署名付きURL生成専用ユーザーに、S3読取IAMポリシーを割り当てる。
当該IAMユーザーのIAM設定画面で、以下のようにIAMポリシーを追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/840b6d7a-1524-49b3-9665-2e0ed71f71e4.png)


## IAMで利用するIAMユーザーにアクセスキーを生成
署名付きURL生成専用ユーザーにアクセスキーとシークレットキーを生成する。当該IAMユーザーのIAM設定画面で、以下のように生成。<br>
セキュリティ認証情報をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/00d08562-2bd3-411e-9e4d-1f97368b2995.png)

<br>

アクセスキーを生成をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/fd170a96-bafa-4530-9b72-fc6c7a9a3d02.png)

<br>

CLIを選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/59fcf918-c031-4899-9b13-0ec133f359d1.png)

<br>

アクセスキーとシークレットキーを生成したら、絶対に漏れない場所にアクセスキーとシークレットをメモしておく。漏れたら再生成する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/a0d29ce5-2465-46d0-9b00-1787d5074001.png)

<br>

### クライアントのAWSCLIに認証情報（変数）を指定
注意）わたしの環境はWindowsクライアントのコマンドプロンプトを使っています。
```shell-session
$ aws configure
AWS Access Key ID [None]: AWS_ACCESS_KEY_ID
AWS Secret Access Key [None]: AWS_SECRET_ACCESS_KEY
Default region name [None]: AWS_DEFAULT_REGION
Default output format [None]: 
```

Default output formatは自由。
アクセスキーとシークレットを変数に指定したら、次に移る。
<br>

## クライアントのAWSCLIにて、現在のセッションのみにアクセスキーを設定
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-envvars.html

お使いのクライアントのOS環境に応じて、コマンドは変更。ここでIAMで生成したアクセスキーとシークレットを「=」以降に入力する。

```shell-session
set AWS_ACCESS_KEY_ID=AKIXXXXXXXXX
set AWS_SECRET_ACCESS_KEY=ZZZZZZZZZZZZZ
set AWS_DEFAULT_REGION=ap-northeast-1
```
ap-northeast-1は自身のリージョンに変更。
<br>

# AWS CLI のS3 署名にsigv4 を利用するように設定
現在はデフォルトでS3署名をsigv4になっているそうだが、念のために実施しておく。
Default プロファイルの場合、以下。

```
aws configure set default.s3.signature_version s3v4
```

<br>

個別のプロファイルの場合、以下。

```
aws configure set profile.your_profile_name.s3.signature_version s3v4
```

<br>

## S3確認
認証情報の設定ができたため、対象S3にアクセスができるかを確認する。

```
aws s3 ls s3://my-bucket/testdir/

2025-05-08 20:51:49          0
2025-05-08 21:17:23     204995 mydoc.txt
```

## 署名付きURL（7日間期限）発行
署名付きURL生成専用ユーザーで認証情報が通ったため、署名付きURLを発行する。
604800は7日間の秒換算となる。

```
aws s3 presign s3://my-bucket/testdir/mydoc.txt --expires-in 604800 --region ap-northeast-1 --endpoint-url https://s3.ap-northeast-1.amazonaws.com
```

実行するとプロンプトが返り、同時に以下のようなURLが生成される。
```
https://s3.ap-northeast-1.amazonaws.com/my-bucket/testdir/mydoc.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIXXXX%2F20250513%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=YYYYMMDDDHHSSZ&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=XXX
```

これが署名付きURLとなる。`t?X-Amz-Algorithm=AWS4-HMAC-SHA256&`がS3 署名にsigv4 を利用している意味となる。
`X-Amz-Credential=AKIXXXX`にアクセスキーが入る。これで7日間期限の署名付きURLを生成することができた。

## 注意
署名付きURL生成専用ユーザーでアクセスキーとシークレットキーを生成したが、アクセスキー生成は漏洩して特権を奪われた事件などがあり、AWSが非推奨としている。非推奨にしているからといって、署名付きURL生成してすぐに（7日間期限以内に）アクセスキーを消すと、署名付きURLが見れなくなる。このため、7日間期限が終わってからアクセスキーを消すようにする。仮に漏洩してもIAMポリシーでS3の参照権限だけにしているため、大きな問題はないが・・・。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/a0d29ce5-2465-46d0-9b00-1787d5074001.png)

<br>

# まとめ
「AWSだと簡単にできますよ！」というのは、驕り。世の中そんな簡単じゃない。