以前 は BPF を使ってコンテナの中のイベントを取得する cxray を作った話を書いた。
cxray で現在取得できるイベントは次の4つ。
- 起動したプロセス
- 開かれたファイル
- TCPv4 での接続先
- TCPv4 での network listener
cxray のユースケースとして、生成されたイベントをホワイトリストとして定義し、別のモニタリングツールのルールなどに利用することを想定している。
ここではシンプルな Rails 環境を動かし、falco のルールを作るところまでやってみる。
環境
次のような docker-compose.yml を用意し、rails が起動するところまでのイベントを取得する。
version: "3"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- "DATABASE_HOST=db"
- "DATABASE_PORT=5432"
- "DATABASE_USER=postgres"
- "DATABASE_PASSWORD=password"
links:
- db
volumes:
- "./:/app"
stdin_open: true
db:
image: postgres:10.1
ports:
- "5432:5432"
environment:
- "POSTGRES_USER=postgres"
- "POSTGRES_PASSWORD=password"
cxray を起動し、 docker-compose up
する。
$ sudo cxray > /tmp/log.json
$ docker-compose up
falco のルールを作る
ログが取れたら falco のルールを作っていく。ログは JSON で吐き出されているので jq
でフィルタしていく。このフィルタ作業もツールとして用意すれば良さそう。
起動したプロセス
$ cat tmp/log.json | jq '.data.event | if .name == "execve" then .data.comm else empty end' | sort -r | uniq
"/usr/local/bin/irb"
"/usr/local/bin/docker-entrypoint.sh"
"/usr/local/bundle/bin/rails"
"/usr/bin/id"
"/bin/mkdir"
"/bin/chown"
"/bin/chmod"
proc.name
にこれらを書いておけば良さそう。
開かれたファイル
$ cat tmp/log.json | jq '.data.event | if .name == "open" then .data.fname else empty end' | sort | uniq
"/proc/self/fd/6"
"/etc/ld.so.cache"
"/lib/x86_64-linux-gnu/libc.so.6"
"/usr/local/lib/tls/x86_64/libruby.so.2.5"
"/usr/local/lib/tls/libruby.so.2.5"
"/usr/local/lib/x86_64/libruby.so.2.5"
...
"/app/app/models/concerns"
"/app/app/models/concerns/.keep"
"/app/app/models/concerns/enc/trans/single_byte.rb"
"/app/app/models/concerns/enc/trans/single_byte.so"
"/app/app/models/enc/trans/single_byte.rb"
"/app/app/models/enc/trans/single_byte.so"
...
"tmp/restart.txt"
めちゃくちゃ多い… しかし /usr/local/
とか /app
はファイルを個別で指定せずディレクトリで指定することにして良さそう。除外する。
$ cat tmp/log.json | jq '.data.event | if .name == "open" then .data.fname else empty end' | sort -r | uniq | grep -v "/usr/local/" | grep -v "/app"
"tmp/restart.txt"
"rakelib"
"postmaster.pid"
"pg_xact/0000"
"pg_xact"
"pg_wal/000000010000000000000001"
"pg_wal"
"pg_subtrans/0000"
"pg_subtrans"
"pg_stat_tmp/global.tmp"
...
"/lib/x86_64-linux-gnu/inotify"
"/lib/tls/x86_64/inotify"
"/lib/tls/inotify"
"/lib/libinotify.so"
"/lib/inotify"
"/etc/resolv.conf"
"/etc/passwd"
"/etc/nsswitch.conf"
"/etc/localtime"
"/etc/ld.so.cache"
"/etc/hosts"
"/etc/host.conf"
"/dev/urandom"
"/dev/null"
こんな感じでだいぶ絞れてくる。
[fd.directory](http://fd.directory)
には /usr/local/
や /app
などを指定。 /app
はアプリケーションディレクトリなので、本当は厳密に、ディレクトリは /app/tmp
のみなどを追加して、それ以外はちゃんとファイル単位でホワイトリストにしてあげることで、アプリケーション領域に書き込みがないかを確認できた方が良い。あとは fd.name
に追加かな。
Network Listener
$ cat tmp/log.json | jq '.data.event | if .name == "inet_listen" then .data.listen_port else empty end' | sort -r | uniq
"3000"
"5432"
今回は rails と postgresql だったので、この2つ。
Network Connection
$ cat tmp/log.json | jq '.data.event | if .name == "tcp_v4_connect" then .data.daddr else empty end' | sort -r | uniq
"172.18.0.2"
$ cat tmp/log.json | jq '.data.event | if .name == "tcp_v4_connect" then .data.dport else empty end' | sort -r | uniq
"5432"
Rails → PostgreSQL の一つだけだな。webhook みたいにアプリケーションから任意のエンドポイントに接続を行うようなアプリケーションは、このルールは難しそうだなー。
で、どんなルールになるの
次のようになる。
- macro: rails
condition: container and container.image contains "rails"
- macro: postgresql
condition: container and container.image contains "postgresql"
- list: allowed_network_listeners_ports
items: [3000, 5432]
- list: allowed_processes
items: ["/usr/local/bin/irb", "/usr/local/bin/docker-entrypoint.sh", "/usr/local/bundle/bin/rails", "/usr/bin/id", "/bin/mkdir", "/bin/chown", "/bin/chmod"
- list: allowed_file_readwrite
items: ["tmp/restart.txt", ... "/etc/resolv.conf"]
- list: allowed_network_destination_hosts
items: ["172.18.0.2"]
- list: allowed_network_destination_ports
items: [5432]
- rule: unexpected network listeners
condition: rails and postgresql and not fd.sport in (allowed_network_listeners_ports)
- rule: unexpected process
condition: rails and not proc.name in (allowed_processes)
こんな感じで cxray によって検知されたイベントを絞り込み、 list や macro に書き上げれば、 ... and not ... in (list)
でルールを書けるようになった。
課題
Possibly lost 5 samples
というエラーが出て、いくつかイベントが拾えないケースがある- これ、精度上げるにはどうすればいいんかな…
jq
で加工/フィルタするの面倒なので falco 向けの generator みたいなの作る