bashのhistoryをsyslog出力、jenkinsでビルド

こんにちは。小宮です。 セキュリティ関連のお仕事で「実行コマンドを記録したい」という要望が最近多くなってきました。 何種類か方法はあると思いますが、今回はbash_historyに時刻を入れて一つのログにまとめてみたいと思います。

これやったあと便利に感じたのはメンテナンスの時作業時刻を報告する場合にログをgrepで解決可能というところです。

jenkinsでビルドするのは、最近はやりの継続的インテグレーションということでやってみました。 最初は心理的な障壁があったんですがやってみると結構楽で手順のもれがないので良いと思いました。 脆弱性の対応で何回かビルドすることになりましたがjenkinsジョブになってるのは便利でした。

CentOS6.5です。 jenkinsさんのジョブは以下のとおりです。 単にパラメータつきのシェルの実行です。

#!/bin/bash
topdir="${HOME}/rpmbuild"
rpmdir="${topdir}/RPMS/x86_64"

if [ -f ${HOME}/.rpmmacros ];then
  echo "%_topdir ${topdir}" > "${HOME}/.rpmmacros"
  echo "%_signature gpg" >> "${HOME}/.rpmmacros"
  echo "%_gpg_name D279xxxx" >> "${HOME}/.rpmmacros"
fi
if [ -d ${HOME}/rpmbuild ];then
  mv ${HOME}/rpmbuild{,.`date +%Y%m%d.%H%M`}
  mkdir -p ${HOME}/rpmbuild/SRPM
fi

case "${TARGET}" in
 *.src.rpm) rpm -Uvh "${TARGET}"
  cp -p ${topdir}/SPECS/bash.spec{,.org}
  sed -i 's/make "CFLAGS=$CFLAGS -fwrapv" "CPPFLAGS=-D_GNU_SOURCE -DRECYCLES_PIDS `getconf LFS_CFLAGS`"/make "CFLAGS=$CFLAGS -fwrapv" "CPPFLAGS=-D_GNU_SOURCE -DRECYCLES_PIDS `getconf LFS_CFLAGS` -DSYSLOG_HISTORY"/g' ${topdir}/SPECS/bash.spec
  sed -i 's/Release: 29%{?dist}/Release: 29%{?dist}_isao_5/g' ${topdir}/SPECS/bash.spec
  sed -i '108s/^$/\n/g' ${topdir}/SPECS/bash.spec
  sed -i '109s/^$/Patch145: bash-syslog_facirity.patch\n/g' ${topdir}/SPECS/bash.spec
  sed -i '181s/^$/%patch145 -p1\n/g' ${topdir}/SPECS/bash.spec
  cd ${HOME}/rpmbuild/SOURCES/
  tar xzf bash-4.1.tar.gz
  cp -rp bash-4.1{,.org}
  sed -i 's/#  define SYSLOG_FACILITY LOG_USER/#  define SYSLOG_FACILITY LOG_LOCAL6/g' ${HOME}/rpmbuild/SOURCES/bash-4.1/config-top.h
  sed -i 's!(SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d UID=%d %s", getpid(), current_user.uid, line)!(SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d PPID=%d SID=%d User=%s UID=%d CMD=%s", getpid(), getppid(), getsid(getpid()), current_user.user_name, current_user.uid, line)!g' ${HOME}/rpmbuild/SOURCES/bash-4.1/bashhist.c
  sed -i 's!(SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d UID=%d %s", getpid(), current_user.uid, trunc)!(SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d PPID=%d SID=%d User=%s UID=%d CMD=%s", getpid(), getppid(), getsid(getpid()), current_user.user_name, current_user.uid, trunc)!g' ${HOME}/rpmbuild/SOURCES/bash-4.1/bashhist.c
  diff -crN bash-4.1.org bash-4.1 > ${HOME}/rpmbuild/SOURCES/bash-syslog_facirity.patch
  rpmbuild -ba ${topdir}/SPECS/bash.spec
 ;;
 *)  echo 'environment variable TARGET must be set.'; exit 1;;
esac

TARGETにしたパラメータは以下です。 http://vault.centos.org/6.5/updates/Source/SPackages/bash-4.1.2-15.el6_5.2.src.rpm

upgradeした時に以下に変わりました。ジョブはバージョン毎に別にしてます。 http://vault.centos.org/6.6/os/Source/SPackages/bash-4.1.2-29.el6.src.rpm

${HOME}/var/lib/jenkinsです。

ソースRPM落として入れて ソースアーカイブ解凍して sedしてパッチ作って 作ったパッチあてられるようにspecファイルsedして ビルドする という流れになっております。 diffでディレクトリまるごとパッチ作るオプションは-crNだったことを調べて知りました。 パッチは敢えて載せると以下のとおりです。

# cat bash-syslog_facirity.patch
diff -crN bash-4.1.org/bashhist.c bash-4.1/bashhist.c
*** bash-4.1.org/bashhist.c     2009-08-15 04:33:02.000000000 +0900
--- bash-4.1/bashhist.c 2014-12-16 19:13:45.272586470 +0900
***************
*** 705,716 ****
    char trunc[SYSLOG_MAXLEN];

    if (strlen(line) < SYSLOG_MAXLEN)
!     syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d UID=%d %s", getpid(), current_user.uid, line);
    else
      {
        strncpy (trunc, line, SYSLOG_MAXLEN);
        trunc[SYSLOG_MAXLEN - 1] = '';
!       syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d UID=%d %s", getpid(), current_user.uid, trunc);
      }
  }
  #endif
--- 705,716 ----
    char trunc[SYSLOG_MAXLEN];

    if (strlen(line) < SYSLOG_MAXLEN)
!     syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d PPID=%d SID=%d User=%s UID=%d CMD=%s", getpid(), getppid(), getsid(getpid()), current_user.user_name, current_user.uid, line);
    else
      {
        strncpy (trunc, line, SYSLOG_MAXLEN);
        trunc[SYSLOG_MAXLEN - 1] = '';
!       syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d PPID=%d SID=%d User=%s UID=%d CMD=%s", getpid(), getppid(), getsid(getpid()), current_user.user_name, current_user.uid, trunc);      }
  }
  #endif
diff -crN bash-4.1.org/config-top.h bash-4.1/config-top.h
*** bash-4.1.org/config-top.h   2009-12-23 05:29:39.000000000 +0900
--- bash-4.1/config-top.h       2014-12-16 19:07:53.607579557 +0900
***************
*** 103,109 ****
     bash_add_history() to be sent to syslog(). */
  /* #define SYSLOG_HISTORY */
  #if defined (SYSLOG_HISTORY)
! #  define SYSLOG_FACILITY LOG_USER
  #  define SYSLOG_LEVEL LOG_INFO
  #endif

--- 103,109 ----
     bash_add_history() to be sent to syslog(). */
  /* #define SYSLOG_HISTORY */
  #if defined (SYSLOG_HISTORY)
! #  define SYSLOG_FACILITY LOG_LOCAL6
  #  define SYSLOG_LEVEL LOG_INFO
  #endif

ログフォーマットはこちらのurlを参考に親プロセスIDとSUしたPID等を載せるように変えました。(監査的な追いやすさがあがるかなと思いまして)

ファシリティがlocal6にかえてあります。rsyslog.confで以下のようにする想定です。

*.info;mail.none;authpriv.none;cron.none;local6.none    /var/log/messages
# bash_history log
local6.*                                                /var/log/bash_history

別途yumのupdate対象からbashをはずす必要はあると思います。(yum-updatesd有効な場合は後からyum.confexclude=bash*するなど) あとからやらないとレシピ回す時にカスタムbashにupdateされなくなったりするので注意が必要です。 Chef(yum)で入れるときに、options "--disablerepo=base,updates"を入れる必要がありました。他リポジトリのパッケージでupdateされない対策です。他にはChefでレシピ流す時のrun_listの順序内でカスタムリポジトリを入れるレシピを先頭にしないとリポジトリが無いと判断されてinstallやupgradeされないという現象が起こりえます。

RPMSignというプラグインつかってgpg署名してカスタムyumリポジトリにscpしたりしてます。他にプラグインはSSHpluginを入れました。

gpg鍵とパスフレーズをシステム設定で設定しといて、 ジョブでは鍵選んでコマンドラインオプションのとこに署名するパッケージのフルパス書きます(ワイルドカード可)。

実際どのようにログが出るかといいますと、以下のようになります。

Aug 21 11:11:54 localhost -bash: HISTORY: PID=18837 PPID=18836 SID=18837 User=komiyay UID=501 CMD=sudo su -
Aug 21 11:44:55 localhost bash: HISTORY: PID=19298 PPID=18864 SID=18837 User=root UID=0 CMD=knife solo data bag edit ssl nginx

scpするのとyumリポジトリのメタデータの差分作るジョブは別につくっていて、 scp別にするのはパッケージのビルドに失敗して微妙に変な名前がついた場合に消す手間を省きたかったんです。

bash_historyは改ざんされるリスクがあるのでリアルタイムに別のサーバに転送するなど対策がまだ必要で 方法としてはrsyslogやsyslog-ngやtd-agentなどがあるかと思いますが、超長くなるので省略します。

jenkinsさんは自動的にいろいろしてくれるし暗黙の手順がなくなるので便利ですね。いろいろ勉強になりました。 jenkinsのことを社内の開発の平形さんに教えていただきました。ありがとうございました。おかげさまで心理的な障壁が減りました。

余談ですが、 bashじゃないやつを使いたいしbashだけ使うような運用でカバーしきれないという話だと、 psacct(RHEL系)かpacct(Debian系)いれてlastcomm--user等のオプションつけて確認のち各ユーザのhistory確認する等になるのかなと思います。たぶん。 ちょっとググったら/etc/profile に script コマンドの自動起動をしこんでいる人がいました。起きたことを把握するのには便利そうですが個人的にはパッケージでできる範囲が嬉しいなと思いました。 デフォルトだとhisotryには時刻が出ないのでbash以外のシェルで時刻を出すようにするにはそれぞれの方法があるようでして、ググったところ以下Linkの通りでした。 tcshのhistoryに日付を入れる - Toolbox zshの履歴でコマンド実行時刻を見れるようにする - Qiita 上記以外のをお使いの方は自己責任で調べていただければと。 ちなみに今回bashは改造してsyslogに出してるので必要ないのですが、そういうのせずに各々のhistoryファイルに時刻出したい場合は3.0以降ならシェル変数「HISTTIMEFORMAT」を設定するとよいようです。あとshとcshはヒストリを編集できないようなので時刻だしたりは無理そうな気がします。 あとhistoryファイルの改ざんを検知可能にしておくという目的を果たすにはauditdなどが必要でしょうか。 auditdはルールちゃんと決めるのとまともな運用を考えるのは手間がかかりそうというかログの可読性がアレなこともあってちゃんと使ったことないです。

参考: the syslog recorded history history - C - C Program Develop [技術ブログvol.2] psacctパッケージでプロセスアカウンティング | 技術情報ブログ | マネージドホスティングのディーネット Linux/UNIX 上でコマンドの実行履歴を残す方法 - drk7jp historyコマンドの結果に日時も表示できるようにする - takami_hirokiの日記

では読んでいただいてありがとうございました。