サーバレス（AWS Lambda, S3）なウィルス対策（ClamAV）

# はじめに
 AWS Lambdaでアンチウィルスソフト（ClamAV）を動作させ、AWS S3のオブジェクトをチェックできるようにします。
![ClamAV](https://qiita-image-store.s3.amazonaws.com/0/107056/1a2d2ff6-44c8-856f-544b-3b3a1272a1f2.png)

# できるようになること
* ウィルスチェック済みのオブジェクト
`av-status: CLEAN` タグが付与されます。
![ウィルスチェック済み](https://qiita-image-store.s3.amazonaws.com/0/107056/e328a7bf-b189-b7e5-5670-05ac3f913665.png)  
  
  
* ウィルス感染を検知したオブジェクト
`av-status: INFECTED` タグが付与されます。
![ウィルス感染](https://qiita-image-store.s3.amazonaws.com/0/107056/ad18e2fc-6ed6-fcb0-8ea3-0094e2a3c3e4.png)  
  
  
* 感染したオブジェクトをGETしたときの応答
ステータスコード403と共に以下のXML応答があります。
![GETしたときの応答](https://qiita-image-store.s3.amazonaws.com/0/107056/317c2c8c-ac99-be4d-5e9b-cf59e661ac68.png)  
  
  

+ アンチウィルスソフトは[ClamAV](https://www.clamav.net/)を利用します。ClamAVは、[npmのアンチウィルスソフト](https://www.npmjs.com/policies/security)としても利用されているオープンソースです。現時点で660万種のウィルスを認識します。

+ S3バケットにオブジェクトをアップロードすると、Lambda上のウィルスチェックソフトがS3のオブジェクトをスキャンしウィルスかどうか判定します。
+ ウィルスチェックでクリーン判定がでるまで、オブジェクトをGetできないようにします。
+ ウィルスチェックで感染判定がでた場合は、ウィルスチェック完了後もオブジェクトをGetできないようにします。また、判定結果の書き換えも禁止します。オブジェクトのDeleteは許可します。
+ ウィルスチェックソフトの定義ファイルをLambdaを使って定期的に更新します。

# 構築手順
## EC2でLambdaを作成
ClamAVの必要最低限のファイルをLambdaに同梱する作業を行います。
Lambda作成は、[bucket-antivirus-function](https://github.com/upsidetravel/bucket-antivirus-function)を利用します。
ただし、最近、Amazon Linux 2がでてきた関係で、このプロジェクトはDockerを使う部分が動作していません。ここでは、無理せずにLambdaの動作環境である下記AMIのEC2で作業します。
AMI: [amzn-ami-hvm-2017.03.1.20170812-x86_64-gp2](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/current-supported-versions.html)
インスタンスタイプはt2.nanoで十分です。

GitHubからbucket-antivirus-functionをダウンロードしlambda生成スクリプトを実行します。

```
$ sudo yum update
$ sudo pip install --upgrade pip
$ sudo ln -s /usr/local/bin/pip /usr/bin/pip
$ sudo yum install git
$ git clone https://github.com/upsidetravel/bucket-antivirus-function.git
$ sudo mkdir /opt/app
$ sudo mkdir /opt/app/build
$ cd bucket-antivirus-function/
$ sudo ./build_lambda.sh
```

下記のLambdaが作成されました。
/opt/app/build/lambda.zip
14134998 byte

2019/11/18追加情報
一部、エラーが発生するため、下記のように修正して実行しました。

```build_lambda.sh
#yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
#yum install -y epel-release
sudo amazon-linux-extras install epel
```


# バケットの準備
AlamAVのウィルス定義ファイルを格納するバケットと、ウィルスチェック対象のバケットを準備します。

# ウィルス定義ファイル更新用Lambda
- 関数パッケージ： lambda.zip
- ランタイム： Python 2.7
- ハンドラ： update.lambda_handler
- 基本設定
  - メモリ： 512 MB
  - タイムアウト： 5 分
- 環境変数：
  - Key: AV_DEFINITION_S3_BUCKET
  - Value: ウィルス定義ファイル用のバケット名
- トリガー
  - CloudWatch Event
    - スケジュール式: １日１回なら「rate(1 day)」、３時間に１回なら「rate(3 hours)」です。
- 実行ロール
「def bucket name」は各自の環境に合わせて設定します。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:PutObjectVersionTagging",
                "s3:GetObjectTagging",
                "s3:PutObjectTagging"
            ],
            "Resource": [
                "arn:aws:s3:::<<def bucket name>>/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
```

# ウィルスチェック実行用Lambda
- 関数パッケージ： lambda.zip
- ランタイム： Python 2.7
- ハンドラ： scan.lambda_handler
- 基本設定
  - メモリ： 1024 MB
  - タイムアウト： 5 分
- 環境変数：
  - Key: AV_DEFINITION_S3_BUCKET
  - Value: ウィルス定義ファイル用のバケット名
- トリガー
  - S3
    - バケット： チェック対象のバケット名
    - イベントタイプ： オブジェクトの作成（すべて）
- 実行ロール
「def bucket name」、「check bucket name」は各自の環境に合わせて設定します。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:HeadBucket"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::<<def bucket name>>/*",
                "arn:aws:s3:::<<check bucket name>>/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
```


# バケットポリシー

+ 未チェックのオブジェクトは、rootとチェック用Lambda以外はGetを制限します。
+ ウィルスに感染しているオブジェクトはGetを制限します。
+ チェックの状態を確認したり、オブジェクトの削除は可能です。
+ 「account id」, 「check bucket name」, 「antivirus role」, 「antivirus function」を各自の環境に合わせて設定します。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "NotPrincipal": {
                "AWS": [
                    "arn:aws:iam::<<account id>>:role/<<antivirus role>>",
                    "arn:aws:sts::<<account id>>:assumed-role/<<antivirus role>>/<<antivirus function>>",
                    "arn:aws:iam::<<account id>>:root"
                ]
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<<check bucket name>>/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:ExistingObjectTag/av-status": "CLEAN"
                }
            }
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:PutObjectTagging"
            ],
            "Resource": "arn:aws:s3:::<<check bucket name>>/*",
            "Condition": {
                "StringEquals": {
                    "s3:ExistingObjectTag/av-status": "INFECTED"
                }
            }
        }
    ]
}
```

# 動作確認

テスト用ウィルスファイルをS3バケットにPutして試してみます。

```
$ curl http://www.eicar.org/download/eicar.com -o eicar.com
$ curl http://www.eicar.org/download/eicar.com.txt -o eicar.com.txt
$ curl http://www.eicar.org/download/eicar_com.zip -o eicar_com.zip
$ curl http://www.eicar.org/download/eicarcom2.zip -o eicarcom2.zip
```  

以下のウィルスを検知できるようになりました。

- 単純なウィルス
- 拡張子偽装
- zipファイル内のウィルス
- zipファイルに自己解凍のウィルス混入


# 制限事項
+ **ファイルサイズ**
Lambdaのメモリサイズの上限は、ここ数年かなり大きくなりましたが、ファイルサイズは500MBのままです。ClamAVのウィルス定義ファイルが約160MBのため、チェック可能なオブジェクトサイズは約300MBまでになります。

+ **同時チェック数**
AWSの各アカウントにはLambda同時実行数の制約があります。S3オブジェクト１つに対してLambdaが１つ実行されます。S3に多数のオブジェクトを短時間に書き込む必要があるシステムの場合、AmazonにLambdaの同時実行数の制約を緩和してもらうように申請しておく必要があります。

+ **実行時間**
ウィルスチェックには10秒〜40秒かかります。コストを考慮するのならば、複数ファイルをまとめてチェックする仕組みを実装したほうがよさそうです。ただし、その場合は、Lambdaの実行時間の上限である300秒を意識する必要がでてきます。メモリを1024MBから3GBに増やしたり、ウィルスチェック用のLambdaを定期的に呼びだすことで実行時間を短くすることが可能です。

# おわりに
　サーバレスシステムを構築する場合、並列処理によるスケールアップを考慮し、S3とLambdaをベースにシステムを構築していきます。
　既存のウィルスチェックソフトは、EC2にインストールして動作させることはできますが、サーバレスシステムには対応していません。
　ここ数年、業務でサーバレスなシステムを構築していますが、セキュリティーに関する記事が少ないので紹介してみました。