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

More than 1 year has passed since last update.

posted at

updated at

Lambda@EdgeでレスポンスにHTTPヘッダを付与するやつをGitHub Actionsで自動デプロイする

概要

  • フロントにNuxt.jsを採用していると、npm run startするだけだとX-Frame-OptionsとかCSPとかx-xss-protectionといったヘッダがレスポンスに付与されない
  • FastifyをNodeサーバーとして使っているのでそちらで頑張ってもよかったけど、せっかくAWSでCloudFront->ALB->Fargateな仕組みを使っているので、AWSで頑張ってみようと思った
  • Lambda@Edgeを使って、Nuxtからのレスポンスにヘッダを付与するやつを組んだ
  • アプリケーションと連動して動いたほうがいいし、手動でデプロイする運用がしんどいと思ったので、GitHub ActionsでCLIベースの自動デプロイが組めるところまで持ってきた
  • それをやるがためにCloudFormationまで導入しててんやわんやした

日本語文献がそんなになかったので、まとまりがないメモですが貼っておきます。

Lambda@Edgeのソース内容

下記ブログを参考にしました。CSPだけ模倣するわけにいかなかったので、配列ベースでドメインを管理してjoinする感じで組む改修はしました。

http://blog.serverworks.co.jp/tech/2020/02/27/add-security-headers-lambda-edge-cloudfront/

'use strict';

exports.handler = (event, context, callback) => {

  //Get contents of response
  const cf = event.Records[0].cf
  const request = cf.request
  const response = cf.response;
  const headers = response.headers;

  //Set new headers
  headers['strict-transport-security'] = [{
    key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubdomains; preload'
  }];
  headers['content-security-policy'] = [{
    key: 'Content-Security-Policy',
    // 配列ベースで管理する
    value: `default-src ${CSPDefaultSrcList}; img-src * blob: data:; script-src ${CSPScriptSrcList}; style-src ${CSPStyleSrcList}; object-src 'none';`
  }];
  headers['x-content-type-options'] = [{key: 'X-Content-Type-Options', value: 'nosniff'}];
  // getFrameOptionsはrequestを受け取って、iframe用のページだけDENYしなかったりする
  headers['x-frame-options'] = [{key: 'X-Frame-Options', value: getFrameOptions(request)}];
  headers['x-xss-protection'] = [{key: 'X-XSS-Protection', value: '1; mode=block'}];
  headers['referrer-policy'] = [{key: 'Referrer-Policy', value: 'same-origin'}];

  //Return modified response
  callback(null, response);
};

// 以下略

大まかな設計

  • 雑に説明すると、Lambda@Edgeをデプロイするには、ソースコードを更新するだけじゃなくて、紐付けているCloudFrontも一緒に更新する必要がある
  • プロダクションコードと同じリポジトリに、awsディレクトリを切って、Lambda@Edgeのソースを配置
  • CloudFrontとLambdaを作成するCloudFormationのyamlも置いておく
  • Develop/MasterマージしたときにGitHub Actionsが動いて、前述のyamlを使ってAWS CLIでデプロイする

    • GitHub ActionsでAWS CLI動かすのは公式がアクションを出してくれているので簡単
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-1
    

組み方

  • すでにCloudFrontは事前にコンソールで作っていたので、CloudFormerを使ってCloudFormationのymlにエクスポートした
  • CloudFormerは癖が強いけど、公式ドキュメントのやり方通りにできる

CloudFront/Lambda@EdgeのCloudFormation

自社の構成がバレるので一部だけ引用して貼る。同じyml内にLambdaとCloudFront両方書く

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Parameters:
  AwsLambdaRole:
    Type: String
    Description: Names of existing Roles you want to add to the newly created Managed Policy

#...中略...

Resources:
  AddSecurityHeaderToResponse:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: AddSecurityHeaderToResponse/
      Role: !Ref AwsLambdaRole
      Runtime: nodejs12.x
      Handler: index.handler
      Timeout: 5
      AutoPublishAlias: stg
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service:
                - "lambda.amazonaws.com"
                - "edgelambda.amazonaws.com"
# ...中略...
  StagingCloudFront:
    Type: 'AWS::CloudFront::Distribution'
    Properties:
# ...中略...
        DefaultCacheBehavior:
          LambdaFunctionAssociations:
            - EventType: origin-response
              LambdaFunctionARN: !Ref AddSecurityHeaderToResponse.Version

GitHub Actions

こんな感じでAWSのCLIコマンドを実行すればいい。環境変数は適宜。ifを使えるので環境変数を切り替えることで検証環境と本番環境で1ファイルで運用することができます。

      - name: Deploy latest Lambda function
        run: |
          aws cloudformation package --template-file $AWS_STACK_FILE_NAME --output-template-file packaged.yaml --s3-bucket $BUCKET_NAME --region us-east-1
        working-directory: ./aws/lambda_edge

      - name: Update Cloudfront Distribution
        run: |
          aws cloudformation deploy --template-file packaged.yaml --stack-name $AWS_STACK_NAME --parameter-overrides MainStreamBackendAcm=$AWS_CERTIFICATE_ARN AwsLambdaRole=${{ secrets.AWS_LAMBDA_EDGE_ROLE }} --region us-east-1
        working-directory: ./aws/lambda_edge

メリデメ

  • Pros
    • 一旦CSP頑張ってみることにしたので、アプリケーションでドメイン追加したときに必ず(ほぼ)同期デプロイで出せる
    • Lambdaを使うとローカル開発ではヘッダーを付与するのができないので、せめて同一リポジトリにおいて意識できる
    • Nginxの設定ファイルの管理のほうが個人的に辛いのでそれしなくてよくなったのがよい
    • Nuxt以外のサービスを導入することになったとしても、CloudFrontのレイヤーでHTTPヘッダを担保しているとセキュリティ安心感ある
  • Cons
    • 普通にCloudFormationつらかった
    • IAM権限で必要なのが多すぎて、リソースや権限を*使わないように頑張るのがつらかった
    • 別にインフラ全部をCloudFormationしたいわけじゃなかったのに、Lambda@Edgeをリリースする関係で使い始めてしまったので中途半端

CloudFormationとか全く知らなかったので結構手間取りました。

1日半くらい溶かしてしまったので、まあまあ大変な方でしたね。

参考文献

CloudFormerで現状のCloudFrontの値をとりあえず取得

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-using-cloudformer.html

めっちゃβ版って感じのUXでした

CloudFormationでLambdaのデプロイ

https://qiita.com/ytaka95/items/5899c44c85e71fdc5273

CloudFormationでLambda@Edgeをデプロイする - Qiita

CLIでCloudFormationをデプロイする公式Doc

基本はpackageを動かしたあとにdeployコマンド。

作成も更新も同じコマンドでいい。create-stack/update-stackとかあるけどdeployだけでオールOKっぽい ref: https://stackoverflow.com/questions/49945531/aws-cloudformation-create-stack-vs-deploy

https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html

https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy/index.html

CloudFormationのyamlには外から引数を渡すことができる

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html

開発と本番で違うところがあるからこれで引数で渡せるようにして、GitHub Actionsのyamlで環境変数を使って使い分ける感じ

CloudFormationでCloudFrontをデプロイする

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-viewercertificate.html

公式が出している、ちょっと古いけどサンプルになるやつ

https://github.com/awslabs/serverless-application-model/blob/master/examples/2016-10-31/lambda_edge/template.yaml

CloudFormation deployコマンドを叩いたときに無言で落ちるけど実際は権限エラーだよってやつ

https://github.com/awslabs/serverless-application-model/issues/58

あまり役に立たなかったけど足しにはなった公式のFAQ

https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-template-validation/

これも役に立たなかったけど一応見た

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-validate-template.html

デプロイ時には専用のIAMユーザー作って権限を付ける

コレが一番大変だった。疲れた。

デプロイ用のIAM作って、手元でコマンドを叩いて権限不足で弾かれるのを延々と繰り返す。

このへんとか見た。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-permissions.html

https://gist.github.com/stu-smith/7fec25367e1b83fb0709c708a704ff04


以上です。

よかったらTwitterフォローしてください〜
https://twitter.com/Meijin_garden

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
3
Help us understand the problem. What are the problem?