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

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

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

CloudFront は、署名付き URL の生成をサポートしています。 これを利用して、 S3 バケットにファイルをアップロードできます。

S3 も同様の機能を提供していますが、 CloudFront で利用しているカスタムドメインでファイルをアップロードできることが、重要な違いです(この投稿では触れません)。 これは、ドメインが制限された環境で特に役立ちます。

概要

署名者の指定

最初に、署名者として信頼されたキーグループを作成する必要があります。

キーペア作成

キーペアは、以下の要件を満たす必要があります。

  • SSH-2 RSA key pair
  • base64-encoded PEM format
  • 2048-bit key pair

以下のコマンドで、新規キーペアを作成できます。

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

AWS リソース作成

以下の内容で CloudFormation テンプレートを作成してください。

重要なポイントは以下のとおりです。

  • 公開鍵は PublicKey パラメータに渡される必要があります。(5行目、38行目)
  • S3 バケットポリシーは、 s3:PutObject アクションを許可する必要があります。(27行目)
  • S3 バケットがクエリーストリングを必要とするため、 CloudFront origin request policy は、 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 スタックをデプロイしてください。 公開鍵が、 PublicKey パラメータに渡されていることにご注意ください。

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

テスト用の値を確認するために、以下のコマンドを実行してください。

$ aws cloudformation describe-stacks \
--stack-name example-of-cloudfront-presigned-urls-to-upload-files-to-s3-bucket \
| jq ".Stacks[0].Outputs"

[
  {
    "OutputKey": "CloudFrontPublicKeyId",
    "OutputValue": "<id>"
  },
  {
    "OutputKey": "CloudFrontDistributionDomainName",
    "OutputValue": "<id>.cloudfront.net"
  },
  {
    "OutputKey": "S3BucketName",
    "OutputValue": "uploaded-files-<AWS::AccountId>-<AWS::Region>"
  }
]

テスト

S3 バケットにファイルをアップロードするための署名付き URL を生成するために、 CLOUDFRONT_DOMAIN, KEYPAIR_ID, UTC_OFFSET 変数に実際の値をセットしてください。

CLOUDFRONT_DOMAIN=
KEYPAIR_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=...

署名付き URL を利用してファイルをアップロードするために、以下のコマンドを実行してください。

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

最後に、 S3 バケットにアップロードされたファイルを確認してください。

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

クリーンアップ

以下のコマンドを使用して、プロビジョニングされた AWS リソースを削除してください。

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

まとめ

ドメインが制限された環境で S3 バケットにファイルをアップロードしたい場合、 CloudFront 署名付き URL が有用です。

この投稿が、お役に立てば幸いです。

岩佐 孝浩

岩佐 孝浩

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