DMARCレポートをdmarc-visualizerで可視化する
Gmail様の仰せに従ってせっかくメールサーバーの設定を変更してDKIMやDMARCに対応してGoogle様から日々DMARCのレポートが送られてくるのに、それをただただメールボックスの肥やしにするのは勿体無い!
と言うことで、DMARCレポートを可視化してくれるdmarc-visualizerをインストールしてみました
dmarc-visualizerについて
Githubのdmarc-visualizerページ
https://github.com/debricked/dmarc-visualizer
[dmarc-visualizerのブログ](https://debricked.com/blog/analyze-and-visualize-dmarc-report/)で解説されているようにdmarc-visualizerはDMARCレポートを解析するparsedmarc、解析した情報を格納・検索するためのelasticsearch、elasticsearchに格納されたデータをグラフ化するためのgrafanaの3つのコンテナで構成されています
dmarc-visualizerのインストールと実行
dmarc-visualizerのインストールはdocker compose
一発で必要なコンテナイメージがDocker Hubからインストールされるので簡単に利用を開始することができます
と言ったものの、実際にはdmarc-visualizerがすでに2年以上もメンテナンスされていないこともあって、諸々の関連アプリkーションのバージョンを調整しないと動作しませんでした。
不要な設定の削除とelasticsearchのバージョンアップ(8.12.0)
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,22 +5,18 @@ services:
volumes:
- ./files:/input:ro
- ./output_files:/output
- - ./parsedmarc/parsedmarc.ini:/parsedmarc.ini:ro
- - /etc/localtime:/etc/localtime:ro
command: parsedmarc -c /parsedmarc.ini /input/* --debug
- environment:
- - TZ=Asia/Tokyo
depends_on:
- elasticsearch
restart: on-failure
elasticsearch:
- image: docker.elastic.co/elasticsearch/elasticsearch:7.17.5
+ image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
environment:
- discovery.type=single-node
- - "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
+ - xpack.security.enabled=false
volumes:
- - elasticsearch-data:/usr/share/elasticsearch/data
+ - ./elastic_data:/usr/share/elasticsearch/data
grafana:
build: ./grafana/
@@ -30,7 +26,3 @@ services:
environment:
GF_INSTALL_PLUGINS: grafana-piechart-panel,grafana-worldmap-panel
GF_AUTH_ANONYMOUS_ENABLED: 'true'
-
-volumes:
- elasticsearch-data:
- driver: local
grafanaの最新版へのバージョンアップ
--- a/grafana/Dockerfile
+++ b/grafana/Dockerfile
@@ -1,4 +1,4 @@
-FROM grafana/grafana:8.5.2
+FROM grafana/grafana:latest
ADD --chown=grafana:root https://raw.githubusercontent.com/domainaware/parsedmarc/master/grafana/Grafana-DMARC_Reports.json /var/lib/grafana/dashboards/
RUN chmod 644 /etc/grafana/provisioning
elasticsearchのバージョン設定の変更
--- a/grafana/grafana-provisioning/datasources/all.yml
+++ b/grafana/grafana-provisioning/datasources/all.yml
@@ -9,7 +9,7 @@ datasources:
database: '[dmarc_aggregate-]YYYY-MM-DD'
isDefault: true
jsonData:
- esVersion: 70
+ esVersion: 8.12.0
timeField: 'date_range'
interval: 'Daily'
version: 1
@@ -22,7 +22,7 @@ datasources:
database: '[dmarc_forensic-]YYYY-MM-DD'
isDefault: false
jsonData:
- esVersion: 70
+ esVersion: 8.12.0
timeField: 'arrival_date'
interval: 'Daily'
version: 1
Pythonのバージョンアップ(3.9.18-alpine3.19)とmsgraph-coreのバージョンダウン(<1.0.0)
--- a/parsedmarc/Dockerfile
+++ b/parsedmarc/Dockerfile
@@ -1,7 +1,6 @@
-FROM python:3.9-alpine3.16
+FROM python:3.9.18-alpine3.19
-RUN apk add build-base libxml2-dev libxslt-dev libffi-dev tzdata \
- && pip install parsedmarc
+RUN apk add --update --no-cache libxml2-dev libxslt-dev build-base libffi-dev \
+ && pip install parsedmarc 'msgraph-core<1.0.0'
COPY parsedmarc.ini /
-#COPY GeoLite2-Country.mmdb /usr/share/GeoIP/GeoLite2-Country.mmdb
コンテナのビルド
上記のように必要な設定ファイルの修正が終わったならばdocker compose build
で必要なコンテナをビルドします
docker build
macOSではDocker Hubのアクセスなどでキーチェーンの情報にアクセスする必要があるため、sshでリモートログインしている場合には以下のようにキーチェーンのロックを解除するように要求される場合があるので、その場合にはsecurity -v unlock-keychain
でコンテナのビルドに先立ってキーチェーンをアンロックしておいてください
macOSの場合
macOSでsshリモートログインしてDocker Hubを使うとキーチェーンをアンロックできずエラーになる
docker compose build
[+] Building 0.6s (4/4) FINISHED docker:desktop-linux
=> [grafana internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 316B 0.0s
=> [parsedmarc internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 224B 0.0s
=> ERROR [grafana internal] load metadata for docker.io/grafana/grafana: 0.6s
=> ERROR [parsedmarc internal] load metadata for docker.io/library/pytho 0.6s
------
> [grafana internal] load metadata for docker.io/grafana/grafana:latest:
------
------
> [parsedmarc internal] load metadata for docker.io/library/python:3.9.18-alpine3.19:
------
failed to solve: grafana/grafana:latest: error getting credentials - err: exit status 1, out: `error getting credentials - err: exit status 1, out: `keychain cannot be accessed because the current session does not allow user interaction. The keychain may be locked; unlock it by running "security -v unlock-keychain ~/Library/Keychains/login.keychain-db" and try again``
キーチェーンのアンロック
事前にキーチェーンをアンロックしておく
security -v unlock-keychain ~/Library/Keychains/login.keychain-db
unlock-keychain "/Users/user/Library/Keychains/login.keychain-db"
password to unlock /Users/user/Library/Keychains/login.keychain-db:
parsedmarcの設定ファイルを作成
DMARCレポートを解析するためのコンテナparsedmarcの設定ファイルのサンプルを元に自分が使う環境に合わせて設定ファイルparsedmarc.ini
を作成します
parsedmarcのサンプル設定ファイルparsedmarc.sample.ini
をparsedmarc.ini
にコピーします
cp parsedmarc/parsedmarc.sample.ini parsedmarc/parsedmarc.ini
parsedmarc設定のドキュメント
設定ファイルの使い方についてはparsedmarcのGithubページやparsedmarcのドキュメントファイルを参考にして設定します
ローカルのDMARCレポートファイルを読み込んでレポートを生成する場合
DMARCレポートは事前に何らかの方法でDMARCレポートを受信しているメールサーバーからダウンロードしておき、dmarc-visualizerを展開したフォルダーにfiles
という名前のフォルダを作り、ZIP付圧縮されたままの状態で構わないのでDMARCレポートの添付ファイルをこのフォルダ内にコピーしておきます
[general]
save_aggregate = False
save_forensic = False
[elasticsearch]
hosts = elasticsearch:9200
ssl = False
IMAP4サーバーから直接DMARCのレポートメールを読み込んでレポートを生成する場合
ただし、parsedmarcのIMAP4クライアントは複数のメールサーバーへのアクセスなど、細かな設定はできないので別途自分でスクリプトを書くなどしてDMARCレポートのメールをダウンロードする方が使いやすいと感じました
[general]
save_aggregate = True
save_forensic = True
output = /output/
[imap]
host = mail.example.com
user = dmarc@mail.example.com
password = password
watch = True
[elasticsearch]
hosts = elasticsearch:9200
ssl = False
IMAP4サーバーからDMARCレポートを取得するスクリプト
以下はPythonでちょろちょろっと書いた複数のIMAPサーバーからDMARCレポートをダウンロードしてfilesフォルダに集めるためのスクリプトです
先の「ローカルのDMARCレポートファイルを読み込んでレポートを生成する場合」の設定との組み合わせで利用することが出来ます
スクリプトはGmailからのDMARCレポートをZIP圧縮で受け取った場合にしか配慮していないし、パスワードとかハードコードなので色々ツッコミどころ満載ですが、とりあえずの実験用です
このスクリプトでIMAPサーバーからDMARCレポートを取得後にdocker compose up
でコンテナを起動することでダウンロードしたレポートを可視化することが出来ます
#!/usr/bin/python3
import imaplib
import email
from email.header import decode_header, make_header
import re
# IMAPユーザー、サーバー、パスワードのペア
imap_accounts = {
'dmarc-reports@hogehoge.tld': ['imap.hogehoge.tld', 'ABCdEFGhIJ'],
'dmarc-reports@gmail.com': ['imap.gmail.com', 'abcd 1234 EFgH IjkL']}
# IMAPサーバーを順次処理
for imap_user in imap_accounts:
imap_server = imap_accounts[imap_user][0]
imap_password = imap_accounts[imap_user][1]
imap_search_subject = imap_accounts[imap_user][2]
# Gmailにログイン
# ログインにはGmailで発行したアプリパスワードが必要なことに注意
imap = imaplib.IMAP4_SSL(imap_server)
imap.login(imap_user, imap_password)
# IMAP4サーバー(Gmail)からメールボックス一覧を取得
mboxes = imap.list()
print(mboxes)
# メールボックスを順次処理
for mbox in mboxes[1]:
# パターンマッチしたいのでバイナリから文字列に
s = mbox.decode()
print(s)
# メールボックスの情報を属性、階層区切り文字、メールボックス名に分割
m = re.match('\((\\\\.*)\) (".*") (.*)$', s)
mbox_attribute = m.group(1)
# 属性が '\Noselect' (選択不可)ならスキップ
if "\\Noselect" in mbox_attribute:
continue
mbox_name = m.group(3)
# メールボックスを選択
imap.select(mbox_name)
# メールボックスからDMARCのレポートを検索
response, messages = imap.search(None, imap_search_subject)
messages = messages[0].split(b' ')
# 検索したメールを順番に処理
for mail in messages:
# メールの内容を取得できなければスキップ
if not mail:
continue
_, data = imap.fetch(mail, '(RFC822)')
# メールの中身をパースしてmsg構造体に格納
msg = email.message_from_bytes(data[0][1])
# Subjectからレポート対象ドメインを取得、レポート送信者
# 、レポートIDを取得
subject = str(make_header(decode_header(msg["Subject"])))
subject.replace('\n', '')
print(subject)
# と思ったが、予想以上にSubjectのフォーマットが複雑なので後回しw
#m = re.match('Report domain: (.*) Submitter: (.*) Report-ID: (.*)$', subject)
#if m is not None:
# report_domain = m.group(1)
# メッセージのパートから添付ファイルを検出
for part in msg.walk():
filename = part.get_filename()
if (filename and
(part.get_content_type() == 'application/zip' or
part.get_content_type() == 'application/gzip')):
# ZIPファイル(DKIMレポート)や
#.gzファイル(Aggregate Report)が添付されていたら
# ファイルに保存
with open('files/' + filename, 'wb') as f:
f.write(part.get_payload(decode=True))
imap.close()
imap.logout()
DMARCレポート解析用コンテナの設定ファイル(parsedmarc.ini)が用意できてfilesの下へとDMARCレポートをダウンロードしたならdocker compose up
でコンテナを実行しレポートを生成すします
docker compose up -d
[+] Running 11/11
✔ elasticsearch 10 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿] 0B/0B Pulled 24.1s
✔ e4d291082c72 Pull complete 1.2s
✔ e6947a0eeb2c Pull complete 0.8s
✔ af7f7cf6eb22 Pull complete 0.6s
✔ 89732bc75041 Pull complete 1.1s
✔ d38d8a4afa78 Pull complete 10.6s
✔ fb65ac828db6 Pull complete 1.6s
✔ d3b6064b8ab8 Pull complete 1.7s
✔ f8484251cc74 Pull complete 2.1s
✔ 9ffe1c016006 Pull complete 2.4s
✔ e40fcac62c91 Pull complete 2.6s
[+] Running 3/4
⠙ Network dmarc-visualizer_default Created 1.1s
✔ Container dmarc-visualizer-grafana-1 Started 0.8s
✔ Container dmarc-visualizer-elasticsearch-1 Started 0.8s
✔ Container dmarc-visualizer-parsedmarc-1 Started 0.5s
docker compose
でのコンテナ起動直後にはDMARCレポートを解析するためのparsedmarc、データの格納と検索のためのelasticsearch、解析後にelasticsearchに格納されたデータをグラフ化するためのgrafanaの3つのコンテナが起動されます
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0a5f47b805f4 dmarc-visualizer-parsedmarc "parsedmarc -c /pars…" 3 seconds ago Up Less than a second dmarc-visualizer-parsedmarc-1
35300cde4ad9 docker.elastic.co/elasticsearch/elasticsearch:8.12.0 "/bin/tini -- /usr/l…" 3 seconds ago Up 2 seconds 9200/tcp, 9300/tcp dmarc-visualizer-elasticsearch-1
99e791bd0010 dmarc-visualizer-grafana "/run.sh" 3 seconds ago Up 2 seconds 0.0.0.0:3000->3000/tcp dmarc-visualizer-grafana-1
parsedmarcによるDMARCレポートの解析が終わるとコンテナparsedmarcは終了し、elasticsearchとgrafinaの2つのコンテナだけが残ります
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
35300cde4ad9 docker.elastic.co/elasticsearch/elasticsearch:8.12.0 "/bin/tini -- /usr/l…" 36 seconds ago Up 35 seconds 9200/tcp, 9300/tcp dmarc-visualizer-elasticsearch-1
99e791bd0010 dmarc-visualizer-grafana "/run.sh" 36 seconds ago Up 35 seconds 0.0.0.0:3000->3000/tcp dmarc-visualizer-grafana-1
解析したDMARCレポートをブラウザで表示する
コンテナを立ち上げたホストOSで http://localhost:3000/
へとアクセスすることでgrafanaのレポートを表示することが出来ます