S3 へのファイルアップロードに CloudFront の署名付き URL を利用する

S3 へのファイルアップロードに CloudFront の署名付き URL を利用する

岩佐 孝浩
岩佐 孝浩
5 min read
CloudFront S3

はじめに

CloudFront は、署名付き URL を生成する機能をサポートしています。S3 も同様の機能を提供していますが、CloudFront を使用することでカスタムドメイン経由でのアップロードが可能となり、特にドメイン制限がある環境において非常に有用です。

公式ドキュメント: Amazon CloudFront Private Content

このアーキテクチャは、CloudFront をセキュアなファイルアップロードのフロントサービスとして活用し、カスタムドメインの使用を可能にし、制限のある環境での制御を強化します。

アーキテクチャ図

信頼済み署名者の指定

まず、信頼済み署名者として使用する 信頼済みキーグループ を作成する必要があります。

公式ドキュメント: Trusted Signers for CloudFront

キーペアの作成

キーペアは次の要件に従う必要があります。

  • 種類: SSH-2 RSA キーペア
  • 形式: Base64 エンコードされた PEM
  • キーサイズ: 2048 ビット

次のコマンドを使用してキーペアを作成します。

openssl genrsa -out private_key.pem 2048
openssl rsa -pubout -in private_key.pem -out public_key.pem

AWS リソースの作成

必要な AWS リソースをプロビジョニングするために CloudFormation テンプレート を作成します。

テンプレートの重要なポイント

  • PublicKey パラメータ (5 行目) に 公開鍵 を渡し、それを使用します (38 行目)。
  • S3 バケットポリシーが s3:PutObject アクションを許可していることを確認してください (27 行目)。
  • AllViewerExceptHostHeader オリジンリクエストポリシーを使用します (85 行目)。

テンプレート例:

AWSTemplateFormatVersion: 2010-09-09
Description: Example of CloudFront pre-signed URLs to upload files to S3 Bucket

Parameters:
  PublicKey:
    Type: String

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub uploaded-files-${AWS::AccountId}-${AWS::Region}

  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: 2008-10-17
        Id: PolicyForCloudFrontPrivateContent
        Statement:
          - Sid: AllowCloudFrontServicePrincipal
            Effect: Allow
            Principal:
              Service: cloudfront.amazonaws.com
            Action:
              - s3:PutObject
            Resource: !Sub ${S3Bucket.Arn}/*
            Condition:
              StringEquals:
                "AWS:SourceArn": !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}

  CloudFrontPublicKey:
    Type: AWS::CloudFront::PublicKey
    Properties:
      PublicKeyConfig:
        Name: signer1
        EncodedKey: !Ref PublicKey
        CallerReference: cloudfront-caller-reference-example

  CloudFrontKeyGroup:
    Type: AWS::CloudFront::KeyGroup
    Properties:
      KeyGroupConfig:
        Name: cloudfront-key-group-1
        Items:
          - !Ref CloudFrontPublicKey

  CloudFrontOriginAccessControl:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: !Ref S3Bucket
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Enabled: true
        HttpVersion: http2and3
        Origins:
          - Id: !GetAtt S3Bucket.DomainName
            DomainName: !GetAtt S3Bucket.DomainName
            OriginAccessControlId: !Ref CloudFrontOriginAccessControl
            S3OriginConfig:
              OriginAccessIdentity: ''
        DefaultCacheBehavior:
          AllowedMethods:
            - HEAD
            - DELETE
            - POST
            - GET
            - OPTIONS
            - PUT
            - PATCH
          Compress: true
          # CachingDisabled
          # See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html#managed-cache-policy-caching-disabled
          CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
          # AllViewerExceptHostHeader
          # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html#managed-origin-request-policy-all-viewer-except-host-header
          OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac
          TargetOriginId: !GetAtt S3Bucket.DomainName
          TrustedKeyGroups:
            - !Ref CloudFrontKeyGroup
          ViewerProtocolPolicy: https-only

Outputs:
  CloudFrontDistributionDomainName:
    Value: !GetAtt CloudFrontDistribution.DomainName
  CloudFrontPublicKeyId:
    Value: !Ref CloudFrontPublicKey
  S3BucketName:
    Value: !Ref S3Bucket

次のコマンドで CloudFormation スタックをデプロイします。

PUBLIC_KEY=$(cat public_key.pem)
aws cloudformation deploy \
  --template-file template.yaml \
  --stack-name cloudfront-presigned-urls-example \
  --parameter-overrides PublicKey=$PUBLIC_KEY

デプロイされたリソースを確認します。

aws cloudformation describe-stacks \
--stack-name cloudfront-presigned-urls-example \
| jq ".Stacks[0].Outputs"

テスト

事前署名 URL を生成する

次の変数を設定します。

CLOUDFRONT_DOMAIN=<CloudFront domain>
KEYPAIR_ID=<Key pair ID>
UTC_OFFSET=+9

URL を生成します。

PRESIGNED_URL=$(aws cloudfront sign \
--url https://$CLOUDFRONT_DOMAIN/upload-test.txt \
--key-pair-id $KEYPAIR_ID \
--private-key file://private_key.pem \
--date-less-than $(date -v +5M "+%Y-%m-%dT%H:%M:%S$UTC_OFFSET"))

echo $PRESIGNED_URL
# https://<distribution-id>.cloudfront.net/upload-test.txt?Expires=...&Signature=...Key-Pair-Id=...

ファイルをアップロードする

echo 'Hello World' > example.txt
curl -X PUT -d "$(cat example.txt)" $PRESIGNED_URL

アップロードされたファイルを確認します。

aws s3 cp s3://uploaded-files-<AWS::AccountId>-<AWS::Region>/upload-test.txt ./
cat ./upload-test.txt

クリーンアップ

コストを回避するために、リソースを削除します。

aws s3 rm s3://uploaded-files-<AWS::AccountId>-<AWS::Region>/upload-test.txt
aws cloudformation delete-stack --stack-name cloudfront-presigned-urls-example

まとめ

CloudFront の事前署名 URL を使用することで、特に ドメイン制限がある環境 において、S3 バケットへのファイルアップロードを安全に行う効果的な方法を実現できます。適切に設定することで、ワークフローを効率化し、強固なセキュリティプラクティスを維持できます。

Happy Coding! 🚀

岩佐 孝浩

岩佐 孝浩

Software Developer at KAKEHASHI Inc.
AWS を活用したクラウドネイティブ・アプリケーションの要件定義・設計・開発に従事。 株式会社カケハシで、処方箋データ収集の新たな基盤の構築に携わっています。 Japan AWS Top Engineers 2020-2023