S3 へのファイルアップロードに CloudFront の署名付き URL を利用する
はじめに
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! 🚀