はじめに
最近アップロードされたファイルウィルススキャンする要件を満たすために、ClamAVをLambdaで動かしてウィルススキャンする様開発した機会があったので、ClamAVをAWS Lambdaで動かすまでの諸々を記載したいと思います。
下図の様にS3にファイルがアップロードされたトリガーで、Lambdaを起動し、LambdaでClamAVのスキャンを実行してウィルススキャンする流れです。

今回はClamAVは使ってウィルススキャンすることとなりましたが、
先日、AWS re:Inforce 2024でS3のファイルに対してマルウェア検出と保護の機能である「Amazon GuardDuty Malware Protection for Amazon S3」のリリースが発表されたため、今後S3のファイルに対して ClamAV を使ってウィルススキャンする方式が採用される機会は少なくなるのではと予想されますが、今回の開発を通して、ClamAVについて学ぶことがあったのでアウトプットします。
ClamAVとは
正式名称:Clam AntiVirus で 略称: ClamAV で本記事では略称で記載しています。
オープンソース (GPL) で提供されているクロスプラットフォームのウィルススキャンソフトウェアです。
ClamAV公式サイト 、ClamAVGithub
コマンド
Lambdaで実行する際に必要なClamAVのコマンドは2つだけです。
- clamscan
- freshclam
・clamscan
clamscan {ファイルパス} でファイルパスで指定したファイルに対してウィルススキャンを実行します。
下記サイトに安全なテスト用のマルウェアファイルがあるためダウンロードして使います。
https://www.eicar.org/download-anti-malware-testfile/
実行例: sh-5.2# ls /tmp EICAR.TXT eicar_com.zip eicarcom2.zip sh-5.2# clamscan /tmp WARNING: Failed to set locale LibClamAV Warning: ************************************************** LibClamAV Warning: *** The virus database is older than 7 days! *** LibClamAV Warning: *** Please update it as soon as possible. *** LibClamAV Warning: ************************************************** /tmp/eicar_com.zip: Win.Test.EICAR_HDB-1 FOUND /tmp/eicarcom2.zip: Win.Test.EICAR_HDB-1 FOUND /tmp/.gitignore: OK /tmp/EICAR.TXT: Win.Test.EICAR_HDB-1 FOUND ----------- SCAN SUMMARY ----------- Known viruses: 8685899 Engine version: 0.103.9 Scanned directories: 1 Scanned files: 4 Infected files: 3 Data scanned: 0.00 MB Data read: 0.00 MB (ratio 0.00:1) Time: 17.847 sec (0 m 17 s) Start Date: 2024:07:02 01:43:51 End Date: 2024:07:02 01:44:09
・ウィルス定義ファイルが古いとWarningが出力されます。
・ファイル毎のスキャン結果とSUMMARYが出力されます。
・SUMMARYには、スキャンしたファイル数、ウィルス検出したファイル数、処理時間等が出力されます。
・freshclam
ウィルス定義ファイルを更新します。
実行例: sh-5.2# freshclam ClamAV update process started at Fri Jul 5 18:24:34 2024 Trying to retrieve CVD header from https://database.clamav.net/daily.cvd Time: 1.3s, ETA: 0.0s [========================>] 512B/512B OK daily database available for update (local version: 27203, remote version: 27326) Current database is 123 versions behind. Downloading database patch # 27204... Time: 16.0s, ETA: 0.0s [========================>] 60.89MiB/60.89MiB Testing database: '/var/lib/clamav/tmp.01c90f3f6a/clamav-0ef08e4da800f4c5fe35e8fece256a5a.tmp-daily.cvd' ... Database test passed. daily.cvd updated (version: 27326, sigs: 2063837, f-level: 90, builder: raynman) Trying to retrieve CVD header from https://database.clamav.net/main.cvd main.cvd database is up-to-date (version: 62, sigs: 6647427, f-level: 90, builder: sigmgr) Trying to retrieve CVD header from https://database.clamav.net/bytecode.cvd bytecode.cld database is up-to-date (version: 335, sigs: 86, f-level: 90, builder: raynman) sh-5.2#
やること
- 前提
- ECR のコンテナイメージで動作する AWS Lambda を GitHub Actions からデプロイする を使い、LambdaはGitHub Actionsでデプロイします。
- Dockerfile 作成
- ClamAVを使ってウィルススキャンする Lambda 処理作成
- LambdaのCloudFormationテンプレート 作成
- GitHub Actions ワークフローファイル作成
- デプロイ前準備
- 動作テスト
Dockerfile 作成
- lambda/python:3.12 のイメージに、ClamAVをインストール
- Dockerビルド時にウィルス定義ファイルを更新 ・・・ Lambda起動の度にウィルス定義ファイルを更新すると処理時間がかかるためDockerビルド時に更新する様にしました
FROM public.ecr.aws/lambda/python:3.12
RUN set -ex
&& dnf -y update
&& dnf install -y tar gzip
# Copy function code
COPY lambda/ ${LAMBDA_TASK_ROOT}
# Install dependencies
RUN pip3 install -r requirements.txt
# Install ClamAV
RUN set -ex
&& dnf install -y clamav clamav-update
# Update virus definition file
RUN set -ex
&& freshclam
ENV
TZ='Asia/Tokyo'
CMD [ "app.lambda_handler" ]
ClamAVを使ってウィルススキャンする Lambda 処理作成
- S3イベントトリガーで渡ってくる引数からバケット、ファイルパスを取得してLambda処理内にダウンロード
- ダウンロードしたファイルをClamAVでスキャン
import os
import json
import boto3
import subprocess
import urllib.parse
s3 = boto3.client('s3')
def lambda_handler(event, context):
"""
Lambdaメイン処理
"""
print(json.dumps(event, ensure_ascii=False))
# s3からファイルダウンロード
bucket_name = event['Records'][0]['s3']['bucket']['name']
key = urllib.parse.unquote_plus(
event['Records'][0]['s3']['object']['key'], encoding='utf-8')
file_name = os.path.basename(key)
download_path = f'/tmp/{file_name}'
s3.download_file(bucket_name, key, download_path)
print('スキャン対象:'+event['Records'][0]['s3']['object']['key'])
# スキャン対象ファイルの存在出力
res = subprocess.run(["ls", "-la", "/tmp"],
stdout=subprocess.PIPE, text=True)
print(res.stdout)
# ClamAV スキャン実行
res = subprocess.run(["clamscan", download_path],
stdout=subprocess.PIPE, text=True)
print(res.stdout)
return {
'statusCode': 200,
}
LambdaのCloudFormationテンプレート 作成
- Lambda、Lambdaの実行ロール、S3、S3イベントトリガーを定義
- Lambdaは指定されたECR イメージのコンテナを起動する指定
- Lambdaのメモリーは1536M(1.5G)を指定、1024MだとClamAVのスキャンが実行できない(タイムアウトする)
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Parameters:
Project:
Type: String
AccountId:
Type: String
Resources:
ClamAVFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${Project}
CodeUri: lambda/
MemorySize: 1536
Timeout: 900
EphemeralStorage:
Size: 512
Role: !GetAtt ClamAVFunctionRole.Arn
PackageType: Image
ImageUri: !Sub ${AWS::AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${Project}-ecr:latest
ImageConfig:
Command:
- "app.lambda_handler"
Events:
S3TriggerFunctionEvent:
Type: S3
Properties:
Bucket: !Ref ClamAVTriggerBucket
Events: s3:ObjectCreated:*
ClamAVFunctionRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: !Sub ${Project}-run-role
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: !Sub ${Project}-run-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- Effect: Allow
Action:
- s3:GetObject
Resource: "*"
ClamAVTriggerFunctionPermission:
Type: "AWS::Lambda::Permission"
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt ClamAVFunction.Arn
Principal: "s3.amazonaws.com"
SourceArn: !GetAtt ClamAVTriggerBucket.Arn
ClamAVTriggerBucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Sub ${Project}-${AWS::AccountId}
GitHub Actions ワークフローファイル作成
- ECR のコンテナイメージで動作する AWS Lambda を GitHub Actions からデプロイするのGitHub Actions ワークフローファイルを一部修正して使います。
--capabilities CAPABILITY_IAM⇒--capabilities CAPABILITY_NAMED_IAMへ修正します。CloudFormationでIAMロールを作成するため指定を変える必要があります。
name: Lambda Deploy Development
on:
workflow_dispatch:
env:
AWS_ACCOUNT_ID: XXXXX
AWS_IAM_ROLE_ARN: arn:aws:iam::XXXXX:role/lambda-ecr-deploy-role
AWS_DEFAULT_REGION: ap-northeast-1
PROJECT: clamav-lambda
SAM_CLI_TELEMETRY: 1
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.AWS_IAM_ROLE_ARN }}
role-session-name: github-actions-${{ github.run_id }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- run: aws sts get-caller-identity
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build Docker
working-directory: ./
run: |
docker build -t ${{ env.PROJECT }}-ecr -f ./Dockerfile .
docker tag ${{ env.PROJECT }}-ecr:latest ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${{ env.PROJECT }}-ecr:latest
- name: Push Image to Amazon ECR
working-directory: ./
run: |
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${{ env.PROJECT }}-ecr:latest
- name: ECR Lambda Stack
env:
STACK_NAME: ${{ env.PROJECT }}-stack
TEMPLATE_NAME: lambda-stack.yaml
working-directory: ./
run: |
sam build --use-container
--template-file ${TEMPLATE_NAME}
sam deploy --no-confirm-changeset
--stack-name ${STACK_NAME}
--template-file ${TEMPLATE_NAME}
--s3-bucket ${{ env.PROJECT }}-${AWS_ACCOUNT_ID}
--image-repository ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${{ env.PROJECT }}-ecr
--parameter-overrides Project=${{ env.PROJECT }} AccountId=${AWS_ACCOUNT_ID}
--capabilities CAPABILITY_NAMED_IAM
--no-fail-on-empty-changeset
- name: Lambda Renew Image
run: |
aws lambda update-function-code
--function-name ${{ env.PROJECT }}
--image-uri ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${{ env.PROJECT }}-ecr:latest
デプロイ前準備とデプロイ
- 先にECR リポジトリ作成します。LambdaのCloudFormationテンプレートで指定されている名前でプライベートなECR リポジトリを作成します。
- ECR のコンテナイメージで動作する AWS Lambda を GitHub Actions からデプロイする では、CloudFormationテンプレートでLambda関数のみ作成していますが、今回は、IAMロール、S3バケット、S3イベントトリガーを作成するために、必要な権限をGitHub ActionsのIAMロールに権限付与します。
- GitHub Actions 実行します。実行例は、こちらの記事 を参考にしてください。
動作テスト
- https://www.eicar.org/download-anti-malware-testfile/のテストファイルをS3へアップロードします。
- S3イベントトリガーでLambdaの動作結果をCloudWatchのログで確認します。
