JavaScriptを有効にしてください

【clamAV】 S3にアップロードされるファイルへウィルススキャンをかけたい①

 ·  ☕ 5 分で読めます

S3にアップロードされるファイルのウィルススキャンができる機能を実装中です。

clamavというライブラリを見つけました。clamavをAWSのlambdaで実行する時に、色々環境を構築していたのですが、サーバーレスに対する理解が深まったので、ログとして残しておくための記事になります。

サーバーレスとは

サーバーレスとは、従来のサーバー管理の手間を省き、開発者がアプリケーションのコードに集中できるようにするコンピューティングのパラダイムです。開発者は、サーバーのプロビジョニング、スケーリング、メンテナンスを気にすることなく、アプリケーションのビジネスロジックに専念できます。サーバーレスアーキテクチャは、AWS Lambdaのようなイベント駆動のコンピューティングサービスを利用することで、リソースの使用量に基づく料金設定を可能にします。これにより、アプリケーションが非アクティブな場合でもサーバーを稼働させておくためのコストが発生しなくなります。

Ruby on Railsのようなサーバーサイド言語を使用する場合、AWSでアプリケーションを実行するためには、通常、EC2やECS Fargateといったサーバーを立てて実行環境を構築する必要があります。しかし、サーバーレスのアーキテクチャでは、これらのプロセスが抽象化され、開発者はコードの実行に集中できます。

AWSにおけるサーバーレス

AWSでは、サーバーレスアーキテクチャの構築とデプロイを容易にするためのいくつかのツールを提供しています。その中でも代表的なのが、SAM(Serverless Application Model)とSLS(Serverless Framework)です。

SAM

SAMは、サーバーレスアプリケーションの構築を支援するオープンソースのフレームワークです。関数、API、データベース、イベントソースマッピングなどを表現するための省略構文を提供し、リソースごとに数行でアプリケーシションを定義し、モデル化することができます【66†source】。

SLS

Serverless Framework(SLS)は、AWS Lambda関数とそれらが必要とするAWSインフラストラクチャリソースの開発とデプロイを支援するCLIツールです。このフレームワークは、構造、自動化、ベストプラクティスを提供し、開発者が複雑なイベント駆動のサーバーレスアーキテクチャの構築に集中できるようにします【70†source】。

サーバーレス環境で動くlambda functionのデプロイを手軽にするためのサービスです。

serverless.yamlに数行記述して、sls deployするだけでcloudformationが動いて、lambda functionとlambdaの実行に必要な環境がセットアップされます。スッゲェですね。

https://www.serverless.com/framework/docs/providers/aws/guide/deploying

サーバーレスアーキテクチャの採用により、開発者はアプリケーションのビジネスロジックに集中し、インフラストラクチャの管理から解放されます。これは、開発者の生産性を向上させ、アプリケーションのスケーリングとメンテナンスを自動化し、コストを効率化するのに役立ちます。

今回作成しようとしているclamda-layer内のserverless.yml は下記のように定義されています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ 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が指定されています。

ちなみに、中身はこうなっています。処理は追って説明します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
$ 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**を定義します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ 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が指定されています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ 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が必要なので、下記のインストラクションで作成しておくと良いかもですね。

https://www.serverless.com/framework/docs/providers/aws/guide/credentials

https://octomblog.com/posts/virus-scan2/ に続く…


octpsubaru
著者
octpsubaru
Web Developer