S3 署名付きURL(Presigned URL)の生成方法:1時間の有効期限で非公開ファイルへの一時アクセスを付与する

非公開のS3バケットに保存されたファイルを、認証なしのユーザーに一時的にダウンロードさせたい場面は頻繁に発生する。IAMユーザーを発行するわけにもいかず、バケットをパブリックにするのは論外だ。そこで使うのがS3署名付きURL(Presigned URL)で、これはAWS SDKを使って1時間などの有効期限付きで発行できる。

TL;DR:S3署名付きURLの要点

項目内容
目的非公開S3オブジェクトへの一時的なHTTPアクセスを付与する
有効期限生成時に指定(例:3600秒 = 1時間)
認証情報生成者のIAM認証情報がURLに埋め込まれる
主な用途ファイルダウンロードリンク、アップロード委譲
バケットポリシーパブリックアクセス設定の変更は不要
SDKPython(boto3)、JavaScript(AWS SDK v3)など主要言語対応

S3署名付きURLの仕組み

署名付きURLは、IAM認証情報(アクセスキーまたはIAMロール)を使ってリクエストに署名したURLを事前に生成する仕組みだ。URLを受け取ったユーザーは、AWSの認証情報を持っていなくても、そのURLにHTTP GETリクエストを送るだけでオブジェクトを取得できる。有効期限が切れるか、署名に使った認証情報が無効になれば、URLも機能しなくなる。

ホテルのルームキーに近い。フロントが発行した一時的なカードで、チェックアウト後は使えなくなる。マスターキーを渡しているわけではない。

重要な点として、署名付きURLの有効性は生成時に使用したIAM認証情報の有効性に依存する。IAMロールの一時的な認証情報(例:EC2インスタンスプロファイルやAssumeRoleで取得したトークン)を使って生成した場合、そのトークンの有効期限が署名付きURLの有効期限より先に切れると、URLも無効になる。これは見落としやすい落とし穴だ。

sequenceDiagram participant App as アプリケーションサーバー participant SDK as AWS SDK participant IAM as IAM認証情報 participant User as ユーザー participant S3 as Amazon S3 App->>SDK: generate_presigned_url(bucket, key, 3600) SDK->>IAM: 認証情報で署名を生成 IAM-->>SDK: 署名済みURL SDK-->>App: https://bucket.s3.amazonaws.com/key?X-Amz-Signature=... App-->>User: 署名付きURLを返却 User->>S3: HTTP GET(署名付きURL) S3->>S3: 署名と有効期限を検証 alt 有効な署名かつ期限内 S3-->>User: 200 OK + オブジェクト else 期限切れまたは無効な署名 S3-->>User: 403 Forbidden end
  1. アプリケーションサーバーがIAM認証情報を使ってSDKで署名付きURLを生成する
  2. 生成されたURLはユーザーに返却される(例:APIレスポンス)
  3. ユーザーはそのURLに直接HTTPリクエストを送る
  4. S3はURLの署名を検証し、有効であればオブジェクトを返す
  5. 有効期限切れまたは署名無効の場合は403エラーを返す

署名付きURLの生成に必要なIAM権限

署名付きURLを生成するコード自体には特別な権限は不要だが、URLを使って実際にオブジェクトを取得するためには、生成者のIAMエンティティが対象オブジェクトへのs3:GetObject権限を持っている必要がある。権限がない状態で生成したURLは、アクセス時に403エラーになる。

最小権限のIAMポリシー例:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-bucket/private/*"
    }
  ]
}

アップロード用の署名付きURL(PUT)を発行する場合は、s3:PutObjectを同様に付与する。s3:GetObjects3:PutObjectはリソースレベルで制限できるため、バケット全体ではなく特定のプレフィックス配下に絞ることを推奨する。

S3署名付きURLの生成方法:Python(boto3)

最もシンプルな実装がこれだ。generate_presigned_urlメソッドに操作名、パラメータ、有効期限(秒)を渡すだけで署名済みURLが返ってくる。

import boto3
from botocore.exceptions import ClientError

def generate_presigned_url(bucket_name, object_key, expiration=3600):
    """
    S3オブジェクトへの署名付きURLを生成する

    :param bucket_name: バケット名
    :param object_key: オブジェクトキー(パス)
    :param expiration: 有効期限(秒)、デフォルト3600秒(1時間)
    :return: 署名付きURL文字列、エラー時はNone
    """
    s3_client = boto3.client('s3', region_name='us-east-1')

    try:
        url = s3_client.generate_presigned_url(
            ClientMethod='get_object',
            Params={
                'Bucket': bucket_name,
                'Key': object_key
            },
            ExpiresIn=expiration
        )
    except ClientError as e:
        print(f'エラー: {e}')
        return None

    return url

# 使用例
url = generate_presigned_url('example-bucket', 'private/report.pdf', expiration=3600)
print(url)

region_nameを明示的に指定することを強く推奨する。環境変数やデフォルト設定に依存すると、Lambda関数やコンテナ環境でリージョンが意図せず変わることがある。生成されたURLのホスト名に含まれるリージョンと、実際のバケットのリージョンが一致していないと署名検証に失敗する。

S3署名付きURLの生成方法:JavaScript(AWS SDK v3)

AWS SDK for JavaScript v3では、@aws-sdk/s3-request-presignerパッケージのgetSignedUrl関数を使う。v2とは構造が異なるため注意が必要だ。

🔽 JavaScript(AWS SDK v3)のコード例を展開
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3Client = new S3Client({ region: 'us-east-1' });

async function generatePresignedUrl(bucketName, objectKey, expiresInSeconds = 3600) {
  const command = new GetObjectCommand({
    Bucket: bucketName,
    Key: objectKey,
  });

  try {
    const url = await getSignedUrl(s3Client, command, { expiresIn: expiresInSeconds });
    return url;
  } catch (err) {
    console.error('署名付きURL生成エラー:', err);
    throw err;
  }
}

// 使用例
generatePresignedUrl('example-bucket', 'private/report.pdf', 3600)
  .then(url => console.log(url))
  .catch(err => console.error(err));

AWS CLIで署名付きURLを生成する

動作確認やスクリプトからの利用には、CLIが手軽だ。

aws s3 presign s3://example-bucket/private/report.pdf \
  --expires-in 3600 \
  --region us-east-1

このコマンドは標準出力に署名付きURLを返す。--expires-inの単位は秒で、デフォルトは3600秒(1時間)だ。最大値についてはAWS公式ドキュメントで確認すること。なお、CLIを実行する環境のIAM認証情報が使われるため、本番環境のIAMロールと開発環境のIAMユーザーで生成したURLの有効性が異なる場合がある。

よくある障害パターン:「URLを生成できたのにアクセスできない」

本番で実際に踏んだパターンを記録しておく。

症状:署名付きURLを生成してユーザーに渡したが、アクセスすると403エラーが返る。URLの形式は正しく見える。

最初の誤診:バケットポリシーかACLの問題だと思い、バケット設定を確認し始める。

実際の原因:Lambda関数のIAMロールに割り当てられた一時的な認証情報(STSトークン)の有効期限が、署名付きURLの有効期限(1時間)より先に切れていた。STSの一時認証情報はデフォルトで1時間だが、設定によってはそれより短い場合がある。署名付きURLはURLの有効期限と生成に使った認証情報の有効期限の短い方が実質的な有効期限になる。

確認方法:生成に使ったIAMロールの認証情報の有効期限を確認する。

aws sts get-caller-identity

Lambda環境では環境変数AWS_SESSION_TOKENの有効期限を確認するか、CloudTrailでSTSのAssumeRoleイベントを追う。

修正:長期間有効な署名付きURLが必要な場合は、IAMユーザーの長期認証情報(アクセスキー)を使うか、STSトークンの有効期限を署名付きURLの有効期限より長く設定する。ただし長期認証情報の扱いにはSecrets Managerなどを使った適切な管理が必要だ。

graph TD A["署名付きURL生成"] --> B{"生成に使った認証情報の種類"} B -->|"IAMユーザー長期認証情報"| C["URL有効期限のみが制限"] B -->|"STS一時認証情報
(IAMロール等)"| D{"どちらの期限が短いか"} D -->|"URL有効期限 < STS有効期限"| E["URL有効期限で失効 正常動作"] D -->|"STS有効期限 < URL有効期限"| F["STS失効時点でURL無効 403エラー"] C --> G["例:3600秒後に失効"] E --> G F --> H["例:STSが30分で失効 →URLも30分で無効"]
  1. STS一時認証情報の有効期限 < URL有効期限の場合、URLは認証情報失効時点で無効になる
  2. STS一時認証情報の有効期限 > URL有効期限の場合、URLは指定した有効期限で正常に機能する
  3. どちらの場合も、有効期限の短い方が実質的な制限になる

S3署名付きURLのセキュリティ上の注意点

署名付きURLはURLを知っている人なら誰でもアクセスできる。以下の点を運用上考慮すること:

  • URLの漏洩リスク:ログ、ブラウザ履歴、Refererヘッダーにより意図せず露出する可能性がある
  • 有効期限は最小限に:ユースケースに必要な最短時間を設定する。「とりあえず24時間」は避ける
  • S3バケットのパブリックアクセスブロック:署名付きURLはバケットのパブリックアクセスブロック設定の影響を受けない。バケット自体は非公開のままで問題ない
  • CloudFront署名付きURLとの使い分け:CDNキャッシュが必要な場合や、より細かいアクセス制御が必要な場合はCloudFront署名付きURLまたは署名付きCookieを検討する

まとめと次のステップ:S3署名付きURLの実装

S3署名付きURLは、非公開オブジェクトへの一時アクセスを付与する最もシンプルな方法だ。実装自体は数行で完結するが、IAM認証情報の有効期限との関係と、URLが漏洩した場合のリスクを理解した上で有効期限を設計することが重要になる。

次のステップとして以下を参照:

用語集

用語説明
署名付きURL(Presigned URL)IAM認証情報で署名された、有効期限付きのS3アクセスURL。URLを知っていれば認証なしでアクセス可能
STS(Security Token Service)一時的なAWS認証情報を発行するサービス。IAMロールのAssumeRoleなどで使われる
ExpiresIn署名付きURL生成時に指定する有効期限(秒単位)
s3:GetObjectS3オブジェクトの取得を許可するIAMアクション。ダウンロード用署名付きURLの生成者に必要
パブリックアクセスブロックS3バケットへのパブリックアクセスを制限するバケットレベルの設定。署名付きURLはこの設定の影響を受けない

Related Posts

コメント

このブログの人気の投稿

EC2 SSH接続タイムアウトの原因と修正方法 — セキュリティグループのインバウンドルール完全ガイド

S3パブリックアクセス拒否の原因と解決策:バケットレベルの「Block Public Access」が優先される仕組み

EC2インスタンスIDをメタデータから取得する方法 — IMDSv2が安全な理由