Docker USER命令の理解

コンテナ化の世界では、セキュリティと適切なユーザー管理は、アプリケーションの安定性とセキュリティに大きな影響を与える可能性のある重要な側面です。 Dockerfile の USER 命令 は、イメージのビルド プロセス中とコンテナーの実行時の両方でコマンドを実行するユーザーを決定する基本的なツールです。 デフォルトでは、 USER が指定されていない場合、Dockerはrootユーザーとしてコマンドを実行するため、重大なセキュリティリスクが生じる可能性があります。 

このブログ投稿では、 USER 命令に関連するベストプラクティスと一般的な落とし穴について掘り下げます。 さらに、これらのプラクティスの重要性を説明するための実践的なデモを提供します。 USER命令を理解し、正しく実装することは、安全で効率的なDocker環境を維持するために不可欠です。ユーザー権限を効果的に管理し、Dockerコンテナが安全かつ意図したとおりに実行されるようにする方法を探ってみましょう。

バナードッカーのヒント

Docker Desktop 

提供されているコマンドと例は、統合コンポーネントとして Docker Engine を含む Docker Desktop での使用を目的としています。これらのコマンドをDocker Community Edition(スタンドアロンのDockerエンジン)で実行することは可能ですが、出力がこの記事に示されているものと一致しない場合があります。 ブログ記事 「How to Check Your Docker Installation: Docker Desktop vs. Docker Engine 」では、違いと、使用しているものを判断する方法について説明しています。

UID/GID: 復習

ベストプラクティスについて説明する前に、UID / GIDの概念と、Dockerを使用する際にそれらが重要である理由を確認しましょう。 この関係は、これらのベスト プラクティスのセキュリティ面に大きく影響します。

Linuxおよびその他のUnixライクなオペレーティングシステムは、UID(ユーザーID)と呼ばれる個々のユーザーを識別するために数値識別子を使用します。 グループは、別の数値識別子である GID (グループ ID) によって識別されます。 これらの数値識別子は、 ユーザ名グループ名に使用されるテキスト文字列にマッピングされますが、数値識別子はシステムによって内部的に使用されます。

オペレーティング システムは、これらの識別子を使用して、システム リソース、ファイル、およびディレクトリへのアクセス許可とアクセスを管理します。 ファイルまたはディレクトリには、UID や GID などの所有権設定があり、どのユーザーやグループがアクセス権を持つかが決まります。 ユーザーは複数のグループのメンバーになることができるため、権限管理が複雑になる可能性がありますが、柔軟なアクセス制御が提供されます。

Docker では、これらの UID と GID の概念は コンテナー内で保持されます。 Dockerコンテナを実行するときに、UIDとGIDが指定された特定のユーザーとして実行するように構成できます。 さらに、ボリュームをマウントする場合、Docker はホストマシン上のファイルとディレクトリの UID と GID を尊重するため、コンテナー内からファイルにアクセスしたり変更したりする方法に影響を与える可能性があります。 このようにUnixライクなUID/GID管理に従うことで、ホスト環境とコンテナ化された環境の両方で一貫したセキュリティとアクセス制御を維持することができます。 

グループ

USERとは異なり、Dockerfile 命令には GROUP ディレクティブはありません。グループを設定するには、ユーザー名 (UID) の後にグループ名 (GID) を指定します。 たとえば、ci グループの自動化ユーザーとしてコマンドを実行するには、Dockerfile に USER automation:ci を記述します。

GID を指定しない場合は、ユーザー アカウントが属するグループの一覧が使用されます。 ただし、GID を指定した場合は、その GID のみ が使用されます。 

現在のユーザー

Docker Desktop は仮想マシン (VM) を使用するため、ホスト (Linux、Mac、Windows HyperV/WSL2) 上のユーザー アカウントの UID/GID は、Docker VM 内ではほぼ確実に一致しません。

UID/GIDは、 id コマンドを使用していつでも確認できます。 たとえば、私のデスクトップでは、プライマリGIDが20のUID503です。

$ id
uid=503(jschmidt) gid=20(staff) groups=20(staff),<--SNIP-->

おすすめの方法

root以外のユーザーを使用してrootアクセスを制限する

上記のように、デフォルトではDockerコンテナはUID 0、またはrootとして実行されます。 つまり、Dockerコンテナが侵害された場合、攻撃者はコンテナに割り当てられたすべてのリソースに対してホストレベルのルートアクセス権を持つことになります。 root以外のユーザーを使用すると、攻撃者がコンテナで実行されているアプリケーションから抜け出すことができたとしても、コンテナがroot以外のユーザーとして実行されている場合、攻撃者の権限は制限されます。 

Dockerfile に USER を設定しない場合、ユーザーはデフォルトで root になることに注意してください。 コンテナーが誰として実行されるかを明確にする場合でも、常に明示的にユーザーを設定します。

UIDとGIDでユーザーを指定する

ユーザー名とグループ名は簡単に変更でき、Linuxディストリビューションごとにシステムユーザーとグループに異なるデフォルト値を割り当てることができます。 UID/GID を使用すると、コンテナの /etc/passwd ファイルが変更されたり、ディストリビューション間で異なっていたりしても、ユーザーが一貫して識別されるようにすることができます。 例えば:

USER 1001:1001

アプリケーションの特定のユーザーを作成する

アプリケーションに特定のアクセス許可が必要な場合は、Dockerfile でアプリケーション専用のユーザーを作成することを検討してください。 これは、 RUN コマンドを使用してユーザーを追加することで実行できます。 

ユーザーを作成してから Dockerfile 内でそのユーザーに切り替える場合、UID/GID は useradd コマンドを使用してイメージのコンテキスト内で設定されるため、UID/GID を使用する必要はありません。 同様に、 RUN コマンドを使用して、ユーザーをグループに追加(および必要に応じてグループを作成)できます。

設定したユーザーに、コンテナーでコマンドを実行するために必要な権限があることを確認します。 たとえば、root 以外のユーザーには、 1024より下のポートにバインドするために必要な権限がない可能性があります。 例えば:

RUN useradd -ms /bin/bash myuser
USER myuser

特権操作のルートへの切り替え

非 root ユーザーを設定した後に Dockerfile で特権操作を実行する必要がある場合は、root ユーザーに切り替え、それらの操作が完了したら非 root ユーザーに戻すことができます。 このアプローチは、最小特権の原則に従います。管理者権限を必要とするタスクのみが管理者として実行されます。 Dockerfile での特権昇格に sudo を使用することはお勧めしません。 例えば:

USER root
RUN apt-get update && apt-get install -y some-package
USER myuser

USERとWORKDIRの組み合わせ

前述のように、コンテナ内で使用されるUID/GIDは、コンテナ内とホストシステムの両方に適用されます。 これにより、次の 2 つの一般的な問題が発生します。

  • root以外のユーザーに切り替えて、使用するディレクトリの読み取りまたは書き込みの権限がない(たとえば、 / の下にディレクトリを作成しようとしたり、 /rootで書き込もうとしたりします。
  • ホストシステムからディレクトリをマウントし、マウント内のディレクトリまたはファイルに対する読み取り/書き込み権限を持たないユーザーに切り替える。
USER root
RUN mkdir /app&&chown ubuntu
USER ubuntu
WORKDIR /app

次の例は、Dockerfile の記述方法に応じて、さまざまなシナリオで UID と GID がどのように動作するかを示しています。 どちらの例も、実行中の Docker コンテナーの UID/GID を示す出力を提供します。 この手順に従う場合は、Docker Desktop のインストールを実行し、 docker コマンドに関する基本的な知識が必要です。

標準 Dockerfile

ほとんどの人は、 Docker を最初に使い始めるときにこのアプローチを取ります。これらはデフォルトを使用し、 USERを指定しません。

# Use the official Ubuntu image as the base
FROM ubuntu:20.04

# Print the UID and GID
CMD sh -c "echo 'Inside Container:' && echo 'User: $(whoami) UID: $(id -u) GID: $(id -g)'"

Dockerfile と USER

この例では、Dockerfile 内で RUN コマンドを使用してユーザーを作成し、その USERに切り替える方法を示します。

# Use the official Ubuntu image as the base
FROM ubuntu:20.04

# Create a custom user with UID 1234 and GID 1234
RUN groupadd -g 1234 customgroup && \
    useradd -m -u 1234 -g customgroup customuser

# Switch to the custom user
USER customuser

# Set the workdir
WORKDIR /home/customuser

# Print the UID and GID
CMD sh -c "echo 'Inside Container:' && echo 'User: $(whoami) UID: $(id -u) GID: $(id -g)'"

次の方法で 2 つのイメージをビルドします。

$ docker build -t default-user-image -f Dockerfile1 .
$ docker build -t custom-user-image -f Dockerfile2 .

デフォルトのDockerイメージ

最初のイメージである、 USER コマンドを提供しないイメージを実行してみましょう。 ご覧のとおり、UID と GID は 0/0であるため、スーパーユーザーは rootです。 ここには2つのことが働いています。 まず、Dockerfile で UID/GID を定義していないため、Docker はデフォルトでスーパーユーザーになります。 しかし、私のアカウントがスーパーユーザーアカウントでない場合、どのようにスーパーユーザーになるのでしょうか? これは、Docker Engine が root 権限で実行されるため、root として実行するようにビルドされたコンテナが Docker Engine から権限を継承するためです。

$ docker run --rm default-user-image
Inside Container:
User: root UID: 0 GID: 0
Custom User Docker Image

カスタム Docker イメージ

これを修正しましょう — Dockerコンテナをrootとして実行することは本当に望ましくありません。 そのため、このバージョンでは、ユーザーとグループのUIDとGIDを明示的に設定します。 このコンテナを実行すると、ユーザーが適切に設定されていることがわかります。

$ docker run --rm custom-user-image
Inside Container:
User: customuser UID: 1234 GID: 1234

ベストプラクティスの実施

どのような環境でもベストプラクティスを適用することは困難な場合があり、この記事で概説されているベストプラクティスも例外ではありません。 Docker は、組織がセキュリティとコンプライアンスとイノベーションと俊敏性のバランスを常に取っていることを理解しており、その取り組みを支援する方法に継続的に取り組んでいます。 Hardened Docker Desktop の一部である Enhanced Container Isolation(ECI) オファリングは、コンテナをrootとして実行することの問題点に対処するように設計されています。

ユーザー名前空間などの強化されたコンテナー分離メカニズムは、特権をより効果的に分離および管理するのに役立ちます。 ユーザー名前空間は、ユーザー ID やグループ ID などのセキュリティ関連の識別子と属性を分離して、コンテナー内の root ユーザーがコンテナー外の root ユーザーにマップされないようにします。 この機能により、攻撃者がコンテナを侵害した場合でも、潜在的な損害とアクセス範囲がコンテナ化された環境に限定され、全体的なセキュリティが大幅に強化されるため、特権エスカレーションのリスクが大幅に軽減されます。

さらに、 Docker Scout をユーザーのデスクトップで活用して、CVEだけでなく、ベストプラクティスに関するポリシー(たとえば、イメージがroot以外のユーザーとして実行され、必須のLABELが含まれていることを確認するなど)を適用できます。

セキュリティの確保

このデモを通じて、潜在的な攻撃対象領域を最小限に抑えてセキュリティを強化するために不可欠な、Dockerコンテナを非rootユーザーとして実行するように構成することの実際的な影響と利点を確認しました。 示したように、Dockerは、特に指定がない限り、本質的にroot権限でコンテナを実行します。 このデフォルトの動作は、特にコンテナが侵害された場合に重大なセキュリティリスクにつながる可能性があり、攻撃者にホストまたはDockerエンジンへの広範なアクセスを許可する可能性があります。

カスタム ユーザー ID とグループ ID を使用する

カスタム ユーザー ID とカスタム グループ ID の使用は、より安全なプラクティスを示しています。 UIDとGIDを明示的に設定することで、Dockerコンテナ内で実行されるプロセスの権限と機能を制限し、特権ユーザーアクセスに関連するリスクを軽減します。 Dockerコンテナ内で定義されたUID/GIDは、ホストシステム上の実際のユーザーに対応する必要がないため、分離とセキュリティが強化されます。

ユーザー名前空間

この投稿では、Dockerの USER 命令を幅広くカバーしていますが、Docker環境を保護するための別のアプローチには、名前空間、特にユーザー名前空間の使用が含まれます。 ユーザー名前空間は、ユーザー ID やグループ ID などのセキュリティ関連の識別子と属性を、ホストとコンテナーの間で分離します。 

ユーザー名前空間を有効にすると、Docker はコンテナー内のユーザー ID とグループ ID をホスト システム上の非特権 ID にマッピングできます。 このマッピングにより、コンテナのプロセスがブレイクアウトしてDockerコンテナ内でroot権限を獲得した場合でも、ホストマシン上ではroot権限を持ちません。 この追加のセキュリティレイヤーは、権限の昇格を防ぎ、潜在的な損害を軽減するのに役立ち、Dockerセキュリティフレームワークをさらに強化しようとしている人にとって不可欠な考慮事項になります。 DockerのECIオファリングは、セキュリティフレームワークの一部としてユーザー名前空間を活用します。

結論

特に開発環境やDocker Desktopにコンテナをデプロイする際には、この投稿で概説されているコンテナの構成と分離の側面を考慮してください。 強化されたコンテナ分離機能を備えた強化されたDocker Desktopなど、 Docker Businessで利用可能な強化されたセキュリティ機能を実装することで、リスクをさらに軽減し、アプリケーションの安全で堅牢な運用環境を確保できます。

さらに詳しく