危険なユーザーが利用するサービスを開発しているあなたに「clamav」を差し上げます🫴
危険なユーザーは存在しないだろうなと思いつつも、「万が一」を見越したサービス設計をしておきたい。そんな堅牢なアプリケーションを開発している人向けの記事になります。
S3 にアップロードされるファイルはすべてウイルススキャンしたい――そんな要件に応えるため、調査の結果ユースケースに合った OSS の ClamAV を採用し、実装しました。
本記事はその実装メモ(S3 → Lambda → ClamAV → タグ付け)です。
構築を進める中で サーバーレス(イベント駆動・従量課金・レイヤー運用)の勘所 も掴めたので、併せて記録しておきます。
clamavとは
Clam AntiVirus is an open source (GPL) anti-virus toolkit for UNIX. It provides a number of utilities including a flexible and scalable multi-threaded daemon, a command line scanner and advanced tool for automatic database updates.
(c.f: https://wiki.archlinux.org/title/ClamAV)
UNIX向けのオープンソースで、主にウィルススキャン目的で作成されたライブラリです。
Linux上で動くアプリケーション向けのanti-malware製品は元から少なかったみたいですね。そこで開発されたのがclamavと。
サーバーレスコンピューティングとは
サーバーレスコンピューティングは、サードパーティが管理するサーバーインフラストラクチャ上でアプリケーションを構築してデプロイできるアプリケーション開発モデルです。
(https://aws.amazon.com/jp/what-is/serverless-computing/)
RailsのようなサーバーサイドアプリをAWSで動かす場合、通常はEC2やECS Fargateで実行環境を用意・運用しますよね。
サーバーレスなら基盤の調達・保守はクラウドベンダー側が担ってくれるので、開発者はコードとイベント設計に集中できる利点があります。
常時serveする必要のないコンピューティングリソースなので、コンピューティング費用とメンテナンス費用の節約ができる優れものです。
イベントドリブンなサービスなので、非同期やステートレスな他サービスとの相性がいいです。
AWSにおけるサーバーレス
AWSでは、サーバーレスアーキテクチャの構築とデプロイを容易にするためのいくつかのツールを提供しています。その中でも代表的なのが、SAM(Serverless Application Model)とSLS(Serverless Framework)です。
SAM
AWS Serverless Application Model (AWS SAM) is an open-source framework for building serverless applications using infrastructure as code (IaC)
yaml形式でAWSのサービスを管理、デプロイできるフレームワークです。sam deploy コマンドを実行するだけでAWS CloudFormation経由で各種yamlに書かれたサービスをデプロイすることができます。ローカルでサーバレス環境の動作確認ができる点が個人的には嬉しい点だなと感じています。
SLS
Makes it easy to use AWS Lambda and other managed cloud services to build applications that auto-scale, cost nothing when idle, and result in radically low maintenance.
こちらもサーバーレス環境で動くlambda functionのデプロイを手軽にするためのサービスです。
serverless.yamlに数行記述して、sls deployするだけでcloudformationが動いて、lambda functionとlambdaの実行に必要な環境がセットアップされます。スッゲェですね。
今回作成しようとしているclamda-layer内のymlファイルには下記のように定義されています。
$ cat serverless.yml
service: clambda-av
provider:
name: aws
region: ap-northeast-1
runtime: nodejs14.x
iamRoleStatements:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObjectTagging
Resource: "arn:aws:s3:::clambda-av-files/*"
functions:
virusScan:
handler: handler.virusScan
memorySize: 2048
events:
- s3:
bucket: clambda-av-files
event: s3:ObjectCreated:*
layers:
- {Ref: ClamavLambdaLayer}
timeout: 120
package:
exclude:
- node_modules/**
- coverage/**
layers:
clamav:
path: layer
sls deployを実行すると、—aws-profileの引数に指定したIAMユーザーでcloudformationが動かされます。
ここでは、S3への実行ロールやlambda functionが作成されています。
lambda functionは、同一ディレクトリにあるhandler.js内の、virusScanというhandlerが指定されています。
ちなみに、中身はこうなっています。処理は追って説明します。
$ cat handler.js
const { execSync } = require("child_process");
const { writeFileSync, unlinkSync } = require("fs");
const AWS = require("aws-sdk");
const s3 = new AWS.S3();
module.exports.virusScan = async (event, context) => {
if (!event.Records) {
console.log("Not an S3 event invocation!");
return;
}
for (const record of event.Records) {
if (!record.s3) {
console.log("Not an S3 Record!");
continue;
}
// get the file
const s3Object = await s3
.getObject({
Bucket: record.s3.bucket.name,
Key: record.s3.object.key,
})
.promise();
// write file to disk
writeFileSync(`/tmp/${record.s3.object.key}`, s3Object.Body);
try {
// scan it
const scanStatus = execSync(
`clamscan --database=/opt/var/lib/clamav /tmp/${record.s3.object.key}`
);
await s3
.putObjectTagging({
Bucket: record.s3.bucket.name,
Key: record.s3.object.key,
Tagging: {
TagSet: [
{
Key: "av-status",
Value: "clean",
},
],
},
})
.promise();
} catch (err) {
if (err.status === 1) {
// tag as dirty, OR you can delete it
await s3
.putObjectTagging({
Bucket: record.s3.bucket.name,
Key: record.s3.object.key,
Tagging: {
TagSet: [
{
Key: "av-status",
Value: "dirty",
},
],
},
})
.promise();
}
}
// delete the temp file
unlinkSync(`/tmp/${record.s3.object.key}`);
}
};
Serverless Framework(SLS)によるLambda Functionの簡単デプロイ
Serverless Framework(SLS)を使うと、簡単なserverless.yaml
の記述とsls deploy
の実行だけで、AWS CloudFormationが動き、Lambda functionとLambdaの実行に必要な環境がセットアップされます。これは非常に便利な機能です。
例えば、下記のようにserverless.yml
を定義します。
$ cat serverless.yml
service: clambda-av
provider:
name: aws
region: ap-northeast-1
runtime: nodejs14.x
iamRoleStatements:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObjectTagging
Resource: "arn:aws:s3:::clambda-av-files/*"
functions:
virusScan:
handler: handler.virusScan
memorySize: 2048
events:
- s3:
bucket: clambda-av-files
event: s3:ObjectCreated:*
layers:
- {Ref: ClamavLambdaLayer}
timeout: 120
package:
exclude:
- node_modules/**
- coverage/**
layers:
clamav:
path: layer
このsls deploy
コマンドを実行すると、—aws-profile
の引数に指定したIAMユーザーでAWS CloudFormationが動きます。このプロセスにより、S3への実行ロールやLambda functionが作成されます。
Lambda functionでは、同一ディレクトリにあるhandler.js
内の、virusScan
というhandlerが指定されています。
$ cat handler.js
const { execSync } = require("child_process");
const { writeFileSync, unlinkSync } = require("fs");
const AWS = require("aws-sdk");
const s3 = new AWS.S3();
module.exports.virusScan = async (event, context) => {
//...
};
CLIから実行するにはIAMが必要なので、下記のインストラクションで作成しておくと良いかもですね。
Dockerコンテナ
https://dev.to/sutt0n/scanning-files-on-lambda-with-a-clamav-lambda-layer-475c でclamavのラムダ用レイヤーを作成していきます。
事前にIAMユーザーを作成して、~/.aws/credentialsに保存しておきましょう。
今回はdevという名前のIAMユーザーを作成しました。これでCLIからAWSのサービスへアクセスすることができます。
[dev]
aws_access_key_id = *******************
aws_secret_access_key = **********************
必要に応じて、build.shに書いてあるdockerコマンドの前にsudoを付け足します。
clamav内のファイル
少し興味があるので、./build.shを叩いた時に走るコマンドを一つ一つ見ていきます。
build.sh
// build.sh
#!/bin/bash
rm -rf ./layer
mkdir layer
docker build --platform=linux/x86_64 -t clamav -f Dockerfile .
docker run --name clamav clamav
docker cp clamav:/home/build/clamav_lambda_layer.zip .
docker rm clamav
mv clamav_lambda_layer.zip ./layer
pushd layer
unzip -n clamav_lambda_layer.zip
rm clamav_lambda_layer.zip
popd
#!/bin/bashって何?
she-bang‘(shabang). This derives from the concatenation of the tokens sharp (#) and bang (!)
どのshellを使うのかを明示的に指定するシンボリックリンクを指している。シェルスクリプトのようなUnix-likeなOperating Systemsでは、she-bangはコマンドとして認識されるそうです。
なので、最初に#がついていても、「これからbin/bashで実行すれば良いんだな」、とシステムが解釈するようです。
各種dockerコマンド
今度はdockerコマンドが書かれていますね。読み解いていきましょう。
docker build --platform=linux/x86_64 -t clamav -f Dockerfile // 1 .
docker run --name clamav clamav // 2
docker cp clamav:/home/build/clamav_lambda_layer.zip . // 3
docker rm clamav // 4
-
docker build --platform=linux/x86_64 -t clamav -f Dockerfile
Dockerイメージをビルドしています。
--platform=linux/x86_64で
イメージをLinuxのx86_64(64ビット)を指定してビルドしています。-t clamavで
イメージの名前をclamav
にしています。-f Dockerfile
はビルド時にカレントディレクトリのDockerfile
が使っています。 -
docker run --name clamav clamav
このコマンドはDockerイメージを実行して、新たなコンテナを作成します。
--name clamav
は作成されるコンテナの名前を指定します。この例ではコンテナの名前がclamav
となります。最後のclamav
は実行するイメージの名前を指定します。この例では名前clamav
のイメージが実行されます。 -
docker cp clamav:/home/build/clamav_lambda_layer.zip .
このコマンドはDockerコンテナからファイルやディレクトリをホストのシステムにコピーします。
clamav:/home/build/clamav_lambda_layer.zip
はコピー元を指定します。この例ではclamav
という名前のコンテナの/home/build/clamav_lambda_layer.zip
というパスのファイルを指定しています。.
はコピー先を指定します。この例ではカレントディレクトリにファイルがコピーされます。 -
docker rm clamav
このコマンドはDockerコンテナを削除します。
clamav
は削除するコンテナの名前を指定します。この例ではclamav
という名前のコンテナが削除されます。
# Dockerfile
FROM amazonlinux:2
WORKDIR /home/build
RUN set -e
RUN echo "Prepping ClamAV"
RUN rm -rf bin
RUN rm -rf lib
RUN yum update -y
RUN amazon-linux-extras install epel -y
RUN yum install -y cpio yum-utils tar.x86_64 gzip zip
RUN yumdownloader -x \*i686 --archlist=x86_64 clamav
RUN rpm2cpio clamav-0*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 clamav-lib
RUN rpm2cpio clamav-lib*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 clamav-update
RUN rpm2cpio clamav-update*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 json-c
RUN rpm2cpio json-c*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 pcre2
RUN rpm2cpio pcre*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 libtool-ltdl
RUN rpm2cpio libtool-ltdl*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 libxml2
RUN rpm2cpio libxml2*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 bzip2-libs
RUN rpm2cpio bzip2-libs*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 xz-libs
RUN rpm2cpio xz-libs*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 libprelude
RUN rpm2cpio libprelude*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 gnutls
RUN rpm2cpio gnutls*.rpm | cpio -vimd
RUN yumdownloader -x \*i686 --archlist=x86_64 nettle
RUN rpm2cpio nettle*.rpm | cpio -vimd
RUN mkdir -p bin
RUN mkdir -p lib
RUN mkdir -p var/lib/clamav
RUN chmod -R 777 var/lib/clamav
COPY ./freshclam.conf .
RUN cp usr/bin/clamscan usr/bin/freshclam bin/.
RUN cp usr/lib64/* lib/.
RUN cp freshclam.conf bin/freshclam.conf
RUN yum install shadow-utils.x86_64 -y
RUN groupadd clamav
RUN useradd -g clamav -s /bin/false -c "Clam Antivirus" clamav
RUN useradd -g clamav -s /bin/false -c "Clam Antivirus" clamupdate
RUN mkdir -p opt/var/lib/clamav
RUN chmod -R 777 opt/var/lib/clamav
RUN LD_LIBRARY_PATH=./lib ./bin/freshclam --config-file=bin/freshclam.conf
RUN tclamav_lambda_layer.zip bin
RUN zip -r9 clamav_lambda_layer.zip lib
RUN zip -r9 clamav_lambda_layer.zip var
RUN zip -r9 clamav_lambda_layer.zip etc
Dockerfile内でコンテナに積んでいるパッケージ
-
clamav
: ClamAVは、ウイルススキャナーであり、特に電子メールサーバーにおけるメールゲートウェイスキャンに使用されます。多くの形式のマルウェアに対応しており、Phishing関連の脅威、ウイルス、トロイの木馬などを検出できます。 -
clamav-lib
: ClamAVのライブラリです。これはClamAVを使用する他のプログラムが依存するライブラリであり、ClamAVの機能を提供します。 -
clamav-update
: ClamAVのウイルスデータベースを更新するためのパッケージです。これにより、ClamAVは最新のマルウェアに対する保護を維持できます。 -
json-c
: JSON-Cは、JSONデータの解析と生成を可能にするCライブラリです。 -
pcre2
: PCRE(Perl Compatible Regular Expressions)は、Perlの正規表現と互換性のある正規表現ライブラリです。PCRE2はその後続版で、より多くの機能と改善されたパフォーマンスを提供します。 -
libtool-ltdl
: libtool-ltdlは、プログラムがプラグインをロードするために使用するライブラリで、動的ロードの抽象化を提供します。 -
libxml2
: libxml2は、XMLとHTMLドキュメントを解析、変換、保存するためのライブラリです。 -
bzip2-libs
: bzip2-libsは、bzip2圧縮アルゴリズムを使用するための共有ライブラリです。 -
xz-libs
: xz-libsは、LZMA圧縮アルゴリズムを使用するための共有ライブラリです。 -
libprelude
: libpreludeは、プレリュードIDS(侵入検知システム)によって使用されるフレームワークで、複数のIDS製品が結果を一元的に収集、共有、相関することを可能にします。 -
gnutls
: GnuTLSは、SSL, TLS, DTLSプロトコルを実装するためのセキュア通信ライブラリです。 -
nettle
: Nettleは、低レベルの暗号ライブラリで、ハッシュ関数、暗号、公開鍵暗号、その他の暗号操作を提供します。
また、yum-utils
、cpio
、tar.x86_64
、`
gzip、
zip、
shadow-utils.x86_64`といったパッケージもインストールされています。これらは、パッケージ管理、ファイルの圧縮・解凍、ユーザーとグループの管理など、一般的なシステム管理作業を支援するツールを提供します。
なお、このDockerfileでは、各パッケージがインストールされるのではなく、yumdownloader
コマンドでダウンロードされ、その後rpm2cpio
とcpio
コマンドで解凍されています。これは、パッケージの中にある特定のファイルだけを抽出し、それを新しいDockerイメージの中に含めるためのものです。
最終的に、これらのファイルはclamav_lambda_layer.zip
という名前のzipファイルにパッケージ化されます。これは、AWS Lambdaで使用するためのレイヤとして使用される可能性があります。レイヤは、Lambda関数のデプロイメントパッケージからライブラリや他の依存関係を分離するための分散パッケージです。
zip -r9 hoge.zip dir/
-
r
: これは "recursive" の略で、指定したディレクトリとそのすべてのサブディレクトリとファイルを圧縮する。 -
9
: これは圧縮レベルを指定している。0から9までの値を指定できる。9は最高の圧縮レベルを意味していて、出力されるzipファイルのサイズを最小限にするために、最も高度な圧縮アルゴリズムを使用しているということになる。
たとえば、zip -r9 clamav_lambda_layer.zip lib
というコマンドは、lib
ディレクトリとその中のすべてのサブディレクトリとファイルを、最高レベルで圧縮した clamav_lambda_layer.zip
という名前のzipファイルを作成する。
$ cd serverless-clamav-lambda-layer/
(.venv) SubarunoMacBook-puro-3:serverless-clamav-lambda-layer subaru$ ls
Dockerfile README.md freshclam.conf handler.test.js package.json
LICENSE build.sh* handler.js package-lock.json serverless.yml
(.venv) SubarunoMacBook-puro-3:serverless-clamav-lambda-layer subaru$ .venv
-bash: .venv: command not found
(.venv) SubarunoMacBook-puro-3:serverless-clamav-lambda-layer subaru$ npm install
⸨###############⠂⠂⠂⸩ ⠇ reify:buffer-alloc: http fetch GET 200 https://registry.npmjs.org/buffer-a
$ bash ./build.sh
[+] Building 7.0s (5/57)
=> [internal] load build definition from Dockerfile 0.2s
=> => transferring dockerfile: 2.16kB 0.1s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/amazonlinux:2 6.3s
=> [auth] library/amazonlinux:pull token for registry-1.docker.io
実行後のディレクトリ
$ ls -a
./ .git/ .npmcheckrc LICENSE build.sh* handler.js layer/ package-lock.json serverless.yml
../ .gitignore Dockerfile README.md freshclam.conf handler.test.js node_modules/ package.json
(.venv) SubarunoMacBook-puro-3:serverless-clamav-lambda-layer subaru$ ls layer/
bin/ etc/ lib/ var/
slsを実行するためのパッケージがないので、グローバルインストールしておきます。
$ npm install -g serverless
s3バケットは全世界で一意な名前にしないといけないので、初期値だとエラーが発生します。
Deploying clambda-av to stage dev (ap-northeast-1)
✖ Stack clambda-av-dev failed to deploy (108s)
Environment: darwin, node 16.13.0, framework 3.25.0 (local), plugin 6.2.2, SDK 4.3.2
Credentials: Local, "dev" profile
Docs: docs.serverless.com
Support: forum.serverless.com
Bugs: github.com/serverless/serverless/issues
Error:
CREATE_FAILED: S3BucketClambdaavfiles (AWS::S3::Bucket)
clambda-av-files already exists
View the full error: https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stack/detail?stackId=arn%3Aaws%3Acloudformation%3Aap-northeast-1%3A061293269148%3Astack%2Fclambda-av-dev%2Fe373dfa0-17b7-11ee-b2bc-063a6e7d620f
作成に成功した場合
$ sls deploy --aws-profile dev
Deploying clambda-av to stage dev (ap-northeast-1)
✔ Service deployed to stack clambda-av-dev (105s)
functions:
virusScan: clambda-av-dev-virusScan (218 kB)
layers:
clamav: arn:aws:lambda:ap-northeast-1:061293269148:layer:clamav:2
2 deprecations found: run 'serverless doctor' for more details
Need a faster logging experience than CloudWatch? Try our Dev Mode in Console: run "serverless dev"
実際にS3にファイルを保存してみる
作成されたS3を見てみましょう。
$ aws s3 cp ./virus.txt s3://clambda-av-files-test1
$ aws s3api get-object-tagging --bucket clambda-av-files-test1 --key virus.txt
紐づいているlambda
デプロイされたlambdaを見てみます。
S3のcreateObjectイベントが発火すると動くlambdaが出来上がっていますね。
cloudwatch logs
S3にファイルを入れた後のログを見てみます
/opt/var/lib/clamav
なんてディレクトリはないよというエラーが出ています。
handler.jsのvirusScan関数の中にある、下記記述でエラーが発生しています。
const scanStatus = execSync(
`clamscan --database=/opt/var/lib/clamav /tmp/${record.s3.object.key}`
);
execSync
node:child_processモジュールの中にある一つのメソッドです。
下記のように呼び出して、Linuxコマンドをjsファイル内で使用できます。
LinuxとUnix
Unixという使い勝手のいいパッケージが昔はあったが、使いづらくね?というイメージを持ったフィンランドの大学生が開発したのがLinuxだそうです。
現在、Unixを使用するのはお金がかかるが、Linuxは無料ですね。MacOSモLinuxを採用しています。コマンドで打っているlsとかpwdとかの、あれです。
関数ないで、コマンドを実行できる関数だそうです。同じような関数は他にもあります。
clamscanコンテナの中身を確認
そこで、さっきdocker buildで作成したclamavのコンテナの中身を見てみます。
clamavのコンテナを使って自作コンテナを作った場合のディレクトリ校正
$ docker run --name clamav -it clamav /bin/bash
bash-4.2# ls
bin clamav_lambda_layer.zip lib opt
bzip2-libs-1.0.6-13.amzn2.0.3.x86_64.rpm etc libprelude-5.2.0-2.el7.x86_64.rpm pcre2-10.23-11.amzn2.0.1.x86_64.rpm
clamav-0.103.8-1.amzn2.0.2.x86_64.rpm freshclam.conf libtool-ltdl-2.4.2-22.2.amzn2.0.2.x86_64.rpm usr
clamav-lib-0.103.8-1.amzn2.0.2.x86_64.rpm gnutls-3.3.29-9.amzn2.0.1.x86_64.rpm libxml2-2.9.1-6.amzn2.5.8.x86_64.rpm var
clamav-update-0.103.8-1.amzn2.0.2.x86_64.rpm json-c-0.11-4.amzn2.0.4.x86_64.rpm nettle-2.7.1-9.amzn2.x86_64.rpm xz-libs-5.2.2-1.amzn2.0.3.x86_64.rpmv
なんだ、存在してるっぽいな、、
bash-4.2# cd opt/var/lib/clamav/
bytecode.cvd daily.cvd freshclam.dat main.cvd
bash-4.2# cd opt/var/lib/clamav/
bytecode.cvd daily.cvd freshclam.dat main.cvd
bash-4.2# cd opt/var/lib/clamav/
bash-4.2# pwd
/home/build/opt/var/lib/clamav
cvd拡張子ってなんなんだろう
.cvd .dat
.cvdは、Bitdefender antivirus and Internet security softwareが使っている、ウィルスの定義が書かれたファイルを保存するための拡張ファイルだそう。
.datは、バイナリーtextファイルと他のプログラムが書かれた拡張ファイルだそう。
ローカル環境
clamavを実行してみる
eicarが提供するウィルスに感染したファイルをclamscanに渡してみます。すると、サポートされていないdatabase ファイルだというエラーが出ます。
$ curl https://secure.eicar.org/eicar.com.txt | clamscan -
LibClamAV Error: cli_loaddbdir: No supported database files found in /usr/local/var/lib/clamav
ERROR: Can't open file or directory
----------- SCAN SUMMARY -----------
Known viruses: 0
Engine version: 1.1.0
Scanned directories: 0
Scanned files: 0
Infected files: 0
Data scanned: 0.00 MB
Data read: 0.00 MB (ratio 0.00:1)
Time: 0.025 sec (0 m 0 s)
Start Date: 2023:07:02 10:39:11
End Date: 2023:07:02 10:39:11
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 68 100 68 0 0 40 0 0:00:01 0:00:01 --:--:-- 40
curl: (23) Failed writing body
どうやらfreshclamコマンドを実行して、clamavが読み込むdatabaseを更新する必要があるようです。
ただ、freshclam.confを適したディレクトリに移動させないとfreshclam
コマンドは実行できないようです。
freshclam.conf.sampleを探せ
freshclam.confを適切なディレクトリに置かないと、freshclamを実行したときに「ファイルが存在しません」というエラーが出てしまう。
$ freshclam
ERROR: Can't open/parse the config file /usr/local/etc/clamav/freshclam.conf
freshclam.conf.sample
は、clamscanパッケージをhomebrewを使ってインストールした時に、一緒にダウンロードされています。
私の場合、/usr/local/etc/clamav/
に合ったので、下記コマンドで適切なかしょに移動させます。
$ cp /usr/local/etc/clamav/freshclam.conf.sample /usr/local/etc/clamav/freshclam.conf
さらに、freshclam.conf内の記述を修正します。vimでfreshclam.confを開いて、Exampleという部分をコメントアウトしましょう。
これを、
# Comment or remove the line below.
Example
こう
# Comment or remove the line below.
#Example
freshclamを再度実行
SubarunoMacBook-puro-3:/ subaru$ freshclam
ClamAV update process started at Sun Jul 2 10:42:21 2023
daily database available for download (remote version: 26956)
ERROR: NULL X509 store
Time: 33.1s, ETA: 0.0s [========================>] 58.73MiB/58.73MiB
WARNING: ******* RESULT 200, SIZE: 61579733 *******
Testing database: '/usr/local/var/lib/clamav/tmp.a5ad13a6dd/clamav-2d384fd99ebd7b280d6d77311b426b7a.tmp-daily.cvd' ...
Database test passed.
daily.cvd updated (version: 26956, sigs: 2038014, f-level: 90, builder: raynman)
main database available for download (remote version: 62)
ERROR: NULL X509 store
Time: 1m 06s, ETA: 0.0s [========================>] 162.58MiB/162.58MiB
WARNING: ******* RESULT 200, SIZE: 170479789 *******
Testing database: '/usr/local/var/lib/clamav/tmp.a5ad13a6dd/clamav-96a1ce8ec33e6a66420cf68eb0b3a638.tmp-main.cvd' ...
Database test passed.
main.cvd updated (version: 62, sigs: 6647427, f-level: 90, builder: sigmgr)
bytecode database available for download (remote version: 334)
ERROR: NULL X509 store
Time: 0.2s, ETA: 0.0s [========================>] 285.12KiB/285.12KiB
WARNING: ******* RESULT 200, SIZE: 291965 *******
Testing database: '/usr/local/var/lib/clamav/tmp.a5ad13a6dd/clamav-02300344bb5af1363a6247c0ba226a2d.tmp-bytecode.cvd' ...
Database test passed.
bytecode.cvd updated (version: 334, sigs: 91, f-level: 90, builder: anvilleg)
clamscanを再度実行
curlでeicarのウィルススキャンを取得し、clamscanに渡してみます。
ちゃんとInfected filesが1と表示されました。
$ curl https://secure.eicar.org/eicar.com.txt | clamscan -
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 68 100 68 0 0 77 0 --:--:-- --:--:-- --:--:-- 77Loading: 1s, ETA: 15s [=> ] 540.00K/8.69M sigs
Loading: 21s, ETA: 0s [========================>] 8.67M/8.67M sigs
Compiling: 5s, ETA: 0s [========================>] 41/41 tasks
stdin: Win.Test.EICAR_HDB-1 FOUND
----------- SCAN SUMMARY -----------
Known viruses: 8669911
Engine version: 1.1.0
Scanned directories: 0
Scanned files: 1
Infected files: 1
Data scanned: 0.00 MB
Data read: 0.00 MB (ratio 0.00:1)
Time: 27.327 sec (0 m 27 s)
Start Date: 2023:07:02 10:50:05
End Date: 2023:07:02 10:50:33
ちなみに、感染していないファイルを渡すと下記のような結果になります。
$ clamscan 節電電球のアイコン素材.png
Loading: 19s, ETA: 0s [========================>] 8.67M/8.67M sigs
Compiling: 4s, ETA: 0s [========================>] 41/41 tasks
/Users/subaru/Downloads/節電電球のアイコン素材.png: OK
----------- SCAN SUMMARY -----------
Known viruses: 8669911
Engine version: 1.1.0
Scanned directories: 0
Scanned files: 1
Infected files: 0
Data scanned: 0.01 MB
Data read: 0.01 MB (ratio 1.00:1)
Time: 25.055 sec (0 m 25 s)
Start Date: 2023:07:02 11:07:33
End Date: 2023:07:02 11:07:58
ローカルでやったことをコンテナ環境でできるようにする
ローカル環境では、下記の手順を踏んでclamscanでinfected fileをfileを検知することができました。
- clamscanパッケージをインストール
- freshman.confを移動
- /usr/local/etc/clamav/freshclam.conf.sampleを/usr/local/etc/clamav/freshclam.confにコピー
- Exampleをコメントアウト
- freshclamコマンドを実行
この手順をコンテナ環境でも実行できればいいということがわかりました。
そこで、コンテナ環境では現状どの手順まで進んでいるのかをおさらいします。
そのために、Dockerfileとコンテナの中身をみます。
clamav-docker
dockerコンテナ内でclamavを実行できるようにするための設定が書いてあります。
コンテナに入って直接clamavをインストールしちゃう作成んでいきます。
yum install -y clamavを実行します。
実行後、./bin/freshclamを実行してdatabaseを更新します。
clamscanが使えるようになったか、下記コマンドを実行しておきましょう。
使えるようになったので、コンテナ内で、必要なディレクトリをzipファイルにしちゃいます。
bash-4.2# zip -r9 layer_final.zip bin
adding: bin/ (stored 0%)
adding: bin/freshclam.conf (deflated 21%)
adding: bin/clamscan (deflated 68%)
adding: bin/freshclam (deflated 56%)
bash-4.2# zip -r9 layer_final.zip var
adding: var/ (stored 0%)
adding: var/spool/ (stored 0%)
adding: var/spool/quarantine/ (stored 0%)
adding: var/lib/ (stored 0%)
adding: var/lib/clamav/ (stored 0%)
bash-4.2# zip -r9 layer_final.zip etc
adding: etc/ (stored 0%)
adding: etc/cron.d/ (stored 0%)
adding: etc/cron.d/clamav-update (deflated 25%)
adding: etc/logrotate.d/ (stored 0%)
adding: etc/logrotate.d/clamav-update (deflated 41%)
adding: etc/sysconfig/ (stored 0%)
adding: etc/sysconfig/freshclam (deflated 49%)
adding: etc/freshclam.conf (deflated 60%)
bash-4.2# zip -r9 layer_final.zip lib
adding: lib/ (stored 0%)
adding: lib/libpreludecpp.so.12 (deflated 67%)
adding: lib/libpcre2-posix.so.1 (deflated 64%)
adding: lib/libhogweed.so.2 (deflated 36%)
adding: lib/libprelude.so.28.1.0 (deflated 84%)
adding: lib/libnettle.so.4.7 (deflated 46%)
adding: lib/libfreshclam.so.2 (deflated 65%)
adding: lib/liblzma.so.5 (deflated 46%)
adding: lib/libclamav.so.9.0.5 (deflated 59%)
adding: lib/libfreshclam.so.2.0.1 (deflated 65%)
adding: lib/libtasn1.so.6.5.3 (deflated 51%)
adding: lib/libbz2.so.1 (deflated 54%)
adding: lib/libtasn1.so.6 (deflated 51%)
adding: lib/libpcre2-8.so.0 (deflated 61%)
adding: lib/libclammspack.so.0.1.0 (deflated 45%)
adding: lib/libltdl.so.7.3.0 (deflated 54%)
adding: lib/libclammspack.so.0 (deflated 45%)
adding: lib/libnettle.so.4 (deflated 46%)
adding: lib/liblzma.so.5.2.2 (deflated 46%)
adding: lib/libprelude.so.28 (deflated 84%)
adding: lib/libbz2.so.1.0.6 (deflated 54%)
adding: lib/libpcre2-posix.so.1.0.1 (deflated 64%)
adding: lib/libjson-c.so.2 (deflated 56%)
adding: lib/libpreludecpp.so.12.0.1 (deflated 67%)
adding: lib/libpcre2-8.so.0.5.0 (deflated 61%)
adding: lib/libxml2.so.2 (deflated 54%)
adding: lib/libjson.so.0 (deflated 70%)
adding: lib/libgnutls.so.28.43.3 (deflated 55%)
adding: lib/libclamav.so.9 (deflated 59%)
adding: lib/libltdl.so.7 (deflated 54%)
adding: lib/libhogweed.so.2.5 (deflated 36%)
adding: lib/libjson.so.0.1.0 (deflated 70%)
adding: lib/libxml2.so.2.9.1 (deflated 54%)
adding: lib/libjson-c.so.2.0.1 (deflated 56%)
adding: lib/libgnutls.so.28 (deflated 55%)
そして、作成したzipファイルをローカル環境から取得します。
まずは、docker psを使ってコンテナの名前を確認します。clamav_new2という名前になってますね。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7faeafc8af1a clamav "/bin/bash" 2 hours ago Up 2 hours clamav_new2
docker cpを使ってコンテナ内で作成したzipファイルを手元にコピーします。
$ docker cp clamav_new2:/home/build/layer_final.zip .
そして、build.shの中身も下記のように変更します。
/layerへlayer_final.zipをunzipするだけです。
$ cat build.sh
#!/bin/bash
rm -rf ./layer
mkdir layer
mv layer_final.zip ./layer
pushd layer
unzip -n layer_final.zip
rm layer_final.zip
popd
./buildを実行後、デプロイしてみましょう
CLIで確認してみましょう。成功していたら、タグがついているはずです。
適当なtxtファイルを用意しておきます。今回はvirus.txtを作成しました。
$ aws s3 cp ./virus.txt s3://clambda-av-files-test1
$ aws s3api get-object-tagging --bucket clambda-av-files-test1 --key virus.txt
{
"TagSet": [
{ "Key": "av-status", "Value": "dirty" }
]
}
ウィルススキャンに成功してそうですね。
まとめ
これで危険なファイルを識別する基盤を作れるようになりました。
あとは、INFECTEDタグがついていた場合は別のS3バケットへ移す、クライアントへ表示させない、といった応用を効かせていけば冒頭のユースケースをクリアできそうな気がします。
参考文献
clamav: https://wiki.archlinux.org/title/ClamAV
clamd: https://docs.clamav.net/manual/Usage/Scanning.html#clamd
serverless flamework: https://github.com/oss-serverless/serverless/tree/main
マルウェアを検出した際の動作を確認するには?: https://eset-support.canon-its.jp/faq/show/17872?site_domain=default
Discussion