クラウドワークス エンジニアブログ

日本最大級のクラウドソーシング「クラウドワークス」の開発の裏側をお届けするエンジニアブログ

DMARCレポートを眺めるのにdmarc-visualizerがおすすめ

この記事は クラウドワークス Advent Calendar 2023 シリーズ2 の 14日目の記事です。

こんにちは。crowdworks.jp SRE チームの田中(@kangaechu)です。 この記事を読んでいる方はGoogle/Yahoo.comから出されたメール送信者のガイドライン対応を絶賛対応中かと思います。

support.google.com

support.google.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レポートをJSONCSVに変換するツールです。また、このツールはElasticsearchやSplunk、ApacheKafkaにも出力することが可能です。

github.com

dmarc-visualizerはDocker Composeにより、以下のソフトウェアをローカルで立ち上げる設定を配布するリポジトリです。

  • parsedmarc
  • Elasticsearch
  • Grafana

github.com

dmarc-visualizerを使用することにより、設定の手間なくDMARCレポートのパース→Elasticsearchへのロード→Grafanaによるビジュアライズを簡単に行うことができます。

crowdworks.jpでのレポート確認までの手順

全体概要は以下のとおりです。

DMARCレポートを眺めるまでの処理概要図

クラウドワークスでは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アドレスを逆引きしたホスト名と送信数の関連をみれたり、

Top 2000 Message Sources by Reverse DNS

Overviewではメールを送信したドメインごとのSPF/DKIM設定状況などを確認することができます。

Overview

身に覚えのないホスト名がいくつかありますね。

まとめ

DMARCレポートはXMLファイルであり、1件ずつ眺めるのは大変です。 そのため、dmarc-visualizerを使用し、DMARCレポートの統計情報を眺めることができるようになりました。 なんとなく設定していたDMARCレポートですが、現状が見れるようになると楽しくなり、 Google/Yahoo.comから出されたメール送信者のガイドライン対応へのモチベーションが上がるのではないでしょうか。

© 2016 CrowdWorks, Inc., All rights reserved.