2024年1月28日日曜日

SPF、DMARC、DKIMに対応するためのDNSとPostfix (OpenDKIM)設定手順

Gmailは2024年2月より、1日あたり5,000件以上のメールを送信する送信元においては、SPF(エスピーエフ)、DMARC(ディーマーク)、DKIM(ディーキム)の設定が必須となるよう、セキュリティポリシーが変更される。

私は自宅検証環境においてメール送受信できるPostfixのメールサーバを構築しているが、SPF以外のDMARCとDKIMの対応ができていなかった。

そこで、本記事では、DNSへレコード追加とPostfixへOpenDKIMを導入し、SPF、DMARC、DKIMに対応したメール送信環境を構築する手順を記載する。

環境

自宅検証環境の構成概要図は以下の通り。今回は、以下構成図の赤枠で示している内部のメールサーバのPostfixと、外部DNSとして利用している「お名前ドットコム」に対して設定変更を行う。

本記事で説明に用いるドメインはexample.tech-mmmm.comとするため、各自の所有するドメインに読み替えて参照いただきたい。

SPF

SPFへの対応はDNSへ登録するのみで完了できる。具体的には以下のようなSPFレコード (TXTレコード)をDNSに登録する。

example.tech-mmmm.com. 3600 IN TXT
  "v=spf1 +ip4:[送信元となるメールサーバのIPアドレス] -all"

DMARC

DMARCへの対応もSPFと同様、DNSへ登録するのみで完了できる。レコードは_dmarc.[ドメイン名]で登録するが、具体的には以下のようなDMARCレコード (TXTレコード)をDNSに登録する。

p=noneはポリシーの設定であり、今回はDMARCの結果において特に何もしない (None)という設定としている。

ruarufはDMARCの判定結果のレポートを送信する自ドメインのメールアドレスを設定すればよい。設定すると、Gmailからは1日1回おおよそAM 9:00 (UTCでいうと0:00)に「Report domain: example.tech-mmmm.com Submitter: google.com」というタイトルのメールがレポートとして送られてくるようになる。

_dmarc.example.tech-mmmm.com. 3600 IN TXT
  "v=DMARC1; p=none; rua=mailto:dmarc-report@example.tech-mmmm.com; ruf=mailto:dmarc-report@example.tech-mmmm.com;"

DKIM

DKIMに関してはメール送信時に署名を付与する仕組みが必要であり、Postfixの場合はMilter (Mail Filter)として動作するOpenDKIMを使用する。

以下に導入手順を記載する。

1. OpenDKIMインストール

EPELのリポジトリ追加後、以下をインストールする。opendkim-toolsに鍵情報を作成するコマンドが含まれているため、併せてインストールすること。

# dnf install epel-release -y
# dnf install opendkim opendkim-tools -y

2. DKIM署名用の鍵を作成

DKIM署名用の鍵は、opendkim-genkeyコマンドで行う。

-sオプションでセレクタと呼ばれる任意の識別するための文字列を指定できる。今回はmyselectorと設定した。-bオプションは鍵の長さを設定するオプションとなり、OpenDKIMのデフォルトは1024bitとなるが、セキュリティの観点から2048bitで設定する。

# mkdir /etc/opendkim/keys/example.tech-mmmm.com
# opendkim-genkey -D /etc/opendkim/keys/example.tech-mmmm.com/ -d example.tech-mmmm.com -s myselector -b 2048
# ls -l /etc/opendkim/keys/example.tech-mmmm.com/
合計 8
-rw-------. 1 root root 887  1月 27 18:01 myselector.private
-rw-------. 1 root root 328  1月 27 18:01 myselector.txt

鍵情報のファイルは、ユーザー・グループを変更しておく。

# chown -R opendkim:opendkim /etc/opendkim/keys/example.tech-mmmm.com/
# ls -l /etc/opendkim/keys/example.tech-mmmm.com/
合計 8
-rw-------. 1 opendkim opendkim 887  1月 27 18:01 myselector.private
-rw-------. 1 opendkim opendkim 328  1月 27 18:01 myselector.txt

3. 設定ファイル修正

OpenDKIMの設定ファイルであるopendkim.confを以下の通り修正する。

# vi /etc/opendkim.conf
Mode    sv						    # vからsvに変更
Socket  inet:8891@localhost				    # アンコメント (UNIXドメインソケットからinetソケットに変更)
#Socket local:/run/opendkim/opendkim.sock		    # コメントアウト
Selector        myselector				    # defaultから変更
KeyTable        /etc/opendkim/KeyTable			    # アンコメント
SigningTable    refile:/etc/opendkim/SigningTable	    # アンコメント
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts   # アンコメント
InternalHosts   refile:/etc/opendkim/TrustedHosts	    # アンコメント

セレクタとドメインのに対して使用する鍵情報を紐づけるKeyTableファイルを以下の通り記載する。

# vi /etc/opendkim/KeyTable
myselector._domainkey.example.tech-mmmm.com example.tech-mmmm.com:myselector:/etc/opendkim/keys/myselector.private

署名する際のメールのドメインと使用する鍵情報を紐づけるSigningTableファイルを以下の通り記載する。

# vi /etc/opendkim/SigningTable
*@example.tech-mmmm.com myselector._domainkey.example.tech-mmmm.com

OpenDKIMが信頼できるホストとして認識するホストやネットワークを指定する。今回は内部環境となるので、0.0.0.0/0で指定する。

# vi /etc/opendkim/TrustedHosts
0.0.0.0/0

4. OpenDKIMサービスを起動

設定完了後、OpenDKIMのサービスを起動させる。

# systemctl start opendkim
# systemctl enable opendkim

起動するとポート8891番でLISTENしているかどうか確認しておく。

# ss -nl | grep 8891
tcp   LISTEN 0      128                                127.0.0.1:8891             0.0.0.0:*

5. Postfixの設定修正

OpenDKIMをMilterとして指定するため、以下3行を追加する。

# vi /etc/postfix/main.cf
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept

設定完了後、Postfixを再起動しておく。

# systemctl restart postfix

6. DNS設定

DKIMはDNSの登録された公開鍵情報を用いて署名の検証を行うため、DNSへレコードの追加が必要となる。レコードは[セレクタ名]._domainkey.[ドメイン名]で登録する。レコードの内容は鍵を生成した際に作成される、/etc/opendkim/keys/example.tech-mmmm.com/myselector.txtのファイルに記載されているで、このまま登録を行えば問題ない。

# cat /etc/opendkim/keys/example.tech-mmmm.com/myselector.txt
myselector._domainkey   IN      TXT     ( "v=DKIM1; k=rsa; "
          "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyLeUjkqVc712NhH1/20UMGW4rxg4F4sQilrauxt5mWPEbu342y+Px+OD4oVZ1yeF9NAAAgExIJBwl854p1bn87rVgam9J/3mYICCD5GhbBy6MEFODgMZXcSvJ8Q2g7S1Y0mro95mI2p+8WtPfxjUTzERemcduNa3eZu7+i1I1GwIDAQAB" )  ; ----- DKIM key myselector for example.tech-mmmm.com

上記の通りDNSに登録を行う。

myselector._domainkey.example.tech-mmmm.com. 3600 IN TXT
  "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyLeUjkqVc712NhH1/20UMGW4rxg4F4sQilrauxt5mWPEbu342y+Px+OD4oVZ1yeF9NAAAgExIJBwl854p1bn87rVgam9J/3mYICCD5GhbBy6MEFODgMZXcSvJ8Q2g7S1Y0mro95mI2p+8WtPfxjUTzERemcduNa3eZu7+i1I1GwIDAQAB"

動作確認

実際にメールを送信した際のGmailにおける受信結果が以下となる。SPF、DKIM、DMARCがすべてPASSしていることがわかる。

以上で、DNSへレコード追加とPostfixへOpenDKIMを導入し、SPF、DMARC、DKIMに対応したメール送信環境を構築する手順は完了となる。

2024年1月21日日曜日

Raspberry Pi OSなどのDebian系OSのviで、方向キーによる移動やバックスペースを使えるようにする

Raspberry Pi OSなどのDebianベースのOSにおいてviを使う際に、入力モード(i または aを押して文字入力ができる状態)にした際に方向キーで移動しようとすると、ABCDの文字が入力されてしまう。

方向キーと文字の関係は以下の通り。
   A
   |
D -+- C
   |
   B
とても不便なので、viで方向キーを用いても通常通りカーソルを移動できるように設定する。併せてviでバックスペースも正常に使用できるように対処する。

設定手順

各ユーザーのホームディレクトリに.vimrcというファイルを作成し、以下2行の設定を書き込んでやればよい。
set nocompatible
set backspace=indent,eol,start
ワンライナーで設定したい場合は以下の通り実施する。すでに.vimrcがある可能性を考慮して、追記でリダイレクトしている。
$ echo -e "set nocompatible\nset backspace=indent,eol,start" >> ~/.vimrc
$ cat ~/.vimrc
set nocompatible
set backspace=indent,eol,start
これだけで、特に再ログイン等も不要で、次回vi実行時から設定が反映され、入力モードであっても方向キーが使えるようになる。

更新履歴

  • 2018/3/26 新規作成
  • 2024/1/21 補足情報を追記
2024年1月20日土曜日

Zabbix APIを使って未クローズの障害イベントをクローズする

Zabbixではトリガーによって検知した障害は、復旧条件を満たさなければクローズされることはなく、いつまでも障害として残り続けてしまう。

Zabbix 4.0以降では、管理画面から手動による障害のクローズができるようになったため、復旧条件の記載が難しいログ監視やSNMP Trap監視などにおいても、クローズができるようになりかなり便利になった。

とはいえ、大量の監視対象がある環境においては、障害のクローズを手作業で実施すること自体が負荷となるケースが存在すると考えられるため、今回はZabbix APIを使って未クローズの障害イベントを一気にクローズする手順を検証した。

環境

環境は以下の通りZabbix 5.0を使用している。ただし、Zabbix 4.0以降でも利用できることを確認するため、Zabbix APIのマニュアルはZabbix 4.0のものを確認して検証を行った。

  • OS : CentOS 8.1
  • Zabbix : 5.0.8
  • APIのURL : http://192.168.11.24/zabbix/api_jsonrpc.php

Zabbix APIを使用する際に、jqコマンドが必要となるため、事前にインストールをしておく。jqコマンドは、Zabbix APIやREST APIなどで利用されているJSON形式のデータを整形して出力したり、必要なデータのみ抽出するのに利用するコマンドとなる。

# dnf install epel-release
# dnf install jq

手順

Zabbix APIを実行する際に送信するJSON形式のデータは、以下構文となる。

{
  "jsonrpc": "2.0",
  "method": "メソッド名",
  "params": {
    "パラメータ1 Key": "パラメータ1 Value",
    "パラメータ2 Key": "パラメータ2 Value",
    ...
  },
  "auth": "Zabbix認証トークン",
  "id": 任意の数字
}

以降の手順では、上記構文の送信データを作成したのち、curlコマンドでそのデータを送信してAPIを実行する、という流れとなる。

また、Zabbix APIのURLやヘッダ情報などは、毎回同一となることから、あらかじめ変数として利用できるようにしておこう。

# header='Content-Type:application/json-rpc'
# apiurl='http://192.168.11.24/zabbix/api_jsonrpc.php'

1. Zabbix APIの認証トークンを取得

Zabbix APIの実行は、認証トークンが必要となる。認証トークンは、user.loginメソッドで取得できる。Zabbixの管理者権限を持つユーザとパスワードを指定して、以下のようにAPIを実行するためのデータを作成する。

# json='{"jsonrpc": "2.0","method": "user.login","params": {"user": "Admin","password": "XXXXXXXX"},"id": 1,"auth": null}'
# echo $json | jq
{
  "jsonrpc": "2.0",
  "method": "user.login",
  "params": {
    "user": "Admin",
    "password": "XXXXXXXX"
  },
  "id": 1,
  "auth": null
}

認証トークンは、以降の作業で常に指定して利用することから、実行結果を変数$zbxauthに代入しておく。

# zbxauth=$(curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq -r ".result")
# echo $zbxauth
e2519a6fbc680a87affc8382b65cdcb7

2. 未クローズの障害イベントを取得

障害イベントのクローズには、eventidが必要となる。problem.getメソッドを使うことで一覧として取得できるので、以下の通り送信データを作成する。

# json='{"jsonrpc": "2.0","method": "problem.get","params": {"output": "eventid"},"id": 3,"auth": "'$zbxauth'"}'
# echo $json | jq
{
  "jsonrpc": "2.0",
  "method": "problem.get",
  "params": {
    "output": "eventid"
  },
  "id": 3,
  "auth": "e2519a6fbc680a87affc8382b65cdcb7"
}

取得したeventidは、次の手順で配列として利用できるようにするため、jqコマンドを使って出力結果を整形して変数$eventidsに格納する。以下コマンドの実行例では、6件の障害イベントが対象となる。

# eventids=$(curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq -c '[ .result[].eventid ]')
# echo $eventids
["34308","50073","50875","50876","50877","50954"]

3. 障害イベントを手動クローズ

イベントに対する捜査はevent.acknowledgeメソッドを使う。event.acknowledgeメソッドでは、イベントに対して実行する処理をactionにて指定するが、その際に指定できる値は以下の通りとなる。

  • 1 - close problem;
  • 2 - acknowledge event;
  • 4 - add message;
  • 8 - change severity.

上記は組み合わせることが可能であり、今回は「1 : 障害のクローズ」と「4 : メッセージ追加」の2つを同時に実行するため、actionを1 + 4 = 5で指定する。

# json='{"jsonrpc": "2.0", "method": "event.acknowledge", "params": {"eventids": '$eventids', "action": 5, "message": "Closed problem by API"}, "auth": "'$zbxauth'", "id": 1}'
# echo $json | jq
{
  "jsonrpc": "2.0",
  "method": "event.acknowledge",
  "params": {
    "eventids": [
      "34308",
      "50073",
      "50875",
      "50876",
      "50877",
      "50954"
    ],
    "action": 5,
    "message": "Closed problem by API"
  },
  "auth": "e2519a6fbc680a87affc8382b65cdcb7",
  "id": 1
}

障害イベントのクローズ前に、GUI上でも障害一覧を確認しておく。1件はホストを無効化しているため障害が表示されていないが、GUI上では5件の障害が表示されている。後ほど、API実行後に赤枠で示したアラートがクローズされることを確認する。

curlにてAPIを実行する。resultに処理を実行したイベントIDの一覧が表示されればOKとなる。

# curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq
{
  "jsonrpc": "2.0",
  "result": {
    "eventids": [
      34308,
      50073,
      50875,
      50876,
      50877,
      50954
    ]
  },
  "id": 1
}

GUI上にて再度障害一覧を確認すると、すべての障害がクローズされ、一覧から消えていることが確認できた。

また、クローズされたイベントを確認すると、APIで指定した「Closed problem by API」のメッセージとともに、ステータスが「解決済」になっていることが確認できた。

4. Zabbix APIの認証トークンを削除

最後に認証トークンを削除するため、user.logoutメソッドを使用する。

# json='{"jsonrpc": "2.0","method": "user.logout","params": [],"id": 4,"auth": "'$zbxauth'"}'
# echo $json | jq
{
  "jsonrpc": "2.0",
  "method": "user.logout",
  "params": [],
  "id": 4,
  "auth": "e2519a6fbc680a87affc8382b65cdcb7"
}

curlにてAPIを実行し、resulttrueであれば、認証トークンの削除が正常に実施されている。

# curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq
{
  "jsonrpc": "2.0",
  "result": true,
  "id": 4
}

スクリプト化してみる

クローズ処理を定期的に実行できるようclose_zabbix_problem.shという名前のスクリプトを作成し、cronに登録して定期的に古い障害イベントをクローズできるようにしてみた。

7日前の未分類、情報、警告ステータスのイベントをクローズする処理となり、クローズする対象を限定するため、イベントID取得時に、time_tillseveritiesといったパラメータを指定している。

#!/bin/bash

# クローズ対象の日付
clode_date=$(date +%s --date '7 day ago')
close_message="Closed problem by API"

# Zabbix APIログイン
header='Content-Type:application/json-rpc'
apiurl='http://192.168.11.24/zabbix/api_jsonrpc.php'
json='{"jsonrpc": "2.0","method": "user.login","params": {"user": "Admin","password": "XXXXXXXX"},"id": 1,"auth": null}'
zbxauth=$(curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq -r ".result")

# イベント一覧(未解決の障害、指定日以前のもの、未分類・情報・警告イベント)を取得
json='{"jsonrpc": "2.0","method": "problem.get","params": {"output": "eventid","time_till": '${clode_date}',"severities": [0,1,2]},"auth": "'${zbxauth}'","id": 1}'
eventids=$(curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq -r '.result[].eventid ' | tr '\n' ' ')

# Zabbix APIでイベントにメッセージ追加しクローズ
for eventid in ${eventids}; do
  json='{"jsonrpc": "2.0","method": "event.acknowledge","params": {"eventids": '${eventid}',"action": 5,"message": "'${close_message}'"},"auth": "'${zbxauth}'","id": 1}'
  curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq
done

# Zabbix APIログオフ
json='{"jsonrpc": "2.0","method": "user.logout","params": [],"id": 4,"auth": "'$zbxauth'"}'
curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq

exit 0

参考

更新履歴

  • 2021/2/17 新規作成
  • 2024/1/20 クローズ処理のスクリプト化の記載を追記

人気の投稿