在Linux宿主機審計docker程序和網路連線
*本文作者:zhouqiao,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。
一、引言
docker容器已經被廣泛應用到各大公司線上、測試等各種環境,在宿主機如何識別出docker程序、docker網路連線就成為一個困擾的問題,如果容器內部署相同的crond或ssh服務,在宿主機上執行ps命令發現一大堆相同名字程序,根本無法區分屬於宿主機還是具體某個container;不過docker提供docker top container_id命令能看到具體某個container在宿主機程序pid相關資訊,但沒有辦法直接列出全部container程序。同時在網路連線方面,在宿主機執行netstat命令, 看不到container連線狀態。
本文開發的docker_util工具能夠在宿主機上全部審計和展示當前docker容器全部存活程序與網路連線資訊。在上文通過Linux聯結器能夠實時審計宿主機程序,有興趣可以看下:基於Linux聯結器的審計程序事件實現方案 。
二、測試環境
在一臺centos(ip 10.89.93.11)部署redis和tomcat兩個container,其中reids開放預設6379, tomcat 開放預設80,8080,8085,同時宿主機部署nginx服務。
root@mytest:/home/zhouqiaozhouqiao$ docker ps CONTAINER IDIMAGECOMMANDCREATEDSTATUSPORTSNAMES 51ec548386efdocker.io/redis:latest"docker-entrypoint..."3 days agoUp 3 days0.0.0.0:6379->6379/tcprelaxed_bell 23f07e13e192docker.io/tomcat"catalina.sh run"7 weeks agoUp 7 weeks8080/tcp, 0.0.0.0:8089->80/tcphungry_bassi
在另一臺centos(ip 10.89.93.12),使用redis-cli連線redis-server,構造一條真實連線。
root@mytest:/home/zhouqiaozhouqiao$ redis-cli -h 10.89.93.11 10.89.93.11:6379>
三、docker程序審計
docker程序是通過pid namespace技術,在不同pid namespace,程序pid是獨立的,但docker是共享主機核心,在宿主機上都有相應的程序pid對映,所以在宿主機能夠獲取全部docker程序和宿主機程序資訊。
ps命令能夠掃描出全部宿主機程序, 遍歷/proc/目錄,結合程序目錄下exe,cmdline等資訊輸出,但沒有辦法區分出docker程序。
下面分析docker程序與宿主機程序區分方式。
3.1 程序區分方式
對比宿主機程序nginx與docker程序redis各程序cgroup(/proc/pid/cgroup)資訊檔案,都協帶container id資訊。
非docker程序(nginx程序 pid:22982)
root@mytest:/home/zhouqiaozhouqiao$ cat /proc/22982/cgroup 10:freezer:/ 9:perf_event:/ 8:memory:/user.slice 7:cpuset:/ 6:devices:/user.slice 5:net_cls:/ 4:blkio:/user.slice 3:cpuacct,cpu:/user.slice 2:hugetlb:/ 1:name=systemd:/user.slice/user-85001637.slice/session-3822015.scope
docker程序(redis程序 pid:17147)
root@mytest:/home/zhouqiaozhouqiao$ cat /proc/17147/cgroup 10:freezer:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope 9:perf_event:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope 8:memory:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope 7:cpuset:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope 6:devices:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope 5:net_cls:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope 4:blkio:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope 3:cpuacct,cpu:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope 2:hugetlb:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope 1:name=systemd:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope
可以看到兩類程序cgroup資訊檔案有明顯不同,其中docker程序明顯多出51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14,通過docker inspect可以確定這是redis container的id。
3.2 cgroup檔案驗證
對於常用container,有systemd Docker和Non-systemd Docker兩種管理方式,都可以驗證出/proc/pid/cgroup檔案都協帶64位container id資訊。
// systemd Docker
container id為51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14
8:memory:/system.slice/docker-51ec548386efd801fe28b3ce6b1905305841c997a3f7325ae6961a2cc7e88d14.scope
// Non-systemd Docker
container id為de630f22746b9c06c412858f26ca286c6cdfed086d3b302998aa403d9dcedc42
8:memory:/docker/de630f22746b9c06c412858f26ca286c6cdfed086d3b302998aa403d9dcedc42
//k8s
container id為9170559b8aadd07d99978d9460cf8d1c71552f3c64fefc7e9906ab3fb7e18f69
4:memory:/kubepods/burstable/pod5f399c1a-f9fc-11e8-bf65-246e9659ebfc/9170559b8aadd07d99978d9460cf8d1c71552f3c64fefc7e9906ab3fb7e18f69
cgroup檔案資訊可以作為判斷程序是否屬於宿主機程序還是某個container.
3.3 docker程序審計
開發docker_util工具審計docker程序首先是遍歷/proc目錄掃描,識別出程序pid號,再讀取程序/proc/pid/cgroup檔案資訊確定屬於宿主機程序或某個docker container程序。
下面是執行docker_util獲取的結果,輸出pid 17147和40618的程序資訊,與部署的redis,tomcat docker一致。
Pid:17147, Exe:/usr/local/bin/redis-server, Status:LISTEN, Laddr:Ip:0.0.0.0,Port:6379, Raddr:Ip:0.0.0.0,Port:0 Pid:17147, Exe:/usr/local/bin/redis-server, Status:ESTABLISHED, Laddr:Ip:172.17.0.7,Port:6379, Raddr:Ip:10.89.93.12,Port:59358 Pid:40618, Exe:/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, Status:LISTEN, Laddr:Ip:127.0.0.1,Port:8005, Raddr:Ip:0.0.0.0,Port:0 Pid:40618, Exe:/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, Status:LISTEN, Laddr:Ip:0.0.0.0,Port:8009, Raddr:Ip:0.0.0.0,Port:0 Pid:40618, Exe:/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, Status:LISTEN, Laddr:Ip:0.0.0.0,Port:8080, Raddr:Ip:0.0.0.0,Port:0
四、docker網路連線審計
4.1 netstat失效原因
常用的獲取主機網路連線命令netstat為什麼不能掃描出docker網路連線,分析原始碼,netstat的實現原理,主要包括如下三個步驟:
1)遍歷程序fd(/proc/pid/fd)目錄獲取檔案型別為socket的inode號(inode->pid) 2)遍歷/proc/net/tcp和/proc/net/udp 網路狀態檔案,按行解析網路連線資訊(inode->ip port四元組、連線狀態) 3)通過網路連線中的inode資訊與第一步獲取的inode關聯,將程序與網路連線對應起來
該原理與獲取linux程序級網路流量統計實現一致,有興趣可以看下:一種linux程序網路流量統計方法和實現 。
回到正題,那是(/proc/pid/fd,/proc/net/tcp)這兩檔案中是哪個資訊缺失docker程序網路連線呢?
檢視docker redis程序(/proc/17147/fd) fd目錄, 可以看到socket有兩個inode,分別是 1927741637, 1927719349
root@mytest:/home/zhouqiaozhouqiao$ ls -l /proc/17147/fd total 0 lr-x------ 1 systemd-bus-proxy ssh_keys 64 Dec 14 10:05 0 -> pipe:[1927741605] l-wx------ 1 systemd-bus-proxy ssh_keys 64 Dec 14 10:05 1 -> pipe:[1927741606] l-wx------ 1 systemd-bus-proxy ssh_keys 64 Dec 14 10:05 2 -> pipe:[1927741607] lr-x------ 1 systemd-bus-proxy ssh_keys 64 Dec 14 10:05 3 -> pipe:[1927741635] l-wx------ 1 systemd-bus-proxy ssh_keys 64 Dec 14 10:05 4 -> pipe:[1927741635] lrwx------ 1 systemd-bus-proxy ssh_keys 64 Dec 14 10:05 5 -> anon_inode:[eventpoll] lrwx------ 1 systemd-bus-proxy ssh_keys 64 Dec 14 10:05 6 -> socket:[1927741637] lrwx------ 1 systemd-bus-proxy ssh_keys 64 Dec 14 10:05 7 -> socket:[1927719349]
再看網路狀態檔案cat /proc/net/tcp |grep 1927741637 或 cat /proc/net/tcp |grep 1927719349, 都沒有檢索出docker 程序inode號,也就是/proc/net/tcp沒有docker程序連線資訊。
cat /proc/net/tcp |grep 1927741637 cat /proc/net/tcp |grep 1927719349
檢視核心程式碼, 可以分析出/proc/net/tcp只會輸出宿主機net namespace的程序連線, 也就是說網路狀態檔案 /proc/net/tcp與某宿主機程序(/proc/net/pid/tcp)內容是一致的。
再次對比宿主機nginx程序ns資訊,與redis ns資訊,可以發現兩者ns namespace都完全不一樣,與核心程式碼解釋一致。
//宿主機程序ns
root@mytest:/home/zhouqiaozhouqiao$ ls -l /proc/22982/ns total 0 lrwxrwxrwx 1 root root 0 Nov 30 14:25 ipc -> ipc:[4026531839] lrwxrwxrwx 1 root root 0 Nov 30 14:25 mnt -> mnt:[4026531840] lrwxrwxrwx 1 root root 0 Nov 30 14:25 net -> net:[4026531956] lrwxrwxrwx 1 root root 0 Nov 30 14:25 pid -> pid:[4026531836] lrwxrwxrwx 1 root root 0 Nov 30 14:25 user -> user:[4026531837] lrwxrwxrwx 1 root root 0 Nov 30 14:25 uts -> uts:[4026531838]
//docker程序ns
root@mytest:/home/zhouqiaozhouqiao$ ls -l/proc/17147/ns total 0 lrwxrwxrwx 1 systemd-bus-proxy ssh_keys 0 Dec 14 10:05 ipc -> ipc:[4026533327] lrwxrwxrwx 1 systemd-bus-proxy ssh_keys 0 Dec 14 10:05 mnt -> mnt:[4026533325] lrwxrwxrwx 1 systemd-bus-proxy ssh_keys 0 Dec 14 10:05 net -> net:[4026533330] lrwxrwxrwx 1 systemd-bus-proxy ssh_keys 0 Dec 14 10:05 pid -> pid:[4026533328] lrwxrwxrwx 1 systemd-bus-proxy ssh_keys 0 Dec 14 10:05 user -> user:[4026531837] lrwxrwxrwx 1 systemd-bus-proxy ssh_keys 0 Dec 14 10:05 uts -> uts:[4026533326]
netstat 無法採集到docker網路連線根本原因是docker程序與宿主機程序namespace不一致,而網路狀態檔案/proc/net/tcp只有宿主機程序網路連線資訊,沒有docker程序網路連線資訊。
4.2 docker網路連線審計
docker程序的網路連線資訊全部在/proc/pid/net/tcp。
//redis tcp
root@mytest:/home/zhouqiaozhouqiao$ cat /proc/17147/net/tcp sllocal_address rem_addressst tx_queue rx_queue tr tm->when retrnsmtuidtimeout inode 0: 00000000:18EB 00000000:0000 0A 00000000:00000000 00:00000000 000000009990 1927741637 1 ffff882024a73c00 100 0 0 10 0 1: 070011AC:18EB 0C5D590A:E7DE 01 00000000:00000000 02:00000690 000000009990 1927719349 2 ffff882014e41680 20 4 1 22 21
可以看到inode號1927741637, 1927719349,與/proc/17147/fd目錄socket連線inode號一致。
開發docker_util工具審計docker網路連線,在netstat原理上增加docker程序判斷和增加程序網路狀態檔案(/proc/pid/net/tcp等)掃描。
1)遍歷程序fd(/proc/pid/fd)目錄獲取檔案型別為socket的inode號(inode->pid)
2)判斷程序是docker程序,遍歷/proc/pid/net/tcp和/proc/pid/net/udp 網路狀態檔案,按行解析網路連線資訊(inode->ip port四元組、連線狀態)
3)通過網路連線中的inode資訊與第一步獲取的inode關聯,將程序與網路連線對應起來
下面是執行docker_util獲取的結果,輸出pid 17147 redis程序網路連線結果:
Pid:17147, Exe:/usr/local/bin/redis-server, Status:LISTEN, Laddr:Ip:0.0.0.0,Port:6379, Raddr:Ip:0.0.0.0,Port:0 Pid:17147, Exe:/usr/local/bin/redis-server, Status:ESTABLISHED, Laddr:Ip:172.17.0.7,Port:6379, Raddr:Ip:10.89.93.12,Port:59358
可以看出redis有兩條連線,inode號為1927741637號是reids-server LINSTEN連線,1927719349號是10.89.93.12客戶連線redis-server的tcp連線,源埠是59358。
五、結論
通過程序cgroup(/proc/pid/cgroup)檔案資訊和程序網路狀態(/proc/pid/net/tcp或udp)等資訊結合能夠在Linux宿主機上審計docker容器程序和網路連線。
*本文作者:zhouqiao,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。