2done.org

Linuxメインなメモ書き。二度寝してから書く。(ご意見は Twitter あたりで。そのうちコメント欄つけます。)

rsyslog + SE-PostgreSQL で改竄されない?ログサーバ案

2011/11/08

はじめに

構想中のネタです。いつか(仕組み的に)正しく動作できれば誰かが幸せになると思っています。

syslog, rsyslog, syslog-ng などで、折角、1ヵ所にログを集めても、不正ログイン(特に同一ID/PASSの環境)や未知の脆弱性などによってログを消される危険があります。

こうした心配をしないで良いようログを改竄されないログ収集サーバが欲しいと思っていました。この野望が PostgreSQL 9.1.0 (SE-PostgrrSQL)リリースにより実現可能になりそうだったので試してみました。

環境

1台のクライアントログをデータベースサーバで受けます。データベースサーバのログについては今回は忘れてください。

データベースサーバ

クライアント

その他

本来は ipsec-tools を使って Labeled IPSec を構築することが望ましい

前提条件

データベースサーバにおいて、ここ(2done.org)ここ(はてな) を 参考にSE-PostgreSQL の構築が完了し、SELECT sepgsql_getcon(); の結果が得られていること。

sample=# SELECT sepgsql_getcon();
sepgsql_getcon
-------------------------------------------------------
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023<
(1 row)

データベースサーバ設定

rsyslog用データベースを作成する

rsyslog で使用するデータベースとテーブルを作成します。データベースは以下を使用します。

テーブル作成用SQL はパッケージ rsyslog-pgsql に含まれます。少し加工した上でテーブルを作成します。

データベース名 Syslog
ユーザ名 rsyslog
パスワード 4RcA2Wr7

# yum -y install rsyslog-pgsql

# cp /usr/share/doc/rsyslog-pgsql-5.8.5/createDB.sql /tmp/
# vim /tmp/createDB.sql
# diff -urN /usr/share/doc/rsyslog-pgsql-5.8.5/createDB.sql /tmp/createDB.sql --- /usr/share/doc/rsyslog-pgsql-5.8.5/createDB.sql     2011-08-11 20:14:29.000000000 +0900 +++ /tmp/createDB.sql   2011-11-07 15:05:35.615577477 +0900 @@ -1,5 +1,3 @@ -CREATE DATABASE 'Syslog' WITH ENCODING 'SQL_ASCII'; -\c Syslog;  CREATE TABLE SystemEvents  (          ID serial not null primary key,
# su - postgres
$ createuser -P rsyslog
Enter password for new role:
Enter it again:
Shall the new role be a superuser? (y/n) n Shall the new role be allowed to create databases? (y/n) n Shall the new role be allowed to create more new roles? (y/n) n
$ createdb -O rsyslog Syslog
$ psql -U rsyslog Syslog < /tmp/createDB.sql NOTICE:  CREATE TABLE will create implicit sequence "systemevents_id_seq" for serial column "systemevents.id" NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "systemevents_pkey" for table "systemevents" CREATE TABLE NOTICE:  CREATE TABLE will create implicit sequence "systemeventsproperties_id_seq" for serial column "systemeventsproperties.id" NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "systemeventsproperties_pkey" for table "systemeventsproperties" CREATE TABLE

TCP/IP接続を許可する

# vim /usr/local/pgsql/data/postgresql.conf
-#listen_addresses = 'localhost'         # what IP address(es) to listen on;
+listen_addresses = '*'                 # what IP address(es) to listen on;

pg_hba.conf を設定する

接続許可IPとユーザの設定を行います。今回はクライアント(192.168.11.8)から rsyslog ユーザで接続に来た場合のみ接続を許可します。

# vim /local/pgsql/data/pg_hba.conf
+host    all             rsyslog         192.168.11.8/32         md5

設定が終わったら PostgreSQL を再起動します。

# run_init /etc/init.d/postgresql restart

ラベルを設定する

SELECT, INSERT することだけ許可するラベルを付与します。こうすることで許可のないクライアントからの UPDATE や DELETE が禁止されます。ラベルを設定する前に既定のラベルを確認します。

$ psql Syslog

Syslog=# \d
                      List of relations
 Schema |             Name              |   Type   |  Owner
--------+-------------------------------+----------+---------
 public | systemevents                  | table    | rsyslog
 public | systemevents_id_seq           | sequence | rsyslog
 public | systemeventsproperties        | table    | rsyslog
 public | systemeventsproperties_id_seq | sequence | rsyslog
(4 rows)

Syslog=# SELECT objname,provider,label FROM pg_seclabels WHERE objname = 'systemevents';
   objname    | provider |                  label
--------------+----------+------------------------------------------
 systemevents | selinux  | unconfined_u:object_r:sepgsql_table_t:s0
(1 row)

Syslog=# SELECT objname,provider,label FROM pg_seclabels WHERE objname = 'systemeventsproperties';
        objname         | provider |                  label
------------------------+----------+------------------------------------------
 systemeventsproperties | selinux  | unconfined_u:object_r:sepgsql_table_t:s0
(1 row)

2011/11/07 現在テーブルに付与できるラベルは以下の5種類です。

今回は、SELECT と INSERT を許可するため、sepgsql_fixed_table_t を付与します。

Syslog=# SECURITY LABEL ON TABLE systemevents IS 'system_u:object_r:sepgsql_fixed_table_t:s0';
Syslog=# SECURITY LABEL ON TABLE systemeventsproperties IS 'system_u:object_r:sepgsql_fixed_table_t:s0';

Syslog=# SELECT objname,provider,label FROM pg_seclabels WHERE objname = 'systemevents';
   objname    | provider |                   label
--------------+----------+--------------------------------------------
 systemevents | selinux  | system_u:object_r:sepgsql_fixed_table_t:s0
(1 row)

Syslog=# SELECT objname,provider,label FROM pg_seclabels WHERE objname = 'systemeventsproperties';
        objname         | provider |                   label
------------------------+----------+--------------------------------------------
 systemeventsproperties | selinux  | system_u:object_r:sepgsql_fixed_table_t:s0
(1 row)

クライアント設定

モジュールをインストールする

rsyslog から出力されたログを PostgreSQL に格納するためモジュールをインストールします。

# yum -y install rsyslog-pgsql

rsyslog の設定

出力先 PostgreSQL Server の設定を行います。フォーマットは以下の通りです。

$ModLoad ompgsql
*.*    :ompgsql:PostgreSQLサーバのアドレス,データベース名,ユーザー名,パスワード

# cp -p /etc/rsyslog.conf /etc/rsyslog.conf.org

# vim /etc/rsyslog.conf

# diff -urN /etc/rsyslog.conf.org /etc/rsyslog.conf
--- /etc/rsyslog.conf.org       2011-09-06 23:00:25.000000000 +0900
+++ /etc/rsyslog.conf   2011-11-08 00:21:43.184886437 +0900
@@ -14,8 +14,11 @@
 #$UDPServerRun 514

 # Provides TCP syslog reception
-#$ModLoad imtcp
-#$InputTCPServerRun 514
+$ModLoad imtcp
+$InputTCPServerRun 514
+
+$ModLoad ompgsql
+*.*    :ompgsql:192.168.11.7,Syslog,rsyslog,4RcA2Wr7


 #### GLOBAL DIRECTIVES ####

これで設定は完了です。rsyslog を再起動すれば PostgreSQL にログを出力するようになり・・・ません。

もちろん、単に PostgreSQL を使うだけならこれで終了です。しかし、今回は SE-PostgreSQL を使用しているため、もう一工夫必要です。

ローカルからデータベースに接続してみる

データベースサーバのローカルから rsyslog のサーバに接続してみましょう。正常に接続ができるはずです。

$ psql -U rsyslog -W Syslog

今度は ホストに 127.0.0.1 を指定して rsyslog のサーバに接続してみましょう。今度はエラーが出るはずです。

$ psql -h 127.0.0.1 -U rsyslog -W Syslog
psql: FATAL:  SELinux: unable to get peer label: Protocol not available

これは SE-PostgreSQL 特有の問題です。何も設定しない状態では、getpeercon_raw で接続元ラベルを取得する際にリモートラベルを取得できず、Fail します。

リモートのラベルを取得するためには、Labeled IPSec を利用する必要があります。

※ ローカルのラベルは何も設定しなくても取得できます。

参考: postgresql-9.1.0/contrib/sepgsql/hooks.c

static void
sepgsql_client_auth(Port *port, int status)
{
        char       *context;

        if (next_client_auth_hook)
                (*next_client_auth_hook) (port, status);

        /*
         * In the case when authentication failed, the supplied socket shall be
         * closed soon, so we don't need to do anything here.
         */
        if (status != STATUS_OK)
                return;

        /*
         * Getting security label of the peer process using API of libselinux.
         */
        if (getpeercon_raw(port->sock, &context) < 0)
                ereport(FATAL,
                                (errcode(ERRCODE_INTERNAL_ERROR),
                                 errmsg("SELinux: unable to get peer label: %m")));

        sepgsql_set_client_label(context);

        /*
         * Switch the current performing mode from INTERNAL to either DEFAULT or
         * PERMISSIVE.
         */
        if (sepgsql_permissive)
                sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE);
        else
                sepgsql_set_mode(SEPGSQL_MODE_DEFAULT);
}

Labeled IPSec を設定する

今回は面倒なのでやめます。手抜きをします。

Failback Context を設定する

何かしらの理由でネットワーク越しのラベルが取得できない場合にデフォルトのラベルを割り当てる男前な仕組みを利用します。

# yum -y install netlabel_tools

クライアントから到達した通信に対し、常に staff_t を設定するように設定を行います。(ここが悩み所解決したらアップデートします。)

# netlabelctl unlbl add default address:192.168.11.7/32 label:staff_u:staff_r:staff_t:s0
# netlabelctl unlbl list
accept:on interface:DEFAULT,address:192.168.11.7/32,label:"staff_u:staff_r:staff_t:s0"

これでクライアントから接続された場合、staff_t が付与されるようになります。

Failback Context を確認する

ローカルから接続する

自身のラベルを確認します。

$ id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

次に、データベースに接続し、ラベルを確認します。

$ psql Syslog
Syslog# SELECT sepgsql_getcon();
                    sepgsql_getcon
-------------------------------------------------------
 unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
(1 row)

ユーザと同一のラベルとなっていることが確認できました。

クライアント側から接続する

自身のラベルを確認します。

$ id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

次に、データベースに接続し、ラベルを確認します。

$ psql -h 192.168.11.7 -U rsyslog -W Syslog
Syslog=> SELECT sepgsql_getcon();
       sepgsql_getcon
----------------------------
 staff_u:staff_r:staff_t:s0
(1 row)

無事、デフォルトのラベルが設定されました。次はいよいよ rsyslog を再起動します。

rsyslog を再起動する

クライアント側で rsyslog を再起動します。

# /etc/init.d/rsyslog restart

出力確認

クライアント側でログを発生させ、その結果が正しく出力されることを確認します。

# logger -p kern.err "#### TEST ####"
# psql -h 192.168.11.7 -U rsyslog -W Syslog
Syslog=> SELECT receivedat,devicereportedtime,facility,priority,fromhost,message FROM systemevents WHERE message LIKE '%###%';
     receivedat      | devicereportedtime  | facility | priority | fromhost |     message
---------------------+---------------------+----------+----------+----------+-----------------
 2011-11-08 02:04:42 | 2011-11-08 02:04:42 |        1 |        3 | c1       |  #### TEST ####
(1 row)

なんだかよさそうな感じです。続いて、不正にデータベースに接続されたことを想定し、UPDATE と DELETE を行います。

UPDATE を実行する

クライアント名: c1 を hogehoge に変更します。

Syslog=> UPDATE systemevents SET fromhost = 'hogehoge' WHERE fromhost = 'c1';
ERROR:  SELinux: security policy violation

エラーがでました。UPDATE は失敗です。

DELETE を実行する

全てのログを DELETE します。

Syslog=> DELETE FROM systemevents;
ERROR:  SELinux: security policy violation

エラーがでました。同じく DELETE も失敗です。

ログを確認する

PostgreSQL のログに拒否ログが出力されます。SELinux に慣れている人には見覚えのあるあのログです。

LOG:  SELinux: denied { update } scontext=staff_u:staff_r:staff_t:s0 tcontext=system_u:object_r:sepgsql_fixed_table_t:s0 tclass=db_table name="table systemevents"
STATEMENT:  UPDATE systemevents SET fromhost = 'hogehoge' WHERE fromhost = 'c1';
ERROR:  SELinux: security policy violation
STATEMENT:  UPDATE systemevents SET fromhost = 'hogehoge' WHERE fromhost = 'c1';
LOG:  SELinux: allowed { delete } scontext=staff_u:staff_r:staff_t:s0 tcontext=system_u:object_r:sepgsql_fixed_table_t:s0 tclass=db_table name="table systemevents"
STATEMENT:  DELETE FROM systemevents;
ERROR:  SELinux: security policy violation
STATEMENT:  DELETE FROM systemevents;

雑感

SE-PostgreSQL を利用することで改竄されないログサーバが作れるような気がしてきました。

もう少し捻ったり、付け加えたりすると良いモノができるのではないでしょうか。

ああ、尻切れトンボ・・・