ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

HaconiwaでDockerのsshdコンテナを利用してみる

こんにちは。これは技術記事です。みなさんは、技術記事ですか?

まず、 Haconiwa の近況報告なんですが、この度 mruby で書き直し、ひとまず MRI 版と同じ程度の機能までは実装できました。

github.com

これに伴い、Linux用のバイナリを配布する形にインストール方法を変更しました。また、今後MRI版の開発(haconiwa-mri とプロジェクトをリネーム済みです)は積極的には行わない予定です。何か機能追加したい際は mruby 版の方へPRを!

(一応、MRI版のリリース記事は以下です)

udzura.hatenablog.jp


ということで、今日はこのmruby版のHaconiwaを用いて、Dockerのsshdコンテナを再利用してsshdを立ち上げてみる手順を紹介します。

環境について

Ubuntu Xenial に Docker をインストールしたマシンをご用意ください。

Dockerのインストールは公式の手順の通りで。

docs.docker.com

sshdファイルシステムを用意する

ひとまず、sshdのコンテナをpullしてきましょう。何でもいいのですが、pull数が多い以下を使います。

$ docker pull rastasheep/ubuntu-sshd:14.04
14.04: Pulling from rastasheep/ubuntu-sshd
56eb14001ceb: Pull complete 
7ff49c327d83: Pull complete 
6e532f87f96d: Pull complete 
3ce63537e70c: Pull complete 
44eac7a33f9b: Pull complete 
75541fec9952: Pull complete 
2e8400dfc265: Pull complete 
0b5d47f4abfc: Pull complete 
85e1f2a6c198: Pull complete 
5dfceaa8deda: Pull complete 
Digest: sha256:2ddef06b17b83c3becea892eff3fae3587eebd062b151fce1517fed25a50236b
Status: Downloaded newer image for rastasheep/ubuntu-sshd:14.04

完了したら、一度立ち上げておきます。一応sshできるかを確認してもいいと思います。

$ docker run -P -d rastasheep/ubuntu-sshd:14.04
dbeddbcba4f5ec0b19d13d71828cff5095383f302619b91bdf16a2afb7ddd82e

$ docker inspect dbeddbcb | grep HostPort
                        "HostPort": "32768"

$ ssh -p 32768 root@localhost
The authenticity of host '[localhost]:32768 ([::1]:32768)' can't be established.
ECDSA key fingerprint is SHA256:FET56VENNef0pue72lv2diTZoDny5C2/ie2S6Tfqu38.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[localhost]:32768' (ECDSA) to the list of known hosts.
root@localhost's password: 
root@dbeddbcba4f5:~# exit

このコンテナのファイルシステムdocker export でエクスポートします。その後、適当なディレクトリ(今回は /var/lib/haconiwa/sshd_root )に展開します。

$ docker export dbeddbcb > sshd.tar
$ sudo mkdir -p /var/lib/haconiwa/sshd_root
$ sudo tar -xf sshd.tar -C /var/lib/haconiwa/sshd_root
$ ls -l /var/lib/haconiwa/sshd_root
total 8520
drwxr-xr-x  2 root root    4096 Jun 23 23:34 bin
drwxr-xr-x  2 root root    4096 Apr 10  2014 boot
-rw-------  1 root root 8646656 Jun 24 10:29 core
drwxr-xr-x  4 root root    4096 Jul 14 00:01 dev
drwxr-xr-x 67 root root    4096 Jul 14 00:01 etc
drwxr-xr-x  2 root root    4096 Apr 10  2014 home
drwxr-xr-x 12 root root    4096 Jul  8 15:14 lib
drwxr-xr-x  2 root root    4096 Jun 23 23:33 lib64
drwxr-xr-x  2 root root    4096 Jun 23 23:33 media
drwxr-xr-x  2 root root    4096 Apr 10  2014 mnt
drwxr-xr-x  2 root root    4096 Jun 23 23:33 opt
drwxr-xr-x  2 root root    4096 Apr 10  2014 proc
drwx------  2 root root    4096 Jul 14 00:02 root
drwxr-xr-x  8 root root    4096 Jul 14 00:01 run
drwxr-xr-x  2 root root    4096 Jun 24 10:29 sbin
drwxr-xr-x  2 root root    4096 Jun 23 23:33 srv
drwxr-xr-x  2 root root    4096 Mar 12  2014 sys
drwxrwxrwt  2 root root    4096 Jun 23 23:34 tmp
drwxr-xr-x 10 root root    4096 Jul  8 15:14 usr
drwxr-xr-x 11 root root    4096 Jul 14 00:01 var

最後に、今回はネットワークを分離しないので、sshdのポートとして22番ではなく2222番を使うように、ファイルシステム配下のsshd_configを編集します。

$ sudo vi /var/lib/haconiwa/sshd_root/etc/ssh/sshd_config
# Port 22
# ->
Port 2222

これでファイルシステムの用意ができました。

Haconiwaを立ち上げる

まずは haconiwa バイナリをインストールします。バイナリ一つで動きますので、以下の手順でOKです。

VERSION=0.1.3
wget https://github.com/haconiwa/haconiwa/releases/download/v${VERSION}/haconiwa-v${VERSION}.x86_64-pc-linux-gnu.tgz
tar xzf haconiwa-v${VERSION}.x86_64-pc-linux-gnu.tgz
sudo install hacorb hacoirb haconiwa /usr/local/bin
$ haconiwa
haconiwa - The MRuby on Container
commands:
    run       - run the container
    attach    - attach to existing container
    kill      - kill the running container
    version   - show version
    revisions - show mgem/mruby revisions which haconiwa bin uses

続いて、hacoファイル test.haco を用意します。要するにRubyDSLです。今回は、以下の感じで。

Haconiwa::Base.define do |config|
  config.name = "new-haconiwa001" # to be hostname

  root = Pathname.new("/var/lib/haconiwa/sshd_root")
  config.add_mount_point "devpts", to: root.join("dev/pts"), fs: "devpts"
  config.add_mount_point "tmpfs", to: root.join("tmp"), fs: "tmpfs"
  config.mount_independent_procfs
  config.chroot_to root

  config.namespace.unshare "ipc"
  config.namespace.unshare "uts"
  config.namespace.unshare "mount"
  config.namespace.unshare "pid"

  config.cgroup["pids.max"] = 1024

  config.capabilities.drop "cap_sys_admin"
end

このファイルをhaconiwaでキックしてみると、隔離された環境でbashが立ち上がっているのがわかると思います。PID、プロセスツリーなども独立。

$ sudo haconiwa run test.haco 
root@new-haconiwa001:/# ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.3  18200  3288 ?        S    07:08   0:00 /bin/bash
root         4  0.0  0.2  15564  2104 ?        R+   07:08   0:00 ps auxf

ここで、上記のhacoファイルに以下の2行を追加して、sshdを起動し、かつデーモン化できるようにしておきます。その後、同じように haconiwa run でhaconiwaプロセスと立ち上げっぱなしにしておきます。

Haconiwa::Base.define do |config|
  config.name = "new-haconiwa001" # to be hostname
  config.init_command = %w(/usr/sbin/sshd -D) # <- ここ
  config.daemonize!                           # <- ここも
  #...

end
$ sudo haconiwa run test.haco 
Container successfullly up. PID={container: 23939, supervisor: 23938}

このコンソールはそのままに、次の手順に入ります。

Haconiwaに入ってみよう

このプロセスにアタッチする方法は二つあります。一つは、 haconiwa attach を用いる方法です。

以下のようなコマンドを発行します。

$ sudo haconiwa attach test.haco

すると、以下のようにまたbashが立ち上がり、sshdのコンテナに入れていることがわかります。

root@new-haconiwa001:/# ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         2  0.0  0.3  18200  3344 pts/1    S    07:37   0:00 /bin/bash
root         6  0.0  0.2  15564  2216 pts/1    R+   07:37   0:00  \_ ps auxf
root         1  0.0  0.5  61380  5484 pts/0    S+   07:37   0:00 /usr/sbin/sshd -D

もう一つの方法は、sshdなので、sshで入ってみることです。今、母艦の 2222 番ポートをコンテナもリスンしているはずなので、そこ経由で入ってみましょう。

$ ssh -p 2222 root@localhost
root@localhost's password: 
Last login: Thu Jul 14 07:24:14 2016 from ::1
root@new-haconiwa001:~# ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.5  61380  5484 pts/0    S+   07:37   0:00 /usr/sbin/sshd -D
root         7  0.0  0.5  66012  5508 ?        Ss   07:39   0:00 sshd: root@pts/2    
root         9  0.0  0.3  18216  3392 pts/2    Ss   07:39   0:00  \_ -bash
root        20  0.0  0.2  15564  2116 pts/2    R+   07:39   0:00      \_ ps auxf

本当に隔離されてるの??

今、hacoファイルで

config.cgroup["pids.max"] = 1024

と書いてあったことを覚えていますでしょうか。この設定があるということは、 fork bomb を受けても問題がないはずですね。

試してみましょう。

$ sudo haconiwa attach test.haco
root@new-haconiwa001:/# :(){ :|:& };:
[1] 28
root@new-haconiwa001:/# bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes
...

少し待つとforkが落ち着き、システムの状態が回復します。PIDがずいぶん進んでますね...

root@new-haconiwa001:/# ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        23  0.0  0.3  18208  3324 pts/1    S    07:43   0:00 /bin/bash
root      7178  0.0  0.2  15564  2136 pts/1    R+   07:44   0:00  \_ ps auxf
root         1  0.0  0.5  61380  5484 pts/0    S+   07:37   0:00 /usr/sbin/sshd -D

ということで、無事リソースの隔離と制限ができているようです。カーネル4.4.0最高!

また、他にも、 cap_sys_admin ケーパビリティが落とされていることなどもわかります。

$ ssh -p 2222 root@localhost
root@new-haconiwa001:~# capsh --print | grep cap_sys_admin
root@new-haconiwa001:~# 

色々と変えて試してみても良いでしょう(コンテナにアタッチする際は、都度ケーパビリティを決定するので、 haconiwa attach -A/-D などのオプションで試せます)。

$ sudo haconiwa attach test.haco -D cap_sys_time
root@new-haconiwa001:/# date -s 10:00
date: cannot set date: Operation not permitted
Thu Jul 14 10:00:00 UTC 2016

コンテナを落とすには?

バージョン 0.1.3 から、 haconiwa kill で落とせます。よかったよかった。 --signal/-s で送り込むシグナル名も指定可能です。

$ sudo haconiwa kill test.haco
Kill success

こんな感じで、Dockerの資産をある程度利用しつつコンテナで遊ぶことができますので、ちょっとしたこと(例えば、各種OSのビルド環境が作りたい!などなど...)をお試しください。

そしてご覧の通りいろんな機能がありません。ネットワーク分離のことを考えていませんし、rlimit対応もしたいし、setuid/setgidもするなどしたいですし、いつかはオーケストレーションもやっていきたいのですが、頑張ります...。

では、良いお年を!