SJ blog
security
A

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

CloudFront署名付きURL vs 署名付きCookie — プライベートコンテンツ配信

CloudFrontの署名付きURL(Signed URL)と署名付きCookie(Signed Cookie)の違い、キーペアの作成、有効期限設定、S3との組み合わせ、OACによるS3直接アクセス禁止設定を解説。

一言結論

単一ファイルの一時公開には署名付きURL、複数ファイルへのまとめたアクセス制御には署名付きCookieを使うのが基本で、いずれもOACでS3への直接アクセスを封鎖して初めて意味を持つ。

プライベートコンテンツ配信の概要

CloudFrontでプライベートなS3コンテンツを配信する場合、S3を直接公開せずCloudFront経由でのみアクセスを許可する設計が必要だ。

❌ 悪い例(S3を直接公開):
  ユーザー → S3直接アクセス(誰でもアクセス可能)

✅ 良い例(CloudFront + 署名付きURL):
  ユーザー → CloudFront(署名検証) → S3(CloudFrontのみ許可)

          署名付きURLが必要

署名付きURL vs 署名付きCookie

署名付きURL:
  → 1つのファイルへのアクセスを制御
  → URLに署名を含む
  → 例: 動画ファイル1本のダウンロードリンク
  
署名付きCookie:
  → 複数ファイルへのアクセスを制御
  → Cookieに署名を含む(URLは変わらない)
  → 例: プレミアム会員が全動画にアクセス
  → RTMPディストリビューション(廃止)でも対応

使い分け:
  単一ファイルのDL → 署名付きURL
  複数ファイル・配信システム → 署名付きCookie
  URLを変更したくない場合 → 署名付きCookie

Origin Access Control(OAC)の設定

S3バケットへの直接アクセスをブロックし、CloudFrontからのみ許可する。

# OACの作成
aws cloudfront create-origin-access-control \
  --origin-access-control-config '{
    "Name": "my-oac",
    "OriginAccessControlOriginType": "s3",
    "SigningBehavior": "always",
    "SigningProtocol": "sigv4"
  }'

# S3バケットポリシー(CloudFrontサービスプリンシパルのみ許可)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-private-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/DISTRIBUTION_ID"
        }
      }
    }
  ]
}

キーグループと署名者(Signer)

署名付きURL/Cookieの種類:

1. 信頼されたキーグループ(推奨):
   → CloudFrontキーグループにRSA公開鍵を登録
   → 秘密鍵はアプリサーバーで管理
   → IAMユーザーは不要

2. CloudFront キーペア(旧方式、非推奨):
   → AWSルートアカウントでキーペアを生成
   → セキュリティ上推奨しない
# RSA キーペアの生成
openssl genrsa -out private_key.pem 2048
openssl rsa -pubout -in private_key.pem -out public_key.pem

# CloudFrontに公開鍵を登録
aws cloudfront create-public-key \
  --public-key-config '{
    "Name": "my-signing-key",
    "CallerReference": "my-signing-key-001",
    "EncodedKey": "'$(cat public_key.pem | tr -d '\n')'"
  }'

# キーグループを作成
aws cloudfront create-key-group \
  --key-group-config '{
    "Name": "my-key-group",
    "Items": ["PUBLIC_KEY_ID"]
  }'

署名付きURLの生成(Python)

from datetime import datetime, timedelta
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import base64
import json

def create_signed_url(url, private_key_path, key_pair_id, expiry_minutes=60):
    """CloudFront署名付きURLを生成"""
    
    expiry = int((datetime.utcnow() + timedelta(minutes=expiry_minutes)).timestamp())
    
    policy = json.dumps({
        "Statement": [{
            "Resource": url,
            "Condition": {
                "DateLessThan": {"AWS:EpochTime": expiry}
            }
        }]
    }, separators=(',', ':'))
    
    # 署名の生成
    with open(private_key_path, 'rb') as f:
        private_key = load_pem_private_key(f.read(), password=None, backend=default_backend())
    
    signature = private_key.sign(policy.encode('utf-8'), padding.PKCS1v15(), hashes.SHA1())
    encoded_signature = base64.b64encode(signature).decode('utf-8')
    encoded_signature = encoded_signature.replace('+', '-').replace('=', '_').replace('/', '~')
    
    encoded_policy = base64.b64encode(policy.encode('utf-8')).decode('utf-8')
    encoded_policy = encoded_policy.replace('+', '-').replace('=', '_').replace('/', '~')
    
    signed_url = f"{url}?Policy={encoded_policy}&Signature={encoded_signature}&Key-Pair-Id={key_pair_id}"
    return signed_url

# 使用例(boto3 CloudFrontSignerを使う方が実用的)
import boto3
from botocore.signers import CloudFrontSigner
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding

def rsa_signer(message):
    with open('private_key.pem', 'rb') as f:
        private_key = load_pem_private_key(f.read(), password=None)
    return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())

cf_signer = CloudFrontSigner('KEY_PAIR_ID', rsa_signer)

signed_url = cf_signer.generate_presigned_url(
    url='https://d1234.cloudfront.net/videos/movie.mp4',
    date_less_than=datetime.utcnow() + timedelta(hours=1)
)

署名付きCookieの設定

# 署名付きCookieの生成
signed_cookies = cf_signer.generate_signed_cookies(
    url='https://d1234.cloudfront.net/premium/*',  # ワイルドカード可
    date_less_than=datetime.utcnow() + timedelta(hours=24)
)

# HTTP レスポンスにCookieをセット
response.set_cookie('CloudFront-Policy', signed_cookies['CloudFront-Policy'])
response.set_cookie('CloudFront-Signature', signed_cookies['CloudFront-Signature'])
response.set_cookie('CloudFront-Key-Pair-Id', signed_cookies['CloudFront-Key-Pair-Id'])

試験頻出ポイント

シナリオ回答
単一動画ファイルの一時ダウンロードリンク署名付きURL
プレミアム会員が全コンテンツにアクセス署名付きCookie
S3への直接アクセスをブロックOAC(Origin Access Control)
有効期限付きアクセス制御署名付きURL(DateLessThan条件)
IPアドレスでのアクセス制限署名付きURL(IpAddress条件)

まとめ

CloudFrontの署名付きURLは単一ファイル、署名付きCookieは複数ファイルのアクセス制御に適している。OACでS3の直接アクセスを禁止し、CloudFront経由のみを許可する構成が現在の推奨設計だ。