UnboundのResponse Policy Zone (RPZ)機能を使い DNS firewall を実現した際のメモ。

概要

DNS firewallはマルウェア配布サイトやフィッシングサイトへの接続を防ぐために特定のドメイン名に対する問い合わせを無効にする機能のこと。

Unboundは設定ファイル unbound.conflocal-zoneslocal-data を使うことでDNS firewall相当の機能を実現できたが、DNS firewall を実現する一般的な方法としてResponse Policy Zone (RPZ) が提案されている(https://tools.ietf.org/html/draft-vixie-dnsop-dns-rpz-00)。このRPZがunboundでもversion 1.10から使用できるようになった(https://nlnetlabs.nl/news/2020/Feb/20/unbound-1.10.0-released/)。

通常のDNSレコードと同様にzoneファイルを作成しドメイン毎のアクション(NXDOMAINを返すなど)を定義する。ブロックする場合は NXDOMAIN を返すように CNAME . を設定する。

Action RR type and RDATA
NXDOMAIN CNAME .
NODATA CNAME *.
PASSTHRU CNAME rpz-passthru.
DROP CNAME rpz-drop.

例えば example.com をブロックする場合は次のようなゾーンファイル /var/unbound/db/rpz.local を用意する。

1
example.com CNAME .

そしてこれを次のような設定ファイルで読み込む。

1
2
3
4
5
6
server:
    module-config: "respip validator iterator"

rpz:
    name: rpz.local
    zonefile: /var/unbound/db/rpz.local

環境

  • Unbound 1.11.0
  • OpenBSD 6.8

RPZの動作確認

ログ出力 rpz-log: yes とRPZアクションの無効化 rpz-action-override: disabled を組合せることでログにRPZログを記録できる。設定 rpz-log-name を追加することで、どのRPZにマッチしたかの情報を残すことができる。

1
2
3
4
5
6
7
8
9
server:
    module-config: "respip validator iterator"

rpz:
    name: rpz.local
    zonefile: /var/unbound/db/rpz.local
    rpz-action-override: disabled
    rpz-log: yes
    rpz-log-name: "rpz.local"

統計値

RPZの各アクション毎の実行回数が unbound-control stats, unbound-control stats_noreset で確認できる。設定で extended-statics を有効にする必要がある。

1
2
> unbound-control stats_noreset | grep -i rpz
num.rpz.action.nxdomain=26662

公開されている RPZ 用の Domain リスト

自分でzonefileをメンテナンスするのは手間がかかる。以下のようなブロック用のドメインリストを提供しているサイトを活用するのが現実的だろう。しかし、どのリストが良いか(使うべきか)不明である。判断基準がない。

ひとまず日本用情報が含まれていそうな280blocker.netとRPZ形式のzone fileを複数提供しているEnergizedProtectionを利用した(ちなみにEnergizedProtectionは280blockerも含んでいるような記述が実際のリストは空となっている https://block.energized.pro/assets/sources/filter/280blocker.txt)。

280blocker.net

提供されているのは単純なドメインのリストなので RPZ 用の zone file に変換する必要がある。例えば次のようなコマンドで変換できる。

1
2
3
ftp -o - "https://280blocker.net/files/280blocker_domain_$(date "+%Y%m").txt" \
    | tr -cd "\0-\177" | tr -d "\r" \
    | awk '/^[a-zA-Z0-9]/{print $1 " CNAME ."}'

コマンド tr で非ASCII文字と改行コードCRを削除している。このようにして作成したzonefileを /var/unbound/db/rpz.280blocker として保存して次のような設定を unbound に加える。

1
2
3
4
5
6
rpz:
    name: "rpz.280blocker"
    zonefile: /var/unbound/db/rpz.280blocker
    rpz-action-override: disabled
    rpz-log: yes
    rpz-log-name: "rpz.280blocker"

上の設定では実際に NXDOMAIN は返さずRPZにマッチしたログのみを記録する。ログを見て問題なさそうであれば rpz-action-override: disabled を削除する。

EnergizedProtection

RPZ形式のファイルが提供されているので設定ファイルの url で指定すれば良い。提供されているリストのうちメモリ使用量が45MB程度となるbluGoのリストを選択。より大きいリストにするとzonefileの読み込みに時間がかかる。

Package unbound RES text size
unified 972M 43M
basic 565M 25M
blu 217M 9.5
bluGo 134M 5.2M
Spark 45M 1.1M
1
2
3
4
5
6
rpz:
    name: "rpz.txt"
    url: https://block.energized.pro/bluGo/formats/rpz.txt
    rpz-action-override: disabled
    rpz-log: yes
    rpz-log-name: "rpz.energized"

さきほどと同様に上の設定では実際に NXDOMAIN は返さずRPZにマッチしたログのみを記録する。ログを見て問題なさそうであれば rpz-action-override: disabled を削除する。

クライアント毎の設定

Unboundのtag機能である define-tagtags を組合せることでクライアント毎に特定のRPZを有効にできる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
server:
    define-tag: "malware social"
    access-control-tag 127.0.0.10/32 "social"
    access-control-tag 127.0.0.20/32 "social malware"
    access-control-tag 127.0.0.30/32 "malware"

rpz:
    name: malware.rpz.local
    zonefile: malware.rpz.local
    tags: "malware"

rpz:
    name: social.rpz.local
    zonefile: social.rpz.local
    tags: "social"

しかし、クライアント側の IPv6 は変化するのでこれは難しい。サブネット毎ぐらいであれば実現可能であるが、現在のネットワークは複数のサブネットを利用するような設計になっていないのでこれも難しい。DNSサーバのアドレスをIPv4のみ配布すれば良いがそれだとIPv6のみの端末が名前解決できなくなってしまう。IPv6のネットワークでクライアントアドレス毎に動作を変えるというは可能かもしれないがIPv4と比較すると有効性は小さいのかもしれない。

参考