CloudFront 署名付き URL で複数の S3 Bucket から単一カスタムドメインで配信する
はじめに
こんにちは。PKSHA Technology で SWE をしている須藤です。
今回は、S3 からのファイル配信で遭遇した課題と、その解決策について紹介します。
S3 署名付き URL は、一時的なアクセス権限を付与してファイルをダウンロード・アップロードできる便利な機能です。
しかし、S3 署名付き URL はバケットごとに異なるドメインとなるため、利用者のセキュリティ要件によっては、バケット追加のたびにファイアウォールやプロキシの設定変更が必要です。

S3 署名付き URL ではバケットごとに異なるドメインへアクセスが必要
用途ごとにバケットを分けることで、一時ファイルは数日で自動削除、重要データは長期保持など、それぞれに適したライフサイクルポリシーを設定できます。しかし、バケットが増えるたびにドメインも増え、前述の問題が発生します。
ドメイン追加のたびに設定変更が必要になると、お客様の運用負荷増加や、設定漏れによる不具合に繋がる可能性があります。
本記事では、CloudFront の署名付き URL とカスタムドメインを活用し、複数の S3 バケットから単一ドメインで統合配信するアーキテクチャを解説します。
本記事で扱わないこと。
- RSA 鍵ペアの生成および CloudFront Key Group の作成
- AWS Certificate Manager (ACM) での SSL 証明書発行(us-east-1 リージョン)
- Route 53 でのホストゾーン作成と DNS 設定
- S3 バケットの作成とバケットポリシーの基本設定
これらの詳細手順については、AWS 公式ドキュメントや参考文献をご参照ください。
S3 署名付き URL と CloudFront 署名付き URL の違い
両者の主な違いを整理します。
| 観点 | S3 署名付き URL | CloudFront 署名付き URL |
|---|---|---|
| ドメイン | バケットごとに異なる | 単一カスタムドメイン |
| 署名方式 | AWS Signature V4(IAM 認証情報) | RSA 鍵ペア |
| 有効期限 | 最大 7 日 ※ | 任意に設定可能 |
| キャッシュ | なし | あり(Edge Location) |
| 設定場所 | バケットポリシー | CloudFront + Key Group |
ドメイン形式の違い
S3 署名付き URL
https://<bucket-name>.s3.<region>.amazonaws.com/<key>?X-Amz-Algorithm=...
CloudFront 署名付き URL
https://<your-domain>/<key>?Expires=...&Signature=...&Key-Pair-Id=...
CloudFront を使えば、任意のカスタムドメイン(例: files.example.com)を設定し、単一ドメインで複数バケットのファイルを配信できます。
署名方式の違い
S3 署名付き URL は AWS の IAM 認証情報(アクセスキー)を使って署名します。一方、CloudFront 署名付き URL は RSA 鍵ペアを使用します。
CloudFront では事前に公開鍵を Key Group として登録し、アプリケーション側で秘密鍵を使って署名を生成します。鍵ペアの生成や Key Group の設定手順については、以下の記事が参考になります。
アーキテクチャ概要
以下の構成で、複数の S3 バケットを単一ドメインで配信します。

CloudFront を使った単一ドメイン配信アーキテクチャ
ポイント
- 単一ドメイン: すべてのバケットへのアクセスがカスタムドメインに統一
- パスベースルーティング: URL のパスパターンで振り分け先バケットを決定
- Origin Access Control (OAC): S3 バケットへの直接アクセスをブロックし、CloudFront 経由でのみアクセスを許可
パスベースルーティング
CloudFront の Behavior(ビヘイビア)設定で、パスパターンごとに異なる Origin(S3 バケット)へルーティングします。
設定例
| パスパターン | Origin(S3 バケット) | 用途 |
|---|---|---|
/* (Default) |
bucket-a | その他(デフォルト) |
/path-b/* |
bucket-b | ファイル種別 B |
/path-c/* |
bucket-c | ファイル種別 C |
CloudFront の設定イメージ
以下は、Terraform での設定例です(一部省略)。
resource "aws_cloudfront_distribution" "files" {
# Origin 定義
origin {
domain_name = aws_s3_bucket.bucket_a.bucket_regional_domain_name
origin_id = "bucket-a"
origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
}
origin {
domain_name = aws_s3_bucket.bucket_b.bucket_regional_domain_name
origin_id = "bucket-b"
origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
}
origin {
domain_name = aws_s3_bucket.bucket_c.bucket_regional_domain_name
origin_id = "bucket-c"
origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
}
# デフォルト Behavior
default_cache_behavior {
target_origin_id = "bucket-a"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
trusted_key_groups = [aws_cloudfront_key_group.main.id] # 省略すると署名なしでアクセス可能
# ...
}
# パスベースの Behavior
ordered_cache_behavior {
path_pattern = "/path-b/*"
target_origin_id = "bucket-b"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
trusted_key_groups = [aws_cloudfront_key_group.main.id]
# ...
}
ordered_cache_behavior {
path_pattern = "/path-c/*"
target_origin_id = "bucket-c"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
trusted_key_groups = [aws_cloudfront_key_group.main.id]
# ...
}
# カスタムドメイン
aliases = ["<your-domain>"]
# SSL 証明書(ACM で発行、us-east-1 リージョン必須)
viewer_certificate {
acm_certificate_arn = var.acm_certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
}
# Route 53 で CloudFront へのエイリアスレコードを作成
resource "aws_route53_record" "files" {
zone_id = var.route53_zone_id
name = "<your-domain>"
type = "A"
alias {
name = aws_cloudfront_distribution.files.domain_name
zone_id = aws_cloudfront_distribution.files.hosted_zone_id
evaluate_target_health = false
}
}
カスタムドメインを CloudFront に紐づけるには、以下が必要です。
- ACM 証明書 — us-east-1 リージョンで発行(CloudFront はグローバルサービスのため)
- Route 53 レコード — CloudFront ディストリビューションへの A レコード(Alias)
Go SDK での署名付き URL 生成
実際のアプリケーションで署名付き URL を生成する実装例を紹介します。
必要なパッケージ
import (
"crypto"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/feature/cloudfront/sign"
)
署名付き URL 生成
type CloudFrontSigner struct {
domain string // カスタムドメイン
signer *sign.URLSigner
}
func NewCloudFrontSigner(domain, keyPairId string, privateKey crypto.Signer) *CloudFrontSigner {
return &CloudFrontSigner{
domain: domain,
signer: sign.NewURLSigner(keyPairId, privateKey),
}
}
func (s *CloudFrontSigner) CreateSignedURL(key string, expire time.Duration) (string, error) {
// パスの正規化
if !strings.HasPrefix(key, "/") {
key = "/" + key
}
rawURL := "https://" + s.domain + key
expiresAt := time.Now().Add(expire)
return s.signer.Sign(rawURL, expiresAt)
}
使用例
// 秘密鍵は Secrets Manager 等から安全に取得
privateKey := getPrivateKeyFromSecretsManager()
signer := NewCloudFrontSigner(
"<your-domain>", // カスタムドメイン
"KXXXXXXXXXX", // Key Pair ID
privateKey,
)
// パス A のファイルの署名付き URL を生成
urlA, err := signer.CreateSignedURL("/path-a/file-123.dat", 5*time.Minute)
// => https://<your-domain>/path-a/file-123.dat?Expires=...&Signature=...&Key-Pair-Id=...
// パス B のファイルの署名付き URL を生成
urlB, err := signer.CreateSignedURL("/path-b/file-456.dat", 5*time.Minute)
// => https://<your-domain>/path-b/file-456.dat?Expires=...&Signature=...&Key-Pair-Id=...
パスが /path-a/* の場合は Bucket A、/path-b/* の場合は Bucket B に自動的にルーティングされますが、クライアントから見えるドメインは単一のカスタムドメインで統一されています。
まとめ
CloudFront 署名付き URL + パスベースルーティングを導入すると、以下のメリットがあります。
- 単一ドメインに統一: ファイアウォール許可は初回のみ
- パスで振り分け: バケット追加も URL のパス変更だけ
- OAC でセキュリティ強化: S3 への直接アクセスをブロック
バケット数が増えるほど運用負荷の差が顕著になります。ドメイン管理の運用負荷を改善したい場合に、ぜひ検討してみてください!
参考文献
Discussion