S3 バケットを AWS Lambda を使って、ウィルススキャンしてみた

こんにちは、コンサルティング部の望月です。

1月がはじまったと思ったら、もう終わりそうで、この調子だと『シン・エヴァンゲリオン劇場版』の公開ももうすぐですね!!

さて、今回は S3 バケットに保存されているファイルのウィルススキャンを AWS Lambda を使ってやってみたのでブログにまとめました。

現在、S3 にはウィルススキャンという機能はなく、S3 へファイルをアップロードする前か、アップロード後にダウンロードし、ウィルススキャンする方法が多いかと思います。

今回の方法は、下記ソフトウェアを使って Lambda を利用するため、ウィルススキャン用に EC2 など別途用意することなく、手軽に行うことができるため、簡単に試してみることができます。

upsidetravel/bucket-antivirus-function: Serverless antivirus for cloud storage.

やってみた

前提

  • ウィルス定義ファイル保存用 S3 バケットが作成されていること
    • ここでは例として "av-definition" とします
      • IAMポリシーやバケットポリシーの部分などは適切に変更してください
  • ウィルススキャン用 S3 バケットが作成されていること
    • ここでは例として "antivirus-scan" とします
      • IAMポリシーやバケットポリシーの部分などは適切に変更してください
  • メールで確認したい場合は SNS Topic も作成しておくこと
  • 作業用 EC2 インスタンスが作成されていること
    • Amazon Linux 2 を利用
    • IAM ロールで "AmazonS3FullAccess" を割り当てます
      • 動作確認などで S3 へアクセスします

Lambda のパッケージを作成

  • 作業用 EC2 インスタンスにログインし、必要なパッケージをインストールします
    • git はリモートリポジトリをクローンするためにインストール
    • docker はLambda パッケージのビルドのためにインストール
$ sudo yum install -y docker git
$ sudo systemctl start docker
  • ソースを取得し、Lambda パッケージをビルドします
$ git clone https://github.com/upsidetravel/bucket-antivirus-function.git
$ cd bucket-antivirus-function/
$ sudo make all
  • 以下パスにビルドファイルが作成されていることを確認し、手元へダウンロードしておきます。
$ ls -lha build/
/home/ec2-user/bucket-antivirus-function/build/lambda.zip

ウィルス定義ファイル更新用 Lambda の IAM ロール

  • ロール名
    • lambda-bucket-antivirus-update-role
  • ポリシー名
    • lambda-bucket-antivirus-update-policy
  • <<>> を適切に変更してください
{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Sid":"WriteCloudWatchLogs",
         "Effect":"Allow",
         "Action":[
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
         ],
         "Resource":"*"
      },
      {
         "Sid":"s3GetAndPutWithTagging",
         "Action":[
            "s3:GetObject",
            "s3:GetObjectTagging",
            "s3:PutObject",
            "s3:PutObjectTagging",
            "s3:PutObjectVersionTagging"
         ],
         "Effect":"Allow",
         "Resource":[
            "arn:aws:s3:::<<av-definition>>/*"
         ]
      },
      {
         "Sid": "s3HeadObject",
         "Effect": "Allow",
         "Action": "s3:ListBucket",
         "Resource": [
             "arn:aws:s3:::<<av-definition>>/*",
             "arn:aws:s3:::<<av-definition>>"
         ]
      }
   ]
}

ウィルススキャン用 Lambda の IAM ロール

  • ロール名
    • lambda-bucket-antivirus-function-role
  • ポリシー名
    • lambda-bucket-antivirus-function-policy
  • <<>> を適切に変更してください
{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Sid":"WriteCloudWatchLogs",
         "Effect":"Allow",
         "Action":[
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
         ],
         "Resource":"*"
      },
      {
         "Sid":"s3AntiVirusScan",
         "Action":[
            "s3:GetObject",
            "s3:GetObjectTagging",
            "s3:GetObjectVersion",
            "s3:PutObjectTagging",
            "s3:PutObjectVersionTagging"
         ],
         "Effect":"Allow",
         "Resource": [
           "arn:aws:s3:::<<antivirus-scan>>/*"
         ]
      },
      {
         "Sid":"s3AntiVirusDefinitions",
         "Action":[
            "s3:GetObject",
            "s3:GetObjectTagging"
         ],
         "Effect":"Allow",
         "Resource": [
           "arn:aws:s3:::<<av-definition>>/*"
         ]
      },
      {
         "Sid":"kmsDecrypt",
         "Action":[
            "kms:Decrypt"
         ],
         "Effect":"Allow",
         "Resource": [
           "arn:aws:s3:::<<antivirus-scan>>/*"
         ]
      },
      {
         "Sid":"snsPublish",
         "Action": [
            "sns:Publish"
         ],
         "Effect":"Allow",
         "Resource": [
           "arn:aws:sns:ap-northeast-1:<<account id>>:<<sns topic>>"
         ]
      },
      {
         "Sid":"s3HeadObject",
         "Effect":"Allow",
         "Action":"s3:ListBucket",
         "Resource":[
             "arn:aws:s3:::<<av-definition>>/*",
             "arn:aws:s3:::<<av-definition>>"
         ]
      }
   ]
}

ウィルス定義ファイル更新用 Lambda の作成

設定内容

  • 関数名
    • bucket-antivirus-update
  • ランタイム
    • Python 3.7
  • ロール
    • lambda-bucket-antivirus-update-role
  • トリガーを追加
    • CloudWatch Events
      • ルール名
        • bucket-antivirus-update
      • スケジュール式
        • rate(3 hours)
  • 関数コード
    • Lambda.zip
  • ハンドラ
    • update.lambda_handler
  • 環境変数
    • Key
      • AV_DEFINITION_S3_BUCKET
    • Value
      • (av-definition)
  • メモリ (MB)
    • 1024
  • タイムアウト
    • 5 分

補足

  • ロールには上記で作成したウィルス定義ファイル更新用 Lambda の IAM ロールを指定
  • 関数コードには手元にダウンロードしたビルドファイルは、アップロード
  • 環境変数の "AV_DEFINITION_S3_BUCKET" には、ウィルス定義ファイル保存用 S3 バケットを指定
  • Lambda 作成後にテスト実行し、ウィルス定義ファイルをダウンロードしておきます

ウィルススキャン用 Lambda の作成

設定内容

  • 関数名
    • bucket-antivirus-function
  • ランタイム
    • Python 3.7
  • ロール
    • lambda-bucket-antivirus-function-role
  • トリガーを追加
    • S3
      • バケット
        • (antivirus-scan)
      • イベントタイプ
        • すべてのオブジェクト作成イベント
  • 関数コード
    • Lambda.zip
  • ハンドラ
    • scan.lambda_handler
  • 環境変数
    • Key
      • AV_DEFINITION_S3_BUCKET
    • Value
      • (av-definition)
    • Key
      • AV_STATUS_SNS_ARN
    • Value
      • (SNS Topic の ARN)
  • メモリ (MB)
    • 1024
  • タイムアウト
    • 5 分

補足

  • ロールには上記で作成したウィルススキャン用 Lambda の IAM ロールを指定
  • 関数コードには手元にダウンロードしたビルドファイルは、アップロード
  • 環境変数の "AV_DEFINITION_S3_BUCKET" には、ウィルス定義ファイル保存用 S3 バケットを指定
  • 環境変数の "AV_STATUS_SNS_ARN" には、メールを送信する SNS Topic の ARN を指定

ウィルス検知を試してみる

  • EICAR のテストファイルをダウンロードします
    • 注意:ウィルス対策ソフトが導入されている環境にダウンロードすると検知し、情シスにごめんなさいが必要となる可能性があります
$ curl https://secure.eicar.org/eicar.com -o eicar.com
$ curl https://secure.eicar.org/eicar.com.txt -o eicar.com.txt
$ curl https://secure.eicar.org/eicar_com.zip -o eicar_com.zip
$ curl https://secure.eicar.org/eicarcom2.zip -o eicarcom2.zip
  • ウィルススキャン用 S3 バケットにファイルをアップロードします
    • <<>> を適切に変更してください
$ aws s3 cp eicar.com s3://<<antivirus-scan>>/
upload: ./eicar.com to s3://<<antivirus-scan>>/eicar.com
$ aws s3 cp eicar.com.txt s3://<<antivirus-scan>>/
upload: ./eicar.com.txt to s3://<<antivirus-scan>>/eicar.com.txt
$ aws s3 cp eicarcom2.zip s3://<<antivirus-scan>>/
upload: ./eicarcom2.zip to s3://<<antivirus-scan>>/eicarcom2.zip
$ aws s3 cp eicar_com.zip s3://<<antivirus-scan>>/
upload: ./eicar_com.zip to s3://<<antivirus-scan>>/eicar_com.zip
  • アップロード後、Lambda によりウィルススキャンされ、タグにシグニチャ、スキャン日時、ステータスが追加されます
    • ステータスは正常時は "CLEAN"、ウィルス検知時には "INFECTED" となります。
    • <<>> を適切に変更してください
$ aws s3api get-object-tagging --bucket <<antivirus-scan>> --key eicar.com --output text
TAGSET  av-signature    Eicar-Test-Signature FOUND
TAGSET  av-timestamp    2020/01/22 09:59:05 UTC
TAGSET  av-status   INFECTED

$ aws s3api get-object-tagging --bucket <<antivirus-scan>> --key eicar.com.txt --output text
TAGSET  av-signature    Eicar-Test-Signature FOUND
TAGSET  av-timestamp    2020/01/22 09:59:13 UTC
TAGSET  av-status   INFECTED

$ aws s3api get-object-tagging --bucket <<antivirus-scan>> --key eicarcom2.zip --output text
TAGSET  av-signature    Eicar-Test-Signature FOUND
TAGSET  av-timestamp    2020/01/22 09:59:18 UTC
TAGSET  av-status   INFECTED

$ aws s3api get-object-tagging --bucket <<antivirus-scan>> --key eicar_com.zip --output text
TAGSET  av-signature    Eicar-Test-Signature FOUND
TAGSET  av-timestamp    2020/01/22 09:59:22 UTC
TAGSET  av-status   INFECTED

S3 バケットポリシーを使ったアクセス制限

  • また、タグに追加されたステータスを利用し、アクセス制限を行うことができます
    • 例として、リポジトリの README に記載されているものを紹介します

"CLEAN" でない場合、オブジェクトのダウンロードを拒否します

  • "CLEAN"でないオブジェクトは、rootとウィルススキャン用の Lambda 以外は Get を制限します
    • ウィルスチェック前のファイルに関しても、上記制限がかかります
  • ウィルススキャン用 S3 バケットに設定をします
  • <<>> を適切に変更してください
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "NotPrincipal": {
                "AWS": [
                    "arn:aws:iam::<<account id>>:role/lambda-bucket-antivirus-function-role",
                    "arn:aws:sts::<<account id>>:assumed-role/lambda-bucket-antivirus-function-role/bucket-antivirus-function",
                    "arn:aws:iam::<<account id>>:root"
                ]
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<<antivirus-scan>>/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:ExistingObjectTag/av-status": "CLEAN"
                }
            }
        }
    ]
}
  • 正常なファイル "10m-file" とウィルスファイル "eicar.com" を S3 へアップロードし、ローカルにダウンロードします
    • ウィルス検知され、ステータス "INFECTED" となったウィルスファイル "eicar.com" をローカルにダウンロードしようとするとエラーになります
    • ステータス "CLEAN" なファイル "10m-file" はローカルにダウンロードすることができます
  • <<>> を適切に変更してください
$ aws s3 cp eicar.com s3://<<antivirus-scan>>/
$ aws s3 cp s3://<<antivirus-scan>>/eicar.com /tmp/
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden
$ aws s3 cp 10m-file s3://<<antivirus-scan>>/
$ aws s3 cp s3://<<antivirus-scan>>/10m-file /tmp/
download: s3://<<antivirus-scan>>/10m-file to ../../tmp/10m-file

$ aws s3api get-object-tagging --bucket <<antivirus-scan>> --key eicar.com
{
    "TagSet": [
        {
            "Value": "Eicar-Test-Signature FOUND",
            "Key": "av-signature"
        },
        {
            "Value": "2020/01/21 09:54:25 UTC",
            "Key": "av-timestamp"
        },
        {
            "Value": "INFECTED",
            "Key": "av-status"
        }
    ]
}
$ aws s3api get-object-tagging --bucket <<antivirus-scan>> --key 10m-file
{
    "TagSet": [
        {
            "Value": "OK",
            "Key": "av-signature"
        },
        {
            "Value": "2020/01/21 09:56:24 UTC",
            "Key": "av-timestamp"
        },
        {
            "Value": "CLEAN",
            "Key": "av-status"
        }
    ]
}
$

"INFECTED" オブジェクトのダウンロードとタグ付けの拒否します

  • ウィルスに感染しているオブジェクトは Get を制限します
    • ウィルスチェック前のファイルに関しては、制限はかかりません
  • ウィルススキャン用 S3 バケットに設定をします
  • <<>> を適切に変更してください
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": ["s3:GetObject", "s3:PutObjectTagging"],
      "Principal": "*",
      "Resource": ["arn:aws:s3:::<<antivirus-scan>>/*"],
      "Condition": {
        "StringEquals": {
          "s3:ExistingObjectTag/av-status": "INFECTED"
        }
      }
    }
  ]
}
  • 正常なファイル "10m-file" とウィルスファイル "eicar.com" を S3 からローカルにダウンロードします
    • ウィルス検知され、ステータス "INFECTED" となったウィルスファイル "eicar.com" をローカルにダウンロードしようとするとエラーになります
    • ステータス "CLEAN" なファイル "10m-file" はローカルにダウンロードすることができます
  • <<>> を適切に変更してください
$ aws s3 cp s3://<<antivirus-scan>>/eicar.com /tmp/
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden
$ aws s3 cp s3://<<antivirus-scan>>/10m-file /tmp/
download: s3://<<antivirus-scan>>/10m-file to ../../tmp/10m-file

まとめ

AWS Lambda を利用し、S3 バケットのファイルに対して、ウィルススキャンを行なうことができました。

手軽にできる反面、ClamAV が OSS となるため、ウィルス検知については過信せず自分達でもウィルス情報などをキャッチアップしていくことや、AWS Lambda のメモリ内でウィルススキャンを行うため、容量の大きいファイルなどに関してはスキャンできないなど制限もいくつかあるかと思います。

ウィルス対策はいろいろと悩みが悩みが尽きませんが、ひとつの方法として要件にマッチするようであれば、こちらの方法を検討するのもアリかと思います。

参考