S3で発行した署名付きURLの有効期限が設定した期限より早く切れてしまう

## 概要
- S3に発行した署名付きURLの有効期限をコード内で最長の7日間で設定していたが、7日経っていないのにアクセスできないという課題が発生した
- 原因を調査したところ、署名付きURLの生成時に一時的なトークンが発行されていることがわかった
- どうやら、署名付きURLにて設定した期限（今回だと7日）が来る前に、先に一時的なトークンの有効期限が切れてしまい、結果共有したURLでアクセスできないという問題が発生していた

## 結論
- 署名付きURLの有効期限を12時間以上に設定する場合、IAMユーザーの認証情報を付与して署名付きURLを生成する必要がある

## 原因がわかるまでの過程
- 状況
  - Pythonで、botoを使って署名付きURLを生成していた
      ```python
        def generate_presigned_url(self, access_key, secret_key, filename, bucket, expires_in) -> str:
            s3 = boto3.client(
                "s3",
                region_name="ap-northeast-1",
                config=Config(s3={"addressing_style": "path"}, signature_version="s3v4"),
            )
            presigned_url = s3.generate_presigned_url(
                ClientMethod="get_object",
                Params={
                    "Bucket": bucket,
                    "Key": "test/" + filename,
                },
                ExpiresIn=expires_in,
                HttpMethod="GET",
            )
            return presigned_url
    ```

- 上記のコードで生成された署名付きURLリンクを見てみると、クエリパラメータに `X-Amz-Security-Token`というパラメータが付与されていることがわかった
### X-Amz-Security-Tokenとは
- [リファレンス](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/RESTAuthentication.html)を見ると、「一時的なセキュリティ認証情報の使用」というセクションに下記のような文章がある（公式リファレンスから引用）
  >一時的なセキュリティ認証情報を使用してリクエストに署名する場合は (「リクエストの実行」を参照)、x-amz-security-token ヘッダーを追加して、対応するセキュリティトークンをリクエストに含める必要があります。
  >AWS Security Token Service API を使用して一時的なセキュリティ認証情報を取得すると、そのレスポンスには、一時的なセキュリティ認証情報とセッショントークンが含まれます。リクエストを Amazon S3 に送信するときに、x-amz-security-token ヘッダーでセッショントークン値を指定します。

- 上記を読み、boto3で特に認証情報などを指定しないで署名付きURLを発行した場合に`X-Amz-Security-Token`がクエリパラメーターに付与されていることを見ると、AWS SDK(botoなど)は「一時的なセキュリティ認証情報(AWS Security Token Service)」を取得して、各サービスへの操作を行っていることがわかる。

- 一時的なセキュリティ認証情報(AWS Security Token Service)の有効期限
  - [リファレンス](https://docs.aws.amazon.com/ja_jp/whitepapers/latest/navigating-gdpr-compliance/temporary-access-tokens-through-aws-sts.html#:~:text=%E6%9C%89%E5%8A%B9%E6%9C%9F%E9%96%93%E3%81%AF%2015%20%E5%88%86%E3%81%8B%E3%82%89%E6%9C%80%E9%95%B7%2012%20%E6%99%82%E9%96%93%E3%81%BE%E3%81%A7%E8%A8%AD%E5%AE%9A%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82)には、下記の通り記載がある通り、最長12時間とのこと。また、期限切れしたのちは、トークンを使用して行われた操作に関しては、アクセスが許可されなくなるとの文言も記載されている。
  >一時的なセキュリティ認証情報は、短期的な使用のためのものです。有効期間は 15 分から最長 12 時間まで設定できます。一時的な認証情報の有効期限が切れると、AWS はその認証情報を認識せず、その認証情報を使用して行われた API リクエストからのいかなる種類のアクセスも許可しません。

- 整理してみると、
  - IAMユーザーの認証情報(aws_access_keyやaws_secret_access_keyなど)を指定しないで署名付きURLを生成したところ、URLに`X-Amz-Security-Token`というクエリパラメーターが付与されていた
  - `X-Amz-Security-Token`とは、SDKを通じて、各AWSサービスにアクセスするために AWS Security Token Service API を使用して取得した同トークンを設定するKeyであることがわかった
  - `X-Amz-Security-Token`に設定できる期限は、15分〜12時間までということがわかった
  - `X-Amz-Security-Token`に付与するトークンの有効期限が切れた場合、リソースに対してトークンを使用しての一切のアクセスができなくなることがわかった
  - `X-Amz-Security-Token`が付与された署名付きURLを発行日の翌日に開くと、[トークン切れの画面](https://qiita.com/drafts/ffc0f17045233a253b98/edit#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%8C%E5%88%87%E3%82%8C%E3%81%9F%E6%99%82%E3%81%AE%E3%82%A8%E3%83%A9%E3%83%BC%E7%94%BB%E9%9D%A2%E3%81%A8url%E3%81%AE%E6%9C%89%E5%8A%B9%E6%9C%9F%E9%99%90%E3%81%8C%E5%88%87%E3%82%8C%E3%81%9F%E6%99%82%E3%81%AE%E7%94%BB%E9%9D%A2)が表示されてアクセスできなかった（署名付きURL有効期限は7日で設定）
- 上記から署名付きURLの有効期限が切れていないのに、リンク先のリソースにアクセスできなくなる原因は、署名付きURLのリンクの有効期限が切れたことではなく、一時的なトークンの有効期限が切れていることが原因だとわかった。

### トークンが切れた時のエラー画面とURLの有効期限が切れた時の画面
`<Code>`の部分をみると、明確にエラーの内容が異なることがわかる
- トークンが切れた時のエラー画面
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/051e4580-737d-6ac8-c0b7-2afd639cff6c.png)

- URLの有効期限が切れた時の画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531653/82447bd4-2254-97aa-2409-904cec56f3a7.png)


## 解決策
- IAMユーザーの認証情報を指定して、IAMユーザー権限で署名付きURLを発行する
```diff_python
    self.s3 = boto3.client(
        "s3",
        region_name="ap-northeast-1",
+       aws_access_key_id=access_key, # 追加
+        aws_secret_access_key=secret_key, # 追加
        config=Config(s3={"addressing_style": "path"}, signature_version="s3v4"),
    )
    presigned_url = self.s3.generate_presigned_url(
        ClientMethod="get_object",
        Params={
            "Bucket": bucket,
            "Key": "test/" + filename,
        },
        ExpiresIn=expires_in,
        HttpMethod="GET",
    )
    return presigned_url
```

- 上記のように付与して署名付きURLを発行してみると、`X-Amz-Security-Token`（一時的な認証情報）が付与されていないことがわかる
- これで、トークンの有効期限がなくなったので、純粋に署名付きURLに付与した期限にて、アクセスできる期間をコントロールできる


## まとめ
- まだ自分も全てを理解し切れていないため、曖昧な部分もあるかつ、もしかしたら誤った解釈をしている可能性があるかもしれません。
- リファレンスベースと起こった事象ベースで記載しているので、わかりづらい記事になっていると思いますが。私も理解し切れてはいないためです、すみません。ですが、的外れではないと思うのとかつ、起こっていた原因は解決できたので、一旦備忘として記載したいと思ったのでかきました。
- もし、異なった解決方法やよりよいベストプラクティスなどあれば、ぜひ教えていただきたいです。