👌

go-auditとfluentdとElasticsearchでつくるLinux監査システム

10 min read 2

はじめに

Linuxのauditフレームワークはシステム上のセキュリティに関係する各種イベントを収集し、システム上で行われた動作を追跡するものである。それ自体がなにかセキュリティを強化するものではなく、問題を追跡し分析しなければ脅威に対するアクションをとることはできない。

一般的にはauditdデーモンによりLinuxカーネルから監査ログを出力することが多いと思う。ただこの監査ログは決して人間によって読みやすいものではなく、ausearchaureportなど付属するコマンドをつかって監査ログを検索・表示することになる。

下記はvagrantユーザがsudo tail -f /var/log/audit/audit.logした際の監査ログ出力の抜粋。

# sudo ausearch -i

type=PROCTITLE msg=audit(07/12/2021 15:31:55.950:66) : proctitle=sudo tail -f /var/log/audit/audit.log
type=PATH msg=audit(07/12/2021 15:31:55.950:66) : item=1 name=/lib64/ld-linux-x86-64.so.2 inode=265245 dev=fc:03 mode=file,755 ouid=root ogid=root rdev=00:00 nametype=NORMAL cap_fp=none cap_fi=none cap_fe=0 cap_fver=0 cap_frootid=0
type=PATH msg=audit(07/12/2021 15:31:55.950:66) : item=0 name=/usr/bin/sudo inode=272025 dev=fc:03 mode=file,suid,755 ouid=root ogid=root rdev=00:00 nametype=NORMAL cap_fp=none cap_fi=none cap_fe=0 cap_fver=0 cap_frootid=0
type=CWD msg=audit(07/12/2021 15:31:55.950:66) : cwd=/home/vagrant
type=EXECVE msg=audit(07/12/2021 15:31:55.950:66) : argc=4 a0=sudo a1=tail a2=-f a3=/var/log/audit/audit.log
type=SYSCALL msg=audit(07/12/2021 15:31:55.950:66) : arch=x86_64 syscall=execve success=yes exit=0 a0=0x5592e98d4cd0 a1=0x5592e98d28d0 a2=0x5592e98cefd0 a3=0x8 items=2 ppid=2299 pid=9927 auid=vagrant uid=vagrant gid=vagrant euid=root suid=root fsuid=root egid=vagrant sgid=vagrant fsgid=vagrant tty=pts1 ses=42 comm=sudo exe=/usr/bin/sudo key=(null)

さて、こういった監査ログ出力群を精査して以下の質問に簡単に答えられるだろうか?

  1. 過去1週間で外向けTCP接続がいくつ作成されたか
  2. TCP接続を作成したプロセスとユーザの比率はどうなっているか

…はい、とっても難しい。どうせならダッシュボードで可視化しておいてほしい。
やりましょう、go-auditとfluentdとElasticsearchで。

以下はUbuntu20.04で検証した際の手順。他のディストリビューションの方は適宜読み替えるように。

go-auditによる監査ログ収集

go-auditとは

go-auditauditdと同じくLinuxカーネルから様々なsyscall呼び出しなどの監査ログを収集するもの。明らかな違いとしては、go-auditがJSON出力をサポートしていること。fluentd/logstashなどのモダンなログ収集ツールとの連携が容易になる。またコンフィグもYAML形式でありシンプルに管理運用できるところがメリットだろう。

より詳細な説明はSlackのセキュリティアーキテクトである作者による記事を参照すること。

go-auditのインストール

go-auditはGoで作成されたツール。アーキテクチャごとにクロスコンパイルされたバイナリがあるので、これを利用するのが簡単でよい。

$ curl -sf https://gobinaries.com/slackhq/go-audit | sudo PREFIX=/usr/sbin sh

go-auditのコンフィグはYAML形式である。
ディスク容量などキャパシティ管理方針に依るところではあるが、可能な限りgo-auditでは収集ログをフィルタせず収集先(ここではElasticsearch)で絞り込み・精査するのが良いと思う。万が一セキュリティ異常が発生した場合には、何が有用なログなのかその時にならないとわからないものなので。

$ sudo mkdir /etc/go-audit
$ cat << EOL | sudo tee /etc/go-audit/go-audit.yml
message_tracking:
  enabled: true
  log_out_of_order: false
  max_out_of_order: 500

log:
  flags: 0
  
# 収集するイベントタイプの範囲を指定する
# 1100-1199: userspace messages
# 1200-1299: auditd daemon messages
# 1300-1399: syscall event messages
# 参考: https://github.com/torvalds/linux/blob/master/include/uapi/linux/audit.h
# 参考: https://github.com/linux-audit/audit-userspace/blob/master/lib/libaudit.h
events:
  min: 1100
  max: 1399

# ログはファイル出力する
output:
  file:
    enabled: true
    attempts: 2
    path: /var/log/go-audit/go-audit.log
    mode: 0644
    user: root
    group: root

# 収集ルール
# 必要なルール(重要ファイルのopenなど)はここで追加する
# 参考: https://github.com/Neo23x0/auditd/blob/master/audit.rules
rules:
  - -b 1024
  - -a exit,always -S connect -k netconns_out
  - -a exit,always -S listen  -k netconns_in
  - -a exit,always -S execve  -k execve

# go-audit自身がnetlinkソケットに接続したログはフィルタする
filters:
  - syscall: ""
    message_type: 1305
    regex: .*
EOL

では起動する。
もしサービス化するのであればこちらの公式systemdユニットファイルを利用するとよい。

$ sudo go-audit -config /etc/go-audit/go-audit.yml

さてこれで何らかのNetwork IOやプロセス起動をすると逐一/var/log/go-audit.logに出力されるようになっている。
下記はvagrantユーザがsudo tail -f /var/log/go-audit/go-audit.logした際の監査ログ出力の抜粋。

{
  "sequence": 1053,
  "timestamp": "1626105161.783",
  "messages": [
    {
      "type": 1300,
      "data": "arch=c000003e syscall=257 success=yes exit=0 a0=55b5827dfaf0 a1=55b5827df360 a2=55b582819870 a3=8 items=2 ppid=10366 pid=10539 auid=1000 uid=1000 gid=1000 euid=0 suid=0 fsuid=0 egid=1000 sgid=1000 fsgid=1000 tty=pts3 ses=47 comm=\"sudo\" exe=\"/usr/bin/s    },
    {
    {
      "type": 1302,
      "data": "item=0 name=\"/etc/shadow\" inode=6948426 dev=fc:03 mode=0100640 ouid=0 ogid=42 rdev=00:00 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0"
    },
    {
      "type": 1327,
      "data": "proctitle=7375646F007461696C002D66002F7661722F6C6F672F676F2D61756469742E6C6F67"
    }
  ],
  "uid_map": {
    "0": "root",
    "1000": "vagrant"
  }
}

auditdgo-auditもnetlinkソケットに接続して監査ログを取得するが、同時に接続することはできない。そのためauditdが動いているシステムでは事前にこれをsudo systemctl stop auditdで停止しておく必要がある。

fluentdで監査ログを転送する

fluentdのインストール

細かくは説明しないので公式手順を参照すること。

$ curl -L https://toolbelt.treasuredata.com/sh/install-ubuntu-focal-td-agent4.sh | sudo sh

あわせてgo-auditログ形式をElasticsearch等のデータストアで利用しやすい形に再フォーマットするfluentdプラグインfluent-plugin-go-audit-parserを作ったので、こちらもインストールしておく。
go-auditはパフォーマンスを優先しコードは最小限に保たれていて、いくつかのログフィールドはデコードされていない。このfluentdプラグインによって、これらのログはデコードされ容易に検索できるよう整形される。

$ sudo td-agent-gem install fluent-plugin-go-audit-parser

次にgo-auditによって出力された/var/log/go-audit.logを監視し、Elasticsearchに転送するfluentdコンフィグを用意する。

$ cat << EOL | sudo tee /etc/td-agent/td-agent.conf
<source>
  @type tail
  @id go-audit.tail
  path /var/log/go-audit/go-audit.log
  <parse>
    @type json
  </parse>
  tag audit
</source>

<filter audit>
  @type go_audit_parser
  @id go-audit.parse
</filter>

<match audit>
  @type elasticsearch
  @id go-audit.elasticsearch
  hosts 127.0.0.1:9200
  logstash_format true
  logstash_prefix audit
  reload_connections false
  suppress_type_name true
  <buffer>
    @type file
    path /tmp/go-audit.elasticsearch
    flush_interval 60s
    retry_max_times 1
  </buffer>
</match>

fluentd-plugin-go-audit-parserで整形されたgo-auditログは次のようなフォーマットになる。

# {
#   "sequence": 5180,
#   "messages": {
#     "syscall": {
#       "type": 1300,
#       "arch": "c000003e",
#       "syscall": 257,
#       "success": "yes",
#       "exit": "4",
#       "a0": "ffffff9c",
#       "a1": "7ff30a34b1a1",
#       "a2": "80000",
#       "a3": "0",
#       "items": "1",
#       "ppid": "4835",
#       "pid": 4891,
#       "auid": { "id": 1000, "name": "vagrant" },
#       "uid": { "id": 1000, "name": "vagrant" },
#       "gid": 1000,
#       "euid": { "id": 0, "name": "root" },
#       "suid": { "id": 0, "name": "root" },
#       "fsuid": { "id": 0, "name": "root" },
#       "egid": 1000,
#       "sgid": 1000,
#       "fsgid": 1000,
#       "tty": "pts2",
#       "ses": 32,
#       "comm": "sudo",
#       "exe": "/usr/bin/sudo",
#       "key": "etcpasswd"
#     },
#     "path": {
#       "type": 1302,
#       "item": "0",
#       "name": "/etc/shadow",
#       "inode": 6948416,
#       "dev": "fc:03",
#       "mode": "0100640",
#       "ouid": { "id": 0, "name": "root" },
#       "ogid": 42,
#       "rdev": "00:00",
#       "nametype": "NORMAL",
#       "cap_fp": "0",
#       "cap_fi": "0",
#       "cap_fe": "0",
#       "cap_fver": "0",
#       "cap_frootid": "0"
#     },
#     "proctitle": {
#       "type": 1327,
#       "proctitle": "sudo tail -f /var/log/go-audit/go-audit.log"
#     }
#   },
#   "message_types": [ "syscall", "path", "proctitle" ]
# }

2021-07-15 04:43:01.795056298 +0000 audit: {"sequence":5180,"messages":{"syscall":{"type":1300,"arch":"c000003e","syscall":257,"success":"yes","exit":"4","a0":"ffffff9c","a1":"7ff30a34b1a1","a2":"80000","a3":"0","items":"1","ppid":"4835","pid":4891,"auid":{"id":1000,"name":"vagrant"},"uid":{"id":1000,"name":"vagrant"},"gid":1000,"euid":{"id":0,"name":"root"},"suid":{"id":0,"name":"root"},"fsuid":{"id":0,"name":"root"},"egid":1000,"sgid":1000,"fsgid":1000,"tty":"pts2","ses":32,"comm":"sudo","exe":"/usr/bin/sudo","key":"etcpasswd"},"path":{"type":1302,"item":"0","name":"/etc/shadow","inode":6948416,"dev":"fc:03","mode":"0100640","ouid":{"id":0,"name":"root"},"ogid":42,"rdev":"00:00","nametype":"NORMAL","cap_fp":"0","cap_fi":"0","cap_fe":"0","cap_fver":"0","cap_frootid":"0"},"proctitle":{"type":1327,"proctitle":"sudo tail -f /var/log/go-audit/go-audit.log"}},"message_types":["syscall","path","proctitle"]}  

ではfluentdサービスを起動する。

$ sudo systemctl start td-agent

これで監査ログがElasticsearchへ転送されはじめた。よかったね。
ただし、まだ転送先のElasticsearchを立ち上げていないので、エラーが発生してしまう。急いで立ち上げよう。

Elasticsearchで監査ログを収集する

ElasticsearchとKibanaのインストール

$ wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
$ sudo apt-get install apt-transport-https
$ echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-7.x.list
$ sudo apt-get update && sudo apt-get install elasticsearch kibana

$ sudo systemctl start elasticsearch
$ sudo systemctl start kibana

はい、これでElasticsearchにaudit-*というインデックスが作成された。
あとは良しなに検索ができる。

Kibanaで確認してみる

こんな感じ。
discover

軽くダッシュボードも作ってみるとこんな感じ。
dashboard

これで冒頭に設定した問いにいつでも答えられるね。
楽しい🎉

Discussion

go-auditを整形するfluentdプラグインを作ってrubygemsに登録したので、記事も修正した。

ログインするとコメントできます