【AWS】ECS on Fargate構成のマルウェア対策

## 1. **はじめに**
(株)電通総研 XI本部 羽根田と申します。インフラ構築運用サービス「DMAP」を運営しています。
ECS on Fargate構成のマルウェア対策について、まとまった情報が見つけられずRFPへの回答に困った経験はありませんでしょうか？自身の経験もとに検討・実装した内容をご紹介します。

マルウェアが混入したイメージをデプロイしてしまうケースや、ユーザのファイルアップロードを受け付けるWebアプリケーションにおいてマルウェアがアップロードされるケースなどへの対策や実装についてご紹介します。

## 2. **コンテナへのマルウェア混入経路**
ECS on Fargate構成においては、以下のようなマルウェア混入の可能性が考えられます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3336189/d9c5b9a7-e1bb-fce0-cbeb-ece8c9d8fac6.png)

1. **Dockerイメージのマルウェア混入**
    * マルウェアが混入したDockerイメージをデプロイしてしまい、コンテナ内でマルウェアが実行される可能性
    * マルウェアの混入経路は以下の通り
      * ベースとなるDockerイメージにマルウェアが混入
      * DockerHubなどのレジストリへ配置されたDockerイメージにマルウェアが混入
      * Githubへ配置したファイルにマルウェアが混入

2. **アップロードファイルのマルウェア混入**
    * ユーザがアップロードしたファイルにマルウェアが混入しており、コンテナ内でマルウェアが実行される可能性

3. **脆弱性の悪用によるマルウェア注入**
    * アプリケーションに脆弱性がある場合に、攻撃者によってマルウェアが注入・実行される可能性

4. **未知のマルウェアの混入**
    * 未知のマルウェアが検知をすり抜けてしまい、EFSやS3などのユーザファイルのアップロード先領域に混入してしまう可能性

## 3. **構成図**
* 全体構成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3336189/cfeacff9-e756-1ca1-7a9a-6a0ec40b3a10.png)

ポイントは以下です。

1. イメージのマルウェアスキャン
CICDパイプライン上でマルウェアスキャンを実施し、Dockerイメージのマルウェア混入を防止
2. アップロードファイルスキャン
ユーザのファイルアップロードを受け付けるWebアプリケーションにおいて、ファイルアップロード時にマルウェアスキャンを実施し、マルウェア混入を防止
3. コンテナのリードオンリー化
コンテナをReadOnlyRootFilesystemで実行し、攻撃者によるコンテナ内へのマルウェアの書き込みを防止
4. 定期スキャン
未知のマルウェアに対する対策として、ユーザファイルのアップロード先領域を定期的にスキャンし、マルウェアの検知を行う

## 4. **セキュリティ対策の実装**
### 4.1 **イメージのマルウェアスキャン**
CDパイプライン上でマルウェアスキャンを実施し、Dockerイメージのマルウェア混入を防止します。具体的には、Trend Micro Artifact Scannerを利用し、コンテナイメージにウィルススキャンを実施します。

Trend Micro Artifact Scanner (以降TMAS)はCLIツールを任意の環境に実装して実行します。仕組みとしては、スキャン対象のコンテナイメージのSBOMを作成し、SaaSとして存在するバックエンドにSBOMを送信してスキャンをするものです。CLIツールとして実装するので、CI/CDパイプラインや開発時の任意タイミングでのスキャンが可能です。
参考文献：[Trend Micro Artifact Scannerについて](https://docs.trendmicro.com/ja-jp/documentation/article/trend-vision-one-artifact-scanner-tmas)

TMASとGithubActionsを用いた実装例は以下の記事を参照ください。

https://qiita.com/tatsufumi_haneda/items/4834ac0f413779dce638#51-%E3%82%A6%E3%82%A3%E3%83%AB%E3%82%B9%E3%82%B9%E3%82%AD%E3%83%A3%E3%83%B3

### 4.2 **アップロードファイルスキャン**
アップロードファイルのマルウェアスキャンを実施し、マルウェア混入を防止します。具体的には、アプリケーションのファイルアップロード時に、ファイルをコンテナ上の一時領域に保管させClamAVを用いてスキャンを実施します。

なお、前提として、アプリケーション自身にファイルアップロードをトリガーとしてコマンドを実行する機能があることが必要です。

構成図は以下になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3336189/77db10db-dc15-33b9-1bfa-3798a55c9f41.png)

設計ポイントは以下になります。

* [ClamAV](https://hub.docker.com/r/clamav/clamav)コンテナを起動したECSタスクを立ち上げておきます。
* ユーザがファイルをアップロードした際にアプリケーションコンテナ上で、対象ファイルをClamAVの`clamdscan`コマンドでスキャンします。
* 裏で、ClamAVコンテナに対象ファイルが送信され、スキャン結果が応答されます、
* スキャン結果がマルウェアだった場合、アプリケーションコンテナ上では`clamdscan`コマンドのオプションに従ってファイルの削除操作などが行われます。

構築方法に関して以下に記載します。

1. [ClamAV](https://hub.docker.com/r/clamav/clamav)コンテナでは以下のイメージを利用
    * [ClamAV](https://hub.docker.com/r/clamav/clamav)
    * `stable`というタグのイメージを利用します。
2. ECRを作成してpushします。タグは`stable`とします。
3. ECSクラスタを作成
4. サブネット、セキュリティグループを作成します。
    * AppコンテナからTCP3310でClamAVコンテナにアクセスを許可
5. ClamAC向けのNLBを作成します。
    * TCP3310をリスナーに設定します
    * ClamAV NLBのCFnは後述しています。
6. ClamAC向けのECSタスク定義、ECSサービスを作成します。
    * CFnは後述しています。
7. アプリケーションコンテナにClamAVクライアントを導入します
    * 設定ファイル`/etc/clamd.d/scan.conf`にNLBのDNS名もしくはIPアドレスを指定する必要があります。
    
        ```Dockerfile
        FROM amazonlinux:latest
        
        # ClamAVのインストール
        RUN yum install -y clamav;\
        yum install -y sudo;\
        dnf clean all;\
        sudo sh -c 'echo -e "TCPAddr clamavnlb-xxxxxxxxxxxx.elb.ap-northeast-1.amazonaws.com\nTCPSocket 3310" >> /etc/clamd.d/scan.conf';\ # NLBのDNS名もしくはIPアドレスを指定
        ```

8. アプリケーションでファイルがアップロードされた際に、以下のコマンドを実行するようにコーディング、またはアプリケーションの設定を記述します。
    
    ```shell
    /usr/bin/clamdscan --remove $FILE
    ```
    `$FILE`はアップロードファイルの絶対パスを持つ変数
    
<details><summary>ClamAV NLBのCFnはコチラ！</summary>

```yml
  InternalNLB:
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    Properties:
      Name: "clamavnlb"
      Scheme: "internal"
      Type: "network"
      LoadBalancerAttributes:
        - Key: "deletion_protection.enabled"
          Value: "false"
      SecurityGroups:
        - # セキュリティグループを指定
      Subnets:
        - # サブネットを指定
  NLBListener:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
      DefaultActions: 
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      LoadBalancerArn: !Ref InternalNLB
      Port: 3310
      Protocol: TCP
```

</details>

<details><summary>ClamAV ECSタスク定義のCFnはコチラ！</summary>

```yml
  ECSTaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      Cpu: !Ref ECSTaskCPUUnit
      ExecutionRoleArn: !Ref ECSTaskExecutionRole
      TaskRoleArn: !Ref ECSTaskRole
      Family: !Sub "clamavtask"
      Memory: 3072
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      #ContainerDefinitions
      ContainerDefinitions:
        - Name: "clamav"
          # clamavのイメージを保管したECRを指定
          Image: "xxxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/clamav:stable" 
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-region: !Ref "AWS::Region"
              awslogs-group: !Ref ECSLogGroup
              awslogs-stream-prefix: "clamavservice"
              awslogs-create-group: "true"       
          MemoryReservation: 1024
          PortMappings:
            - HostPort: 3310
              Protocol: tcp
              ContainerPort: 3310
          Essential: true
          Environment:
            - Name: 'CLAMAV_NO_FRESHCLAMD'
              Value: 'false'
            - Name: 'CLAMD_STARTUP_TIMEOUT'
              Value: '1800'
            - Name: 'CLAMAV_NO_CLAMD'
              Value: 'false'
            - Name: 'FRESHCLAM_CHECKS'
              Value: '6'
            - Name: 'CLAMAV_NO_MILTERD'
              Value: 'true'
          HealthCheck:
            Command: 
              - "CMD-SHELL"
              - "echo PING | nc 127.0.0.1 3310 | grep -q PONG"
            Retries: 3
            Timeout: 10
            Interval: 30
            StartPeriod: 120
          ReadonlyRootFilesystem : false
```

</details>

<details><summary>ClamAV ECSサービスのCFnはコチラ！</summary>

```yml
  ECSService:
    Type: AWS::ECS::Service
    DependsOn:
      - NLBListener
      - NLBListener
    Properties:
      ServiceName: "clamavservice"
      Cluster: # ECSクラスターを指定
      DesiredCount: 1
      PropagateTags: TASK_DEFINITION
      LaunchType: FARGATE
      EnableExecuteCommand: true
      HealthCheckGracePeriodSeconds: 300
      LoadBalancers:
        - TargetGroupArn: !Ref TargetGroup
          ContainerPort: 3310
          ContainerName: "clamav"
      DeploymentController:         
        Type: ECS
      NetworkConfiguration:              
       AwsvpcConfiguration:             
         AssignPublicIp: ENABLED        
         SecurityGroups:                
           - # セキュリティグループを指定
         Subnets:                       
           - # サブネットを指定
      TaskDefinition: !Ref ECSTaskDefinition     
      PlatformVersion: "1.4.0"    
```

</details>

### 4.3 **コンテナのリードオンリー化**
ECSタスク定義でコンテナをReadOnlyRootFilesystemで実行し、攻撃者によるコンテナ内へのマルウェアの書き込みを防止します。
この時の注意点として、アプリケーションが書き込むディレクトリはボリュームマウントしておく必要があります。
これにより、アプリケーションの起動に必要なファイルの書き込みを許可し、それ以外のファイルの書き込みを禁止します。
オマケとして、ECSタスクへecs execでログインするためのボリュームマウントも行います。

以下はTomcatを利用したDockerfileの場合の例です。

```Dockefile
#Tomcatの書き込みフォルダをボリュームマウント
VOLUME ${CATALINA_HOME}/webapps
VOLUME ${CATALINA_HOME}/conf
VOLUME ${CATALINA_HOME}/work
VOLUME ${CATALINA_HOME}/temp

#アプリケーションの書き込みフォルダをボリュームマウント　※アプリケーションによって変更
VOLUME /tmp

#ECSタスクへecs execでログインするためのボリュームマウント
RUN mkdir -p /var/lib/amazon
RUN chmod 775 /var/lib/amazon
VOLUME /var/lib/amazon
RUN mkdir -p /var/log/amazon
RUN chmod 775 /var/log/amazon
VOLUME /var/log/amazon
```

なお、Tomcatの場合の注意点として、コンテナ起動後にwebappsディレクトリにアプリケーションが配置された際に起動されてしまうことを防ぐ必要があります。このためにtomcatを再起動しない限りwar ファイルが展開されないように設定する必要があります。

具体的には、server.xml でautoDeploy="false"と定義します。

```xml
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="false">
```

### 4.4 **定期スキャン**
未知のマルウェアに対する対策として、ユーザファイルのアップロード先領域を定期的にスキャンし、マルウェアの検知を行います。
どうしてもファイルアップロード時点では未知のマルウェアが存在します。

これに対しては、ユーザーファイルのアップロード先のEFSやS3などの領域を定期的にスキャンし、マルウェアの検知を行う必要があります。

* EFSのマルウェア検知については既にQiitaで記載してくださっている方がいらっしゃいますので、参考にしてください。

https://qiita.com/maru3ka9/items/976c265f6ec2b729c24f

* S3のマルウェア検知についても既にQiitaで記載してくださっている方がいらっしゃいますので、参考にしてください。

https://qiita.com/zumax/items/bbfb6d4b682a51e7b870

## おわりに
ECS on Fargate構成のマルウェア対策について、以下の4つの対策についてご紹介しました。

1. Dockerイメージのマルウェアスキャン
2. アップロードファイルのマルウェアスキャン
3. コンテナのリードオンリー化
4. 定期スキャン

これらの対策を組み合わせることで、ECS on Fargate構成におけるマルウェア対策を強化することができます。
お客様のRFP回答などにもお役に立てていただければ幸いです。

よろしければ、いいねやフォローをいただけると励みになります。