何も考えない場合Dockerはroot権限で動くが、そうしたければ非root権限(=Rootless)で動かすこともできる。
Dockerの実行権限について
Dockerのデーモンとコンテナはプロセスとして動き、それぞれが権限を持つ。
そのためDockerの話をしていてRootlessという単語が出てきたら、何がRootlessなのか(デーモンが?コンテナが?)を意識する必要がある。
以下でDockerデーモン・Dockerコンテナとroot権限・非root権限の組み合わせを考える。
| Dockerデーモン | Dockerコンテナ | 説明 |
|---|---|---|
| root | root | デフォルト。何でもできて便利だが脆弱性によってホスト、コンテナともに乗っ取られるかもしれない。 |
| root | 非root | デフォルトよりマシだが、ホスト側の脆弱性によってホストを乗っ取られるかもしれない。 |
| 非root | root | デフォルトよりマシだが、コンテナ側の脆弱性によってコンテナを乗っ取られるかもしれない。 |
| 非root | 非root | デフォルトに比べてホスト、コンテナともに被害は抑えられるが、ネットワークやファイル読み書きで問題が出ることがある。 |
権限は安全と利便性の両方を考慮して決めるのが良いと思う。
デーモン・コンテナ共にユーザー権限で間に合うのであればそうすれば良いし、5分以内にコンテナを立ち上げないと地球が爆発してしまう事態であれば両方rootで済ませてしまってもいいかもしれない。
Rootlessモードを使う
Rootlessモードについて
Rootlessモード(Rootless Dockerともいう)を使うとDockerデーモンを非root権限で動作させることができる。
Rootlessモードを使うとセキュリティが向上する一方で、以下のような不便さを抱えることにもなる。
- 80や443などのWell-knownポートを使うには工夫が必要となる
- 権限が狭まる分、Dockerコンテナが上手く起動しないこともある
docker context lsでDockerのコンテキスト(接続先デーモン)の一覧と権限を確認できる。
アスタリスクの行のDOCKER ENDPOINTがunix:///var/run/docker.sockであればroot権限で動作している。
$ docker context ls
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
Rootlessモードをインストールする
$ sudo apt install uidmap
従属UID/GIDが65536以上であることを確認する。
$ grep ^$(whoami): /etc/subuid
ユーザー名:100000:65536
$ grep ^$(whoami): /etc/subgid
ユーザー名:100000:65536
root権限のDockerデーモンが動いている場合は以下のコマンドを実行して止める。
# Dockerデーモン(docker.service)とソケット(docker.socket)を停止し、無効化する
$ sudo systemctl disable --now docker.service docker.socket
# Dockerソケットを削除する
$ sudo rm /var/run/docker.sock
ユーザーにRootlessモードをインストールする。ユーザー権限でインストールするのでsudoは要らない。
$ /usr/bin/dockerd-rootless-setuptool.sh install
Rootlessモードになっているか確認する。docker context lsを実行したとき、アスタリスクの行のDESCRIPTIONがRootless modeであればRootlessモードになっている。
$ docker context ls
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default Current DOCKER_HOST based configuration unix:///var/run/docker.sock
rootless * Rootless mode unix:///run/user/1000/docker.sock
最後にDockerデーモンを再起動する。
$ systemctl --user restart docker.service
Rootlessモードを無効にする
Rootlessモードを元に戻したいときは、以下を実行する。
# RootlessモードのDockerのデーモンを停止する
$ systemctl --user stop docker.service
$ systemctl --user disable docker.service
# RootlessモードのDockerのソケットを削除する
$ sudo rm /run/user/1000/docker.sock
# コンテキストを通常に戻す
$ docker context use default
# Dockerを再起動する
$ sudo systemctl enable docker.service docker.socket
$ sudo systemctl restart docker.service docker.socket
# コンテキストを確認する
$ docker context ls
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
rootless Rootless mode unix:///run/user/1000/docker.sock
Rootlessモードを有効にする
やっぱりRootlessモードにしたいときは、以下を実行する。
# Dockerのデーモンを停止する
$ sudo systemctl stop docker.service docker.socket
$ sudo systemctl disable docker.service docker.socket
# Dockerのソケットを削除する
$ sudo rm /var/run/docker.sock
# コンテキストをRootlessにする
$ docker context use rootless
# Dockerを再起動する
$ systemctl --user enable docker.service
$ systemctl --user restart docker.service
# コンテキストを確認する
$ docker context ls
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default Current DOCKER_HOST based configuration unix:///var/run/docker.sock
rootless * Rootless mode unix:///run/user/1000/docker.sock
ターミナルを開いている間だけRootlessモードを有効にする
上記の手順は恒久的にモードを切り替える方法だが、ターミナルを開いている間だけRootlessモードを有効にしたいときもある。
そういうときは以下を実行する。
$ export DOCKER_CONTEXT=rootless
RootlessモードでWell-knownポートを使う
Rootlessモードで80や443などのWell-knownポートを使いたい場合、工夫が要る。
バイナリにWell-knownポートのバインド権限を与えることができるsetcapというコマンドがあるので、それが使える。
RootlessモードのDockerはrootlesskitを使ってポートフォワーディングを行うのでrootlesskitにバインド権限を与えれば良い。
# rootlesskitにWell-knownポートのバインド権限を与える
# cap_net_bind_service=ep: Well-knownポートのバインド権限をep(effective+permitted)
$ sudo setcap cap_net_bind_service=ep $(which rootlesskit)
$ systemctl --user restart docker.service
# 権限を確認する
$ getcap $(which rootlesskit)
/usr/bin/rootlesskit cap_net_bind_service=ep
# 権限を削除する
$ sudo setcap -r $(which rootlesskit)
$ systemctl --user restart docker.service
RootlessモードのDockerデーモンを常駐させる
Rootlessモードならではの注意点の一つとして、SSHセッションを閉じるとDockerデーモンが終了してしまうことが挙げられる。
Rootless モードの Docker デーモンは~/.config/systemd/user/docker.serviceで起動する。これはsystemdのユーザーサービスであり、systemdのユーザーサービスは SSH セッションを閉じると終了してしまうのである。
このことを知らないと、例えばWebアプリケーションのコンテナをRootlessモードで起動して喜ぶもSSHセッションを閉じたら応答が無くなってしまった、という事態に出くわす。
対処法としては以下のコマンドでユーザーサービスの常駐を有効化させる。これによりSSHセッションを閉じてもRootlessモードのDockerデーモンを起動し続けることができる。
# ユーザーサービスの常駐を有効化する
$ sudo loginctl enable-linger $(whoami)
# 常駐の状態を確認する
$ loginctl show-user $(whoami) --property=Linger
Linger=yes
# ユーザーサービスの常駐を無効化する
$ sudo loginctl disable-linger $(whoami)
コンテナを非root権限で動作させる
各ツールにオプションを指定してやることで、コンテナを非root権限で動作させることができる。
$UIDはユーザーID、$GIDはグループIDを示す。
なお自分のユーザーID、グループIDはidコマンドで確認できる。
docker
--user: $UID:$GIDでユーザーを指定できる。
Dockerfile
USER $UID:$GIDでユーザーを指定できる。
docker-compose.yml
user: $UID:$UIDでユーザーを指定できる。
コンテナの実行権限を確認する
コンテナがどの権限で動いているかは以下のコマンドで確認できる。
$ docker exec $CONTAINER_NAME id
uid=0(root) gid=0(root) groups=0(root)
