CloudFront署名付きURLでのファイルのダウンロードおよびアップロード

CloudFrontの署名付きURLを使ったアップロードとダウンロードを試すメモ。

以前はrootユーザーで「CloudFrontのキーペア」を作る必要があったが、IAMユーザーで「信頼されたキーグループ」を利用することができるようになっており、今後はこちらが推奨される。

参考リンク

手順

キーペアの準備

キーペアを作成する。

プライベートキーを作成する。

$ openssl genrsa -out private_key.pem 2048
Generating RSA private key, 2048 bit long modulus
.....................................+++
...+++
e is 65537 (0x10001)

パブリックキーを抽出する。

$ openssl rsa -pubout -in private_key.pem -out public_key.pem
writing RSA key

CloudFrontにパブリックキーをアップロードする。

パブリックキーを作成する。

f:id:sotoiwa:20210927155659p:plain

f:id:sotoiwa:20210927155721p:plain

キーグループを作成する。

f:id:sotoiwa:20210927155748p:plain

f:id:sotoiwa:20210927155808p:plain

ディストリビューションの作成

適当なS3バケットを用意する。

f:id:sotoiwa:20210927160507p:plain

OAIを使ったディストリビューションを作成する。

f:id:sotoiwa:20210927160225p:plain

他はデフォルトで作成する。

適当に置いたファイルにCloudFront経由でアクセスできることを確認する。

$ curl http://d2rn9ssidbwc6t.cloudfront.net/test.txt
Hello World

ディストリビューションの設定変更

ビヘイビアを編集する。

f:id:sotoiwa:20210927160658p:plain

f:id:sotoiwa:20210927160719p:plain

アクセスできなくなったことを確認する。

$ curl http://d2rn9ssidbwc6t.cloudfront.net/test.txt
<?xml version="1.0" encoding="UTF-8"?><Error><Code>MissingKey</Code><Message>Missing Key-Pair-Id query parameter or cookie value</Message></Error>

ダウンロード

PythonでCloudFront署名付きURLを作成する例は以下にある。

venvを作成してbotocoreをインストールする。

python3 -m venv .env
source .env/bin/activate
pip install cryptography botocore

スクリプトを作成する。

generate_url.py

import datetime
import os

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from botocore.signers import CloudFrontSigner


path_to_key = os.getenv('PATH_TO_KEY')
key_id = os.getenv('KEY_ID')
url = os.getenv('URL')
expires_in_seconds = os.getenv('EXPIRES_IN_SECONDS')
expire_date = datetime.datetime.now() - datetime.timedelta(seconds=int(expires_in_seconds))


def rsa_signer(message):
    with open(path_to_key, 'rb') as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None,
            backend=default_backend()
        )
    return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())

cloudfront_signer = CloudFrontSigner(key_id, rsa_signer)

# Create a signed url that will be valid until the specific expiry date
# provided using a canned policy.
signed_url = cloudfront_signer.generate_presigned_url(
    url, date_less_than=expire_date)
print(signed_url)

スクリプトを実行する。

export PATH_TO_KEY="./private_key.pem"
export KEY_ID="K1GRQBXU1LP9D1"
export URL="https://d2rn9ssidbwc6t.cloudfront.net/test.txt"
export EXPIRES_IN_SECONDS="3600"
URL=$(python generate_url.py)
echo ${URL}
https://d2rn9ssidbwc6t.cloudfront.net/test.txt?Expires=1632752859&Signature=Zd9XVJ8p563S5-H1Fq1KgjWtKzg6Oq2nIumgiYGdDvUJ5pDVS06idJOhUYlQTvAcL~KU3REfX2brISu299f51uPEwyGIFEKKxyayROalin7CoPtOlEEwK06EiBEP8QOJg6K6drTyFzJbtnxQ~vPl6fBOa6TUYUalaQlc3k4LVyU7z3aiX9fVhxX2C~lc9Y2sD8QgPtjn5MWREJcduZcKoMCtGy~3AXohhuZYkWQh7UwCf0PX1ILBiEvMCRUGffR643gIqfepFUT49qjdAqp2FmeVftm~EeqFAmPRjw1OfpBQIUR8Dv0huPpt3D0t3smON38rdq6wZSUpJi5xQgtagg__&Key-Pair-Id=K1GRQBXU1LP9D1

生成されたURLにアクセスする。

curl ${URL}
Hello World

と思ったら、どうやら署名付きURLの生成はAWS CLIでもできた!

aws cloudfront sign --url ${URL} --key-pair-id ${KEY_ID} --private-key file://${PATH_TO_KEY} --date-less-than 2021-9-28

アップロード

アップロードを許可するには、PUTを許可する。(ついでにHTTPSのみにしておく。)

f:id:sotoiwa:20210927160825p:plain

バケットポリシーでOAIからのPutObjectを許可する必要がある。

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EY4QI76AG4Z6G"
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::test-XXXXXXXXXXXX/*"
        }
    ]
}

同じスクリプトでURLを生成する。新たにアップロードするファイル用にURLは変える。

export PATH_TO_KEY="./private_key.pem"
export KEY_ID="K1GRQBXU1LP9D1"
export URL="https://d2rn9ssidbwc6t.cloudfront.net/test2.txt"
export EXPIRES_IN_SECONDS="3600"
URL=$(python generate_url.py)

x-amz-content-sha256ヘッダを付与する必要がある。

CloudFront に PUT リクエストを送信してファイルを Amazon S3 バケットにアップロードする場合は、リクエストに x-amz-content-sha256 ヘッダーを追加する必要があります。ヘッダー値にはリクエストの本文の SHA256 ハッシュが含まれている必要があります。詳細については、Amazon Simple Storage Service API リファレンスの「一般的なリクエストヘッダー」ページで x-amz-content-sha256 ヘッダーに関するドキュメントを参照してください。

なお、OAI経由ではPOSTに対応していないため、マルチパートアップロードはできない。

POST リクエストはサポートされません。

オブジェクトの所有者がOAIになるため、x-amz-acl:bucket-owner-full-controlヘッダを付与する必要もある。

FILE="test2.txt"
DIGEST=$(openssl dgst -sha256 -hex < ${FILE} 2>/dev/null | sed 's/^.* //')
curl -D - -XPUT \
    -H "X-Amz-Content-SHA256: ${DIGEST}" \
    -H "x-amz-acl:bucket-owner-full-control" \
    --upload-file ${FILE} ${URL}

これでアップロード成功した。

$ curl -D - -XPUT \
>     -H "X-Amz-Content-SHA256: ${DIGEST}" \
>     -H "x-amz-acl:bucket-owner-full-control" \
>     --upload-file ${FILE} ${URL}
HTTP/2 200 
content-length: 0
date: Mon, 27 Sep 2021 06:31:24 GMT
x-amz-version-id: O5iexVYv2_zgKu4tP046Jpl6TVaIoMA0
x-amz-server-side-encryption: AES256
etag: "cbf41347bb1978f6f32087b2cf01e351"
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 376eb6903adf0a53329357c7636f4f3b.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C1
x-amz-cf-id: gRIqakR-MoVZvijoL9KJAhtG7JDilYLF850ennszVjYDTe3jcwLrkw==

f:id:sotoiwa:20210927161128p:plain

f:id:sotoiwa:20210927161509p:plain