ルートレスコンテナーのユーザー名前空間について
Table of Contents
目次
はじめに
この記事では、ユーザー名前空間の概念について、例を使用して説明します。特に、podman
、buildah
、skopeo
など、非ルートユーザーとして実行される場合にユーザー名前空間を活用するコンテナー化テクノロジーでの適用について説明します。
この記事の内容を完全に理解するために、前提として必要な知識がいくつかあります。 次の概念について理解しておくことが推奨されます。
- Linux Containers
- Rootless Containers
- Linux Namespaces
- Accessing Linux Namespaces with tools such as
nsenter
コンテナーにおけるユーザー名前空間とは
ユーザー名前空間は、非ルートユーザーがホストシステム上の UID と GID の範囲を名前空間内の別の UID と GID の範囲にマップできるようにする Linux 内の機能です。 このマッピングは主にコンテナー化技術で使用され、コンテナーを作成および実行する非ルートユーザーが、コンテナー内で自分以外のユーザーであるかのようにプロセスを実行し、ファイルを作成できるようにします。
たとえば UID が 1000
、名前が "bob" のユーザーがいるとします。 /etc/subuid
ファイルと /etc/subgid
ファイルを適切に設定することで、このユーザーにはホスト上で自分の UID として使用する UID 範囲が与えられます。 たとえば /etc/subuid
と /etc/subgid
の内容を以下のとおりと仮定します。
$ cat /etc/subuid
bob:10000:65536
$ cat /etc/subgid
bob:10000:65536
これらのファイルには 3 つのフィールドがあります。
- マッピングされた UID または GID を受信するユーザーのユーザー名または UID。
- マッピングで使用されるホスト上の 開始 UID または GID。
- ユーザー名前空間の開始 UID または GID からの拡張 範囲。
以下の表は、/etc/subuid
と /etc/subid
のマッピングを視覚的に表しています。
bob
1000 10000 165535
│ │ │
▼ ▼ ▼
┌───────┬┬────────┬────────┬────────────────────────┐
│ ││ │ │ │
│ ││ │ │ │
host │ ││ │ │ │
│ ││ │ │ │
│ ││ │ │ │
│ ││ │ │ │
└───────┴┴────────┴────────┴────────────────────────┘
│ │ │
┌───────┘ │ │
│ │ │
│ ┌───────────────┘ └────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────┐
│ │
│ │
user │ │
namespace │ │
│ │
│ │
└───────────────────────────────────────────────────┘
0 1 65335
上記の例では、"bob" は UID 0
としてユーザー名前空間にマッピングされています。名前空間内のユーザー 0
(root
として解釈されます) がプロセスを開始すると、ホストはユーザー bob
によってプロセスが開始されたと認識します。 ユーザー名前空間内で、UID が 1001
、名前が appuser
のユーザーがプログラムを実行すると、ユーザー名前空間内の UID 1001
は、ホスト上の開始範囲 10000
に UID 1001
を加えたものにマップされます。つまり、"ホスト" 上では UID 11001
が使用されます。
上記の例からわかるように、UID 10000
で開始し、65536 個の ID で拡張されユーザーは、ユーザー名前空間で bob
ユーザーによる使用が可能です。
このメカニズムにより、非 root ユーザーはホスト上の実際の UID や GID を必要とせずにコンテナー内で別のユーザーとしてプロセスの実行やファイルの作成を行うことができ、セキュリティー上のリスクの発生を回避できるため、このメカニズムはルートレスコンテナーのコアコンポーネントと言えます。 システム上の /etc/subuid
および /etc/subgid
を介してユーザーが操作できる範囲を指定することで、範囲内の ID を使用するすべてのファイルまたはプロセスを bob
ユーザーに帰属させることができます。
`/etc/subuid` と `/etc/subgid` で複数の範囲を使用する
/etc/subuid
ファイルと /etc/subgid
ファイルでは、1 ユーザーに対して複数の範囲を指定できます。 上記の "bob" の例を使用して、次の例を見てみましょう。
$ cat /etc/subuid
bob:10000:65536
bob:220000:6000
$ cat /etc/subgid
bob:10000:65536
bob:220000:6000
この例では、bob には合計 71536
個の UID と GID が割り当てられています。これらの UID と GID は、名前空間 内 で 1
から始まります (0
はホスト上で bob
の UID と GID として予約されています)。 この合計 71536
個の ID は、ホスト上の 2 つの範囲、つまりユーザー名前空間内の ID 1
- 65536
に対応するホスト上の 10000
- 165535
の範囲と、ユーザー名前空間内の ID 65537
- 71536
に対応するホスト上の 220000
- 226000
の範囲で設定されています。 次の図は、これがどのようにマッピングされるかを示しています。
bob
1000 10000 165535 220000 226000
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌───────┬┬────────┬────────┬────────────┬───────┬───┐
│ ││ │ │ │ │ │
│ ││ │ │ │ │ │
host │ ││ │ │ │ │ │
│ ││ │ │ │ │ │
│ ││ │ │ │ │ │
│ ││ │ │ │ │ │
└───────┴┴────────┴────────┴────────────┴───────┴───┘
│ │ │ │ │
┌───────┘ │ │ │ │
│ │ │ │ │
│ ┌───────────────┘ └───┬────────┘ └───┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────────────────────┬────────────────────┐
│ │ │
│ │ │
user │ │ │
namespace │ │ │
│ │ │
│ │ │
└──────────────────────────────┴────────────────────┘
0 1 65335 71535
この方法では、/etc/subuid
ファイルおよび /etc/subgid
ファイルで使用されるすべてのユーザー名前空間は 連続 しています。つまり、マッピングされた範囲はホスト上の UID および GID の範囲にまたがる場合がありますが、ユーザー名前空間内に空の範囲またはマッピングされていない範囲を持つことはできません。
`/etc/subuid` と `/etc/subgid` で複数のユーザーを使用する
同様に、/etc/subuid
ファイルおよび /etc/subuid
ファイル内で複数のユーザーを使用することも可能です。 ただし、コンテナーがコンテナー内のホスト上で同じ UID または GID を使用すると、2 ユーザー間で権限の問題が発生する可能性があるため、使用される範囲が重複しないようにすることが重要です。 このマッピングが即座に問題を引き起こすことはありませんが、潜在的な問題を防ぐためには重複がないことを確認することが強く推奨されます。
`podman`、`buildah`、`skopeo` などのコンテナーツールを使用しているルートレスユーザーによるユーザー名前空間の使用方法
ルートレスコンテナーでは、root 以外の任意のユーザーとして実行されるため、権限が拒否され、任意のユーザーまたはグループとしてプロセスを実行したり、ファイルを操作したりすることはできません。 ここで、"ユーザー名前空間" の概念が必要になります。ユーザー名前空間は、このユーザーが対話したり、成り代わったりすることが許可されているシステム上の UID と GID のマッピングを表します。
/etc/subuid
を使用した、ホスト上の UID が 1000
のユーザー "bob" を例として使用してみましょう。
$ cat /etc/subuid
bob:10000:65536
ユーザーが httpd
イメージを実行するコンテナーを起動すると、コンテナー内のプロセスがコンテナー内の 1001
UID として実行されます。
$ podman run -d -p 8080:80 --name httpd registry.redhat.io/rhel8/httpd-24
$ podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
df2143c98b8e registry.redhat.io/rhel8/httpd-24:latest /usr/bin/run-http... 7 seconds ago Up 7 seconds ago 0.0.0.0:8080->80/tcp httpd
コンテナー内では、default
ユーザーが UID 1001
として使用されており、UID マッピングは、プロセスの /proc
にある uid_map
ファイルを介してコンテナー内から確認できます。
$ podman exec -it httpd id
uid=1001(default) gid=0(root) groups=0(root)
$ podman exec -it httpd cat /proc/self/uid_map
0 1000 1
1 10000 65536
この default
ユーザーとしてコンテナー内にファイルを作成し、さらにコンテナー内からこのファイルを検査すると、ファイルが 1001
によって所有されていることがわかります。
$ podman exec -it httpd touch /var/www/html/testfile
$ podman exec -it httpd ls -n /var/www/html/testfile
-rw-r--r--. 1 1001 0 0 May 3 14:49 /var/www/html/testfile
しかし、ホストからユーザー名前空間を考慮せずに手動で検査すると、default
ユーザーの UID 1001
がホスト上では 11000
として示されます。
$ podman inspect httpd | jq '.[].GraphDriver.Data.UpperDir'
"/home/bob/.local/share/containers/storage/overlay/4a983d97bfa19d3cef195e52a5897fc83a9b7af4d7c5cac756ed7082c43203ff/diff"
$ ls -n /home/bob/.local/share/containers/storage/overlay/4a983d97bfa19d3cef195e52a5897fc83a9b7af
4d7c5cac756ed7082c43203ff/diff/var/www/html/testfile
-rw-r--r--. 1 11000 1000 0 May 3 10:49 /home/bob/.local/share/containers/storage/overlay/4a983d97bfa19d3cef195e52a5897fc83a9b7af4d7c5cac756ed7082c43203ff/diff/var/www/html/testfile
これは前述のマッピングによるもので、ホスト上のユーザー 11000
は、/etc/subuid
ファイルでマッピングされているユーザー bob
に属する範囲内にあり、これに基づき bob
はホスト上でこの UID としてファイルを作成したりプロセスを開始したりできます。
これは、ルートレス環境を実行しているときに特定のユーザーとしてコンテナー内で操作を実行する場合に不可欠です。この方法を使用すると、/etc/subuid
ファイルと /etc/subgid
ファイルを適切に設定した通常のユーザーは、コンテナー内で他のユーザーに成り代わることができ、コンテナー内で完全な Linux 環境を表現できますが、ホスト上の安全な ID 範囲にマッピングされます。コンテナー内のユーザーが名前空間環境を離れても、ユーザー "bob" が持っていない権限は取得できません。
ルートユーザーがコンテナーにユーザー名前空間を使用しない理由
podman
、buildah
、skopeo
などのコンテナーツールを非 root ユーザーとして実行する場合、特定の状況では特別な注意を払う必要があります。特に、コンテナー内でユーザー ID とグループ ID が適切に割り当てられ、管理されていること、およびコンテナーを最初に作成したユーザーにホスト上で簡単に変換できることを確認する必要があります。
たとえば、Red Hat Apache コンテナーイメージを root として実行するとします。 そのようにコンテナーを起動すると、コンテナー内の root ユーザーとしてではなく、UID が 1001 の default
ユーザーとして、httpd
プロセスを実行する httpd
コンテナーが作成されます。
\# podman run -d -p 8080:80 --name httpd registry.redhat.io/rhel8/httpd-24
\# podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bb1b2780d132 registry.redhat.io/rhel8/httpd-24 /usr/bin/run-http... 7 seconds ago Up 6 seconds ago 0.0.0.0:8080->80/tcp httpd
\# podman exec -it httpd id
uid=1001(default) gid=0(root) groups=0(root)
このユーザーがルートフルコンテナーで実行中にファイルを作成したりプロセスを起動したりすると、ホスト上のユーザー 1001
がそれを行っていると認識されます。 この点を証明するために、コンテナー内で UID が 1001
の default
ユーザーとしてファイルを作成できます。
\# podman exec -it httpd touch /var/www/html/testfile.txt
\# podman exec -it httpd ls -lattr /var/www/html/testfile.txt
-rw-r--r--. 1 default root 0 May 3 12:41 /var/www/html/testfile.txt
コンテナー内では、想定どおり default
ユーザーが所有していることが明示されます。 コンテナー内ではなくホスト自体のファイルの場所を取得すると、作成されたファイルがホスト上の 1001
によって直接所有されていることがわかります。
\# podman inspect httpd | jq '.[].GraphDriver.Data.MergedDir'
"/var/lib/containers/storage/overlay/a9c449534602e0dfe51254d3353a4c51d263857b83401113a164bc17c0251844/merged"
\# ls -lattr /var/lib/containers/storage/overlay/a9c449534602e0dfe51254d3353a4c51d263857b83401113a164bc17c0251844/merged/var/www/html/testfile.txt
-rw-r--r--. 1 1001 root 0 May 3 08:41 /var/lib/containers/storage/overlay/a9c449534602e0dfe51254d3353a4c51d263857b83401113a164bc17c0251844/merged/var/www/html/testfile.txt
ホストにはユーザー 1001
は設定されていませんが、ルートフルコンテナー内でそのユーザーが作成したファイルは、コンテナーが設定した UID と GID を使用して作成されています。 ルートフルコンテナーの場合、これは問題になりません。なぜなら、ほとんどの場合において、これらのユーザーの存在はコンテナー内に制限され、bind でマウントされたボリュームを除き、ホストに書き込むことはないからです。 ホスト上の root ユーザーは、UID と GID を切り替えるだけで簡単に他のユーザーに成り代わることができます。
コンテナー内のユーザー名前空間に関する問題のトラブルシューティング
すべてのシナリオを網羅できるわけではありませんが、一般的なトラブルシューティング手順のリストを以下に示します。
直面している問題や使用している環境に当てはまる状況がなく、有効なサポート契約をお持ちの場合は、テクニカルサポート にサポートを依頼してください。
ユーザー名前空間が有効になっているかの確認方法
ユーザー名前空間は完全に無効にすることができます。カーネルの調整可能なパラメーターで名前空間の数を制限することで、ユーザー名前空間を許可または拒否します。
ユーザー名前空間の現行の制限は、次のコマンドで確認できます。
$ sysctl user.max_user_namespaces
user.max_user_namespaces = 14803
root ユーザーの場合、この値を 0
に変更してユーザー名前空間を無効にするか (非 root ユーザーが podman
、skopeo
、buildah
などのツールを使用できないようにする)、0 より大きい値に変更できます。 これを一時的に設定するには、root ユーザーが任意の値を指定して次のコマンドを実行します。
\# sysctl -w user.max_user_namespaces=63556
永続性を保つには、値が適切な sysctl.d
ファイルにあることを確認する必要があります。
\# echo "user.max_user_namespaces = 63556" >> /etc/sysctl.d/user-ns.conf
適切な値をここで具体的に示すことはできません。環境と予想されるワークロードに適したものを選択してください。
ユーザー ID またはグループ ID のマッピングが正常に機能しているかの確認方法
ユーザー名前空間が /etc/subuid
ファイルまたは /etc/subgid
ファイルから適切にマッピングされているかを確認するには、podman unshare
コマンドを使用して現在のマッピングを取得します。
以下を含む /etc/subuid
ファイルの場合:
$ cat /etc/subuid
bob:10000:65536
次のコマンドを実行すると、このような出力が生成されます。
$ podman unshare cat /proc/self/uid_map
0 1000 1
1 10000 65536
これは、コンテナー内の UID 0
がホスト上の UID 1000
にマップされ、UID 1
から 65535
がホスト上の UID 10000
から UID 165535
にマップされた (65536
個の UID に拡張された) ことを示しています。
エラーがある場合、または範囲が /etc/subuid
と正確に一致しない場合は、ファイルの構文が正しいことを確認するか、Red Hat サポートにお問い合わせください。
コンテナー内の UID または GID がユーザーの既存範囲で対応できる大きさかの確認方法
podman
、skopeo
、または buildah
コマンドをルートレスユーザーとして実行すると、1 ユーザーに対して非常に大きな UID または GID が要求されることがあります。 これは、コンテナーイメージの作成者が、ルートレスユーザーが使用できる範囲を超えた UID を持つユーザーをコンテナー内に作成した場合に発生します。 たとえば次のエラーが発生したとします。
requested 1000320999:12 for /home/largeuid: lchown /home/largeuid: invalid argument
おそらく、UID 1000320999
は /etc/subuid
ファイル内でユーザーに許可されていません。 この問題を解決するには、該当するユーザーの UID 範囲を拡張して 1000320999
も含める必要があります。以下はその例です。
bob:1000000:1000400000
上記の設定がご使用の環境に適切ではない場合は、調整が必要になります。 この解決策の場合、UID 1000000
から UID 1001400000
への単一の名前空間マッピングがホストにマップされ、コンテナー内の UID 1
から 1000400000
までが含まれます。
残念ながら、この解決策では、他のユーザーが使用できない UID がホスト上に大量に作成され、LDAP や Active Directory などの他の操作にこれらの UID を使用する環境で問題が発生する可能性があります。 現在、/etc/subuid
ファイルまたは /etc/subgid
ファイルを使用して、ホスト上の ID を除外した範囲を作成する方法はなく、数値の大きい ID までの範囲全体をホスト上に割り当てる必要があります。
Comments