CloudFront 署名付き URL 経由で S3 にアップロードする

CloudFront 署名付き URL 経由で S3 にアップロードする

CloudFront の署名付き URL を使い、独自ドメイン経由で S3 にアップロードする方法を紹介します。S3 の署名付き URL を直接使えない場合に有用です。

Takahiro Iwasa
5 min read

CloudFront と S3 は、どちらも署名付き URL をサポートしています。CloudFront の署名付き URL を使うと、CloudFront ディストリビューションとその独自ドメインを経由してアップロードできます。クライアントの接続先ドメインが制限されている環境で有用です。

Serve private content with signed URLs and signed cookies

Architecture Diagram

信頼された署名者の指定

信頼された署名者として使用するキーグループを作成します。

Specify signers that can create signed URLs and signed cookies

Important

AWSアカウントを信頼された署名者として使用することも可能ですが、AWSはキーグループの使用を推奨しています。詳細はChoose between trusted key groups (recommended) and AWS accountsを参照してください。

この例では、次の要件を満たす RSA キーペアを使用します。2024 年当時の CloudFront キーグループは、ECDSA 256 署名にも対応していました。

  • タイプ: SSH-2 RSA キーペア
  • フォーマット: Base64 エンコードされた PEM
  • キーサイズ: 2048 ビット

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

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

構築

  • 公開鍵を PublicKey パラメータ(5行目)に渡し、それを使用します(38行目)。
  • S3 バケットポリシーで s3:PutObject アクションを許可します(27 行目)。
  • AllViewerExceptHostHeader オリジンリクエストポリシーを使用します(85 行目)。
template.yaml
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 スタックをデプロイします。

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

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

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

テスト

次の変数を設定します。

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

URL を生成します。ここで使う date -v 構文は macOS 用です。

Terminal window
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=...

ファイルをアップロードします。

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

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

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

クリーンアップ

次のコマンドで、この例で作成したすべての AWS リソースを削除します。

ℹ️ Note

CloudFrontディストリビューションの無効化には数分かかる場合があります。

Terminal window
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 に署名し、その URL へファイルを PUT することで、S3 の署名付き URL を直接使わず、独自ドメイン経由で S3 にアップロードできることを確認しました。

URL の署名に使う秘密鍵は AWS へアップロードしません。CloudFront が CloudFrontPublicKey 経由で受け取るのは公開鍵だけで、秘密鍵は aws cloudfront sign を実行する環境に残ります。信頼されたキーグループを使えば、鍵が漏洩した場合も IAM 認証情報を変更せずに鍵を交換できます。

アップロードを成功させるには、キャッシュビヘイビアで PUT を許可して署名用キーグループを信頼し、オリジンアクセスコントロールと S3 バケットポリシーで CloudFront に s3:PutObject を許可する必要があります。URL を生成できただけで設定が正しいとは判断せず、署名、PUT、S3 からの読み戻しまでを通してテストしてください。

About the author

Takahiro Iwasa

Takahiro Iwasa

Software Developer

This blog shares technical notes from hands-on projects—architecture, implementation, and AWS service integrations.