上級 40分 Lesson 19

CloudFront & Route 53 — OAC・署名付きURL・DNSSEC

CloudFrontのOAC、署名付きURL/Cookie、フィールドレベル暗号化、Route 53 DNSSEC・DNS Firewallを徹底解説

AWS CloudFront Route 53 SCS-C03 CDN DNS Security

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 比較表

項目OACOAI
リリース年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

ユースケース

  • 複数ファイルへのアクセス許可:フルディレクトリ、月間サブスクリプション
  • セッション型アクセス制御:ユーザーログイン後の全リソース
  • キャッシュクリアの簡素化

署名付き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ヶ月の猶予期間)
  ]
}
判断軸署名付き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@EdgeCloudFront Functions
遅延100~200ms< 1ms
タイムアウト30秒1秒
言語Python, Node.js, JavaJavaScript のみ
ログ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)
シナリオ答え理由
複数動画へ24時間アクセス許可Cookieセッション型で複数リソース対応
レポートをメールで配信(24時間有効)URLワンタイムリンク向き
VIP会員の全リソースCookieCookieはドメイン全体に適用
一度きりのダウンロードURLURLは有効期限を短縮可能

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@EdgeCloudFront 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 + ShieldDDoS完全防御の三層構成(必須)
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