メインコンテンツまでスキップ

コンテナ内のユーザー切り替えツール:gosu

Docker技術は、デプロイメントや管理に広く使用されています。

私たちは通常、アプリケーションや関連する依存関係を一緒にパッケージ化し、異なる環境で一貫して実行できるようにします。

よくある問題

ただし、よく使用していると、いくつかのよくある問題に直面することになります。

TTYの変換

比較的一般的なシナリオは、コンテナ内でファイルを出力した後、コンテナを離れて再度そのファイルを見ると、ファイルの権限がすべてrootになっていることです。

そのため、再度chownを使用してファイルの権限を変更する必要があります。

これが何度も繰り返されると、非常に煩わしいですよね?


また、Dockerコンテナ内でsudoを使用して、端末と対話する必要があるアプリケーションを起動する場合、これらのアプリケーションは端末を正しく認識できないことがあります。なぜなら、sudoが新しいセッションを作成する際、端末の所有権と制御を適切に処理しない可能性があるからです。

その結果、端末と対話する必要があるアプリケーションは、正常に動作しなかったり、入力/出力のエラーが発生する可能性があります。

信号転送

例えば、Webサーバー(ApacheやNginxなど)が実行されているコンテナがあるとします。

通常、このコンテナを管理するためにコマンドラインツールを使用することが多いです。コンテナ内でsudoを使ってWebサーバーを起動すると、sudoはWebサーバーを実行するために新しいプロセスを作成します。

問題は、コンテナを停止または再起動したいときに発生します。コンテナ管理システムは、コンテナ内のプロセスに終了信号(例:SIGTERM)を送信しますが、もしWebサーバーがsudoで起動されている場合、その信号はsudoプロセスにのみ送信され、実際のWebサーバーに届かない可能性があります。そのため、Webサーバーが終了信号を受け取らず、適切に終了しないことがあります。

ヒント

sudoの設計はセキュリティを高め、一般ユーザーが他のユーザー(通常はrootユーザー)の権限でコマンドを実行できるようにすることです。この過程で、sudoはコマンドを実行するために新しいセッションを開始します。

この動作は、従来のオペレーティングシステム環境では問題ないことが多いですが、コンテナのような軽量仮想化環境では、sudoが新しいセッションを作成することで、信号転送の問題を引き起こす可能性があります。

gosuとは?

gosuは、コンテナ内でコマンドを簡単かつ安全に実行できるツールです。

異なるユーザー(たとえば、管理者から通常のユーザーへ)としてプログラムを実行する必要がある場合、gosuが役立ちます。gosuは、Docker/libcontainerがコンテナ内でアプリケーションを起動する方法に基づいて設計されており(実際には、libcontainerコードベース内の/etc/passwd処理コードを使用しています)。

もしその動作原理に興味がなければ、簡単に言うと、gosuはあなたの指示に従って「このユーザーとしてこのコマンドを実行してください」と言うと、それを実行し、終了後に痕跡を残さない手助けをしてくれます。

実際の使用シナリオ

gosuを使用する最も一般的なシナリオは、Dockerのエントリーポイント(ENTRYPOINT)スクリプト内で、コンテナ内でrootユーザーから通常のユーザーに切り替えて、権限の問題を避けることです。

以下は具体的な例です:

まず、Dockerfileにgosuをインストールするための指示を追加します:

Dockerfile
# 既存の基本イメージを基に作成
FROM some_base_image:latest

WORKDIR /app

# gosuをインストール
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*

# エントリーポイントスクリプトの準備
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
CMD ["default_command"]

次に、entrypoint.shスクリプトを作成し、環境変数に基づいてユーザーを動的に作成し、gosuを使ってコマンドを実行します:

entrypoint.sh
#!/bin/bash
# USER_IDとGROUP_ID環境変数が設定されているか確認
if [ ! -z "$USER_ID" ] && [ ! -z "$GROUP_ID" ]; then
# ユーザーグループとユーザーを作成
groupadd -g "$GROUP_ID" usergroup
useradd -u "$USER_ID" -g usergroup -m user
# gosuを使ってコマンドを実行
exec gosu user "$@"
else
exec "$@"
fi

実際の例については、Example training docker を参照できます。

セキュリティの注意点

gosuはコンテナ環境で非常に便利ですが、開発者は潜在的なセキュリティリスクにも注意を払っています。ユーザー切り替えを可能にするツールは慎重に使用する必要があります。

家の鍵のようなもので、便利ですが、不適切に使うと安全が損なわれることがあります。したがって、gosuを使用する際は、使用シーンを十分に理解し、安全でない状況での乱用を避けるようにしてください。

関連するディスカッションは、Keeping the TTY across a privilege boundary might be insecure #37 を参照してください。

備考

面倒かもしれませんが、ここで簡単に要点を抜粋します:

開発者は、権限の境界を越えてTTYを保持することがセキュリティリスクである可能性があると指摘しています。

プログラムが高い権限から低い権限に切り替えられるとき、仮想端末を新たに作成せず、親プロセスで開かれたファイル記述子(標準入力、出力など)が新しいプロセスで引き継がれる可能性があります。これにより、攻撃者はTTYバッファに入力文字を注入し、キーボード入力を模倣して、未承認のコマンドを実行することができます。このような脆弱性は、設計上許容されています。

例えば、次のコードは「id\n」という文字を1文字ずつ標準入力に注入します:

#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>

int main()
{
for (char *cmd = "id\n"; *cmd; cmd++) {
if (ioctl(STDIN_FILENO, TIOCSTI, cmd)) {
fprintf(stderr, "ioctl failed\n");
return 1;
}
}
return 0;
}

このコードが悪意のあるものである理由は、TIOCSTIというioctl呼び出しを使ってキーボード入力を模倣し、ユーザーの明示的な操作なしに「id」というコマンドを注入するためです。この手法で悪意のあるコードを挿入したり、権限昇格を狙う攻撃が可能になります。

ただし、この脆弱性は主に非意図的な使用シナリオに関係しています。設計された目的通りに、Dockerコンテナ内でエントリーポイントとして使用し、Dockerが新しいTTYを割り当てれば、リスクは大幅に低減します。

したがって、予期される環境で正しく使用すれば、基本的には過度に心配する必要はありません。