こんにちは。技術本部 Bill One Engineering Unit の佐々木です。クラウド請求書受領サービスのBill Oneで、今後リリース予定の請求書発行に関する機能の開発を担当しています。
今回はBill Oneで使用しているファイルストレージ(Google Cloud Storage、以下GCS)にアップロードされたファイルに対して、マルウェアスキャンを実施してウイルスを発見した場合に通知する仕組みを検証した話をします。
導入の背景
SansanではPremiseとして「セキュリティと利便性を両立させる」を掲げ、セキュリティ向上に取り組んでいます。
Bill Oneにおいても、ここ数年、Emotetを始めとしたメール攻撃が流行していることや、メールでの請求書受領に対応していることから、サービス上にアップロードされたファイルに対するウイルスチェックの仕組みを検討してみることにしました。
参考にした情報
前提として今回の仕組みは、Cloud アーキテクチャセンターの記事、及びコードを参考にさせていただきました。ハンズオン形式で、早めに大枠での動作確認が取れたので大変助かりました。
アーキテクチャ
以下が今回、構築したアーキテクチャになります。
黒色の点線はファイルアップロード時の動線、青色の点線は定期的な処理の動線、赤色の点線はウイルスが検知された場合の動線になります。
ファイルアップロードの検知
Bill Oneでは主に以下の場面で、GCSにファイルをアップロードします。
- 「請求書を発行する企業」が、Bill One画面から請求書などのファイルをアップロードする
- 「請求書を発行する企業」が、郵送した請求書などの書類を弊社のオペレーターがスキャンする
- 「請求書を発行する企業」が、Bill One用のメールアドレス宛のメールに請求書などのファイルを添付して送信する
- 「請求書を受領する企業」が、Bill One画面から請求書などのファイルをアップロードする
上記のような場面で、GCSに新たにファイルが追加されたことを検知する仕組みとして、Google Cloudの提供する以下の2つのサービスを使用しました。
Cloud Audit Logs
「いつ、誰が、どこで、何をしたか」を監査ログとして記録するサービスです。下記のEventarcと組み合わせて、ファイル作成時のログ出力イベントを検知し任意のアプリケーションにイベントデータを送信することができます。
記事にあるように、Eventarcのみでも、指定したGCSバケットにおけるファイル追加の検知・イベント送信は可能ですが、Bill Oneではユーザーの組織単位でバケットを保持しており、数千のバケットに対応するEventarcリソースを作成・管理するのは運用コストが大きいと考え、Cloud Audit Logs を選択しました。
Eventarc
Eventarcとはサービス間のイベントを管理してくれるサービスです。具体的にはイベント発生を検知し、イベントデータをJSON形式でアプリケーションにPostしてくれます。 下記のCloud Runとの連携が容易であるため選択しました。
GCS上にファイルをアップロードした際のイベントデータの例(一部の項目を加工、削除しています。)
{ "protoPayload": { "status": {}, "authenticationInfo": { "principalEmail": "xxxxxx@sansan.com" }, "requestMetadata": { }, "serviceName": "storage.googleapis.com", "methodName": "storage.objects.create", "authorizationInfo": [ ], "resourceName": "projects/_/buckets/bucket_name/file_name.csv", "serviceData": { "@type": "type.googleapis.com/google.iam.v1.logging.AuditData", "policyDelta": { "bindingDeltas": [ { "member": "projectOwner:project_name", "action": "ADD", "role": "roles/storage.legacyObjectOwner" }, ] } }, "resourceLocation": { } }, "insertId": "nlaxmieexa5m", "resource": { "type": "gcs_bucket", "labels": { "project_id": "project_name", "location": "asia-northeast1", "bucket_name": "bucket_name" } }, "timestamp": "2022-02-02T13:57:52.135060910Z", "severity": "INFO", "logName": "projects/project_name/logs/cloudaudit.googleapis.com%2Fdata_access", "receiveTimestamp": "2022-02-02T13:57:52.518021436Z" }
ウイルス検知ツール
ウイルス検知ツールとしてClamAVを試してみました。ClamAVは主にCisco Systems社が開発しているOSSのウイルス検知ツールです。元々はメールのスキャンを目的としたツールでしたが、機能拡張を続け、一般的なウイルス検知ツールのような使い方も可能になっています。今回は以下の理由により検証用のウイルス検知ツールとして ClamAVを使用しました。ClamAVを選択した主な理由は、以下の2点になります。
- GCS上のファイルへのスキャンが可能であること
- 無料で利用できること
ウイルス検知ツールの実行環境
Google Cloudの提供するサーバーレスのコンテナ実行環境のCloud Run上のDockerコンテナでClamAVとそのコマンドを実行するNodeアプリケーション(コードをカスタマイズしたもの)を実行しています。
Cloud RunはBill Oneでメインで使用してるサーバーレスコンテナ環境でもあります。
ウイルスが検出された場合の通知
Slack上のモニタリング用のチャンネルに下記のような情報を通知します。下記はCSIRTに提供してもらったEmotetを実際に検出した例となります。
アップロード者は Cloud Audit Logs に記録されるIAMユーザー(イベントデータのJSONのprincipalEmail)であり、「実際にアプリケーション上から操作したユーザー」を特定するものではありません。 なので、Cloud Runアプリケーション経由でアップロードされた場合は、Cloud Runのサービスを実行するサービスアカウント、ということになります。
ウイルス定義ファイルの最新化
ウイルス検知ツールを有効に活用するには最新のウイルス定義をダウンロードしスキャン時に参照する必要があります。 以下のコードにならって、2つの方法でウイルス定義を最新化しています。
インスタンス起動時
Cloud Runインスタンスを起動するタイミング(DockerfileのCMD)で、ClamAVの定義の最新化のコマンド(fleshclam)を行い最新化します。
バックグラウンド
Cloud Runのパラメーターで --no-cpu-throttling 、min-instancesオプションを指定することにより、バックグラウンドでClamAVのウイルス定義更新用のデーモンを起動したCloud Runインスタンスを常駐させています。 ClamAVはデフォルトの設定で、1日12回、最新のウイルス定義をチェックし最新の定義があればダウンロードし更新します。(実際にウイルス定義が更新されるのはほぼ1日1回のようです。)
deployコマンド
gcloud run deploy $SERVICE \ --project $PROJECT \ --image asia.gcr.io/${$PROJECT}/bill-one-malware-scanner:$TAG \ --platform managed \ --region asia-northeast1 \ --no-allow-unauthenticated \ --memory 4Gi \ --min-instances $MIN_INSTANCES \ --max-instances $MAX_INSTANCES \ --no-cpu-throttling \ --service-account $SERVICE_ACCOUNT \ --set-env-vars $ENV_VARS
発生しうる問題
実運用を想定すると、以下のような問題にぶつかると考えられます。
スキャン対象のファイルが存在しない
Bill Oneは請求書PDFのデータ化を行うシステムと、GCS上のJSONファイルでデータのやり取りを行います。このJSONファイルは使用後に削除するので、タイミングによってはスキャン時には存在せず、エラーになる事が考えられます。
対策として、ファイル取得時のファイルの存在チェックに加えて、メタデータ取得時、スキャン時にtry~catchで囲むことで発生したエラーを無視するようにしました。
なお、サービスアカウントで区別可能な場合は、監査ログの監査対象ユーザーから特定のサービスアカウントを除くこともできます。
ClamAVでスキャン可能なファイルサイズのデフォルトは25MBになります。Bill Oneではおよそ200MBの請求書PDFファイルを最大として想定しています。
対策としてClamAVの以下の3項目の設定をチューニングしてみました。その他にもNodeアプリ側でGCSのクライアントAPIを使用してのファイルサイズチェック、Scanにかかるタイムアウト時間の延長などを行いました。
StreamMaxLength 512MB
転送されるファイルの上限。あまりに巨大なファイルが転送されるとCloud Runのメモリが足りなくなってしまうため、下記のMaxScanSizeと同じ値を設定しています。
MaxScanSize 512MB
1ファイルの最大データサイズ。圧縮されたファイルの場合は合計値。合計値なので1MBのZipファイルに1MBのファイルが入っている場合に2MBとなります。最大の倍以上の余裕を持って設定しています。
MaxFileSize 256MB
スキャン対象の最大サイズ。想定の最大に余裕を持たせた値です。
インスタンス生成時にエラー
ウイルスチェックのCloud Runサービスはインスタンス起動時に apt-get update とClamAVのウイルス定義ファイルの更新を行うため、インスタンスの立ち上がりが遅く、請求書の受領が集中すると、インスタンスのスタートが追いつかず、下記のようなエラーが頻発することが容易に想像できます。
The request was aborted because there was no available instance.
対策として、Cloud Runのパラメーターで最低インスタンス数を指定することでエラーの発生頻度を緩和しました。またEventarcはエラー時に再実行もしてくれ実害がないため、インスタンス生成時のエラーはError Reporting上でミュートとしました。
改善点について
今回の検証結果から、改善点としては以下のような点が挙げられると考えます。
メタデータの活用
ウイルス検知時にファイルにメタデータを付与していますが、そのメタデータを利用する仕組みは今のところないため、メタデータを利用して運用担当者向け画面での警告表示を行う必要があります。
アップロードユーザーの特定
Slack通知を元に「実際にファイルをアップロードしたユーザー」「メールを送信したユーザー」を特定する際にファイルのパスからDBを検索して特定するような運用を行なっています。ユーザーを特定した上でSlackに通知できると良いと思います。
他のウイルス検索エンジンでの動作
今回はClamAVの検証について記載しましたが、ウイルスチェック機能はBill One本体と疎結合にしているため、スキャンエンジンを入れ替えるのも難しくありません。ウイルス検索エンジンのコンテナを差し替えれば、他のエンジンでも動作します。 Bill Oneでは、セキュリティをとりまく状況や顧客のニーズにあわせ、最適なエンジンを選定する方針です。
まとめ
ここまで読んでいただきありがとうございました。今回は、GCSにアップロードされたファイルに対して、ウイルスチェックを検証してみた話を書きました。私はセキュリティやGoogle Cloudを専門とするエンジニアではありませんが、Bill Oneでは手を挙げれば専門外のタスクも任せてもらえる文化があります。これからもSansanのValuesの「変化を恐れず、挑戦していく」を忘れずにどんどん手を挙げていこうと思います。
最後に、身に覚えのないメールの添付ファイルは開かない、などの基本的なウイルス対策を心掛けてまいりましょう。