本記事は 2021 年 3 月 9 日に公開した英語ブログ 10 Kubernetes Security Context settings you should understand を日本語化した内容です。
はじめに
Kubernetes でワークロードをセキュアに実行するのは手間がかかります。さまざまな設定が Kubernetes API のセキュリティに影響を与えるため、正しく実装するためにはかなりの知識が必要です。Kubernetes がこの領域で提供する最も強力なツールに securityContext
設定があり、ポッド (Pod) やコンテナのマニフェスト (各種リソースの定義) で常に活用できます 。このチートシートを活用して、さまざまな securityContext
設定について、それらの意味と適切な使用法を確認していきましょう。
- runAsNonRoot
- runAsUser / runAsGroup
- seLinuxOptions
- seccompProfile
- 特権コンテナと特権付与を避ける
- Linux カーネルのケイパビリティ
- 読み込み専用ファイルシステムを用いる
- procMount
- fsGroup / fsGroupChangePolicy
- sysctls
ポッドとコンテナの設定
Kubernetes の securityContext
設定は PodSpec と ContainerSpec の両方の API で定義されており、この記事ではそれぞれの設定の横に [P] と [C] の注釈を付けて、対象領域を表示しています。 ある設定が両方の対象領域で利用可能で設定されている場合、コンテナでの設定が優先されることに注意してください。
それでは、順不同で securityContext
の設定を見ていきましょう。
1. runAsNonRoot [P/C]
コンテナは名前空間 (namespace) とコントロールグループ (cgroups) を使用してプロセスを制限していますが、デプロイメント設定に 1 つ誤りがあるだけで、プロセスによるホスト上リソースへのアクセスを許可してしまいます。プロセスが root として実行された場合、これらリソースに対してホストの root アカウントと同じアクセス権が与えられます。 さらに、他のポッド設定またはコンテナ設定が制約を減らすために使用されている場合 (例えば procMount や capabilities)、root UID を使うことは悪用のリスクを増大させます。よほどの理由がない限り、コンテナを root で実行してはいけません。
では、デプロイするイメージで root が使用されている場合、どうすれば良いでしょうか?
オプション1: ベースイメージで提供されたユーザーを使用する
多くの場合、ベースイメージに作成済みのユーザーが存在し利用可能で、その利用は開発チームやデプロイメントチームに任されています。例えば、公式の Node.js イメージには UID 1000 の node
というユーザーが存在しており実行できます。しかし、Dockerfile では node
を実行ユーザーとして明示的に設定していません。 runAsUser 設定で実行時に設定するか、Dockerfileを使ってイメージ内の実行ユーザーを変更する必要があります。前者は、UID 1000 がアプリケーションディレクトリ内のファイルを読めることを前提としてます。そこで代わりに、Dockerfile を使用して独自のイメージを構築する例を見てみましょう。
イメージの構築についてはあまり立ち入らないことにして、ビルド済みの npm アプリケーションがあると仮定します。以下は、node:slim
に基づいてイメージを構築し、作成済みの node ユーザとして実行する最小限の Dockerfile です。
FROM node:slim
COPY --chown=node . /home/node/app/ # <--- Copy app into the home directory with right ownership
USER 1000 # <--- Switch active user to “node” (by UID)
WORKDIR /home/node/app # <--- Switch current directory to app
ENTRYPOINT ["npm", "start"] # <--- This will now exec as the “node” user instead of root
USER
で始まっている行が重要です。node
をこのイメージから起動するコンテナ内のデフォルトユーザーにします。ユーザー名ではなくUIDを使用しているのは、runAsNotRoot: true
を指定してデプロイすると、Kubernetes がコンテナの起動前にイメージのデフォルトユーザー名を UID にマッピングできず、エラーが返されるためです。
オプション2: ベースイメージでユーザーが提供されない場合
では、node のベースイメージがあらかじめユーザーを提供していない場合、どうすれば良いでしょうか。 一般的には、Dockerfile でユーザーを作成してそれを使用します。先ほどの例に追加してみましょう。
FROM node:slim
RUN useradd somebody -u 10001 --create-home --user-group # <--- Create a user
COPY --chown=somebody . /home/somebody/app/
USER 10001
WORKDIR /home/somebody/app
ENTRYPOINT ["npm", "start"]
ユーザーを作成する RUN
行だけを追加しました。この構文はベースイメージのディストリビューションによって異なります。また後の行では、ユーザーとパスの指定を適宜変更しました。
注: これは node.js と npm ではうまくいきますが、ツールによってはファイルシステム上の別の部分で所有権変更が必要となることがあります。何か問題が発生した場合は、ツールのドキュメントを参照してください。
2. runAsUser / runAsGroup [P/C]
コンテナイメージには、特定のユーザーやグループとしてプロセスを実行するよう設定されている場合があります。これは runAsUser
および runAsGroup
設定で上書きできます。マウントされたボリューム内ファイルの所有者 ID に合わせて設定するのが一般的です。
...
spec:
containers:
- name: web
image: mycorp/webapp:1.2.3
securityContext:
runAsNonRoot: true
runAsUser: 10001
...
コンテナに対する runAsUser の設定例
権限の問題でイメージを実行できない可能性があるため、この設定の使用には注意が必要です。たとえば、jenkins/jenkins
という CI 公式サーバーイメージは、jenkins:jenkins
というグループ:ユーザーとして実行され、アプリケーションファイルはすべてそのユーザーが所有します。別のユーザーを設定した場合、そのユーザーがイメージの /etc/passwd
ファイルに存在しないため、起動に失敗します。仮に存在したとしても、jenkins:jenkins
が所有するファイルへの読み書きに問題が発生するでしょう。これは docker run
コマンドを使って、簡単に確認できます。
$ docker run --rm -it -u eric:eric jenkins/jenkins
docker: Error response from daemon: unable to find user eric: no matching entries in passwd file.
上で述べたように、コンテナのプロセスを root ユーザーで実行しないことが望ましいですが、runAsUser
や runAsGroup
の設定に依存してはいけません。将来、誰かがこの設定を削除する可能性があるからです。runAsNonRoot も必ず true に設定してください。
3. seLinuxOptions [P/C]
SELinux は、Linux システム上のアプリケーション、プロセス、ファイルへのアクセスを制御するためのポリシーベースのシステムで、Linux カーネルに Linux Security Modules フレームワークを実装したものです。SELinux は、ラベルという概念に基づいています。このラベルは、要素の集合であるシステム内の全要素に一括で適用されます。ラベルはセキュリティコンテキストとして知られていて (Kubernetes の securityContext
と混同しないでください)、user
、role
、type
、およびオプションのフィールドである level
で構成されており、フォーマットは user:role:type:level
です。
SELinux は、あるコンテキストのどのプロセスが、システム内のラベル付けされたオブジェクトへアクセス可能か、ポリシーによって定義します。SELinux では、アクセスを拒否する厳格モード (Enforcing) と、アクセスをログに記録する寛容モード (Permissive) を設定することができます。SELinux のコンテナにおける一般的な使い方では、コンテナプロセスおよびコンテナイメージにラベルを付け、プロセスがイメージ内のファイルにのみアクセスするよう制限します。
デフォルトの SELinux ラベルは、コンテナのインスタンス化のタイミングでコンテナランタイムによって適用されます。securityContext
の seLinuxOptions 設定を使用すると、カスタムの SELinux ラベルを適用することができます。コンテナの SELinux ラベルを変更すると、「コンテナエスケープ」攻撃の可能性があることに注意が必要です。これはコンテナ化されたプロセスがコンテナイメージを抜け出して、ホストのファイルシステムにアクセスする攻撃です。
この機能は、ホスト OS が SELinux をサポートしている場合にのみ適用されることにも注意してください。
4. seccompProfile [P/C]
Seccomp は secure computing mode の略で、Linux カーネルの機能の一つであり、特定プロセスによるユーザー空間からカーネルへのシステムコールを制限することができます。seccomp プロファイルは JSON 形式で定義され、一連のシステムコールと、各システムコールに対応して実行されるデフォルトのアクションが指定できます。
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"name": "accept",
"action": "SCMP_ACT_ALLOW",
"args": []
},
{
"name": "accept4",
"action": "SCMP_ACT_ALLOW",
"args": []
},
...
]
}
seccomp プロファイル例 (https://training.play-with-docker.com/security-seccomp/ から引用)
Kubernetes はカスタムプロファイルを適用するための仕組みを、 securityContext
の seccompProfile
設定により提供します。
seccompProfile:
type: Localhost
localhostProfile: profiles/myprofile.json
type フィールドには3つの値を指定することができます。
-
Localhost
: コンテナ内の seccomp プロファイルへのパスを提供する localhostProfile 設定を伴います -
Unconfined
: プロファイルを適用しない設定 -
RuntimeDefault
: コンテナランタイムのデフォルト値を適用 (type 未指定時のデフォルト値)
これらの設定は、PodSecurityContext または securityContext のどちらかで指定できます。両方が設定されている場合は、 securityContext のコンテナレベル設定が適用されます。 securityContext の設定 API は Kubernetes v1.19 でリリースされたことに注意してください。以前のバージョンにデプロイする場合は構文が異なるため、Kubernetesのドキュメントサイトで詳細と例を参照してください。
ほとんどのセキュリティ関連の設定と同様に、ここでも最小特権の原則を心がけてください。コンテナが必要とする権限のみを与え、それ以上の権限を与えないようにします。まず、実行されているシステムコールを単純に記録するプロファイルを作成し、次にアプリケーションをテストして、許可するシステムコールを決定します。このプロセスの詳細については、Kubernetes のチュートリアルを参照してください。
5. 特権コンテナと特権付与を避ける [C]
コンテナに特権 (privileged) を与えることは危険です。特定の権限を簡易的に渡す方法として利用されますが、同じことはケイパビリティ (capabilities) により管理できます。コンテナランタイムは特権フラグを制御しますが、事実上コンテナにすべての権限を付与し、デバイスの cgroup コントローラーによる制限を解除します。また、Linux セキュリティモジュールの設定を変更し、コンテナ内のプロセスがコンテナから抜け出すこと (コンテナエスケープ) を可能にします。
コンテナはホスト内でプロセスの分離を行うため、コンテナが root として実行されていても、コンテナランタイムがコンテナに付与していない権限が存在します。しかし特権フラグを設定すると、コンテナランタイムはシステムの root が持つ全権限を許可するため、基盤となるホストシステムへのフルアクセスが可能になり、セキュリティの観点から非常に危険な状態になります。
特権フラグの使用は避け、コンテナで追加の権限が必要な場合は、ケイパビリティ設定で必要な権限のみを追加してください。コンテナがホストカーネルのシステムレベル設定を制御する必要がある場合 (特定のハードウェアへのアクセスやネットワークの再構成など)、およびホストファイルシステムにアクセスする必要がある場合を除いて、特権フラグは必要ありません。
特権コンテナについて詳しくは、Matt のブログ記事「Docker の特権コンテナは本当に必要か? (Privileged Docker containers—do you really need them?)」をご覧ください。
6. Linux カーネルのケイパビリティ [C]
ケイパビリティ (capabilities) はカーネルレベルの権限で、一律に root ですべてを実行する代わりに、カーネルコールの権限の制御を可能にするものです。ケイパビリティには、ファイル権限の変更、ネットワークサブシステムの制御、システム全体の管理機能の実行といった機能が含まれます。securityContext
において、Kubernetes はケイパビリティ内の権限を削除または追加する設定を提供します。個々の権限、もしくは、カンマで区切ったリストを文字配列で渡すことができます。または、-all
を使用してすべての権限を追加または削除することもできます。この構成はコンテナランタイムに渡され、コンテナの作成時に権限の組み合わせを構成します。securityContext
に capabilities セクションが存在しない場合、コンテナランタイムが提供するデフォルトの権限の組み合わせがコンテナに提供されます。
securityContext:
capabilities:
drop:
- all
add: ["MKNOD"]
すべてのケイパビリティを削除し、アプリケーションが実際に必要とする権限のみを追加することをおすすめします。多くの場合、アプリケーションの通常操作ではいかなる権限も不要です。すべての権限を削除してテストし、監査ログを監視してエラーをデバッグすることで、どの権限がブロックされたかを確認できます。
securityContext
で削除または追加する権限をリストアップするとき、カーネルが権限の名前の冒頭につける CAP_
を必ず削除してください。デバッグのために capsh
ツールを使用すると、コンテナで有効になっているケイパビリティを人間が読める形で正確に出力することができます。このツールはほとんどのディストリビューションで利用可能です。ただし本番用コンテナでこのツールを利用可能なまま残しておかないでください。どの権限が有効になっているか、攻撃者が簡単に調べられるようになってしまいます。ビットマップを読むのが好きな人は、/proc/1/status
ファイルで有効化された権限を確認することもできます。
コンテナのデフォルトのケイパビリティを削除して、Kubernetes のセキュリティを向上させる方法を学ぶこともできます。
7. 読み込み専用ファイルシステムを用いる [C]
コンテナが侵入され、かつ読み書き可能なファイルシステムを持っている場合、攻撃者はその設定を変更、ソフトウェアをインストールできるため、他の攻撃を実行する可能性があります。読み取り専用のファイルシステムを使用することで、攻撃者が実行できるアクションを制限し、この種の被害拡大を防止することができます。一般にコンテナでは、コンテナのファイルシステムへの書き込みを必要としないはずです。アプリケーションにステートフルなデータがある場合は、データベースやボリューム、その他のサービスなど、外部でデータ保存を行うべきです。 加えてすべてのログを、標準出力やログ転送装置、またはその両方に書き出し、一元的に照合できるようにしましょう。
8. procMount [C]
デフォルトでコンテナランタイムは、潜在的なセキュリティ問題を防ぐために、コンテナ内部に対して /proc
ファイルシステムの特定の部分を隠しています。しかし、/proc
の隠れた部分へのアクセスが必要な場合もあります。特に、クラスタでのビルドプロセスの一部としてよく使用される、ネストされたコンテナ (コンテナ内でコンテナを起動) を使用する場合に必要となります。この実現には、有効な選択肢は2つしかありません。標準のコンテナランタイムの動作を維持する Default
、または /proc
ファイルシステムの隠蔽をすべて解除する Unmasked
です。
自分のしていることを本当に理解している場合にのみ、この方法を使ってください。イメージのビルドの目的で使用している場合、ビルドツールの最新バージョンを確認しましょう。実は多くの場合、この設定は不要です。アップグレードして、使用しているツールで有効なデフォルトの procMount
に戻してください。
最後に、もしどうしてもこの方法を使う必要があるなら、ネストされたコンテナに対してだけ使ってください。ホストシステムの /proc
ファイルシステムは決してコンテナに公開しないでください。
9. fsGroup / fsGroupChangePolicy [P]
fsGroup
設定でグループを定義すると、ポッドがボリュームをマウントしたときに、Kubernetes はボリューム内の全ファイルのアクセス権限をそのグループに与えます。この動作は fsGroupChangePolicy
によっても制御され、onRootMismatch
または Always
を設定できます。onRootMismatch
に設定すると、コンテナのルートディレクトリのパーミッションと一致しない場合のみ、パーミッションが変更されます。
fsGroup
の使用には注意が必要です。ボリューム全体のグループ所有権を変更すると、低速または大規模なファイルシステムでポッド起動の遅くなるかもしれません。また、同じボリュームを共有する他のプロセスが新しい GID に対するアクセス権限を持っていない場合、そのプロセスにも悪影響が及ぶ可能性があります。このため、NFS などの共有ファイルシステムのプロバイダーの中には、この機能を実装していないものがあります。また、これらの設定はエフェメラル (一時的) なボリュームには影響しません。
10. sysctls [P]
Sysctls は Linux カーネルの機能で、管理者がカーネルの設定を変更することができます。一般的な Linux オペレーティングシステムでは /etc/sysctl.conf
を使用して定義され、また sysctl
ユーティリティを使用して変更することができます。
securityContext
の sysctls
設定を使用すると、コンテナ内で特定の sysctls を変更することができます。オペレーティングシステムの sysctls のうち、カーネル名前空間にあってコンテナ毎に変更できるのはごくわずかなサブセットのみです。このサブセットのうち、いくつかは安全だと考えられます。他のポッドに影響を与える可能性により、大部分の項目は安全でないと見なされます。安全でない sysctls の項目はクラスタでは一般に無効になっており、クラスタ管理者が個別に有効にする必要があります。
基盤となるオペレーティングシステムを不安定にする可能性を考えると、sysctls
を使ったカーネルパラメーターの変更は、特定の要件がない限り避けるべきです。また、そのような変更はクラスタのオペレーターと一緒にレビューするべきです。
実行時の securityContext に関する注意点
多くの場合、ここで説明したセキュリティ設定とポリシーベースの Admission Control (Kubernetes API Server のリクエスト制御機能) を組み合わせて、コンテナをクラスタに起動する前に必要な設定を確認することができます。securityContext
設定と PodSecurityPolicy
を組み合わせ、特定の securityContext 設定を強制することで、ポリシーに従ったコンテナのみが起動されます。securityContext
設定は、Dynamic Admission Control と mutating webhooks 機能の使用によって、起動時にコンテナ構成へ追加することもできます。
結論
securityContext
設定を使用してアプリケーションのデプロイメントを堅牢にするには、留意すべき点が多くあります。適切に設定すればとても効果的に活用できます。ワークロードと環境に適した設定をあなたのチームが実施するために、この 10 個のリストを役立ててください。Snyk は Kubernetes の yaml ファイルをスキャンすることで、一般的な設定ミスを検知することができます。