この記事は クラウドワークス Advent Calendar 2023 シリーズ2 の 14日目の記事です。
こんにちは。crowdworks.jp SRE チームの田中(@kangaechu)です。 この記事を読んでいる方はGoogle/Yahoo.comから出されたメール送信者のガイドライン対応を絶賛対応中かと思います。
Postmaster @ Yahoo & AOL — More Secure, Less Spam: Enforcing Email Standards...
みなさん、DNSにDMARCの設定を追加しましたか? ひとまずDMARCで設定したレポート送信先メールアドレスに大量のレポートが届いているものの、次になにしたらいいんだろう?と思っている方も多いかと思います。
DMARCレポートを解析し、いい感じに表示するSaaSもたくさんありますが、ひとまず受け取ったレポートを手元で見てみたい、という方。 そんな方におすすめなのがdmarc-visualizer。この記事では弊社での導入方法をご紹介します。
dmarc-visualizerとは
dmarc-visualizerを紹介する前に、parsedmarcを紹介します。 parsedmarcはPythonで書かれたOSSで、DMARCレポートをJSONやCSVに変換するツールです。また、このツールはElasticsearchやSplunk、ApacheKafkaにも出力することが可能です。
dmarc-visualizerはDocker Composeにより、以下のソフトウェアをローカルで立ち上げる設定を配布するリポジトリです。
- parsedmarc
- Elasticsearch
- Grafana
dmarc-visualizerを使用することにより、設定の手間なくDMARCレポートのパース→Elasticsearchへのロード→Grafanaによるビジュアライズを簡単に行うことができます。
crowdworks.jpでのレポート確認までの手順
全体概要は以下のとおりです。
クラウドワークスではDMARCレポートをGoogle Workspaceで払い出したメーリングリストに送信しています。 メールはメーリングリストの各メンバーに配信されています。 DMARCのレポートメールにはdmarcラベルを付与した状態となっています。
これを 「1. 受け取ったDMARCレポートをGoogle Driveに保存」でGoogle App Scriptを使い、メールの添付ファイルをGoogle Driveに保存します。 「2. dmarc-visualizerの準備」で準備したリポジトリを元に、DMARCレポートのファイルを「3. 入力ファイルを配置」で配置し、起動します。 DMARCレポートのパース・Elasticsearchへのロードができたら「4. レポート結果を確認」で結果を確認します。
1. 受け取ったDMARCレポートをGoogle Driveに保存
先ほど説明したとおり、DMARCレポートはメールに添付されています。 当初はGMailの画面からメールを開いて添付ファイルをダウンロード、を繰り返していました。 しかし、メールはDMARCを送信するサーバごとに存在し、添付ファイルは1メールに10個以上ある場合もあり、すぐ辛くなりました。
そのため、Google App ScriptによりGMailのdmarcラベルを付与されたメールから添付ファイルを全てGoogle Driveに保存するスクリプトを作成しました。
/** * Searches for emails with a specific label, downloads their attachments, and saves them to Google Drive. */ function main() { var folderBase = "dmarc"; // 出力先のフォルダ名 // GMailのメール検索演算子 // https://support.google.com/mail/answer/7190?hl=ja var queryLabel = "label:dmarc"; // GMailの検索タグ var startDate = new Date(Date.now()); startDate.setDate(startDate.getDate() - 1); // 前日分を処理 startDate.setUTCHours(0); startDate.setMinutes(0); startDate.setSeconds(0); startDate.setMilliseconds(0); var endDate = new Date(startDate); endDate.setDate(endDate.getDate() + 1); var startDateUnix = startDate.getTime() / 1000; var endDateUnix = endDate.getTime() / 1000; var query = `${queryLabel} before: ${endDateUnix} after: ${startDateUnix}`; Logger.log(query); var folderName = `${folderBase}/${Utilities.formatDate(startDate, "UTC", "yyyy-MM-dd")}`; saveAttachmentsToDrive(query, folderName); } /** * Searches for emails with a specific label, downloads their attachments, and saves them to Google Drive. * @param {string} query - The search query to find emails with the desired label and date range. * @param {string} folderName - The name of the folder in Google Drive where the attachments will be saved. */ function saveAttachmentsToDrive(query, folderName) { var folder = createFolderByPath(folderName); deleteAllFilesInFolder(folder); // Save attachments from emails matching the search query var threads = GmailApp.search(query); for (var i = 0; i < threads.length; i++) { var messages = threads[i].getMessages(); for (var j = 0; j < messages.length; j++) { Logger.log(messages[j].getSubject() + " (" + messages[j].getDate() + ")"); var attachments = messages[j].getAttachments(); for (var k = 0; k < attachments.length; k++) { var attachment = attachments[k]; folder.createFile(attachment); } } } } /** * Creates a folder in Google Drive recursively. * @param {string} path - The path of the folder to create, separated by forward slashes. * @returns {Folder} - The created folder object. */ function createFolderByPath(path) { var folders = path.split('/'); var folder = DriveApp.getRootFolder(); for (var i = 0; i < folders.length; i++) { var subFolders = folder.getFoldersByName(folders[i]); if (subFolders.hasNext()) { folder = subFolders.next(); } else { folder = folder.createFolder(folders[i]); } } return folder; } /** * Deletes all files in a given folder. * @param {Folder} folder - The folder in Google Drive where the files will be deleted. */ function deleteAllFilesInFolder(folder) { if (folder) { var files = folder.getFiles(); while (files.hasNext()) { var file = files.next(); file.setTrashed(true); } } }
このスクリプトはGMailの指定されたラベル、かつUTCで前日分のメールから添付ファイルを指定のフォルダ(この場合はdmarc)に保存します。 処理のほとんどはGitHub Copilotに書いてもらいました。べんりー。
このスクリプトは毎日1回、午前9時半くらいに実行しています。
2. dmarc-visualizerの準備
dmarc-visualizerを準備します。
git clone https://github.com/debricked/dmarc-visualizer cd dmarc-visualizer docker compose build docker compose pull
parsedmarcがうまくビルドできない場合はPythonのバージョンによるのかもしれません。 pythonのバージョンを3.11に落としたところ、うまく動きました。 自分は https://github.com/kangaechu/dmarc-visualizer/tree/test で実行しています。
3. 入力ファイルを配置
Google DriveにたまったDMARCレポートのファイルをfiles/以下にコピーします。 Google Driveには日毎にフォルダが作成されていますが、フォルダ以下のファイルのみをコピーしてください。 また、zip/gzipなど圧縮されていますが圧縮されたままで大丈夫です。
ファイルをコピーしたら docker compose up -d
で起動します。
parsedmarc
コンテナがパース・Elasticsearchへのロード処理を実施します。
処理が完了すると parsedmarc
コンテナは終了しますので、コンテナが終了したらレポート結果を確認することができます。
Elasticsearchにロード後はfiles/に置いたファイルは削除しても大丈夫です。
4. レポート結果を確認
http://localhost:3000 をブラウザで開き、Grafanaのダッシュボード「DMARC Reports」を開きます。
Top 2000 Message Sources by Reverse DNS では送信元のメールサーバのIPアドレスを逆引きしたホスト名と送信数の関連をみれたり、
Overviewではメールを送信したドメインごとのSPF/DKIM設定状況などを確認することができます。
身に覚えのないホスト名がいくつかありますね。
まとめ
DMARCレポートはXMLファイルであり、1件ずつ眺めるのは大変です。 そのため、dmarc-visualizerを使用し、DMARCレポートの統計情報を眺めることができるようになりました。 なんとなく設定していたDMARCレポートですが、現状が見れるようになると楽しくなり、 Google/Yahoo.comから出されたメール送信者のガイドライン対応へのモチベーションが上がるのではないでしょうか。