PG16:Allow file inclusion in pg_hba and pg_ident files

# はじめに

にゃーん。趣味でポスグレをやっている者だ。

この記事は[PostgreSQL 16 全部ぬこ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/pg16-all-nuko) 3日目の記事です。
今回はPostgreSQLへの認証を制御するファイルに追加された機能について書いてみます。

# 概要

|項目|内容|
|:--|:--|
|タイトル|[Allow file inclusion in pg_hba and pg_ident files](https://commitfest.postgresql.org/40/3558/)|
|Topic|Server Features|
|ステータス|commited|
|Last modified|2022-11-28|
|概要|pg_hba.conf, pg_ident.confに include句が追加された|

# 変更内容
PostgreSQLの設定ファイル(postgresql.conf)には、以前から include 指定がありました。
include 指定により一部の設定を別ファイルに分離するという管理が可能になっていましたが、他のPostgreSQLの設定ファイル(pg_hba.conf, pg_ident.conf)では、include 指定は未対応でした。
以下、ホストベース認証ファイル(``pg_hba.conf``)を例にして、include指定の挙動を確認します。

## include指定のパターン
PostgreSQL 16で追加された include のパターンは以下の3つです。

|include指定|意味|
|:--|:--|
|include|指定されたファイルの内容を取り込む。<br>存在しない場合にはエラーが``pg_hba_file_rules``に報告される。|
|include_if_exists|指定されたファイルの内容を取り込む。<br>存在しない場合にはスキップする。|
|include_dir|インクルード対象のファイルが格納されたディレクトリ上のファイルを読み込む。|

# pg_hba.confでの指定例
例として、``pg_hba.conf``に各inlcude指定を行ったパターンを見てみます。

## 検証準備
以下のロールとデータベースをてきとーに作成します。

```bash
$ createuser user_a
$ createuser user_b
$ createdb db_a
$ createdb db_b
$
```

## include

``pg_hba.conf``を編集します。

```bash
$ cat /data/pgdata/16/pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD
# "local" is for Unix domain socket connections only
include /tmp/user_hba.conf
local   all             postgres                                trust
$
```

``include``に指定したファイルの内容はこんな感じ。

```bash
$ cat /tmp/user_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   db_a            user_a                                  trust
local   db_b            user_b                                  trust
$
```

PostgreSQLサーバをリロードして、``pg_hba_file_rules``ビューを確認します。

```bash
$ pg_ctl -D /data/pgdata/16 reload
server signaled
$ psql postgres -c "TABLE pg_hba_file_rules"
 rule_number |          file_name          | line_number | type  | database | user_name  | address | netmask | auth_method | options | error
-------------+-----------------------------+-------------+-------+----------+------------+---------+---------+-------------+---------+-------
           1 | /tmp/user_hba.conf          |           2 | local | {db_a}   | {user_a}   |         |         | trust       |         |
           2 | /tmp/user_hba.conf          |           3 | local | {db_b}   | {user_b}   |         |         | trust       |         |
           3 | /data/pgdata/16/pg_hba.conf |           4 | local | {all}    | {postgres} |         |         | trust       |         |
(3 rows)
```

きちんとincludeしたホストベース認証の設定が反映されてますね。

一応、認証の確認をします。

```bash
$ psql -U user_a db_a
psql (16devel)
Type "help" for help.

=> \q
$ psql -U user_a db_b
psql: error: connection to server on socket "/tmp/.s.PGSQL.16001" failed: FATAL:  no pg_hba.conf entry for host "[local]", user "user_a", database "db_b", no encryption
$
```
問題なくホストベース認証が機能してますね。

### 指定したファイルが存在しないとき

``include``で指定したファイルが存在しない場合にも、reload時にエラーにはなりません。
エラーにはならないの``pg_hba_file_rules``ビューにも記録されません。

```bash
$ pg_ctl -D /data/pgdata/16 reload
server signaled
$ psql postgres -c "SELECT file_name, line_number, error FROM pg_hba_file_rules"
          file_name          | line_number |                              error
-----------------------------+-------------+-----------------------------------------------------------------
 /data/pgdata/16/pg_hba.conf |           3 | could not open file "/tmp/hoge.conf": No such file or directory
 /data/pgdata/16/pg_hba.conf |           4 |
(2 rows)

$
```

## include_if_exists

``pg_hba.conf``を書き換えます。

```bash
$ cat /data/pgdata/16/pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD
# "local" is for Unix domain socket connections only
include_if_exists /tmp/user_hba.conf
local   all             postgres                                trust
$
```
リロードして、``pg_hba_file_rules``を確認します。

```bash
$ pg_ctl -D /data/pgdata/16 reload
server signaled
$ psql postgres -c "TABLE pg_hba_file_rules"
 rule_number |          file_name          | line_number | type  | database | user_name  | address | netmask | auth_method |
options | error
-------------+-----------------------------+-------------+-------+----------+------------+---------+---------+-------------+-
--------+-------
           1 | /tmp/user_hba.conf          |           2 | local | {db_a}   | {user_a}   |         |         | trust       |
        |
           2 | /tmp/user_hba.conf          |           3 | local | {db_b}   | {user_b}   |         |         | trust       |
        |
           3 | /data/pgdata/16/pg_hba.conf |           4 | local | {all}    | {postgres} |         |         | trust       |
        |
(3 rows)

$
```
存在するときの挙動は``include``指定と同じです。

### 指定したファイルが存在しないとき

``include＿exits_if``で指定したファイルが存在しない場合にも、reload時にエラーにはなりません。
また、この場合にはエラーは``pg_hba_file_rules``ビューにも記録されません。

```bash
$ pg_ctl -D /data/pgdata/16 reload
server signaled
$ psql postgres -c "SELECT file_name, line_number, error FROM pg_hba_file_rules"
          file_name          | line_number | error
-----------------------------+-------------+-------
 /data/pgdata/16/pg_hba.conf |           4 |
(1 row)

$
```

## include_dir
最後に、ホストベース認証ファイルを格納しているディレクトリを指定``include_dir``で指定する方法を紹介します。

``/tmp/user_hba_conf_dir``というディレクトリに``01_user_hba_b.conf``,`` 02_user_hba_a.conf``という2つのファイルを用意します。

```bash
$ pwd
/tmp/user_hba_conf_dir
$ ls
01_user_hba_b.conf  02_user_hba_a.conf
$ cat 01_user_hba_b.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   db_b            user_b                                  trust
$ cat 02_user_hba_a.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   db_a            user_a                                  trust
$
```

``pg_hba.conf``に``include_dir``指定を書きます。

```bash
$ cat /data/pgdata/16/pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD
# "local" is for Unix domain socket connections only
include_dir /tmp/user_hba_conf_dir
local   all             postgres                                trust
$
```
PostgreSQLサーバをリロードして、``pg_hba_file_rules``ビューを確認します。

```bash
$ pg_ctl -D /data/pgdata/16 reload
server signaled
$ psql postgres -c "TABLE pg_hba_file_rules"
 rule_number |                 file_name                 | line_number | type  | database | user_name  | address | netmask |
auth_method | options | error
-------------+-------------------------------------------+-------------+-------+----------+------------+---------+---------+-
------------+---------+-------
           1 | /tmp/user_hba_conf_dir/01_user_hba_b.conf |           2 | local | {db_b}   | {user_b}   |         |         |
trust       |         |
           2 | /tmp/user_hba_conf_dir/02_user_hba_a.conf |           2 | local | {db_a}   | {user_a}   |         |         |
trust       |         |
           3 | /data/pgdata/16/pg_hba.conf               |           4 | local | {all}    | {postgres} |         |         |
trust       |         |
(3 rows)

$
```

``include_dir``で指定したディレクトリ内に複数のファイルが存在する場合、ファイル名順に設定が反映されます（なので、``01_user_b.conf``から反映されます。この規則を利用して、反映される順序をファイル名の付与により制御できるようですね。

# おわりに
今回は、``pg_hba.conf``, ``pg_ident.conf``に追加されたinclude機能について書いてみました。
データベースユーザ数が多い場合の管理方法がちょっと便利になるかもしれませんね。