知っておきたい Kubernetes Security Context 設定 10 選

本記事は 2021 年 3 月 9 日に公開した英語ブログ [10 Kubernetes Security Context settings you should understand](https://snyk.io/blog/10-kubernetes-security-context-settings-you-should-understand/?utm_source=Qiita&utm_medium=Referral&utm_campaign=QiitaBlog) を日本語化した内容です。

[![logo-vertical-solid-background.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2216557/538611f7-03c6-0644-1508-c56a13c5bcf0.png)](https://snyk.io/jp?utm_source=Qiita&utm_medium=Referral&utm_campaign=QiitaBlog)

## はじめに

Kubernetes でワークロードをセキュアに実行するのは手間がかかります。さまざまな設定が [Kubernetes API のセキュリティ](https://snyk.io/learn/kubernetes-security/?utm_source=Qiita&utm_medium=Referral&utm_campaign=QiitaBlog)に影響を与えるため、正しく実装するためにはかなりの知識が必要です。Kubernetes がこの領域で提供する最も強力なツールに `securityContext` 設定があり、ポッド (Pod) やコンテナのマニフェスト (各種リソースの定義) で常に活用できます 。このチートシートを活用して、さまざまな `securityContext` 設定について、それらの意味と適切な使用法を確認していきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2216557/59bb7224-c96f-3a8d-2907-096902eb9167.png)

1. runAsNonRoot
2. runAsUser / runAsGroup
3. seLinuxOptions
4. seccompProfile
5. 特権コンテナと特権付与を避ける
6. Linux カーネルのケイパビリティ
7. 読み込み専用ファイルシステムを用いる
8. procMount
9. fsGroup / fsGroupChangePolicy
10. sysctls

## ポッドとコンテナの設定

Kubernetes の `securityContext` 設定は [PodSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#podsecuritycontext-v1-core) と [ContainerSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#securitycontext-v1-core) の両方の API で定義されており、この記事ではそれぞれの設定の横に [P] と [C] の注釈を付けて、対象領域を表示しています。 ある設定が両方の対象領域で利用可能で設定されている場合、コンテナでの設定が優先されることに注意してください。

それでは、順不同で `securityContext` の設定を見ていきましょう。

## 1. runAsNonRoot <sup>[P/C]</sup>

コンテナは[名前空間 (namespace)](https://www.linux.com/news/understanding-and-securing-linux-namespaces/) と[コントロールグループ (cgroups)](https://docs.docker.com/config/containers/runmetrics/#control-groups) を使用してプロセスを制限していますが、デプロイメント設定に 1 つ誤りがあるだけで、プロセスによるホスト上リソースへのアクセスを許可してしまいます。プロセスが root として実行された場合、これらリソースに対してホストの root アカウントと同じアクセス権が与えられます。 さらに、他のポッド設定またはコンテナ設定が制約を減らすために使用されている場合 (例えば [procMount](https://snyk.io/blog/10-kubernetes-security-context-settings-you-should-understand/#procmount?utm_source=Qiita&utm_medium=Referral&utm_campaign=QiitaBlog) や [capabilities](https://snyk.io/blog/10-kubernetes-security-context-settings-you-should-understand/#capabilities?utm_source=Qiita&utm_medium=Referral&utm_campaign=QiitaBlog))、root UID を使うことは悪用のリスクを増大させます。よほどの理由がない限り、コンテナを root で実行してはいけません。

では、デプロイするイメージで root が使用されている場合、どうすれば良いでしょうか？

#### <font color="DarkViolet">オプション1: ベースイメージで提供されたユーザーを使用する</font>

多くの場合、ベースイメージに作成済みのユーザーが存在し利用可能で、その利用は開発チームやデプロイメントチームに任されています。例えば、公式の [Node.js イメージ](https://hub.docker.com/_/node)には UID 1000 の `node` というユーザーが存在しており実行できます。しかし、Dockerfile では  `node` を実行ユーザーとして明示的に設定していません。 [runAsUser](https://snyk.io/blog/10-kubernetes-security-context-settings-you-should-understand/#runAsUser-runAsGroup?utm_source=Qiita&utm_medium=Referral&utm_campaign=QiitaBlog) 設定で実行時に設定するか、Dockerfileを使ってイメージ内の実行ユーザーを変更する必要があります。前者は、UID 1000 がアプリケーションディレクトリ内のファイルを読めることを前提としてます。そこで代わりに、Dockerfile を使用して独自のイメージを構築する例を見てみましょう。

イメージの構築についてはあまり立ち入らないことにして、ビルド済みの npm アプリケーションがあると仮定します。以下は、`node:slim` に基づいてイメージを構築し、作成済みの node ユーザとして実行する最小限の Dockerfile です。

``` 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 にマッピングできず、エラーが返されるためです。

#### <font color="DarkViolet">オプション2: ベースイメージでユーザーが提供されない場合</font>

では、node のベースイメージがあらかじめユーザーを提供していない場合、どうすれば良いでしょうか。 一般的には、Dockerfile でユーザーを作成してそれを使用します。先ほどの例に追加してみましょう。

``` 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 <sup>[P/C]</sup>

コンテナイメージには、特定のユーザーやグループとしてプロセスを実行するよう設定されている場合があります。これは `runAsUser` および `runAsGroup` 設定で上書きできます。マウントされたボリューム内ファイルの所有者 ID に合わせて設定するのが一般的です。

``` yaml
...
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` コマンドを使って、簡単に確認できます。

``` shell
$ 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 <sup>[P/C]</sup>

SELinux は、Linux システム上のアプリケーション、プロセス、ファイルへのアクセスを制御するためのポリシーベースのシステムで、Linux カーネルに [Linux Security Modules](https://en.wikipedia.org/wiki/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 <sup>[P/C]</sup>

Seccomp は [secure computing mode](https://en.wikipedia.org/wiki/Seccomp) の略で、Linux カーネルの機能の一つであり、特定プロセスによるユーザー空間からカーネルへのシステムコールを制限することができます。seccomp プロファイルは JSON 形式で定義され、一連のシステムコールと、各システムコールに対応して実行されるデフォルトのアクションが指定できます。

``` 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のドキュメントサイト](https://kubernetes.io/docs/tutorials/clusters/seccomp/#create-a-pod-with-a-seccomp-profile-for-syscall-auditing)で詳細と例を参照してください。

ほとんどのセキュリティ関連の設定と同様に、ここでも最小特権の原則を心がけてください。コンテナが必要とする権限のみを与え、それ以上の権限を与えないようにします。まず、実行されているシステムコールを単純に記録するプロファイルを作成し、次にアプリケーションをテストして、許可するシステムコールを決定します。このプロセスの詳細については、Kubernetes の[チュートリアル](https://kubernetes.io/docs/tutorials/clusters/seccomp)を参照してください。

## 5. 特権コンテナと特権付与を避ける <sup>[C]</sup>

コンテナに特権 (privileged) を与えることは危険です。特定の権限を簡易的に渡す方法として利用されますが、同じことはケイパビリティ (capabilities) により管理できます。コンテナランタイムは特権フラグを制御しますが、事実上コンテナにすべての権限を付与し、デバイスの cgroup コントローラーによる制限を解除します。また、Linux セキュリティモジュールの設定を変更し、コンテナ内のプロセスがコンテナから抜け出すこと (コンテナエスケープ) を可能にします。

コンテナはホスト内でプロセスの分離を行うため、コンテナが root として実行されていても、コンテナランタイムがコンテナに付与していない権限が存在します。しかし特権フラグを設定すると、コンテナランタイムはシステムの root が持つ全権限を許可するため、基盤となるホストシステムへのフルアクセスが可能になり、セキュリティの観点から非常に危険な状態になります。

特権フラグの使用は避け、コンテナで追加の権限が必要な場合は、ケイパビリティ設定で必要な権限のみを追加してください。コンテナがホストカーネルのシステムレベル設定を制御する必要がある場合 (特定のハードウェアへのアクセスやネットワークの再構成など)、およびホストファイルシステムにアクセスする必要がある場合を除いて、特権フラグは必要ありません。

特権コンテナについて詳しくは、Matt のブログ記事「[Docker の特権コンテナは本当に必要か? (Privileged Docker containers—do you really need them?)](https://snyk.io/blog/privileged-docker-containers/?utm_source=Qiita&utm_medium=Referral&utm_campaign=QiitaBlog)」をご覧ください。

## 6. Linux カーネルのケイパビリティ <sup>[C]</sup>

ケイパビリティ (capabilities) は[カーネルレベルの権限](https://man7.org/linux/man-pages/man7/capabilities.7.html)で、一律に root ですべてを実行する代わりに、カーネルコールの権限の制御を可能にするものです。ケイパビリティには、ファイル権限の変更、ネットワークサブシステムの制御、システム全体の管理機能の実行といった機能が含まれます。`securityContext` において、Kubernetes はケイパビリティ内の権限を削除または追加する設定を提供します。個々の権限、もしくは、カンマで区切ったリストを文字配列で渡すことができます。または、`-all` を使用してすべての権限を追加または削除することもできます。この構成はコンテナランタイムに渡され、コンテナの作成時に権限の組み合わせを構成します。`securityContext` に capabilities セクションが存在しない場合、コンテナランタイムが提供するデフォルトの権限の組み合わせがコンテナに提供されます。

``` yaml
securityContext:
      capabilities:
        drop:
          - all
        add: ["MKNOD"]
```

すべてのケイパビリティを削除し、アプリケーションが実際に必要とする権限のみを追加することをおすすめします。多くの場合、アプリケーションの通常操作ではいかなる権限も不要です。すべての権限を削除してテストし、監査ログを監視してエラーをデバッグすることで、どの権限がブロックされたかを確認できます。

`securityContext` で削除または追加する権限をリストアップするとき、カーネルが権限の名前の冒頭につける `CAP_` を必ず削除してください。デバッグのために `capsh` ツールを使用すると、コンテナで有効になっているケイパビリティを人間が読める形で正確に出力することができます。このツールはほとんどのディストリビューションで利用可能です。ただし本番用コンテナでこのツールを利用可能なまま残しておかないでください。どの権限が有効になっているか、攻撃者が簡単に調べられるようになってしまいます。ビットマップを読むのが好きな人は、`/proc/1/status` ファイルで有効化された権限を確認することもできます。

[コンテナのデフォルトのケイパビリティ](https://learn.snyk.io/lessons/container-does-not-drop-all-default-capabilities/kubernetes/?utm_source=Qiita&utm_medium=Referral&utm_campaign=QiitaBlog)を削除して、Kubernetes のセキュリティを向上させる方法を学ぶこともできます。

## 7. 読み込み専用ファイルシステムを用いる <sup>[C]</sup>

コンテナが侵入され、かつ読み書き可能なファイルシステムを持っている場合、攻撃者はその設定を変更、ソフトウェアをインストールできるため、他の攻撃を実行する可能性があります。読み取り専用のファイルシステムを使用することで、攻撃者が実行できるアクションを制限し、この種の被害拡大を防止することができます。一般にコンテナでは、コンテナのファイルシステムへの書き込みを必要としないはずです。アプリケーションにステートフルなデータがある場合は、データベースやボリューム、その他のサービスなど、外部でデータ保存を行うべきです。 加えてすべてのログを、標準出力やログ転送装置、またはその両方に書き出し、一元的に照合できるようにしましょう。

## 8. procMount <sup>[C]</sup>

デフォルトでコンテナランタイムは、潜在的なセキュリティ問題を防ぐために、コンテナ内部に対して `/proc` ファイルシステムの特定の部分を隠しています。しかし、`/proc` の隠れた部分へのアクセスが必要な場合もあります。特に、クラスタでのビルドプロセスの一部としてよく使用される、ネストされたコンテナ (コンテナ内でコンテナを起動) を使用する場合に必要となります。この実現には、有効な選択肢は2つしかありません。標準のコンテナランタイムの動作を維持する `Default`、または `/proc` ファイルシステムの隠蔽をすべて解除する `Unmasked` です。

自分のしていることを本当に理解している場合にのみ、この方法を使ってください。イメージのビルドの目的で使用している場合、ビルドツールの最新バージョンを確認しましょう。実は多くの場合、この設定は不要です。アップグレードして、使用しているツールで有効なデフォルトの `procMount` に戻してください。

最後に、もしどうしてもこの方法を使う必要があるなら、ネストされたコンテナに対してだけ使ってください。ホストシステムの `/proc` ファイルシステムは決してコンテナに公開しないでください。

## 9. fsGroup / fsGroupChangePolicy <sup>[P]</sup>

`fsGroup` 設定でグループを定義すると、ポッドがボリュームをマウントしたときに、Kubernetes はボリューム内の全ファイルのアクセス権限をそのグループに与えます。この動作は `fsGroupChangePolicy` によっても制御され、`onRootMismatch` または `Always` を設定できます。`onRootMismatch` に設定すると、コンテナのルートディレクトリのパーミッションと一致しない場合のみ、パーミッションが変更されます。

`fsGroup` の使用には注意が必要です。ボリューム全体のグループ所有権を変更すると、低速または大規模なファイルシステムでポッド起動の遅くなるかもしれません。また、同じボリュームを共有する他のプロセスが新しい GID に対するアクセス権限を持っていない場合、そのプロセスにも悪影響が及ぶ可能性があります。このため、NFS などの共有ファイルシステムのプロバイダーの中には、この機能を実装していないものがあります。また、これらの設定はエフェメラル (一時的) なボリュームには影響しません。

## 10. sysctls <sup>[P]</sup>

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](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) と mutating webhooks 機能の使用によって、起動時にコンテナ構成へ追加することもできます。

## 結論

`securityContext` 設定を使用してアプリケーションのデプロイメントを堅牢にするには、留意すべき点が多くあります。適切に設定すればとても効果的に活用できます。ワークロードと環境に適した設定をあなたのチームが実施するために、この 10 個のリストを役立ててください。Snyk は Kubernetes の yaml ファイルをスキャンすることで、一般的な設定ミスを検知することができます。

:::note info
ブログを読んで、Snykを使ってみたい！と思われた方は、ぜひ[無料トライアル](https://snyk.co/qiita)をお試しください。トライアル中の技術的なご質問は、[サポート](https://support.snyk.io/hc/en-us)に日本語でお寄せください。
:::

:::note info 
[Snyk](https://snyk.io/jp)では、セキュアなコーディングのための情報や脆弱性の最新情報などを発信しています。ぜひ[Twitter](https://twitter.com/snykjp)や[Facebook](https://www.facebook.com/snykJP/)や[ブログ](https://qiita.com/organizations/snyk)のフォローをお願いします。 
:::