Raspberry Pi (Linux)

DKIMおよびDMARC導入

これまで長らくSPFのみでメールサーバーを運用してきたが、最近になってYahooメールなど一部のメールサービスで到達性に不安を感じる場面が増えてきた。
そこで、送信ドメイン認証を強化するため、DKIMおよびDMARCを追加導入することにした。

これにより、SPF・DKIM・DMARCの主要なメール認証方式が揃い、なりすまし対策やメールの信頼性向上が期待できる。

以下、その導入手順を備忘録としてまとめておく。

  1. 必要なパッケージのインストール

まず、OpenDKIM本体と鍵生成ツールをインストールします。

sudo apt update
sudo apt install opendkim opendkim-tools

  1. OpenDKIMの設定

/etc/opendkim.conf の編集
設定ファイルを開き、以下の項目を変更または追記します。
今回は確実でパーミッション問題の起きない TCPソケット(ポート8891) でSendmailと通信させます。

● ログ出力の設定
Syslog yes
SyslogSuccess yes

● 動作モード(s = 署名、v = 検証)
Mode sv

● 鍵やドメインのマッピング設定
KeyTable /etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
InternalHosts refile:/etc/opendkim/TrustedHosts

● Sendmailと通信するためのTCPソケット設定
Socket inet:8891@localhost

関連ファイルの作成

設定用のディレクトリを作成。

sudo mkdir -p /etc/opendkim/keys

① /etc/opendkim/TrustedHosts の作成

信頼する送信元(自分自身)を指定。

127.0.0.1
localhost
::1
sky.0t0.jp

② /etc/opendkim/KeyTable の作成

使用する鍵のセレクタ名(今回は dkim260605)と、ドメイン、秘密鍵のパスを紐付け。

dkim260605._domainkey.sky.0t0.jp sky.0t0.jp:dkim260605:/etc/opendkim/keys/sky.0t0.jp/dkim260605.private

ハニーポット切り替え合理化

海外からのアクセスは、その大半が攻撃やスキャンであるため、ハニーポットへ振り分けるという方針を基本としている。
これまで、主要サーチエンジン以外の海外アクセスについては、下記のように別ホストへ転送していた。

●これまでの振り分けイメージ

Internet
   |
   v
+----------------------------+
|        Router              |
|     iptables / NAT         |
+----------------------------+
   |                      |
   |                      |
 Normal                 Suspicious
 / Allowed              / Unknown
   |                      |
   v                      v
+----------------+   +----------------+
| 192.168.1.1    |   | 192.168.1.2    |
| Main Server    |   | Honeypot       |
+----------------+   +----------------+

従来は、Web以外のプロトコルも含めて転送可能とするため、ハニーポットを別ホストとして分離していた。
しかし最近では、対象をWebアクセスのみに限定しているため、この構成を見直した。
その結果、別ホストを維持するメリットが薄れたため、Apacheのリッスンポートを追加し、単一ホスト上のバーチャルホスト構成でハニーポットを実現する方式へ変更した。

●新しい振り分けイメージ

Internet
   |
   v
+----------------------------+
|        Router              |
|     iptables / NAT         |
+----------------------------+
   |                      |
   |                      |
 Normal                 Suspicious
 / Allowed              / Unknown
   |                      |
   v                      v
+----------------+   +----------------+
| 192.168.1.1:80 |   |192.168.1.1:9080|
| 192.168.1.1:443|   |192.168.1.1:9443|
| Main Server    |   | Honeypot       |
+----------------+   +----------------+

この構成により、物理・仮想ホストの分離を廃しつつ、ポート単位で役割を分離するシンプルな設計となる。
また、ログ管理や証明書運用も一元化できるため、運用負荷の低減にも寄与する。

正規サイトとハニーポット切り替え詳細

4月2日より、「深淵 迎賓館 仕様変更」、「シュレディンガーのサーバー
の内容にてサーバー公開を行っているが、その後 iptables の改良を重ね、構成がほぼFIXしたため、その内容を覚書として記す。

●本構成の振り分けイメージ

Internet
   |
   v
+----------------------------+
|        Router              |
|     iptables / NAT         |
+----------------------------+
   |                      |
   |                      |
 Normal                 Suspicious
 / Allowed              / Unknown
   |                      |
   v                      v
+----------------+   +----------------+
| 192.168.1.1    |   | 192.168.1.2    |
| Main Server    |   | Honeypot       |
+----------------+   +----------------+

上記の振り分けを、PREROUTINGチェインに集約した。
現在はPREROUTINGを確認するだけで、流入トラフィックの性質がほぼ把握できる状態となっている。

Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1       44  2492 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:smtp match-set firehol_level3 src to:192.168.1.2:25
2        0     0 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:smtp match-set firehol_level2 src to:192.168.1.2:25
3        5   228 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:smtp match-set firehol_level1 src to:192.168.1.2:25
4        2   120 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:smtp match-set gcloud src to:192.168.1.2:25
5        0     0 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:smtp match-set other_block src to:192.168.1.2:25
6      126  6626 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:http match-set firehol_level3 src to:192.168.1.2:80
7       50  2592 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:http match-set firehol_level2 src to:192.168.1.2:80
8       12   580 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:http match-set firehol_level1 src to:192.168.1.2:80
9        2   100 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:http match-set gcloud src to:192.168.1.2:80
10       0     0 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:http match-set other_block src to:192.168.1.2:80
11     255 13436 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:https match-set firehol_level3 src to:192.168.1.2:443
12      15   784 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:https match-set firehol_level2 src to:192.168.1.2:443
13      17   920 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:https match-set firehol_level1 src to:192.168.1.2:443
14       4   200 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:https match-set gcloud src to:192.168.1.2:443
15       0     0 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:https match-set other_block src to:192.168.1.2:443
16      13   760 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:ssh match-set jpnet src to:192.168.1.1:22
17       1    60 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:smtp match-set jpnet src to:192.168.1.1:25
18       0     0 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:smtp match-set searchengines src to:192.168.1.1:25
19       1    40 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:http match-set jpnet src to:192.168.1.1:80
20       3   180 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:http match-set searchengines src to:192.168.1.1:80
21      16   988 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:https match-set jpnet src to:192.168.1.1:443
22     176  9872 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:https match-set searchengines src to:192.168.1.1:443
23       0     0 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:submission match-set jpnet src to:192.168.1.1:587
24       1    60 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:imaps match-set jpnet src to:192.168.1.1:993
25       7   356 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:smtp to:192.168.1.2:25
26     150  7602 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:http to:192.168.1.2:80
27     305 18288 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:https to:192.168.1.2:443
28       8   480 DNAT       tcp  --  ppp0   any     anywhere             anywhere             tcp dpt:2222 match-set jpnet src to:192.168.1.2:22

・firehol_level1~3 : FireHOLで公開されているブラックリストIPからのアクセスを Honeypot(192.168.1.2)へ振り分け
・jpnet : 日本国内からのアクセスを Main Server(192.168.1.1)へ振り分け
・searchengines : 主要サーチエンジンからのアクセスを Main Server(192.168.1.1)へ振り分け
・gcloud : スパムアクセスの多い Google Cloud からのアクセスを Honeypot(192.168.1.2)へ振り分け
・other_block : その他、不審と判断したアクセス(主に国内)を Honeypot(192.168.1.2)へ振り分け

Hugoへ移行しての所感

当サイトのブログをHugoへ移行して10日ほど経過し、テーマも最終の形態に落ち着いてきた。
サイトの使い勝手や見た目もblogn時代と比べ無駄を排除した上でかなり向上した。
特にphpの処理が無いため、記事数の多いweblogでも瞬時に表示される。
以前、weblogを記事数2935で公開していた時は、表示されるまでに2秒近くかかっていた。

もう一点、運用面で大きいのは精神的な負担が減ったことだ。
phpやDB、CMSの脆弱性を気にする必要がなく、壊れないものを淡々と置いておく感覚に近い。
個人ブログとしては、これが一番のメリットかもしれない。

また、Raspberry Pi上での運用という点でも、Hugoの相性は非常に良い。
ビルドは別環境で行い、サーバー側は単なる静的ファイル置き場として振る舞うだけなので、CPU負荷やメモリ使用量をほとんど気にしなくて済む。

Hugoへ移行して気になる点といえば、やはり投稿時の手間と、サーバー側ディスクの負担増である。
記事一点を追加する度にブログ全体をビルドして全ファイル差し替えとなるため、例えば本ブログ(retiredlog)の場合、下記ディスク占有容量である5.3Mバイト以上の書き換えが必要となる。
これは、記事数が増えればそれに伴い増加していく。

retiredlog
記事数 88
pagefind 1.3Mバイト
ディスク占有容量 5.3Mバイト

もっとも、ディスク容量については記事数が数千規模になっても数百MB程度で収まる見込みであり、現実的には大きな問題にはならないだろう。
むしろ速度と単純さを優先した結果として、納得して受け入れている。

なお、写真やファイルについては、転送容量とディスク書き込み負担軽減のため、毎回全ファイルを転送する訳ではなく、サーバー側で別途ファイル専用ディレクトリーを用意して、下記(files)のようにシンボリックリンクを張っている。

/mnt/data/sky/retiredlog
合計 352
drwxr-xr-x 11 pi pi 4096 1月 29 06:51 .
drwxr-xr-x 14 pi pi 4096 1月 26 20:03 ..
-rw-r–r– 1 pi pi 8863 1月 31 15:23 404.html
-rw-r–r– 1 pi pi 111 1月 18 07:19 apple-touch-icon.png
drwxr-xr-x 2 pi pi 4096 1月 20 20:22 archives
drwxr-xr-x 8 pi pi 4096 1月 18 07:49 categories
drwxr-xr-x 2 pi pi 4096 1月 24 07:35 css
-rw-r–r– 1 pi pi 16958 10月 11 15:24 favicon.ico
lrwxrwxrwx 1 pi pi 41 1月 26 13:18 files -> /mnt/data/sky/blog_data/retiredlog/files/
drwxr-xr-x 2 pi pi 4096 1月 18 07:49 img
-rw-r–r– 1 pi pi 44360 1月 31 15:23 index.html
-rw-r–r– 1 pi pi 213825 1月 31 15:23 index.xml
drwxr-xr-x 2 pi pi 4096 1月 18 07:49 js
drwxr-xr-x 20 pi pi 4096 1月 30 13:38 page
drwxr-xr-x 4 pi pi 4096 1月 31 14:38 pagefind
drwxr-xr-x 89 pi pi 4096 1月 30 14:59 posts
-rw-r–r– 1 pi pi 12890 1月 31 15:23 sitemap.xml
drwxr-xr-x 3 pi pi 4096 1月 24 07:35 tags

Hugoテーマ Mainroad 改善

今日は朝から冷え込んで、路面にはうっすらと雪が積もっている。
こんな日は外に出ず、暖かい部屋でゆっくり過ごすに限る。

暇な日曜日の時間つぶしとして、このブログのHugoテーマ「Mainroad」の改善に着手した。
デザインは気に入っているが、一点だけ不便を感じていた「個別記事ページでの前後ナビゲーション」を追加することにする。

以下、自分用の備忘録を兼ねたカスタマイズ手順だ。

  1. 個別記事に「過去記事・未来記事」ボタンを追加

Mainroadの layouts/_default/single.html を編集し、記事のヘッダー部分にナビゲーションを配置した。
blogn時代のように、記事の冒頭で前後の流れを確認できるようにしている。

<nav class="post-nav flex">
    {{ with .PrevInSection }}
    <div class="post-nav__item post-nav__item--prev">
        <a class="post-nav__link" href="{{ .RelPermalink }}" rel="prev">
            <span class="post-nav__caption">≪&nbsp;過去の記事</span>
            <p class="post-nav__post-title">{{ .Title }}</p>
        </a>
    </div>
    {{ end }}
    {{ with .NextInSection }}
    <div class="post-nav__item post-nav__item--next">
        <a class="post-nav__link" href="{{ .RelPermalink }}" rel="next">
            <span class="post-nav__caption">新しい記事&nbsp;≫</span>
            <p class="post-nav__post-title">{{ .Title }}</p>
        </a>
    </div>
    {{ end }}
</nav>
  1. メタ情報の整理(日付とカテゴリー)

境界線が二重にならないよう、テーマ標準の post_meta.html を活かしつつ、hugo.toml で表示を制御。
これで日付とカテゴリーがスッキリ一行に収まった。

[Params]
post_meta = [“date”, “categories”]