CloudFront & Route 53 — OAC・署名付きURL・DNSSEC
CloudFrontのOAC、署名付きURL/Cookie、フィールドレベル暗号化、Route 53 DNSSEC・DNS Firewallを徹底解説
1. CloudFront セキュリティ概要
CloudFrontは単なるコンテンツ配信ネットワークではなく、強力なセキュリティ機能を備えたエッジセキュリティサービスです。本講では、実務で頻出のシナリオを軸に、SCS-C03で狙われるポイントを徹底解説します。
CloudFront 5層セキュリティスタック
Loading diagram...
CloudFront 署名付きURL フロー
Loading diagram...
Route 53 DNSSEC 検証チェーン
Loading diagram...
2. OAC (Origin Access Control) vs OAI (旧)
2.1 OAC(現在の推奨)
OACは2022年8月より、OAIの後継として推奨される機能です。
OACの特徴
- 署名付き要求:CloudFrontからオリジンへのすべてのリクエストに暗号署名を付与
- 短期クレデンシャル:署名は定期的にローテーション(手動対応不要)
- S3オブジェクトロック対応:より厳格なオリジン保護
- マルチリージョン対応:複数リージョンのS3に同じポリシー適用可能
- 対応サービス:S3、ALB、カスタムオリジン、MediaPackage、Lambda
OAC設定手順
# AWS CLIでOACを作成
aws cloudfront create-origin-access-control \
--origin-access-control-config \
CallerReference=my-oac-$(date +%s),\
Name=MyS3OAC,\
OriginAccessControlOriginType=s3,\
SigningBehavior=always,\
SigningProtocol=sigv4
CloudFront設定のサンプル(Terraform)
# OAC作成
resource "aws_cloudfront_origin_access_control" "s3_oac" {
name = "my-s3-oac"
description = "OAC for S3 Origin"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
# CloudFront Distribution
resource "aws_cloudfront_distribution" "s3_distribution" {
origin {
domain_name = aws_s3_bucket.my_bucket.bucket_regional_domain_name
origin_id = "myS3Origin"
origin_access_control_id = aws_cloudfront_origin_access_control.s3_oac.id
}
enabled = true
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "myS3Origin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# S3バケットポリシー(CloudFrontのみアクセス許可)
resource "aws_s3_bucket_policy" "cloudfront_policy" {
bucket = aws_s3_bucket.my_bucket.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCloudFrontOAC"
Effect = "Allow"
Principal = {
Service = "cloudfront.amazonaws.com"
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.my_bucket.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
}
}
}
]
})
}
2.2 OAI(旧・非推奨)
OAIの特徴と問題点
- CloudFront用の特別なCanonical User Idを使用
- 署名なしのリクエストをS3に送信(署名はCloudFrontが作成しない)
- 新規ユースケースではサポート外
OAI → OAC 移行手順
# 1. 既存OAIを特定
aws cloudfront list-origin-access-controls
# 2. 新しいOACを作成
aws cloudfront create-origin-access-control \
--origin-access-control-config CallerReference=migration-$(date +%s),\
Name=MigratedOAC,\
OriginAccessControlOriginType=s3,\
SigningBehavior=always,\
SigningProtocol=sigv4
# 3. CloudFront Distribution設定を更新
aws cloudfront get-distribution-config --id DISTRIBUTION_ID > dist-config.json
# dist-config.jsonを編集してOriginAccessControlIdを新OACに指定
# 4. S3バケットポリシーを更新(上記Terraformサンプル参照)
2.3 OAC vs OAI 比較表
| 項目 | OAC | OAI |
|---|---|---|
| リリース年 | 2022年8月 | 2010年代 |
| 署名方式 | SigV4(署名付き) | 無署名 |
| クレデンシャル更新 | 自動 | 手動対応要 |
| S3オブジェクトロック | 対応 | 非対応 |
| マルチリージョン | 対応 | 各リージョン別設定 |
| 推奨度 | 推奨 | 廃止予定 |
| 新規構築 | ✓ | ✗ |
3. 署名付きURL vs 署名付きCookie
CloudFrontで「特定ユーザーのみ」コンテンツにアクセスさせる場合に使用する2つのパターンを解説します。
3.1 署名付きURL(Signed URL)
ユースケース
- 有効期限付きのワンタイムリンク:メール送信、Share機能など
- 個別ファイルへの限定アクセス:PDFレポート、動画ファイル
- キャッシュされたリソースの配布
署名付きURLの構造
https://d1234.cloudfront.net/path/to/file.pdf
?Expires=1672531200
&Signature=abc123xyz...
&Key-Pair-Id=APKAJXYZ123
Pythonでの署名付きURL生成
import json
import base64
import hashlib
import hmac
from datetime import datetime, timedelta
from urllib.parse import urlencode, quote
def create_signed_url(
domain,
key_pair_id,
private_key_path,
file_path,
expiry_minutes=60
):
"""CloudFront署名付きURLを生成"""
# 署名対象の文字列を作成
expiration_timestamp = int(
(datetime.utcnow() + timedelta(minutes=expiry_minutes)).timestamp()
)
policy = {
"Statement": [
{
"Resource": f"https://{domain}{file_path}",
"Condition": {
"DateLessThan": {"AWS:EpochTime": expiration_timestamp}
}
}
]
}
policy_json = json.dumps(policy, separators=(',', ':'))
policy_b64 = base64.b64encode(policy_json.encode()).decode()
# 秘密鍵で署名
with open(private_key_path, 'rb') as f:
private_key = f.read()
signature = base64.b64encode(
hmac.new(
private_key,
policy_b64.encode(),
hashlib.sha1
).digest()
).decode()
# URL生成
url = f"https://{domain}{file_path}?Policy={policy_b64}&Signature={signature}&Key-Pair-Id={key_pair_id}"
# URL安全な文字列に変換
return url.replace('+', '-').replace('=', '_').replace('/', '~')
# 使用例
signed_url = create_signed_url(
domain="d1234.cloudfront.net",
key_pair_id="APKAJXYZ123",
private_key_path="/path/to/private-key.pem",
file_path="/premium/report.pdf",
expiry_minutes=30
)
print(signed_url)
AWS CLIでの生成(更にシンプル)
# AWS SDK(Boto3)を使用した生成
pip install boto3
python3 << 'EOF'
import boto3
from botocore.signers import CloudFrontSigner
import os
# CloudFront署名オブジェクト
def rsa_signer(message):
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
with open('/path/to/private-key.pem', 'rb') as f:
from cryptography.hazmat.primitives.serialization import load_pem_private_key
key = load_pem_private_key(f.read(), password=None, backend=default_backend())
return key.sign(message, padding.PKCS1v15(), hashes.SHA1())
cloudfront_signer = CloudFrontSigner(
"APKAJXYZ123", # Key Pair ID
rsa_signer
)
url = cloudfront_signer.generate_presigned_url(
"https://d1234.cloudfront.net/premium/report.pdf",
date_less_than=datetime.utcnow() + timedelta(hours=1)
)
print(url)
EOF
3.2 署名付きCookie(Signed Cookie)
ユースケース
- 複数ファイルへのアクセス許可:フルディレクトリ、月間サブスクリプション
- セッション型アクセス制御:ユーザーログイン後の全リソース
- キャッシュクリアの簡素化
署名付きCookieの設定
// Node.js + Express での実装例
const cloudfront = require('aws-sdk/clients/cloudfront');
const AWS = require('aws-sdk');
const fs = require('fs');
const crypto = require('crypto');
async function generateSignedCookie(
domainPattern,
keyPairId,
privateKeyPath,
expiryHours = 24
) {
// 秘密鍵を読み込み
const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
// ポリシード文字列
const expirationDate = new Date();
expirationDate.setHours(expirationDate.getHours() + expiryHours);
const expiration = Math.floor(expirationDate.getTime() / 1000);
const policy = {
Statement: [
{
Resource: `https://${domainPattern}/*`,
Condition: {
DateLessThan: {
'AWS:EpochTime': expiration
}
}
}
]
};
const policyString = JSON.stringify(policy);
const policyBase64 = Buffer.from(policyString).toString('base64')
.replace(/\+/g, '-')
.replace(/=/g, '_')
.replace(/\//g, '~');
// SigV4署名生成
const signature = crypto
.createSign('RSA-SHA1')
.update(policyBase64)
.sign(privateKey, 'base64')
.replace(/\+/g, '-')
.replace(/=/g, '_')
.replace(/\//g, '~');
return {
'CloudFront-Policy': policyBase64,
'CloudFront-Signature': signature,
'CloudFront-Key-Pair-Id': keyPairId
};
}
// Express でのCookie設定
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// ユーザー認証(省略)
if (authenticateUser(username, password)) {
const cookies = await generateSignedCookie(
'd1234.cloudfront.net',
'APKAJXYZ123',
'/path/to/private-key.pem'
);
// Cookie設定(HttpOnly, Secure, SameSite必須)
res.cookie('CloudFront-Policy', cookies['CloudFront-Policy'], {
httpOnly: true,
secure: true,
sameSite: 'Lax',
maxAge: 24 * 60 * 60 * 1000 // 24時間
});
res.cookie('CloudFront-Signature', cookies['CloudFront-Signature'], {
httpOnly: true,
secure: true,
sameSite: 'Lax',
maxAge: 24 * 60 * 60 * 1000
});
res.cookie('CloudFront-Key-Pair-Id', cookies['CloudFront-Key-Pair-Id'], {
httpOnly: true,
secure: true,
sameSite: 'Lax',
maxAge: 24 * 60 * 60 * 1000
});
res.json({ message: 'Login successful' });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
3.3 Trusted Key Groups と CloudFront Key Pairs
Key Pair作成と管理
# 1. CloudFront Key Pair を生成(AWS IAMコンソールまたはCLI)
# CloudFrontの秘密キーはダウンロード後、安全に保管
# 2. Terraform での Key Pair ID 登録
resource "aws_cloudfront_distribution" "s3_distribution" {
# ... 前のコード ...
trusted_key_groups {
enabled = true
items = [aws_cloudfront_key_group.primary.id]
}
}
resource "aws_cloudfront_key_group" "primary" {
name = "production-keys"
comment = "Production signing key group"
# 複数のKey Pair IDを登録可能
items = [
"APKAJXYZ123", # Primary key
"APKAQWE456", # Backup key(ローテーション用)
]
}
# 3. キーローテーション(定期的に実施)
# 古いキーはItems から削除する前に、猶予期間を設ける
resource "aws_cloudfront_key_group" "primary" {
name = "production-keys"
items = [
"APKAQWE456", # New key
"APKAJXYZ123", # Old key(1ヶ月の猶予期間)
]
}
3.4 署名付きURL vs Cookie の選択マトリックス
| 判断軸 | 署名付きURL | 署名付きCookie |
|---|---|---|
| 複数ファイル許可 | ✗(URL単位) | ✓(全体許可) |
| ワンタイム配布 | ✓(有効期限短縮可) | ✗ |
| セッション型制御 | ✗ | ✓ |
| 共有・メール送信 | ✓ | ✗(Cookieは転送不可) |
| キャッシュヒット率 | 中 | 高 |
| 複雑度 | 低 | 中 |
4. フィールドレベル暗号化(Field-Level Encryption)
4.1 概要
フィールドレベル暗号化は、CloudFront上で特定のPOSTフィールドをエッジで暗号化し、オリジンサーバーでのみ復号化する機能です。
セキュリティモデル
┌─────────────────────────────────────────────┐
│ ブラウザ ─→ CloudFront ─→ オリジンサーバー │
│ (暗号化開始) (復号化開始) │
│ Pub Key Private Key │
│ credit_card_number (平文) → (暗号化) │
│ → (暗号文) │
│ other_fields (平文のまま転送) │
└─────────────────────────────────────────────┘
4.2 設定手順
Terraform での設定
# 1. フィールドレベル暗号化プロファイルを作成
resource "aws_cloudfront_field_level_encryption_profile" "pci_compliant" {
name = "pci-compliant-profile"
comment = "PCI DSS対応の決済フィールド暗号化"
provider_id = aws_cloudfront_field_level_encryption_config.pci_config.id
for_each_field_to_encrypt {
provider_id = aws_cloudfront_field_level_encryption_config.pci_config.id
}
}
# 2. フィールド暗号化設定を定義
resource "aws_cloudfront_field_level_encryption_config" "pci_config" {
name = "pci-encryption-config"
comment = "Encrypt payment fields with customer public key"
# 暗号化対象フィールドを指定
query_arg_profile_config {
enabled = false # Query stringではなくPOSTボディを対象
}
content_type_profile_config {
enabled = true
# PCI-DSS対応:credit_card_number フィールドを暗号化
content_type_profiles {
items {
content_type = "application/x-www-form-urlencoded"
format = "JSON"
# 暗号化対象フィールドのリスト
field_patterns {
items = ["credit_card_number", "cvv", "cardholder_name"]
}
}
}
}
# 公開鍵設定
provider_id = aws_cloudfront_public_key.merchant_key.id
}
# 3. マーチャント用の公開鍵をCloudFrontに登録
resource "aws_cloudfront_public_key" "merchant_key" {
name = "merchant-encryption-key"
public_key = file("/path/to/public-key.pem")
comment = "Merchant's RSA 2048 public key for field encryption"
}
# 4. Distribution に フィールド暗号化プロファイルを適用
resource "aws_cloudfront_distribution" "payment_api" {
origin {
domain_name = "api.example.com"
origin_id = "paymentOrigin"
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "paymentOrigin"
# フィールド暗号化プロファイルを適用
field_level_encryption_id = aws_cloudfront_field_level_encryption_profile.pci_compliant.id
viewer_protocol_policy = "https-only"
}
enabled = true
}
4.3 オリジンサーバーでの復号化
// Node.js Express でのフィールド復号化例
const crypto = require('crypto');
const fs = require('fs');
function decryptCloudFrontField(encryptedData, privateKeyPath, privateKeyPassword) {
// CloudFrontが暗号化したフィールドを復号化
const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
// 暗号化されたデータはBase64エンコード形式
const encryptedBuffer = Buffer.from(encryptedData, 'base64');
// RSA OAEP + SHA256で復号化
const decrypted = crypto.privateDecrypt(
{
key: privateKey,
passphrase: privateKeyPassword,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
},
encryptedBuffer
);
return decrypted.toString('utf-8');
}
// Express ミドルウェア
app.post('/api/payment', (req, res) => {
// CloudFrontが暗号化したフィールドを復号化
const creditCardNumber = decryptCloudFrontField(
req.body.credit_card_number,
'/secure/private-key.pem',
process.env.PRIVATE_KEY_PASSWORD
);
// 以降は通常のPCI-DSS対応処理
console.log('復号化されたカード番号:', creditCardNumber);
res.json({ success: true });
});
4.4 重要な注意点
| 項目 | 説明 |
|---|---|
| 暗号化範囲 | POST/PUT/PATCH ボディのみ(Query、Headers、Cookies不可) |
| 鍵管理 | AWS KMS と連携しない(自社で秘密鍵を管理) |
| パフォーマンス | わずかな遅延あり(暗号化処理が発生) |
| PCI-DSS | フィールド暗号化単独ではPCI-DSS達成不可(TLS, WAF等との併用必須) |
| キーローテーション | 古い公開鍵を削除すると過去の暗号化データ復号不可に |
5. Geo制限(Geo Blocking)
5.1 ホワイトリスト方式(許可リスト)
指定国のみアクセス許可
resource "aws_cloudfront_distribution" "japan_only" {
default_cache_behavior {
# ... 前のコード ...
viewer_protocol_policy = "redirect-to-https"
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["JP"] # 日本のみ
}
}
enabled = true
}
5.2 ブラックリスト方式(拒否リスト)
指定国をブロック
resource "aws_cloudfront_distribution" "block_countries" {
restrictions {
geo_restriction {
restriction_type = "blacklist"
locations = [
"KP", # 北朝鮮
"IR", # イラン
"CU", # キューバ
"SY" # シリア
]
}
}
enabled = true
}
5.3 国コード ISO 3166-1 alpha-2 リファレンス
| 国 | コード |
|---|---|
| 日本 | JP |
| アメリカ | US |
| 中国 | CN |
| インド | IN |
| ドイツ | DE |
| フランス | FR |
| 英国 | GB |
| カナダ | CA |
| オーストラリア | AU |
| ロシア | RU |
5.4 Geo制限 + Lambda@Edge での動的制御
// Lambda@Edge Viewer Request での動的Geo制限
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
// CloudFrontが付与する地理情報ヘッダーを取得
const countryCode = request.headers['cloudfront-viewer-country']?.[0]?.value;
// ホワイトリスト
const allowedCountries = ['JP', 'US', 'GB', 'DE', 'FR'];
if (!allowedCountries.includes(countryCode)) {
// 403 Forbidden を返す
return {
status: '403',
statusDescription: 'Forbidden',
headers: {
'content-type': [
{
key: 'Content-Type',
value: 'text/plain'
}
]
},
body: 'Access denied from your country.'
};
}
return request;
};
6. セキュリティヘッダー(レスポンスヘッダーポリシー)
6.1 CloudFront レスポンスヘッダーポリシー(Response Headers Policy)
モダンなCloudFrontでは、セキュリティヘッダーを統一的に管理できます。
Terraform での設定
# セキュリティヘッダーポリシーを作成
resource "aws_cloudfront_response_headers_policy" "security_headers" {
name = "security-headers-policy"
comment = "OWASP推奨セキュリティヘッダー"
# HSTS (Strict-Transport-Security)
strict_transport_security {
access_control_max_age_sec = 31536000 # 1年
include_subdomains = true
override = false
}
# X-Frame-Options (Clickjacking対策)
frame_options {
frame_option = "DENY"
override = false
}
# X-Content-Type-Options (MIME Sniffing対策)
content_type_options {
override = false
}
# X-XSS-Protection (古いブラウザ対応)
xss_protection {
mode_block = true
protection = true
override = false
}
# Content-Security-Policy
content_security_policy {
content_security_policy = "default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
override = false
}
# Referrer-Policy
referrer_policy {
referrer_policy = "strict-no-referrer"
override = false
}
# Permissions-Policy (Feature Policy)
permissions_policy {
permissions_policy = "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
override = false
}
# Custom Headers
custom_headers_config {
items {
header = "X-Custom-Security"
value = "enabled"
override = false
}
}
}
# Distributionに適用
resource "aws_cloudfront_distribution" "secure_app" {
default_cache_behavior {
# ... 前のコード ...
response_headers_policy_id = aws_cloudfront_response_headers_policy.security_headers.id
}
enabled = true
}
6.2 各セキュリティヘッダーの詳細
Strict-Transport-Security (HSTS)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
- HTTPS強制(中間者攻撃対策)
preload: Google HSTS Preload Listへの登録対応- 注意: HTTPからのリダイレクトでは効果なし(初回アクセス時に有効化されない)
Content-Security-Policy (CSP)
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'
- XSS(クロスサイトスクリプティング)対策
- ディレクティブの種類:
default-src: すべてのリソースのデフォルトscript-src: JavaScriptの読み込み元style-src: CSS の読み込み元img-src: 画像の読み込み元connect-src: XHR、WebSocket等の通信先
X-Frame-Options
X-Frame-Options: DENY
- Clickjacking対策
- DENY: iframe埋め込み完全禁止
- SAMEORIGIN: 同一オリジンのみ許可
Referrer-Policy
Referrer-Policy: strict-no-referrer
- リファラー情報の露出を制御
strict-no-referrer: すべてのリファラー情報を削除
7. TLS設定(Transport Layer Security)
7.1 最小TLSバージョン指定
resource "aws_cloudfront_distribution" "tls_security" {
viewer_certificate {
cloudfront_default_certificate = false
acm_certificate_arn = aws_acm_certificate.my_cert.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021" # TLS 1.2以上を強制
}
enabled = true
}
TLS バージョンの選択肢
| バージョン | 説明 | 推奨度 |
|---|---|---|
| SSLv3 | 廃止(非推奨) | ✗ |
| TLSv1.0 | 脆弱(非推奨) | ✗ |
| TLSv1.1 | 脆弱(非推奨) | ✗ |
| TLSv1.2 | 標準(推奨) | ✓ |
| TLSv1.3 | 最新(強く推奨) | ✓✓ |
7.2 SNI (Server Name Indication) と 専用IP
# SNI設定(複数ドメインで同一IPを共用)
resource "aws_cloudfront_distribution" "sni_enabled" {
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.my_cert.arn
ssl_support_method = "sni-only" # SNIベース(推奨)
}
enabled = true
}
# 専用IP設定(古いクライアント互換性が必要な場合)
resource "aws_cloudfront_distribution" "dedicated_ip" {
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.my_cert.arn
ssl_support_method = "vip" # 専用IP
# 警告: 月額600USD程度の追加料金
}
enabled = true
}
7.3 ACM証明書の注意点
CloudFront用のACM証明書は us-east-1 専用
# us-east-1でのみ証明書を作成可能
aws acm request-certificate \
--region us-east-1 \
--domain-name example.com \
--subject-alternative-names www.example.com \
--validation-method DNS
8. Lambda@Edge / CloudFront Functions
8.1 Lambda@Edge での セキュリティ実装
// Lambda@Edge - Viewer Request
// 認証トークンの検証、リクエストの改変
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
// 認証トークンを検証
const authToken = headers['authorization']?.[0]?.value;
if (!authToken || !validateJWT(authToken)) {
return {
status: '401',
statusDescription: 'Unauthorized',
body: 'Missing or invalid authorization token'
};
}
// リクエストのHostヘッダーを改変
request.headers['host'] = [
{
key: 'Host',
value: 'api.internal.example.com'
}
];
return request;
};
// Lambda@Edge - Origin Response
// レスポンスヘッダーの追加、キャッシュ制御の動的変更
exports.handler = async (event) => {
const response = event.Records[0].cf.response;
const headers = response.headers;
// セキュリティヘッダーを追加
headers['strict-transport-security'] = [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains'
}
];
headers['x-content-type-options'] = [
{
key: 'X-Content-Type-Options',
value: 'nosniff'
}
];
// キャッシュを動的に制御
const contentType = headers['content-type']?.[0]?.value || '';
if (contentType.includes('text/html')) {
headers['cache-control'] = [
{
key: 'Cache-Control',
value: 'max-age=3600' // HTML は1時間
}
];
} else if (contentType.includes('application/javascript')) {
headers['cache-control'] = [
{
key: 'Cache-Control',
value: 'max-age=31536000' // JS は1年
}
];
}
return response;
};
8.2 CloudFront Functions での軽量セキュリティ処理
// CloudFront Functions - Viewer Request
// Lambda@Edge よりシンプルで低遅延(CloudFront Functions推奨)
function handler(event) {
const request = event.request;
// リクエストURI を検証
if (request.uri.includes('..')) {
return {
statusCode: 400,
statusDescription: 'Bad Request'
};
}
// SQLi, XSS 的なペイロード検出(簡易版)
const uri = request.uri + (request.querystring ? '?' + request.querystring : '');
const suspiciousPatterns = ['union', 'select', '<script>', 'eval('];
if (suspiciousPatterns.some(pattern => uri.toLowerCase().includes(pattern))) {
return {
statusCode: 403,
statusDescription: 'Forbidden'
};
}
return request;
}
8.3 Lambda@Edge と CloudFront Functions の選択基準
| 項目 | Lambda@Edge | CloudFront Functions |
|---|---|---|
| 遅延 | 100~200ms | < 1ms |
| タイムアウト | 30秒 | 1秒 |
| 言語 | Python, Node.js, Java | JavaScript のみ |
| ログ | CloudWatch | リアルタイムログ |
| ユースケース | 複雑な認証・データ変換 | ヘッダー操作・簡易ルーティング |
9. WAF 連携
9.1 CloudFront + AWS WAF
# AWS WAFv2 WebACL作成
resource "aws_wafv2_web_acl" "cloudfront_waf" {
name = "cloudfront-protection"
scope = "CLOUDFRONT" # CloudFront用のスコープ
default_action {
allow {} # デフォルトは許可
}
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 0
action {
block {} # マッチしたらブロック
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
# ルールの例外設定
rule_action_override {
name = "SizeRestrictions_BODY"
action_to_use {
count {} # ブロックではなくカウントのみ
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesCommonRuleSetMetric"
sampled_requests_enabled = true
}
}
rule {
name = "RateLimitRule"
priority = 1
action {
block {}
}
statement {
rate_based_statement {
limit = 2000 # リクエスト数/5分
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimitRuleMetric"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CloudFrontWAFMetric"
sampled_requests_enabled = true
}
}
# CloudFront Distribution に WAF を関連付け
resource "aws_cloudfront_distribution" "waf_protected" {
web_acl_id = aws_wafv2_web_acl.cloudfront_waf.arn
# ... 前のコード ...
enabled = true
}
9.2 WAFログの設定
# S3 バケット(ログ保存先)
resource "aws_s3_bucket" "waf_logs" {
bucket = "my-waf-logs-${data.aws_caller_identity.current.account_id}"
}
resource "aws_s3_bucket_public_access_block" "waf_logs" {
bucket = aws_s3_bucket.waf_logs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# WAFログ設定
resource "aws_wafv2_web_acl_logging_configuration" "cloudfront_logging" {
resource_arn = aws_wafv2_web_acl.cloudfront_waf.arn
log_destination_configs = [aws_cloudwatch_log_group.waf_logs.arn]
redacted_fields {
single_header {
name = "authorization" # 機密情報をマスク
}
}
redacted_fields {
single_header {
name = "cookie"
}
}
}
resource "aws_cloudwatch_log_group" "waf_logs" {
name = "/aws/waf/cloudfront"
retention_in_days = 30
}
10. ログ設定(標準ログ vs リアルタイムログ)
10.1 標準ログ(CloudFront Standard Logs)
S3への非同期ログ出力
resource "aws_cloudfront_distribution" "with_logs" {
logging_config {
include_cookies = false
bucket = aws_s3_bucket.logs.bucket_regional_domain_name
prefix = "cloudfront-logs/"
}
enabled = true
}
# ログの読み取り例
# ts c-ip cs-method cs-uri-stem sc-status cs-bytes time-taken x-edge-location
# 2026-04-27T10:00:00Z 203.0.113.45 GET /index.html 200 5000 150 NRT50
10.2 リアルタイムログ(CloudFront Real-time Logs)
Kinesis Data Streams へのリアルタイム配信
resource "aws_kinesis_stream" "cloudfront_logs" {
name = "cloudfront-realtime-logs"
shard_count = 1
retention_period = 24
}
resource "aws_cloudfront_distribution" "realtime_logging" {
realtime_log_config_arn = aws_cloudfront_realtime_log_config.example.arn
enabled = true
}
resource "aws_cloudfront_realtime_log_config" "example" {
name = "cloudfront-realtime-config"
sampling_rate = 100 # 100% のリクエストをログ
end_points {
stream_type = "Kinesis"
kinesis_stream_config {
role_arn = aws_iam_role.cloudfront_logs.arn
stream_arn = aws_kinesis_stream.cloudfront_logs.arn
}
}
fields = [
"timestamp",
"c-country",
"cs-host",
"cs-uri-stem",
"c-ip",
"cs-user-agent",
"cs-referer",
"cs-method",
"cs-protocol",
"cs-host",
"cs-bytes",
"sc-bytes",
"sc-status",
"x-edge-location",
"x-edge-request-id",
"x-host-header",
"cs-protocol-version",
"x-ssl-protocol",
"x-ssl-cipher",
"x-edge-response-result-type"
]
}
10.3 ログ分析(Athena統合)
-- CloudFront ログをAthenaで分析
-- テーブル作成
CREATE EXTERNAL TABLE IF NOT EXISTS cloudfront_logs (
date STRING,
time STRING,
x_edge_location STRING,
bytes_sent BIGINT,
c_ip STRING,
cs_method STRING,
cs_host STRING,
cs_uri_stem STRING,
sc_status INT,
cs_referer STRING,
cs_user_agent STRING,
cs_uri_query STRING,
cs_cookie STRING,
x_edge_result_type STRING,
x_edge_request_id STRING,
x_host_header STRING,
cs_protocol STRING,
cs_bytes BIGINT,
time_taken DOUBLE,
x_forwarded_for STRING,
ssl_protocol STRING,
ssl_cipher STRING,
x_edge_response_result_type STRING
)
PARTITIONED BY (year STRING, month STRING, day STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LOCATION 's3://my-bucket/cloudfront-logs/'
-- セキュリティインシデント検出
SELECT
date,
time,
c_ip,
cs_method,
cs_uri_stem,
sc_status,
COUNT(*) as request_count
FROM cloudfront_logs
WHERE sc_status = 403 -- WAFブロック
GROUP BY date, time, c_ip, cs_method, cs_uri_stem, sc_status
HAVING COUNT(*) > 50 -- 1時間で50回以上ブロックされたIP
ORDER BY request_count DESC
LIMIT 100;
-- 地理的な異常検出
SELECT
date,
c_country,
COUNT(*) as request_count,
SUM(bytes_sent) as bytes_sent
FROM cloudfront_logs
WHERE c_country NOT IN ('JP', 'US', 'GB', 'DE') -- 想定外の国
GROUP BY date, c_country
ORDER BY request_count DESC;
11. CloudFront できないこと・制約
11.1 主な制約事項
| 制約 | 説明 |
|---|---|
| ACM証明書は us-east-1 専用 | CloudFront用カスタムSSL証明書はus-east-1でのみ作成可能 |
| キャッシュキーの自動管理 | キャッシュ戦略が固い(設定の自由度に制限あり) |
| オリジンプロトコルの混在 | HTTP + HTTPS オリジンの併用は不可 |
| ワイルドカードディストリビューション | すべてのサブドメインをカバーする単一certificateは使用可能だが、複数の独立したDistrを要する場合多い |
| リクエストヘッダーサイズ | 40KB制限 |
| レスポンスボディサイズ | 20MB制限(Lambda@Edge Viewer Responseでは1MB) |
11.2 キャッシュとセキュリティの相互作用
# キャッシュとセキュリティヘッダーのパラドックス例
resource "aws_cloudfront_distribution" "cache_security_balance" {
default_cache_behavior {
# キャッシュ有効化
cached_methods = ["GET", "HEAD", "OPTIONS"]
# ただし機密情報を含むレスポンスはキャッシュされてはいけない
cache_policy_id = aws_cloudfront_cache_policy.standard.id
# セキュリティヘッダーはキャッシュされる
# → 問題: セキュリティポリシー変更時にキャッシュクリアが必要
response_headers_policy_id = aws_cloudfront_response_headers_policy.security_headers.id
}
# Cache invalidation の手動実行が必要な場合がある
# aws cloudfront create-invalidation --distribution-id XXXXX --paths "/*"
}
12. Route 53 セキュリティ
Route 53は権威DNSサーバーとして、ドメイン名解決の「玄関」を守る重要な役割を果たします。
12.1 DNSSEC(DNS Security Extensions)
DNSレスポンスの真正性と完全性を暗号化署名で保証
DNSSEC有効化
# AWS CLIでDNSSEC有効化
aws route53 enable-dnssec \
--hosted-zone-id /hostedzone/Z1234567890ABC \
--region us-east-1
# KSK (Key Signing Key) と ZSK (Zone Signing Key) が自動生成される
Terraform での DNSSEC設定
resource "aws_route53_zone" "secure_zone" {
name = "example.com"
}
# DNSSEC 有効化
resource "aws_route53_dnssec" "example" {
hosted_zone_id = aws_route53_zone.secure_zone.zone_id
signing_status = "signing" # DNSSEC署名を開始
}
# DNSSEC ステータス確認
resource "aws_route53_key_signing_key" "example" {
hosted_zone_id = aws_route53_zone.secure_zone.zone_id
key_management_service_arn = aws_kms_key.dnssec.arn
name = "example-dnssec-key"
status = "ACTIVE"
}
DNSSEC 署名の仕組み
┌─────────────────────────────────────────────┐
│ example.com の DNS レコード │
│ (A, AAAA, MX, TXT等) │
└────────────────────┬────────────────────────┘
│
↓
┌─────────────────────────────────────────────┐
│ Route 53 が ZSK (Zone Signing Key) で署名 │
│ → RRSIG (Resource Record Signature) 生成 │
└────────────────────┬────────────────────────┘
│
↓
┌─────────────────────────────────────────────┐
│ リゾルバー(クライアント)が署名を検証 │
│ DNSSEC Validation = OK / NG │
└─────────────────────────────────────────────┘
DNSSEC の検証ツール
# DNSSEC署名の状態を確認
dig +dnssec example.com
# DNSSECチェーン全体を確認
delv +multiline @ns-123.awsdns-45.com example.com
# DNSSEC Analyzer で検証
# https://dnssec-analyzer.verisignlabs.com/
12.2 Route 53 Resolver + DNS Firewall
組織内のDNSクエリを一元管理し、マルウェアドメイン等をブロック
# VPC用 Resolver Endpoint(インバウンド)
resource "aws_route53_resolver_endpoint" "inbound" {
name = "corp-inbound-resolver"
direction = "INBOUND"
security_group_ids = [aws_security_group.resolver.id]
ip_address_subnet {
subnet_id = aws_subnet.private_1.id
}
ip_address_subnet {
subnet_id = aws_subnet.private_2.id
}
}
# DNS Firewall - ドメイン制御
resource "aws_route53_resolver_firewall_domain_list" "malware_domains" {
name = "malware-domains"
tags = {
Category = "Malware"
}
}
resource "aws_route53_resolver_firewall_rule_group" "org_policy" {
name = "corporate-dns-policy"
share_status = "NOT_SHARED"
}
# ルール: マルウェアドメインをブロック
resource "aws_route53_resolver_firewall_rule" "block_malware" {
action = "BLOCK"
firewall_domain_list_id = aws_route53_resolver_firewall_domain_list.malware_domains.id
firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.org_policy.id
name = "block-malware-domains"
priority = 100
block_response = "NXDOMAIN" # ドメインが存在しないと応答
}
# ルール: 特定カテゴリをブロック(成人向け、ギャンブル等)
resource "aws_route53_resolver_firewall_rule" "block_category" {
action = "BLOCK"
firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.org_policy.id
name = "block-adult-content"
priority = 200
block_response = "NODATA" # レコードなしと応答
}
# VPC に DNS Firewall ルールグループを関連付け
resource "aws_route53_resolver_firewall_rule_group_association" "vpc_association" {
firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.org_policy.id
vpc_id = aws_vpc.corporate.id
name = "corporate-dns-rules"
priority = 100
}
12.3 DNS クエリログ
# CloudWatch Logs グループ
resource "aws_cloudwatch_log_group" "dns_query_logs" {
name = "/aws/route53/query-logs"
retention_in_days = 30
}
# Route 53 Query Logging
resource "aws_route53_query_log_config" "example" {
cloudwatch_log_group_arn = "${aws_cloudwatch_log_group.dns_query_logs.arn}:*"
zone_id = aws_route53_zone.example.zone_id
}
# ログフォーマット
# version query_timestamp query_name query_type query_class response_code
# 1 2026-04-27T10:00:00.123Z example.com A IN NOERROR
12.4 ヘルスチェック + フェイルオーバー
# ヘルスチェック(エンドポイント監視)
resource "aws_route53_health_check" "primary_alb" {
fqdn = "primary.example.com"
port = 443
type = "HTTPS"
resource_path = "/health"
failure_threshold = 3
request_interval = 30
tags = {
Name = "Primary ALB Health Check"
}
}
# フェイルオーバー ルーティング(Primary → Failover)
resource "aws_route53_record" "primary" {
zone_id = aws_route53_zone.example.zone_id
name = "app.example.com"
type = "A"
failover_routing_policy {
type = "PRIMARY"
}
alias {
name = aws_lb.primary.dns_name
zone_id = aws_lb.primary.zone_id
evaluate_target_health = true # ヘルスチェック結果を反映
}
set_identifier = "primary"
}
resource "aws_route53_record" "failover" {
zone_id = aws_route53_zone.example.zone_id
name = "app.example.com"
type = "A"
failover_routing_policy {
type = "SECONDARY"
}
alias {
name = aws_lb.failover.dns_name
zone_id = aws_lb.failover.zone_id
evaluate_target_health = true
}
set_identifier = "failover"
}
12.5 プライベートホストゾーン
# 企業ネットワーク内のプライベートDNS
resource "aws_route53_zone" "private" {
name = "internal.example.com"
vpc {
vpc_id = aws_vpc.corporate.id
}
tags = {
Name = "Corporate Internal DNS"
}
}
# プライベートレコード
resource "aws_route53_record" "database" {
zone_id = aws_route53_zone.private.zone_id
name = "db.internal.example.com"
type = "A"
ttl = 300
records = [aws_db_instance.main.address]
}
resource "aws_route53_record" "cache" {
zone_id = aws_route53_zone.private.zone_id
name = "cache.internal.example.com"
type = "A"
ttl = 300
records = [aws_elasticache_cluster.redis.cache_nodes[0].address]
}
12.6 Resolver Endpoints(インバウンド/アウトバウンド)
インバウンド(VPC外からのクエリ受付)
resource "aws_route53_resolver_endpoint" "inbound" {
name = "inbound-resolver"
direction = "INBOUND"
security_group_ids = [aws_security_group.resolver_inbound.id]
ip_address_subnet {
subnet_id = aws_subnet.private_a.id
}
ip_address_subnet {
subnet_id = aws_subnet.private_b.id
}
}
# セキュリティグループ
resource "aws_security_group" "resolver_inbound" {
name = "resolver-inbound-sg"
description = "Allow DNS queries from corporate network"
vpc_id = aws_vpc.corporate.id
ingress {
from_port = 53
to_port = 53
protocol = "udp"
cidr_blocks = ["10.0.0.0/8"] # 企業内ネットワーク
}
ingress {
from_port = 53
to_port = 53
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
}
アウトバウンド(VPC内からのフォワード)
resource "aws_route53_resolver_endpoint" "outbound" {
name = "outbound-resolver"
direction = "OUTBOUND"
security_group_ids = [aws_security_group.resolver_outbound.id]
ip_address_subnet {
subnet_id = aws_subnet.private_a.id
}
}
# フォワード ルール(特定ドメインのクエリを外部DNSに転送)
resource "aws_route53_resolver_rule" "forward_to_external" {
domain_name = "partner.example.com"
name = "forward-partner-dns"
rule_type = "FORWARD"
target_ip {
ip = "8.8.8.8" # Google Public DNS
}
}
resource "aws_route53_resolver_rule_association" "vpc_forward" {
resolver_rule_id = aws_route53_resolver_rule.forward_to_external.id
vpc_id = aws_vpc.corporate.id
}
13. Route 53 できないこと・制約
| 制約 | 説明 |
|---|---|
| DNSSEC署名の既存ドメイン移行 | 既に別のDNSSECプロバイダーで署名されたドメインは、Route 53でのDNSSEC有効化前にDS記録を削除する必要がある |
| Resolver Endpoints のクォータ | VPC当たり 4つまで(リージョン単位) |
| Query Logging のログ量 | 極めて大規模なDNSクエリ(100万/日以上)の場合、CloudWatch Logsコストが高額に |
| 別リージョンのResolver共有 | Resolver Endpoints は リージョン毎に作成必須(クロスリージョン共有不可) |
14. 連携パターン
14.1 CloudFront + WAF + Shield Advanced = DDoS防御の完全構成
┌───────────────────────────────────────────────────┐
│ インターネット(攻撃元) │
└─────────────────────┬─────────────────────────────┘
│ DDoS攻撃(数Gbps)
↓
┌───────────────────────────────────────────────────┐
│ Shield Advanced + Shield Standard │
│ ・L3/L4 DDoS検出・軽減(自動) │
│ ・攻撃パターンの学習 │
│ ・CloudFront でバックホール │
└─────────────────────┬─────────────────────────────┘
│ 正規トラフィック選別
↓
┌───────────────────────────────────────────────────┐
│ WAF (Web ACL) │
│ ・SQLi, XSS, ボット検出 │
│ ・レート制限(IP当たり2000req/5分) │
│ ・地理制限(許可国のみ) │
└─────────────────────┬─────────────────────────────┘
│ 正当なWebリクエスト
↓
┌───────────────────────────────────────────────────┐
│ CloudFront Edge Locations │
│ ・TLS終端、HTTPSリダイレクト │
│ ・セキュリティヘッダー付与(HSTS, CSP等) │
│ ・Lambda@Edge 認証処理 │
└─────────────────────┬─────────────────────────────┘
│ オリジンへのリクエスト
↓
┌───────────────────────────────────────────────────┐
│ オリジン(S3, ALB, カスタム等) │
│ ・OAC で CloudFront のみアクセス許可 │
└───────────────────────────────────────────────────┘
Terraform 実装例
# 1. Shield Advanced 有効化
resource "aws_shield_protection" "cloudfront" {
name = "cloudfront-shield"
resource_arn = aws_cloudfront_distribution.main.arn
tags = {
Protection = "DDoS"
}
}
# 2. WAF Web ACL(前述参照)
resource "aws_wafv2_web_acl" "cloudfront_waf" {
name = "cloudfront-protection"
scope = "CLOUDFRONT"
# ... 前のコード ...
}
# 3. CloudFront + WAF 連携
resource "aws_cloudfront_distribution" "protected" {
web_acl_id = aws_wafv2_web_acl.cloudfront_waf.arn
origin {
domain_name = aws_s3_bucket.content.bucket_regional_domain_name
origin_id = "S3Origin"
origin_access_control_id = aws_cloudfront_origin_access_control.s3.id
}
enabled = true
}
14.2 CloudFront + S3 OAC + KMS暗号化
# KMS キー(転送中+保存時の暗号化)
resource "aws_kms_key" "s3_encryption" {
description = "KMS key for S3 encryption"
deletion_window_in_days = 30
enable_key_rotation = true
}
# S3 バケット(暗号化有効)
resource "aws_s3_bucket" "content" {
bucket = "my-secure-content-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "content" {
bucket = aws_s3_bucket.content.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3_encryption.arn
}
bucket_key_enabled = true # 暗号化パフォーマンス最適化
}
}
# S3 バケット ポリシー(CloudFront OAC のみアクセス)
resource "aws_s3_bucket_policy" "cloudfront_access" {
bucket = aws_s3_bucket.content.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCloudFrontOAC"
Effect = "Allow"
Principal = {
Service = "cloudfront.amazonaws.com"
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.content.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = "arn:aws:cloudfront::ACCOUNT_ID:distribution/${aws_cloudfront_distribution.main.id}"
}
}
}
]
})
}
# CloudFront Distribution
resource "aws_cloudfront_distribution" "main" {
origin {
domain_name = aws_s3_bucket.content.bucket_regional_domain_name
origin_id = "S3"
origin_access_control_id = aws_cloudfront_origin_access_control.s3.id
}
enabled = true
}
14.3 Route 53 + CloudFront + ALB の多層構成
┌──────────────────────────────────┐
│ example.com (Route 53) │
│ DNSSEC署名有効 │
└────────────────┬─────────────────┘
│
┌──────────▼──────────┐
│ Route 53 Health Check
│ └─ALB エンドポイント監視
└──────────┬──────────┘
│
┌────────────┴────────────┐
│ CloudFront Distribution │
│ ・TLS 1.3対応 │
│ ・WAF統合 │
│ ・セキュリティヘッダー │
│ ・Lambda@Edge 認証 │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ ALB (Application LB) │
│ ・HTTPS終端 │
│ ・セッション管理 │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ ECS, EC2等 アプリケーション
└─────────────────────────┘
実装例
# Route 53 レコード(CloudFront指向)
resource "aws_route53_record" "app" {
zone_id = aws_route53_zone.main.zone_id
name = "example.com"
type = "A"
alias {
name = aws_cloudfront_distribution.main.domain_name
zone_id = aws_cloudfront_distribution.main.hosted_zone_id
evaluate_target_health = true
}
}
# CloudFront Origin(ALB)
resource "aws_cloudfront_distribution" "main" {
origin {
domain_name = aws_lb.main.dns_name
origin_id = "ALB"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
# オリジンカスタムヘッダー(CloudFrontからのリクエストのみ受け付け)
custom_header {
name = "X-Custom-Origin-Secret"
value = aws_secretsmanager_secret_version.origin_secret.secret_string
}
}
enabled = true
}
# ALB セキュリティグループ
resource "aws_security_group" "alb" {
name = "alb-sg"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.cloudfront.id] # CloudFrontのセキュリティグループからのみ許可
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
15. 試験で狙われるポイント
15.1 OAC vs OAI の選択問題
Q: CloudFront から S3 のプライベートバケットにアクセスする最新の推奨方法は?
A: OAC (Origin Access Control)
- OACは署名付きリクエストを使用(SigV4)
- 短期クレデンシャルを自動ローテーション
- OAIは非推奨(後継機能がOAC)
15.2 署名付きURL vs Cookie の判別
| シナリオ | 答え | 理由 |
|---|---|---|
| 複数動画へ24時間アクセス許可 | Cookie | セッション型で複数リソース対応 |
| レポートをメールで配信(24時間有効) | URL | ワンタイムリンク向き |
| VIP会員の全リソース | Cookie | Cookieはドメイン全体に適用 |
| 一度きりのダウンロード | URL | URLは有効期限を短縮可能 |
15.3 フィールドレベル暗号化の目的
Q: CloudFront フィールドレベル暗号化を使用するとき、PCI-DSS対応として不足している要素は?
A: フィールド暗号化単独では PCI-DSS達成不可
- TLS(転送中暗号化)が別途必須
- WAF + Shield での攻撃防御必須
- ログ監視(CloudWatch Logs)必須
- アクセス制御(IAM)必須
15.4 DNSSEC 有効化時の確認事項
# DNSSEC有効化後、必ず確認すべき項目
1. DS(Delegation Signer)記録をレジストラに登録
aws route53 get-dnssec --hosted-zone-id Z123
2. DNSSEC検証を確認
dig +dnssec example.com @ns-123.awsdns-45.com
3. 検証チェーン全体を確認
delv +multiline example.com
15.5 Lambda@Edge vs CloudFront Functions
Q: CloudFrontで100msの遅延増が許容できない認証処理を実装するには?
A: CloudFront Functions
| 選択基準 | Lambda@Edge | CloudFront Functions |
|---|---|---|
| 遅延が大事 | ✗ | ✓ |
| 複雑な処理 | ✓ | ✗ |
| KMS統合 | ✓ | ✗ |
| 30秒の長時間処理 | ✓ | ✗ |
15.6 WAF レート制限の実装
// 試験問題例:
// Q: CloudFront + WAFで、1IP当たり1時間に10000リクエスト以上を許さない設定は?
// A: Rate-based statement
{
rate_based_statement {
limit = 10000 // 5分単位の値(AWS内部で合算)
aggregate_key_type = "IP" // IP単位で制限
}
}
// 注意: Route 53 DDoS chargeを避けるため Cloudflare等の無料キャッシュより小さい値を推奨
15.7 Route 53 Resolver Firewall の優先度
// 複数ルールがある場合、**優先度(priority)の小さい順**に評価される
rule {
priority = 100 // 最初に評価
action = "ALERT"
// ...
}
rule {
priority = 200 // 次に評価
action = "BLOCK"
// ...
}
rule {
priority = 300 // 最後に評価
action = "ALERT"
// ...
}
15.8 CloudFront キャッシュとセキュリティヘッダーの相互作用
Q: セキュリティヘッダーの変更を1時間以内に反映させるには?
A: キャッシュクリア(Invalidation)が必須
# レスポンスヘッダーはキャッシュされる
# ヘッダーポリシー変更時
aws cloudfront create-invalidation \
--distribution-id ABCDEFG1234567 \
--paths "/*"
16. まとめ表
| テーマ | 推奨事項 |
|---|---|
| オリジン保護 | OAC (OAI は非推奨) |
| アクセス制御 | URL(一回限り)or Cookie(セッション) |
| 決済情報保護 | フィールド暗号化 + TLS + WAF |
| 地理的制限 | ホワイトリスト優先(ブロックリストは漏れやすい) |
| セキュリティヘッダー | Response Headers Policy で一元管理 |
| TLS最小版 | TLS 1.2以上、推奨は TLS 1.3 |
| エッジ処理 | CloudFront Functions(軽い)or Lambda@Edge(複雑) |
| WAF + Shield | DDoS完全防御の三層構成(必須) |
| DNS署名 | DNSSEC有効化 + DS記録登録 |
| DNS制御 | Route 53 Resolver Firewall でマルウェアドメイン遮断 |
参考資料
- AWS CloudFront Developer Guide
- AWS Route 53 Developer Guide
- OWASP Top 10 + OWASP Secure Headers Project
- AWS SCS-C03 Exam Guide