RHEL7.6(CentOS7.6) の socket への通信を hosts.allow(deny) で制御する際の注意点

# 概要
RHEL7.Xからサービスの起動管理はsystemdで行われるようになり、これまでxinetdで実装されていたアクセス契機でデーモンを起動して処理を受け付けるような方式は、socket(Unitの一種)で実装されるようになった(RHEL7.Xでもxinetdを利用することは可能であるが、後方互換的な位置づけと思われる)。xinetdは設定が煩雑だったところもあり、新しくRHEL7.6で構築したサーバではsocketを利用してみた。ところが、hosts.allowのアクセス制御を実施するのにあたり問題が２点あり、それが分かるまでに結構嵌ったためその話を書きたいと思う。

# 結論
socketで受け付けるアクセスに関し、hosts.allow(hosts.deny)で制御するならば、下記２点を対応すべし。

・ socketに対応するserviceファイルの`ExecStart=`の内容の頭に、`@-/usr/sbin/tcpd`を付与し、元々頭に`-`があった際は除去する


```/etc/systemd/system/tftp@.service
...
[Service]
(変更前)ExecStart=/usr/sbin/in.tftpd -vvv -c -s /tftpboot
↓
(変更後)ExecStart=@-/usr/sbin/tcpd /usr/sbin/in.tftpd -vvv -c -s /tftpboot
...
```


・ socketファイルの`[socket]`項の`ListenStream or ListenDatagram`のポート番号の前に、`0.0.0.0:`を追記。また、`[socket]`項に、`FreeBind=true`の１行を追加(TCP通信の場合は不要かも)。

```/etc/systemd/system/tftp.socket
...
(変更前)
[Socket]
ListenDatagram=69
↓
(変更後)
[Socket]
ListenDatagram=0.0.0.0:69
FreeBind=true
...
```


# 問題１.TCP Wrapper がサポートされていない

この問題に関しては、以前に書いたRHEL7.Xの変更点全般の記事にも記載したのだが、socketを使う際の注意点として再掲する。[このQiitaの記事][1]を見てもらうのが早いのだが、[RedHat公式ページ][2]によると、"systemdはTCPWrapperをサポートしないので、TCPWrapperを使うならばデーモンをtcpdから起動してね"といったことが記載されている。具体的な対処方法としては下記の通り、socketと連携するservice設定ファイルのExecStart項の頭に、`@-/usr/sbin/tcpd`を付与する。

```/etc/systemd/system/telnet@.service
...

[Service]
(変更前)ExecStart=-/usr/sbin/in.telnetd
↓
(変更後)ExecStart=@-/usr/sbin/tcpd /usr/sbin/in.telnetd
...
```

なお、頭の`@`と`-`に関してだが、ネットを調べても出ないのでマニュアルを見たところ、下記の通り記載があった。

```systemd.service(5)
...(略)...
For each of the specified commands, the first argument must be an
absolute path to an executable. Optionally, if this file name is
prefixed with "@", the second token will be passed as "argv[0]" to
the executed process, followed by the further arguments specified.
If the absolute filename is prefixed with "-", an exit code of the
command normally considered a failure (i.e. non-zero exit status or
abnormal exit due to signal) is ignored and considered success. If
both "-" and "@" are used, they can appear in either order.
...(略)...
```

私の拙い英語力でなんとか読解して下記のように解釈したが、間違っていたら誰か教えて頂きたい...。

・ `@`　２つ目の引数(今回の例の場合、"/usr/sbin/in.telnetd")を１つ目のコマンドライン引数として渡す(=ps コマンドで見えるプロセス名を tcpd→telnetdにする)

・ `-`　コマンド実行結果の戻り値が0でなくとも無視して起動成功とみなす

２点目は付けていてよいのかちょっと気になるが、検証では特に支障なく動いていたし従っておこうかな。


# 問題2.デフォルト設定だと、IPv6アクセスとして受け付けられる

RHEL7.Xのsocketを利用してアクセスを受け付けた場合、デフォルトではなぜかIPv6アクセスとして受け付けられてしまう。これがどう影響するかというと、hosts.allowにIPv4表記で許可セグメントを記載してもIPv6アドレスからのアクセスとして扱われてしまうため、アクセス拒否されてしまう。例えば下記のように`/etc/hosts.allow`、`/etc/hosts.deny`に記載して、`10.0.2.2`から`10.0.2.15`へアクセスしても拒否される。

```/etc/hosts.allow
...(略)...
in.tftpd : 10.0.2.0/255.255.255.0
...(略)...
```

```/etc/hosts.deny
ALL:ALL
```

`/var/log/messages`を見てみると、下記のようなメッセージ出力があった。

```
Jan  9 11:48:42 localhost in.tftpd[17840]: connection refused from ::ffff:10.0.2.15
```

最初は気付けなかったのだが、アドレスの頭の`::ffff:`がIPv6アドレスとして認識されていることを示している。対処としては、下記のようにsocket設定ファイルのポート指定の前に`0.0.0.0:`を付与すると、IPv4アドレスとして処理してくれるようだ。`FreeBind=true`を付与すると、アクセスを受けるサーバ自身が持つNICのアドレス以外でもアクセス受付出来るようになるそうなので、念のため設定しておく。

```/etc/systemd/system/tftp.socket
...
(変更前)
[Socket]
ListenDatagram=69
↓
(変更後)
[Socket]
ListenDatagram=0.0.0.0:69
FreeBind=true
...
```

設定変更を実施したところ、アクセスが通ることが`/var/log/messages`にて確認出来た。
※tftpのデータ送信ポートは動的に決まるため、virtual box のNATで外から通信を通す方法が分からず、やむなくログで `refused` が出力されていないことをもって確認とした。

```
Jan  9 13:16:56 localhost in.tftpd[19045]: RRQ from ::ffff:10.0.2.2 filename test
Jan  9 13:16:56 localhost in.tftpd[19045]: Client ::ffff:10.0.2.2 finished test
```

# おわりに

socket(systemd)とhosts.allowの組み合わせは、デフォルトでサポートされていないだけあって割と嵌った。RHEL7.Xの流儀に合わせるならば、アクセス制御はfirewalldでやるべきなのかもしれない。hosts.allowでやりたいという方がいたならば、本記事が参考になれば幸いである。



[1]:https://qiita.com/Qrg/items/7177c0656e0cd9a7ea0a
[2]:https://access.redhat.com/ja/articles/2623721