CFnでWAF付きApp Runnerを作る

# はじめに

半年ほど前に、AWS App RunnerにWAFが使えるようになりました。

https://aws.amazon.com/jp/about-aws/whats-new/2023/02/aws-app-runner-web-application-firewall-enhanced-security/

今回はWAFを付けたApp RunnerをCFnで作ってみました。
アプリケーションは、過去に作っているplumberのコンテナをECRにPUSHして使いました。

https://qiita.com/a_b_/items/32cbc21bd140321719c2


# 概要

- App RunnerにWAFを付ける場合は、別途`Type: AWS::WAFv2::WebACLAssociation`で宣言する
    - `Type: AWS::AppRunner::Service`のプロパティ内ではない
- サンプル
```yaml
  WebACLAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    Properties:
      WebACLArn: [WebACLのArn]
      ResourceArn: [App RunnerのServiceArn]
```
- App RunnerにアタッチするIAMロールには公式を参考に以下を設定
    - Principalに、`build.apprunner.amazonaws.com`
    - ECRにアクセスするのでManagedPolicyArnsに、`arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess`
    - 公式：https://docs.aws.amazon.com/ja_jp/apprunner/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles

# 参考

https://dev.classmethod.jp/articles/aws-app-runner-waf/

https://dev.classmethod.jp/articles/try-and-inspect-aws-app-runner-waf/

# やったこと

## 準備～リポジトリへPUSH

作業用インスタンスには、Cloud9のt3.smallを用いました。

コンテナにPUSHまでは過去の記事を流用します。

https://qiita.com/a_b_/items/32cbc21bd140321719c2

上記の記事から、以下のセクションをそのまま実行します。

- [ディレクトリ・ファイル作成](https://qiita.com/a_b_/items/32cbc21bd140321719c2#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%BD%9C%E6%88%90)
- [イメージのBuild](https://qiita.com/a_b_/items/32cbc21bd140321719c2#%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%81%AEbuild)
- [コンテナイメージを、ECR にアップロードする](https://qiita.com/a_b_/items/32cbc21bd140321719c2#%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92ecr-%E3%81%AB%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B)

## 作成

以下を行うCFnを実行します。
- App Runner用IAMロールの作成
- WAFの作成
    - 今回はIP許可リストとしています
- App Runnerの作成
- App RunnerにWAFを設定

パラメータは以下です。
- EcrRepoUri：作成したコンテナのURI
    - バージョンも要指定
- AllowAddresses：許可するIP群

```yaml:createAppRunner.yaml
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  EcrRepoUri:
    Type: String
  AllowAddresses:
    Type: CommaDelimitedList
    Default: "1.1.1.1/32,2.2.2.2/32"

Resources:
  RoleForAR:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - build.apprunner.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess

  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: WebACL_AppRunner
      Scope: REGIONAL
      DefaultAction:
        Block: {}
      VisibilityConfig:
        SampledRequestsEnabled: true
        CloudWatchMetricsEnabled: true
        MetricName: WebACL_AppRunner
      Rules:
        -
          Name: rules-allow-ip
          Priority: 0
          Action:
            Allow: {}
          Statement:
            IPSetReferenceStatement:
              Arn: !GetAtt WAFIPSet.Arn
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: rules-allow-ip

  WAFIPSet:
    Type: AWS::WAFv2::IPSet
    Properties:
      Name: IPAllowLists
      Scope: REGIONAL
      IPAddressVersion: IPV4
      Addresses: !Ref AllowAddresses

  ARforPl:
    Type: AWS::AppRunner::Service
    Properties:
      ServiceName: golang-container-app
      SourceConfiguration:
        AuthenticationConfiguration:
          AccessRoleArn: !GetAtt RoleForAR.Arn
        AutoDeploymentsEnabled: true
        ImageRepository:
          ImageIdentifier: !Ref EcrRepoUri
          ImageRepositoryType: ECR
          ImageConfiguration:
            Port: 80
      InstanceConfiguration:
        Cpu: 1 vCPU
        Memory: 2 GB

  WebACLAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    Properties:
      WebACLArn: !GetAtt WebACL.Arn
      ResourceArn: !GetAtt ARforPl.ServiceArn

Outputs:    
  AppRunnerServiceUrl:
    Value: !GetAtt ARforPl.ServiceUrl
```

以下は、コマンドで作成する例になります。IPには確認くんなどで、自分のIPをセットしてください。

```bash
REGION="ap-northeast-1"
STACKNAME="create-apprunner"
aws cloudformation create-stack \
    --stack-name ${STACKNAME} \
    --template-body file://createAppRunner.yaml \
    --region ${REGION} \
    --capabilities CAPABILITY_NAMED_IAM \
    --parameters \
        ParameterKey=EcrRepoUri,ParameterValue='123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-plumber-ecs:latest' \
        ParameterKey=AllowAddresses,ParameterValue='111.111.111.111/32'
```

## 確認

CFnスタックのOutputで`ServiceUrl`を出力しています。最後に以下を付与して確認できます。

- `/hello`
- `/hello/jiro/21`
- `/fn?x=2`
- `/plot`

https://qiita.com/a_b_/items/32cbc21bd140321719c2#%E6%8E%A5%E7%B6%9A

許可IPで制限をかけていますので、「自PCからはOK」「Cloud9からはNG」等でWAFが機能しているか確認できます。


## 削除
CFnで作成していますので、スタックを削除することでリソースの削除ができます。


# おわりに

今回はWAFを付けたApp RunnerをCFnで作成してみました。
目的としては「特定の顧客用にIP制限をかけてAPIを提供する環境」を作りたいと考えており、App RunnerにWAFが付けられるとのことで今回試してみました。
この記事がどなたかのお役に立てれば幸いです。