Translated message

A translation of this page exists in English.

ルートレスコンテナーのユーザー名前空間について

更新 -

Table of Contents

はじめに

この記事では、ユーザー名前空間の概念について、例を使用して説明します。特に、podmanbuildahskopeo など、非ルートユーザーとして実行される場合にユーザー名前空間を活用するコンテナー化テクノロジーでの適用について説明します。

この記事の内容を完全に理解するために、前提として必要な知識がいくつかあります。 次の概念について理解しておくことが推奨されます。

コンテナーにおけるユーザー名前空間とは

ユーザー名前空間は、非ルートユーザーがホストシステム上の 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" が持っていない権限は取得できません。

ルートユーザーがコンテナーにユーザー名前空間を使用しない理由

podmanbuildahskopeo などのコンテナーツールを非 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 が 1001default ユーザーとしてファイルを作成できます。



\# 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 ユーザーが podmanskopeobuildah などのツールを使用できないようにする)、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 がユーザーの既存範囲で対応できる大きさかの確認方法

podmanskopeo、または 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