🌤️

【AWS】Elastic Beanstalk環境のLaravelでCloudFrontの署名付きURLを発行する

2021/08/17に公開

English ver.

https://yutaroshimamura.medium.com/create-cloudfront-signed-url-from-laravel-application-on-elastic-beanstalk-be35b7b8c3ab

概要

CloudFrontの署名付きURL経由 で、S3のオブジェクトにアクセスさせる手順をまとめました。

  • ローカルで動かしました!
  • S3用の署名付きURL発行しました!

のような例はよく見かけたのですが、Elastic Beanstalk環境から、CloudFrontの署名付きURLの実装例があまり見当たらなかったのでメモ程度にまとめています。
間違い等ももしかしたらあるかもなので、こっそり指摘していただけると嬉しいです!

前提

  • AWS Elastic Beanstalk環境
  • PHP (8.0.6)
  • Laravel (8.49.2)

手順

1. aws-sdk-php-laravel をインストール

https://github.com/aws/aws-sdk-php-laravel

composer require aws/aws-sdk-php-laravel
config/app.php
  'providers' => [
    ...
+   Aws\Laravel\AwsServiceProvider::class,
  ]
  'aliases' => [
    ...
+   'AWS' => Aws\Laravel\AwsFacade::class,
  ]

2. キーペアを作成する

こちらの手順(Create a key pair for a trusted key group (recommended))に従って、キーペアを作成する。手順を以下に示す。

キーペア作成 (例: private_key.pem)

openssl genrsa -out private_key.pem 2048

Public Key作成 (例: public_key.pem)

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

3. CloudFrontにPublic Keyを追加

  • Name: 名称
  • Description(任意): 説明
  • Key: 以下の出力を貼り付ける
cat public_key.pem

4. Key GroupにPublic Keyを追加する

  • Name - 名称
  • Description - 説明(任意)
  • Public Keys - 先程追加したKeyを選択

5. ファイル格納用のS3のバケットを作成する

  • 特に何も設定を変更せずに作成する

6. CloudFrontのDistributionを作成する

  • Origin Domain - 5.で作成したバケットのドメインを選択
  • Restrict viewer access - Yes
  • Trusted authorization type - Trusted key groups (recommended)
    • 先程のKey Groupを選択

  • 作成したDistributionのOriginを編集
  • S3 bucket access - Yes use OAI (bucket can restrict access to only CloudFront)
  • Bucket policy - Yes, update the bucket policy
    こうすることでS3のバケットポリシーが上書きされます。

7. 署名付きURLを発行する記述を追加

※以下に出てくる$post->url は、既にstoreAs()等で保存した際のpathを使用しています。

.env
AWS_URL=https://xxxxxxxxxxx.cloudfront.net # ファイル格納用S3がOriginのDistributionのドメイン名
AWS_CLOUDFRONT_KEY_PAIR_ID=<CloudFrontに追加したPublic KeyのID>
PRIVATE_KEY_PATH=<Private Keyを配置するPATH> # 例では /etc/pki/tls/certs/配下
config/app.php
    /*
    |--------------------------------------------------------------------------
    | Other variables
    |--------------------------------------------------------------------------
    |
    | Define other variables here.
    */
    'key_pair_id' => env('AWS_CLOUDFRONT_KEY_PAIR_ID', ''),
    'private_key_path' => env('PRIVATE_KEY_PATH', ''),

$pathはあくまで例で、実際のコードとは若干異なります。S3に保存した際のpathをそのまま、署名付きURL発行に使用しています。

URLを発行する.php
use Aws\Laravel\AwsFacade as AWS;
// S3にファイルを保存(省略)
$path = $request->file('foo')->storeAs('some_path', 'fileName');

// 省略
$client = AWS::createClient('cloudfront');
$signedUrl = $client->getSignedUrl([
    'url' => config('filesystems.disks.s3.url') . '/' . $path,
    'expires' => time() + 30, // 30秒で期限切れ
    'private_key' => realpath(config('app.private_key_path')),
    'key_pair_id' => config('app.key_pair_id'),
]);
// 省略

8. Private KeyをS3にアップロードする

  • 生成したprivate keyをElastic Beanstalk環境が生成したS3のバケットにアップロードします。今回はバケット直下に ひみつかぎ.pem(仮称) をアップロードします。
    例)elasticbeanstalk-ap-northeast-1-xxxxxxxxxxx
  • aws-elasticbeanstalk-ec2-role で鍵ファイルを取得できるようにバケットポリシーを更新
    (自前でec2のロールを付与している場合は、そのロールを指定してください )
バケットポリシー
{
    "Version": "2008-10-17",
    "Statement": [
       ...
        {
            "Sid": "xx-xxxxxxxxx-xxxx-xx-xxxx-xxxxxxx",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::xxxxxxxxxxxx:role/aws-elasticbeanstalk-ec2-role"
                ]
            },
            "Action": [
+                 "s3:GetObject",
            ],
            "Resource": [
+                "arn:aws:s3:::elasticbeanstalk-ap-northeast-1-xxxxxxxxxx/ひみつかぎ.pem",
            ]
        }
    ]

9. .ebextensions配下に設定ファイルを追加する

  • .ebextentions.privatekey.config というファイルを作成

privatekey.config
Resources:
  AWSEBAutoScalingGroup:
    Metadata:
      AWS::CloudFormation::Authentication:
        S3Auth:
          type: "s3"
          buckets: ["elasticbeanstalk-ap-northeast-1-xxxxxxxxxxx"] # Beanstalkのバケット
          roleName: 
            "Fn::GetOptionSetting": 
              Namespace: "aws:autoscaling:launchconfiguration"
              OptionName: "IamInstanceProfile"
              DefaultValue: "aws-elasticbeanstalk-ec2-role" # 他のRoleを紐付けている場合はそちらを指定できます
files:
  # Private key
  "/etc/pki/tls/certs/ひみつかぎ.key":
    mode: "000400"
    owner: root # 必要に応じて適切なuserに変更してください
    group: root # 必要に応じて適切なgroupに変更してください
    authentication: "S3Auth" # S3Authを指定する場合、上記Resourcesの記述も必須
    source: https://elasticbeanstalk-ap-northeast-1-xxxxxxxxxx.s3.ap-northeast-1.amazonaws.com/ひみつかぎ.key

※ハマったところ

  • .ebextentions以外のフォルダにconfigファイルを作っても読み込まれません。
    最初気づかずに、何回デプロイしても毎度、鍵が見当たらないよ系のエラーがでてきて、時間を溶かしました。
  • Resources: は必須です。files:S3Authを指定する場合、これがないことでエラーになります

10. Elastic Beanstalk環境で動作確認

きちんとURLが生成できていることを確認。

期限切れの場合は見れないことも確認。

まとめ

無事にElastic Beanstalk環境のLaravelから、CloudFrontの署名付きURL経由でS3オブジェクトにアクセスすることができました🎉
鍵の扱いが本当にこれでよかったのかが少し気になっているので、もっといい方法知っているよという方がいましたらぜひ教えてください!

今回は単一環境のみでのセットアップだったので、複数環境で使用する場合のメモも今後まとめてみようと思います。

参考

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html

https://owenconti.com/posts/signing-aws-cloudfront-requests-with-laravel/

https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/https-storingprivatekeys.html

Discussion