tcpdump でNSDのクエリログを取る

【環境】
CentOS 7.5
nsd-4.1.20-1.el7.x86_64


NSDDNSサーバ)だと、標準の機能ではクエリログを取得できない。
nsd-4.1.20 の時点では)

権威サーバでクエリログが必要なニーズが無いのかもしれないが、どれだけのクエリがあるのか調べたかったので、ログを取る方法を考えた。

で、思い付いたのが tcpdump でログを取ること。

#!/bin/bash

NIC="ens160"
DST_IP="192.168.1.20"
DST_PORT="53"
LOG_FILE="/var/log/nsd-query/query.log"
ROTATE_SIZE=10485760
REMOVE_RANGE=129600

# check log rotate
if [ ${ROTATE_SIZE} -lt $(ls -l ${LOG_FILE} | awk '{print $5}') ] ; then
  pkill -9 tcpdump
  mv ${LOG_FILE} ${LOG_FILE}"-"$(date "+%Y%m%d-%H%M%S")
fi

# remove old files
find $(dirname ${LOG_FILE}) -type f -name $(basename ${LOG_FILE})"*" -mmin +${REMOVE_RANGE} -delete

# start logger
if [ $(ps aux | grep tcpdump | grep -v 'grep' -c) -lt 1 ] ; then
  /usr/sbin/tcpdump -p -l -nn -tttt -i ${NIC} dst host ${DST_IP} and dst port ${DST_PORT} >> ${LOG_FILE} &
fi

このスクリプトは、以下のような動作を行う。

  • キャプチャするNIC、そのNICのIP、ポート番号を指定
  • クエリログの出力先を指定
  • クエリログをローテーションするファイルサイズ(バイト)の閾値を指定
  • 古いクエリログファイルを削除するための閾値(秒)を指定
  • クエリログのファイルのサイズが設定値を超えた場合、tcpdump プロセスをkill してログファイルをリネームしてmv
  • 指定秒より古いクエリログファイルがあれば削除
  • ps コマンドでtcpdump プロセスが見つからない場合だけ(重複起動をチェック)、ログ取得のためのtcpdump を実行

これを cron などで定期時刻すれば、重複実行を避けつつクエリログを取得し、ログファイルのローテーションを行う。
テキストファイルなので、cat(圧縮したものはzcat)で開き、適当な条件で grep する。


ログファイルのローテーションと言えば、logrotate が手軽だが、tcpdump をローテーションしようとすると、anacron がゾンビのように残ってしまい、うまくローテーションされなかった。

tcpdump にも、出力ファイルをローテーションするオプション(-G)があるが、tcpdump -w で生データを書き出す場合、-r オプションでしか読めず、gzip圧縮して出力場合は直接読み込めない。

圧縮して出力すると、いったん非圧縮ファイルへ書きだす必要があり、圧縮するのを止めようか迷ったが、容量圧迫のプレッシャーに勝てず断念。

また、tcpdump のローテーションでは、古いファイルを削除する仕組みが無い。

これらをうまく解決できず、シェルスクリプト側で無理やりローテーションするようにした。

NSD で複数のスレーブにゾーン転送する場合の記述例

【環境】
CentOS 7.5
nsd-4.1.20-1.el7.x86_64


マスタとして動作しているNSDにて、複数のスレーブ(NSDでもBINDでも)へゾーン転送したい場合の記述例。

ドキュメントに説明が書いてあるが、具体的な記述例がない。

ゾーン オプション

ゾーン毎に一つのzone:節で指定されるオプションが必要です。複数のサーバー
を加えるためにはアクセス制御リストの要素を複数回与えます。これらの要素は
明示的に追加される必要があります。

スレーブが複数ある場合には、設定をその分だけ列挙する。

例)

zone:
        name: "aaa.bbb.domain"
        zonefile: "aaa.bbb.domain.zone"
        provide-xfr: 192.168.1.6 NOKEY
        provide-xfr: 192.168.1.7 NOKEY
        notify: 192.168.1.6 NOKEY
        notify: 192.168.1.7 NOKEY
        notify-retry: 5
provide-xfr: 192.168.1.6 192.168.1.7 NOKEY
 あるいは
provide-xfr: 192.168.1.6, 192.168.1.7 NOKEY

のような書式ではエラーになる。
1つのIPについて1行記載し、スレーブが複数台の場合は繰り返す。

なお、ゾーン転送のログは、verbosity:1 にしないと確認できない。
(デフォルトは、verbosity:0 になっていて、ログに出ない)

参考;
nsd.conf(5) – 日本Unboundユーザー会

Postfix でメールの宛先をもとにフィルタする方法

テスト環境にて、誤ってメールを送信してしまう場合に備え、予め設定した宛先以外にはPostfixがメールを送信しないようにする方法。
但し、テスト環境におけるメールの送信は、他ホストのSMTPを利用せず、自身で動作しているPostfixから送信する前提。

【環境】
CentOS 6/postfix-2.6.6-8.el6.x86_64
CentOS 7/postfix-2.10.1-6.el7.x86_64


/etc/postfix/main.cf にて、ヘッダーのチェックを有効化する。

#header_checks = regexp:/etc/postfix/header_checks
 ↓
header_checks = regexp:/etc/postfix/header_checks


/etc/postfix/header_checks を開いて、正規表現でルールを記載する。

例)全て拒否

/^To: .*/ REJECT


$ mail root のようなローカル配送も失敗する。
 →エラーログ:reject: header To: root from local;
但し、その後、失敗した旨を通知するメールはroot宛等に送信される。


例)OK にマッチしたアドレスなら送信、マッチしない場合は全て拒否

/^To: hoge@myhostname.domain/ OK
/^To: .*/ REJECT


例)特定アドレスと特定ドメインへは送信を許可、マッチしなければ全て拒否

/^To: hoge@myhostname.domain/ OK
/^To: .*@test.domain/ OK
/^To: .*/ REJECT


サービス再起動

CentOS 6
# /etc/init.d/postfix restart

CentOS 7
# systemctl restart postfix


メールを送信して、許可されていない宛先だった場合には、メールログに送信を拒否した旨が記録される。

reject: header To: 

追記(2018/2/20)

/^To: hoge/ OK

のように指定した OK は、DUNNO の別名。
これは後方互換性のため残されている設定値。
他の設定値は、man や以下の日本語ドキュメントを参照。

参考;
Postfix manual - header_checks(5)


注意しなければいけないのが、DUNNO/OK の動作。
DUNNO/OK は、「このテーブルに検索キーが見つからなかったように見せ、次の入力行を検査する」との事。

つまり、このアクションが行われた後、そこで以降のルールをチェックしない訳ではない。

ハマりやすいのは、以下のような定義をした場合。

/@test.domain/ OK
/./ REJECT

To か From かヘッダを記載していないので、たとえ To が OK にマッチしても、From など他のヘッダに対しても検査が行われるので、それが最後のREJECTに引っかかりREJECTされてしまう。

SQLite で正規表現を使う

【環境】
CentOS 7


CentOS 7 の SQLite(標準パッケージ)単体では、正規表現を使えない。

以下からモジュールのソースを入手して、.so ライブラリをロードすれば使えるようになる。
https://github.com/ralight/sqlite3-pcre

ビルド

ビルドには、sqlite-devel パッケージが必要。

$ make
cc -shared -o pcre.so   -fPIC -W -Werror pcre.c -lpcre   -Wl,-z,defs

使い方

ライブラリのロード

sqlite> .load /path-to/pcre.so

※毎回設定するのが面倒な場合は、~/.sqliterc にロードの記述をする。

正規表現を使ったSQLの書式

sqlite> ... WHERE x REGEXP <regex>

試す

テストデータを用意。

sqlite> select * FROM sample;
os
--------------------
Mac OS X 10.9
Mac OS X 10.10
Mac OS X 10.11
Mac OS X 10.12
Mac OS X 10.13
Windows NT 6.3
Windows NT 10.0

ライブラリをロードする。

sqlite> .load ./pcre.so

正規表現でSELECTする。

sqlite> select * FROM sample WHERE os REGEXP '[0-9]\.[0-9]$';
os
--------------------
Mac OS X 10.9
Windows NT 6.3
Windows NT 10.0

PostgreSQL で文字列と数値があるデータを(ちょっと良く)ソートする

【環境】
CentOS 6/PostgreSQL 8.4
CentOS 7/PostgreSQL 9.2


例えば、文字列型のカラムにて、文字列と数値が混じったデータの場合にソートすると、次のようになる。

db=> SELECT * FROM sample_data ORDER BY os;
       os
-----------------
 Mac OS X 10.12
 Mac OS X 10.13
 Mac OS X 10.9
 Windows NT 10.0
 Windows NT 6.3


いい感じにソートするには、少し工夫する。

db=> SELECT * FROM sample_data 
ORDER BY
  SUBSTRING(os FROM '^[A-Za-z ]+'), 
  TO_NUMBER(SUBSTRING(os FROM '[0-9.]+$'), '9999999999');
       os
-----------------
 Mac OS X 10.9
 Mac OS X 10.12
 Mac OS X 10.13
 Windows NT 6.3
 Windows NT 10.0


考え方としては、以下の通り。

・SUBSTRING を使い、ソートするカラムの先頭から英字を抜き出して、ソートの第一条件とする。
・SUBSTRING を使い、ソートするカラムの後ろから数値とドット(小数点)を抜き出して、to_numberで適当な桁数(上記では10桁)のnumeric型にキャストして、ソートの第二条件とする。


ソートの条件をSELECTで抽出してみると、どうなっているか分かりやすい。

db=> SELECT 
  os, 
  SUBSTRING(os FROM '^[A-Za-z ]+'), 
  TO_NUMBER(SUBSTRING(os FROM '[0-9.]+$'), '9999999999') 
FROM 
  sample_data;
        os        |  substring  | to_number
------------------+-------------+-----------
 Windows NT 10.0  | Windows NT  |       100
 Windows NT 6.3   | Windows NT  |        63
 Mac OS X 10.12   | Mac OS X    |      1012
 Mac OS X 10.9    | Mac OS X    |       109
 Mac OS X 10.13   | Mac OS X    |      1013


ここに、'Mac OS X 10.12.1' というデータが入ってくると、うまくいかない(´・ω・`)

db=> SELECT * FROM sample_data
ORDER BY
  SUBSTRING(os FROM '^[A-Za-z ]+'),
  TO_NUMBER(SUBSTRING(os FROM '[0-9.]+$'), '9999999999');
        os
------------------
 Mac OS X 10.9
 Mac OS X 10.12
 Mac OS X 10.13
 Mac OS X 10.12.1
 Windows NT 6.3
 Windows NT 10.0


バージョンの区切りになっているドットの出現回数が違うのが原因。
この場合、出現回ごとに桁数を揃えて(ドットで区切って0詰めする)、ソート条件に追加すると意図したようにソートされる。

db=> SELECT * FROM sample_data
ORDER BY
  SUBSTRING(os FROM '^[A-Za-z ]+'),
  LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 1), 3, '0'),
  LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 2), 3, '0'),
  LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 3), 3, '0');
        os
------------------
 Mac OS X 10.9
 Mac OS X 10.12
 Mac OS X 10.12.1
 Mac OS X 10.13
 Windows NT 6.3
 Windows NT 10.0


試しに、'Mac OS X 11.0' や 'Mac OS X 9.99' という架空のデータを入れて試してみる。

db=> SELECT * FROM sample_data
ORDER BY
  substring(os from '^[A-Za-z ]+'),
  LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 1), 3, '0'),
  LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 2), 3, '0'),
  LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 3), 3, '0');
        os
------------------
 Mac OS X 9.99
 Mac OS X 10.9
 Mac OS X 10.12
 Mac OS X 10.12.1
 Mac OS X 10.13
 Mac OS X 11.0
 Windows NT 6.3
 Windows NT 10.0


いい感じでソートされる。

ソート条件がどうなっているか、SELECTで抽出してみると、桁毎に0詰めになっている事が確認できる。

db=> SELECT 
    os,
    LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 1), 3, '0'),
    LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 2), 3, '0'),
    LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 3), 3, '0')
 FROM sample_data;
        os        | lpad | lpad | lpad
------------------+------+------+------
 Windows NT 10.0  | 010  | 000  | 000
 Windows NT 6.3   | 006  | 003  | 000
 Mac OS X 10.12   | 010  | 012  | 000
 Mac OS X 10.9    | 010  | 009  | 000
 Mac OS X 10.13   | 010  | 013  | 000
 Mac OS X 10.12.1 | 010  | 012  | 001
 Mac OS X 11.0    | 011  | 000  | 000
 Mac OS X 9.99    | 009  | 099  | 000


但し、前半に文字列があり、後半に数字(ドットも可)がある、という前提で、バージョン部分が「x.y.z」というフォーマットしか判別できない。

使える場面が限られるけど、とりあえずメモ。
UserAgentから取り出したOSのソートには使えそう。


参考;
https://www.postgresql.jp/document/9.2/html/functions-string.html
https://www.postgresql.jp/document/9.2/html/functions-formatting.html

rsyslog に *** Slice of root のログが出力される

【環境】

# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)

# rpm -qa | grep rsyslog
rsyslog-8.24.0-12.el7.x86_64

# rpm -qa | grep systemd
systemd-libs-219-42.el7_4.4.x86_64
systemd-sysv-219-42.el7_4.4.x86_64
systemd-219-42.el7_4.4.x86_64


以下のようなログが定期的に出力される。

# cat /var/log/messages
・・・省略
Jan 10 22:30:01 hoge systemd: Created slice User Slice of root.
Jan 10 22:30:01 hoge systemd: Starting User Slice of root.
Jan 10 22:30:01 hoge systemd: Started Session 236 of user root.
Jan 10 22:30:01 hoge systemd: Starting Session 236 of user root.
Jan 10 22:30:01 hoge systemd: Removed slice User Slice of root.
Jan 10 22:30:01 hoge systemd: Stopping User Slice of root.


journald にも出力されているが、rsyslog 側で無視して、ファイルへ書き込まないようにする。
手順は、以前と同じ。
shobon.hatenablog.com


以下のようなファイルを作成する。

# cat /etc/rsyslog.d/ignore-systemd-session-slice.conf
if $programname == "systemd" and ($msg contains "Starting Session" or $msg contains "Started Session" or $msg contains "Created slice" or $msg contains "Starting user-" or $msg contains "Starting User Slice of" or $msg contains "Removed session" or $msg contains "Removed slice User Slice of" or $msg contains "Stopping User Slice of") then stop

rsyslogを再起動して反映し、/var/log/messages に出力されなくなった事を確認する。

# systemctl restart rsyslog


参考;
https://access.redhat.com/solutions/1564823

Packet send failed to や Failed to send packet trying to query name がログに出力される

【環境】

# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)

# rpm -qa | grep samba
samba-client-libs-4.6.2-12.el7_4.x86_64
samba-common-tools-4.6.2-12.el7_4.x86_64
samba-common-4.6.2-12.el7_4.noarch
samba-libs-4.6.2-12.el7_4.x86_64
samba-4.6.2-12.el7_4.x86_64
samba-common-libs-4.6.2-12.el7_4.x86_64

/var/log/messages に次のようなログが頻繁に出力される。

Jan 10 21:07:40 hoge nmbd[1106]: [2018/01/10 21:07:40.837878,  0] ../source3/libsmb/nmblib.c:873(send_udp)
Jan 10 21:07:40 hoge nmbd[1106]:  Packet send failed to 192.168.10.255(138) ERRNO=許可されていない操作です
Jan 10 21:09:20 hoge nmbd[1106]: [2018/01/10 21:09:20.367062,  0] ../source3/libsmb/nmblib.c:873(send_udp)
Jan 10 21:09:20 hoge nmbd[1106]:  Packet send failed to 192.168.10.255(137) ERRNO=許可されていない操作です
Jan 10 21:09:20 hoge nmbd[1106]: [2018/01/10 21:09:20.367202,  0] ../source3/nmbd/nmbd_packets.c:179(send_netbios_packet)
Jan 10 21:09:20 hoge nmbd[1106]:  send_netbios_packet: send_packet() to IP 192.168.10.255 port 137 failed
Jan 10 21:09:20 hoge nmbd[1106]: [2018/01/10 21:09:20.367233,  0] ../source3/nmbd/nmbd_namequery.c:245(query_name)
Jan 10 21:09:20 hoge nmbd[1106]:  query_name: Failed to send packet trying to query name TEST<1d>

ホストのiptables等で、パケットの送信が遮断されているためと思われる。
iptablesで許可すると、journald/rsyslog いずれにも、上記のログは出なくなる)

iptables で制限を解除すれば良いけれど、何らかの事情でそれが出来ない場合の対処法。

CentOS 7の場合、ログはいったん journald が受け取り、rsyslog が受け取っている構造になっている。
ここで、journald → rsyslog へ渡される際に、rsyslog 側で無視するようにすれば、journald にログは残しつつ、rsyslog からファイルへの出力を抑制できる。
(但し、CentOS 7の journald の初期設定では、/var/run/log/journal 以下に書き込まれるので、OSを再起動すると消える)

rsyslog側で無視するには、以下のようなファイルを作成する。

# cat /etc/rsyslog.d/ignore-nmbd-query.conf
if $programname == "nmbd" and ($msg contains "Packet send failed to" or $msg contains "query_name" or $msg contains "send_netbios_packet" or $msg contains "send_udp") then stop

ここには、rsyslog の条件式である「contains」を利用して、特定のプロセスにおいて特定キーワードを含むログを無視するよう記載している。
rsyslog で他に利用可能な値や条件は、以下の通り。
http://www.rsyslog.com/doc/v8-stable/configuration/properties.html
http://www.rsyslog.com/doc/v8-stable/configuration/filters.html


また、CentOS 7のrsyslog は、/etc/rsyslog.d/*.conf を起動時に読み込むので、同様のものがあれば、別ファイルに作成すると管理しやすい。

読み込み箇所↓
# cat /etc/rsyslog.conf | grep '*.conf'
$IncludeConfig /etc/rsyslog.d/*.conf

rsyslogを再起動して反映し、/var/log/messages に出力されなくなった事を確認する。

# systemctl restart rsyslog

journald に記録されたログを見たい場合は、以下のようにする。

# journalctl -u nmb