9
Help us understand the problem. What are the problem?

posted at

updated at

ユーザーがアップロードした画像をAWS S3に保存して、ユーザー本人だけアクセスできるようにする

そのようなニーズ、よくありますよね。
やっていきましょう。

あ、ちなみに別に画像じゃなくても、他のファイル(PDFとかtxtとか)でももちろん行けます。

本記事で出てくるS3 URLなどは、執筆のために一時的に作っただけなので実際にはアクセスできません。

S3バケットを作る

とりあえず動きを再現するだけなら、デフォルトで大体OKです。
気を付けるところは↓です。

s3_1.png

CloudFrontを作る

その前に、公開鍵と秘密鍵ファイルを作る

これはopensslコマンドが存在する環境なら何で実施してもよいです。
Mac, Linux, Windows内のWSL上Linuxなど、どこでも。

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

公開鍵をクリップボードにコピーしておく

public_key.pemの中身をテキストで表示すると、こんな感じの文字列だと思うので。

$ cat public_key.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA58Mo/UyzKZ59RtVMFtXr
N0W3ysaHkTUWdgrDDbKLbryXB9Dq7YgiveBOu3zXndhHiKtdAN8e/szz1zuOCo6y
uO5BpeDMohdpESlS803jim8jXIh7vZX0amyzBsQSO4jnHtBH8lFjSr0CmcyETyJr
4xu3Xa5iDyvQRvcO1i4I0VfFRzd/Hy9XXCvUFC4uKUNAH1StksklF2E1X0cbNnx5
zH8XudWFrt1vWGHFJquyDvGp7EW/JzghL8oE54WNawhddRISTnchiPxaxxCrJaOj
HzIEQ6S4stzzYHTA+ArGRzPfcsbZP92bALSrO3GTjt3uzhZnx0VWQ98B1ZxQixXF
ZQIDAQAB
-----END PUBLIC KEY-----

↓この部分をクリップボードコピーしておきます。

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA58Mo/UyzKZ59RtVMFtXr
N0W3ysaHkTUWdgrDDbKLbryXB9Dq7YgiveBOu3zXndhHiKtdAN8e/szz1zuOCo6y
uO5BpeDMohdpESlS803jim8jXIh7vZX0amyzBsQSO4jnHtBH8lFjSr0CmcyETyJr
4xu3Xa5iDyvQRvcO1i4I0VfFRzd/Hy9XXCvUFC4uKUNAH1StksklF2E1X0cbNnx5
zH8XudWFrt1vWGHFJquyDvGp7EW/JzghL8oE54WNawhddRISTnchiPxaxxCrJaOj
HzIEQ6S4stzzYHTA+ArGRzPfcsbZP92bALSrO3GTjt3uzhZnx0VWQ98B1ZxQixXF
ZQIDAQAB
-----END PUBLIC KEY-----

CloudFrontのパブリックキーを作成しておく

image.png

「キー」には、先ほどコピーした値をコピペします。

cloudfront_pubkey.png

CloudFrontのキーグループを作成しておく

image.png

「パブリックキー」に、つい先ほど作成したものを指定します。

cloudfront_keygroup.png

CloudFrontディストリビューションを作成する

image.png

基本とりあえずデフォルトで、要点は以下です。

ビューワーのアクセスを制限する

cf4.png

OAIを作って選択する

cf1.png

cf2.png

cf3.png

OAIが出来上がるのに少々タイムラグがあるらしく、数分待たないと選択肢に出現しませんでした。焦らないでね。

キャッシュキーとオリジンリクエスト

”CachingOptimized” にすると、画像をアップロードし直した時に即座に反映されません。
アップロードファイルパスが同じまま、ユーザーが頻繁に画像をアップロードし直すケースがある時は注意です。

cf5.png

その他、とりあえず

cf6.png

これで一旦CloudFront作成。

サンプル画像をアップロード

sample.png

フォルダを切ることももちろん可能ですが、今回は面倒なのでルートにアップロードします。

upload.png

ちなみにアップロードは、後述の各種言語用ライブラリでできますが、今はとりあえずということで、AWS S3コンソールからポチっとアップロードしちゃいます。

サンプル画像にダイレクトアクセスしてみる

本例なら、
https://yagrush.s3.ap-northeast-1.amazonaws.com/sample.png

結果

access_denied.png

単なるURLを他人に知られてもアクセスできないですね。
とりあえずここまではOK。

ここで一旦、CloudFrontのデプロイが完了しているか確認します

日付が入って完了していればOK。

deployed.png

S3のバケットポリシーを編集する

ちょっとS3に戻って「バケットポリシー」を編集します。

image.png

Identityの値は、CloudFrontの「オリジンアクセスアイデンティティ」の「ID」値を取ってきて埋めてください。

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

buchet_policy.png

何らかのプログラミング言語で、セキュアアクセスURLを発行する

今回はPythonで書いていますが、色々な言語向けにライブラリが公開されています。

Pythonには boto というライブラリ群があるので、それを使います。

from datetime import datetime, timedelta

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


def rsa_signer(message):
    with open("./private_key.pem", "rb") as pk_file:
        private_key = serialization.load_pem_private_key(pk_file.read(), password=None, backend=default_backend())
    return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())


if __name__ == "__main__":
    # セキュアアクセスURLの期限は30分とする
    expire_date = datetime.utcnow() + timedelta(minutes=30)

    cloudfront_signer = CloudFrontSigner("パブリックキーID", rsa_signer)
    signed_url = cloudfront_signer.generate_presigned_url(
        # ベースとなるURLは、ユーザーIDで動的に生成するか、DBに保存しておくか、など。
        "https://d1nlzlzgqyj95p.cloudfront.net/sample.png",
        date_less_than=expire_date,
    )
    print(signed_url)

https://d1nlzlzgqyj95p.cloudfront.net/sample.png?Expires=1648564338&Signature=BPGE4FutRnkgtfh9BHXsVDOaysE6kqKwQl4rODxUZxr8ukJFgaSfRRrdV8zYKShQbbiPwzYiwvuR0DEhVtFYbzUwL6yVkAjEBHm9aF2~SEOmgsL4aMzLYYjRaMNMG1EuD63~X0JK44mg8XLV-LXWoBj0HJAEegcBnjhbQeZD1KVIjR7oLLS1z-XEBwTvPOCVoTZiJnq7lLPNlcwpFFZhZpmGoqL94D1fy052Ouk0tL8sU8Zhh5-aIl6-ka7NQw9R7NcLnRfzFRiEsKDhqboFHrjToHIVLwv04K2kEucXaqbd30y8yWdt5ozF6Jt5v9dkzZDhdCn1B-Ohn5MiyfnZLg__&Key-Pair-Id=K22C7RPRRNQZNS

ちなみに パブリックキーID は、

image.png

です。

print()されたURLをブラウザで試しに叩いてみましょう。
(ただし本例では、30分以内に、ね。)

result.png

https://yagrush.s3.ap-northeast-1.amazonaws.com/sample.png じゃDeniedだったのに、画像が表示できました!

このような、署名URL

https://d1nlzlzgqyj95p.cloudfront.net/sample.png?Expires=1648564338&Signature=BPGE4FutRnkgtfh9BHXsVDOaysE6kqKwQl4rODxUZxr8ukJFgaSfRRrdV8zYKShQbbiPwzYiwvuR0DEhVtFYbzUwL6yVkAjEBHm9aF2~SEOmgsL4aMzLYYjRaMNMG1EuD63~X0JK44mg8XLV-LXWoBj0HJAEegcBnjhbQeZD1KVIjR7oLLS1z-XEBwTvPOCVoTZiJnq7lLPNlcwpFFZhZpmGoqL94D1fy052Ouk0tL8sU8Zhh5-aIl6-ka7NQw9R7NcLnRfzFRiEsKDhqboFHrjToHIVLwv04K2kEucXaqbd30y8yWdt5ozF6Jt5v9dkzZDhdCn1B-Ohn5MiyfnZLg__&Key-Pair-Id=K22C7RPRRNQZNS

がネットワーク経路上の例外的な仕掛けで傍受されない限りは大分安全ですし、傍受されたとしても、expire_date が短く設定されていれば、悪人がアクセスするころにはURLは期限切れ、という寸法ですね。
ホホホウ。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
9
Help us understand the problem. What are the problem?