はじめに
xhost +
を使うことなしに、安全にDockerで起動したChromeをホストPCに表示する方法をまとめる。
実現方法のまとめ
「ホストPCのユーザID」と「DockerでChromeを実行するユーザID」を揃えることで.Xauthority
を使いDockerのChromeをxhost +
なしでホストPCに表示する
1.適当なディレクトリに Dockerfile を作成する
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
ENV DISPLAY=:0
ENV PULSE_SERVER=unix:/run/user/1000/pulse/native
# 必要なパッケージをインストール
RUN apt-get update && \
apt-get install -y wget pulseaudio socat alsa-base \
libcanberra-gtk-module libcanberra-gtk3-module \
fonts-ipafont-gothic fonts-ipafont-mincho fonts-noto-cjk \
language-pack-ja dbus-x11 && \
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
apt-get install -y ./google-chrome-stable_current_amd64.deb --no-install-recommends && \
apt-get install -y x11-apps mesa-utils sudo &&\
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN echo "#!/bin/bash\n\
set -e\n\
sudo /etc/init.d/dbus start || { echo 'Failed to start dbus'; exit 1; }\n\
export XDG_RUNTIME_DIR=/run/user/\$(id -u)\n\
sudo mkdir -p \$XDG_RUNTIME_DIR || { echo 'Failed to create XDG_RUNTIME_DIR'; exit 1; }\n\
sudo chmod 700 \$XDG_RUNTIME_DIR || { echo 'Failed to chmod XDG_RUNTIME_DIR'; exit 1; }\n\
sudo chown \$(id -un):\$(id -gn) \$XDG_RUNTIME_DIR || { echo 'Failed to chown XDG_RUNTIME_DIR'; exit 1; }\n\
export DBUS_SESSION_BUS_ADDRESS=unix:path=\$XDG_RUNTIME_DIR/bus\n\
dbus-daemon --session --address=\$DBUS_SESSION_BUS_ADDRESS --nofork --nopidfile --syslog-only &\n\
exec \"\$@\"" > /start.sh && chmod +x /start.sh
# ホストのUIDとGIDを利用してユーザーを作成する
ARG USER_ID
ARG GROUP_ID
# 既存のグループを確認して、なければ作成
RUN if ! getent group $GROUP_ID >/dev/null; then \
groupadd -g $GROUP_ID hostgroup; \
fi
# 既存のUIDに対応するユーザーがいない場合、新規ユーザーを作成して、PWなしでsudoを許可
RUN USER_NAME=$(getent passwd $USER_ID | cut -d: -f1) && \
if [ -z "$USER_NAME" ]; then \
USER_NAME="hostuser"; \
useradd -u $USER_ID -g $GROUP_ID -m $USER_NAME; \
fi && \
echo "$USER_NAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# google-chrome を $USER_ID で実行
USER $USER_ID
CMD ["/start.sh", "google-chrome", "--disable-dev-shm-usage"]
2.Dockerイメージを作成する
ubuntu24_chrome3
という名前でDockerイメージを作成する
docker build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) -t ubuntu24_chrome3 .
3.Dockerコンテナを起動する
Chromeのサンドボックスに対応するため特権コンテナとして実行し、Chromeが起動することを確認する
docker run -it --rm \
--name ubuntu24_chrome3 \
--env="DISPLAY=$DISPLAY" \
-v /tmp/.X11-unix:/tmp/.X11-unix \
--env="PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native" \
--volume="$HOME/.Xauthority:/home/ubuntu/.Xauthority:rw" \
--volume="${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native" \
--volume="/etc/machine-id:/etc/machine-id:ro" \
--volume="/run/user/$(id -u)/pulse:/run/user/$(id -u)/pulse" \
--network=host \
--device /dev/dri \
--cap-add=SYS_ADMIN --security-opt seccomp=unconfined \
ubuntu24_chrome3
xhost +
の危険性
xhost +
がよく使われる理由
前回までの記事では xhost +
を使い手軽にDockerでGUIアプリをホストPCに表示させた。コンテナからホストのXサーバーに接続する場合、xhost +
は、この接続を簡単に許可できる。これを使えば、設定なしでどんなコンテナからもアプリを表示できます。手軽さから、開発やテストでよく使われる
xhost +
の危険性
xhost +
は便利だが、セキュリティに大きな穴を開ける。全ての接続を許可するため、誰でもホストPCの画面にアクセスできる。
- セッション乗っ取りのリスク
- データの盗難
- システムの破壊
これらのリスクを避けるため、開発やテスト以外ではxhost +
は使わない方が良い。
xhost +
を使わずGUIアプリをホストPCで表示する
前回の記事でxhost +
を使わない場合
前回の記事のDockerファイルを利用して作成したイメージを実行した場合、xhost +
を使わないと下記のように、X serverが見つからないとのエラーが発生する
[1:1:0816/051448.624023:ERROR:ozone_platform_x11.cc(244)] Missing X server or $DISPLAY
解決策1: xhost +local:
ホストPCで xhost +
の代わりに、xhost +local:
を実行する。リスクは残るがシンプルな解決策である。
xhost +local:
xhost +local:
は、ローカルマシン上のユーザー間でXサーバーにアクセスを許可する場合に使用される。ネットワーク経由でのアクセスが制限されるため、リスクはxhost +
よりも低いが、ローカルマシンの他のユーザーがXサーバーにアクセスできるリスクは残る。
xhost -local:
解決策2: .Xauthorityを利用する
.Xauthorityを利用しする場合、コンテナ内のユーザーとホストPCのユーザーの設定が一致しなければならない。
用意する Dockerfile
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
ENV DISPLAY=:0
ENV PULSE_SERVER=unix:/run/user/1000/pulse/native
# 必要なパッケージをインストール
RUN apt-get update && \
apt-get install -y wget pulseaudio socat alsa-base \
libcanberra-gtk-module libcanberra-gtk3-module \
fonts-ipafont-gothic fonts-ipafont-mincho fonts-noto-cjk \
language-pack-ja dbus-x11 && \
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
apt-get install -y ./google-chrome-stable_current_amd64.deb --no-install-recommends && \
apt-get install -y x11-apps mesa-utils sudo &&\
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN echo "#!/bin/bash\n\
set -e\n\
sudo /etc/init.d/dbus start || { echo 'Failed to start dbus'; exit 1; }\n\
export XDG_RUNTIME_DIR=/run/user/\$(id -u)\n\
sudo mkdir -p \$XDG_RUNTIME_DIR || { echo 'Failed to create XDG_RUNTIME_DIR'; exit 1; }\n\
sudo chmod 700 \$XDG_RUNTIME_DIR || { echo 'Failed to chmod XDG_RUNTIME_DIR'; exit 1; }\n\
sudo chown \$(id -un):\$(id -gn) \$XDG_RUNTIME_DIR || { echo 'Failed to chown XDG_RUNTIME_DIR'; exit 1; }\n\
export DBUS_SESSION_BUS_ADDRESS=unix:path=\$XDG_RUNTIME_DIR/bus\n\
dbus-daemon --session --address=\$DBUS_SESSION_BUS_ADDRESS --nofork --nopidfile --syslog-only &\n\
exec \"\$@\"" > /start.sh && chmod +x /start.sh
# ホストのUIDとGIDを利用してユーザーを作成する
ARG USER_ID
ARG GROUP_ID
# 既存のグループを確認して、なければ作成
RUN if ! getent group $GROUP_ID >/dev/null; then \
groupadd -g $GROUP_ID hostgroup; \
fi
# 既存のUIDに対応するユーザーがいない場合、新規ユーザーを作成して、PWなしでsudoを許可
RUN USER_NAME=$(getent passwd $USER_ID | cut -d: -f1) && \
if [ -z "$USER_NAME" ]; then \
USER_NAME="hostuser"; \
useradd -u $USER_ID -g $GROUP_ID -m $USER_NAME; \
fi && \
echo "$USER_NAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# google-chrome を $USER_ID で実行
USER $USER_ID
CMD ["/start.sh", "google-chrome", "--disable-dev-shm-usage"]
Dockerfileの変更点
項目 | 前回 | 今回 | 変更理由と影響 |
---|---|---|---|
追加パッケージ |
google-chrome-stable 、pulseaudio などの基本パッケージをインストール |
追加でx11-apps 、mesa-utils 、sudo をインストール。 |
- x11-apps とmesa-utils の追加により、X11およびOpenGLアプリケーションのデバッグや動作確認が可能にした - sudo の追加で、ユーザーが管理者権限でコマンドを実行可能にした。 |
DBus設定スクリプトの改良 | Chrome実行時のエラー解決の設定 | Chromeの実行ユーザがroot でない場合に対応 |
sudo の使用により、ルート権限が必要な操作を安全に実行できる |
ホストのUID/GIDに基づくユーザー作成 | n/a | ホストのUID/GIDを使用してコンテナ内にユーザーを作成 | .Xauthorityを用いてXサーバーに接続するため |
google-chrome の実行ユーザ |
root | ホストのUID | - .Xauthorityを用いてXサーバーに接続するため - Chromeを-no-sandboxなしで実行する |
Dockerイメージの作成
ubuntu24_chrome3
という名前をつけてイメージを作成する
docker build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) -t ubuntu24_chrome3 .
Dockerコンテナの起動の失敗
DockerでChromeを前回と同様に起動しようとするとエラーが発生する。
docker run -it --rm \
--name ubuntu24_chrome3 \
--env="DISPLAY=$DISPLAY" \
-v /tmp/.X11-unix:/tmp/.X11-unix \
--env="PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native" \
--volume="$HOME/.Xauthority:/home/ubuntu/.Xauthority:rw" \
--volume="${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native" \
--volume="/etc/machine-id:/etc/machine-id:ro" \
--volume="/run/user/$(id -u)/pulse:/run/user/$(id -u)/pulse" \
--network=host \
--device /dev/dri \
ubuntu24_chrome3
* Starting system message bus dbus [ OK ]
Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
[1:1:0816/055842.050671:FATAL:zygote_host_impl_linux.cc(201)] Check failed: . : Operation not permitted (1)
Dockerコンテナの起動失敗の原因の推察
Chromeは、セキュリティのために「サンドボックス」機能を使用しており、プロセスを分離するために名前空間を使用する。しかし、Dockerコンテナ内では、これらの操作に必要な権限が制限されているため、Chromeのサンドボックス機能が適切に動作せず、エラーが発生しているものと考えられる。
Chromeの起動するユーザをrootからホストPCのユーザと同一のユーザIDに変更した際に、DockerfileのCMDで--no-sandbox
を削除したことが原因だと考えられる。
Xeyesなら起動は成功
docker run -it --rm \
--name ubuntu24_chrome3 \
--env="DISPLAY=$DISPLAY" \
-v /tmp/.X11-unix:/tmp/.X11-unix \
--env="PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native" \
--volume="$HOME/.Xauthority:/home/ubuntu/.Xauthority:rw" \
--volume="${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native" \
--volume="/etc/machine-id:/etc/machine-id:ro" \
--volume="/run/user/$(id -u)/pulse:/run/user/$(id -u)/pulse" \
--network=host \
--device /dev/dri \
ubuntu24_chrome3 /usr/bin/xeyes
特権をつけてDockerコンテナを起動
Chromeのサンドボックスに対応するため特権コンテナとして実行し、Chromeが起動することを確認する
docker run -it --rm \
--name ubuntu24_chrome3 \
--env="DISPLAY=$DISPLAY" \
-v /tmp/.X11-unix:/tmp/.X11-unix \
--env="PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native" \
--volume="$HOME/.Xauthority:/home/ubuntu/.Xauthority:rw" \
--volume="${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native" \
--volume="/etc/machine-id:/etc/machine-id:ro" \
--volume="/run/user/$(id -u)/pulse:/run/user/$(id -u)/pulse" \
--network=host \
--device /dev/dri \
--privileged \
ubuntu24_chrome3
まとめ
DockerでGUIアプリを使うとき、xhost +
は便利なのだが、非常に危険でもある。ある程度のリスクが許容できるなら xhost +local:
を選択、そうでない場合には /.Xauthority
を使うことを考えると良い。
ただし、Chromeのように特権が必要な場合には、コンテナ内で悪意のあるコードが実行された場合、ホストシステム全体に影響を及ぼす可能性があり、別のセキュリティの懸念が発生する。
--privileged
の代わりに--cap-add=SYS_ADMIN --security-opt seccomp=unconfined
を指定することで、部分的な特権付与でセキュリティの懸念を減じるほうが良いだろう。
蛇足
--volume="$HOME/.Xauthority:/home/ubuntu/.Xauthority:rw"
でマウントするユーザのホームディレクトリ(/home/ubuntu/)は下記のようにして確認できる
docker run -it --rm \
--name ubuntu24_chrome3 \
ubuntu24_chrome3 /bin/bash -c 'echo $HOME'