TT Lab
はじめる
学ぶ 学習パス コース

Kubernetesディストリビューション — 自分で立てる

OpenShift の任意 UID と SCC — まずイメージが備えていなければならない

TT Labで続きを見る

一言でいうと

OpenShiftは、コンテナをイメージのUSERではなく、プロジェクトごとに割り当てられた任意のUIDで動かし、そのユーザーをrootグループ(GID 0)に入れます。そのため、書き込むディレクトリをrootグループ所有でグループ書き込み可能にしたイメージだけが、そのまま移行できます。

なぜ必要なのか

通常のKubernetesでは、イメージにUSERがなければrootで、あればそのユーザーで動きます。そのため、多くのイメージが「rootが作ったディレクトリにrootが書き込む」という前提の上に成り立っています。OpenShiftは、この前提を受け入れません。OCP 4.21のイメージ作成ガイドラインは、デフォルトで任意に割り当てたUIDでコンテナを動かすと述べ、その理由を、コンテナエンジンの脆弱性でプロセスがエスケープしても、ホストで高い権限を得られないようにするためだと説明しています。

UIDは毎回変わりうるので、イメージの作成者は「どのユーザーで動くか」を事前に知ることができません。そのためガイドラインは、所有者の代わりにグループを基準にします。コンテナのユーザーは常にrootグループのメンバーなので、書き込むディレクトリとファイルをrootグループ所有にし、グループに読み取り・書き込み(実行ファイルなら実行)の権限を与えれば、どのUIDが来ても動きます。ガイドラインの例がchgrp -R 0 <디렉터리> && chmod -R g=u <디렉터리>です(プレースホルダーはディレクトリです)。また、このユーザーには特権がないので、1024未満のポートは開けません。

先にお断りしておくことが1つあります。OpenShiftはシングルノードのインストールでも、最低8 vCPU・16GB RAM・120GBのストレージが必要とドキュメントに書かれていて、8GiBのこのラボのVMでは動かせません。この記事のOpenShiftの動作はドキュメントで確認したもので、次のラボはk3sで同じ条件を手で作り、症状を再現します。

どう動くのか

UIDを選ぶのはSCC(Security Context Constraints)です。OCP 4.19 SCCドキュメントによると、認証されたユーザーがデフォルトで使うSCCはrestricted-v2で、次のような性質を持ちます。

runAsUser        MustRunAsRange   범위를 네임스페이스 주석에서 가져온다
seLinuxContext   MustRunAs        MCS 레벨도 네임스페이스 주석에서
fsGroup          MustRunAs        supplemental-groups 주석, 없으면 uid-range 로
capabilities     ALL 을 떨어뜨림   NET_BIND_SERVICE 만 명시적으로 더할 수 있다
seccomp          runtime/default
allowPrivilegeEscalation  설정하지 않거나 false 여야 한다

このコードブロックの韓国語の部分は、順に、範囲をネームスペースのアノテーションから取得すること、MCSレベルもネームスペースのアノテーションから取得すること、supplemental-groupsのアノテーションを使い、なければuid-rangeを使うこと、ALLをドロップし、NET_BIND_SERVICEだけを明示的に追加できること、allowPrivilegeEscalationは設定しないかfalseでなければならないことを述べています。

範囲は、プロジェクト(ネームスペース)のopenshift.io/sa.scc.uid-rangeアノテーションで、<시작>/<길이>のブロックを1つ受け取ります(プレースホルダーは開始値と長さです)。PodがrunAsUserを要求しなければ、範囲の最小値がデフォルト値になります。バージョンによる違いもあります。4.19のドキュメントではrestricted-v2がデフォルトですが、4.20と4.21 SCCドキュメントには、ユーザーネームスペース(hostUsers: false)を強制するrestricted-v3が、新規インストールのデフォルトとして追加されました。どちらの場合も、範囲のUIDで動くという点は同じです。

SCCとは別に、Pod Security Admissionも動きます。4.21 Pod Security Admissionドキュメントは、両者を独立した2つの仕組みとして説明しています。グローバルにはprivilegedを強制し、restrictedは警告と監査にだけ使い、ネームスペースの警告・監査ラベルは、サービスアカウントが使えるSCCに合わせて自動的に同期されます。ワークロードは両方を通過する必要があります。

現場での姿

k3sのVMで、restricted-v2が埋める値(uid 1000680000、gid 0、fsGroup)を直接入れ、よくあるレガシーイメージ(rootが作った/app/data、権限755)を動かした結果です(実測)。

id: uid=1000680000 gid=0(root) groups=0(root),1000680000
whoami: whoami: unknown uid 1000680000
HOME=/
startup failed: cannot write /app/data/started
/app/run.sh: line 6: can't create /app/data/started: Permission denied

ここでよく試される回避策を3つ、同じVMで測ってみました(実測)。

イメージをchgrp -R 0 /app && chmod -R g=u /appで直すと、範囲の最初のUIDと最後のUID(1000689999)のどちらでも起動しました。このとき、もう一度引っかかりました。スクリプトファイルに実行ビットがなかった最初のビルドは、exec: "/app/run.sh": permission deniedでRunContainerErrorになりました。ガイドラインが実行ファイルにグループの実行権限を求める理由です。

whoamiの失敗とHOME=/は、containerdで見た姿です。OpenShiftのドキュメントは、CRI-Oが任意のUIDをコンテナの/etc/passwdに入れてくれることがあると説明しているので、OpenShiftでは名前の参照ができることがあります。ただし4.21のドキュメントは、同じ箇所で、イメージに/etc/passwdが入っているとCRI-Oがその注入に失敗して、実行中のUIDを解決できないことがあるとも書き、そのファイルの権限を開放する方法は危険だと警告しています。そのため、「うちのk3sでwhoamiが動かない」をOpenShiftの症状として書き写してはいけません。一方、HOMEにキャッシュを書き込むツールは、どこでもHOMEを書き込み可能なパスに向けておくほうが安全です。

実務で本当に大切なこと

次のラボですること

k3sのVMにOpenShiftのプロジェクトを模したネームスペースを作り、レガシーイメージが任意のUIDで権限拒否により止まることを記録します。ユーザー情報の調査、buildahでのイメージの修正とk3sへの移行、2つのUIDでの起動、fsGroup・emptyDir・root initContainerの比較、読み取り専用のルートまで試したうえで、レポートにまとめます。