以前 は BPF を使ってコンテナの中のイベントを取得する cxray を作った話を書いた。
cxray で現在取得できるイベントは次の4つ。

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) でルールを書けるようになった。

課題