Podとコンテナにセキュリティコンテキストを設定する

セキュリティコンテキストはPod・コンテナの特権やアクセスコントロールの設定を定義します。 セキュリティコンテキストの設定には以下のものが含まれますが、これらに限定はされません。

  • 任意アクセス制御: user ID (UID) と group ID (GID)に基づいて、ファイルなどのオブジェクトに対する許可を行います。

  • Security Enhanced Linux (SELinux): オブジェクトにセキュリティラベルを付与します。

  • 特権または非特権として実行します。

  • Linux Capabilities: rootユーザーのすべての特権ではなく、一部の特権をプロセスに与えます。

  • AppArmor: プロファイルを用いて、個々のプログラムのcapabilityを制限します。

  • Seccomp: プロセスのシステムコールを限定します。

  • allowPrivilegeEscalation: あるプロセスが親プロセスよりも多くの特権を得ることができるかを制御します。 この真偽値は、コンテナプロセスに no_new_privs フラグが設定されるかどうかを直接制御します。 コンテナが以下の場合、allowPrivilegeEscalationは常にtrueになります。

    • コンテナが特権で動いている
    • CAP_SYS_ADMINを持っている
  • readOnlyRootFilesystem: コンテナのルートファイルシステムが読み取り専用でマウントされます。

上記の項目は全てのセキュリティコンテキスト設定を記載しているわけではありません。 より広範囲なリストはSecurityContextを確認してください。

始める前に

Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

バージョンを確認するには次のコマンドを実行してください: kubectl version.

Podにセキュリティコンテキストを設定する

Podにセキュリティ設定を行うには、Podの設定にsecurityContextフィールドを追加してください。 securityContextフィールドはPodSecurityContextオブジェクトが入ります。 Podに設定したセキュリティ設定はPod内の全てのコンテナに適用されます。こちらはsecurityContextemptyDirボリュームを持ったPodの設定ファイルです。

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    supplementalGroups: [4000]
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox:1.28
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo
    securityContext:
      allowPrivilegeEscalation: false

設定ファイルの中のrunAsUserフィールドは、Pod内のコンテナに対して全てのプロセスをユーザーID 1000で実行するように指定します。 runAsGroupフィールドはPod内のコンテナに対して全てのプロセスをプライマリーグループID 3000で実行するように指定します。このフィールドが省略されたときは、コンテナのプライマリーグループIDはroot(0)になります。runAsGroupが指定されている場合、作成されたファイルもユーザー1000とグループ3000の所有物になります。 またfsGroupも指定されているため、全てのコンテナ内のプロセスは補助グループID 2000にも含まれます。/data/demoボリュームとこのボリュームに作成されたファイルはグループID 2000になります。加えて、supplementalGroupsフィールドが指定されている場合、全てのコンテナ内のプロセスは指定されている補助グループIDにも含まれます。もしこのフィールドが指定されていない場合、空を意味します。

Podを作成してみましょう。

kubectl apply -f https://k8s.io/examples/pods/security/security-context.yaml

Podのコンテナが実行されていることを確認します。

kubectl get pod security-context-demo

実行中のコンテナでshellを取ります。

kubectl exec -it security-context-demo -- sh

shellで、実行中のプロセスの一覧を確認します。

ps

runAsUserで指定した値である、ユーザー1000でプロセスが実行されていることが確認できます。

PID   USER     TIME  COMMAND
    1 1000      0:00 sleep 1h
    6 1000      0:00 sh
...

shellで/dataに入り、ディレクトリの一覧を確認します。

cd /data
ls -l

fsGroupで指定した値であるグループID 2000で/data/demoディレクトリが作成されていることが確認できます。

drwxrwsrwx 2 root 2000 4096 Jun  6 20:08 demo

shellで/data/demoに入り、ファイルを作成します。

cd demo
echo hello > testfile

/data/demoディレクトリでファイルの一覧を確認します。

ls -l

fsGroupで指定した値であるグループID 2000でtestfileが作成されていることが確認できます。

-rw-r--r-- 1 1000 2000 6 Jun  6 20:08 testfile

以下のコマンドを実行してください。

id

出力はこのようになります。

uid=1000 gid=3000 groups=2000,3000,4000

出力からrunAsGroupフィールドと同じくgidが3000になっていることが確認できるでしょう。runAsGroupが省略された場合、gidは0(root)になり、そのプロセスはグループroot(0)とグループroot(0)に必要なグループパーミッションを持つグループが所有しているファイルを操作することができるようになります。また、groupsの出力に、gidに加えて、fsGroupssupplementalGroupsフィールドで指定したグループIDも含まれていることも確認できるでしょう。

shellから抜けましょう。

exit

コンテナイメージ内の/etc/groupから暗黙的にマージされるグループ情報

Kubernetesは、デフォルトでは、Podで定義された情報に加えて、コンテナイメージ内の/etc/groupのグループ情報をマージします。

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    supplementalGroups: [4000]
  containers:
  - name: sec-ctx-demo
    image: registry.k8s.io/e2e-test-images/agnhost:2.45
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext:
      allowPrivilegeEscalation: false

このPodはsecurity contextでrunAsUserrunAsGroupsupplementalGroupsフィールドが指定されています。しかし、コンテナ内のプロセスには、コンテナイメージ内の/etc/groupに定義されたグループIDが、補助グループとして付与されていることが確認できるでしょう。

Podを作成してみましょう。

kubectl apply -f https://k8s.io/examples/pods/security/security-context-5.yaml

Podのコンテナが実行されていることを確認します。

kubectl get pod security-context-demo

実行中のコンテナでshellを取ります。

kubectl exec -it security-context-demo -- sh

プロセスのユーザー、グループ情報を確認します。

$ id

出力はこのようになります。

uid=1000 gid=3000 groups=3000,4000,50000

groupsにグループID50000が含まれていることが確認できるでしょう。これは、ユーザー(uid=1000)がコンテナイメージで定義されており、コンテナイメージ内の/etc/groupでグループ(gid=50000)に所属しているためです。

コンテナイメージの/etc/groupの内容を確認してみましょう。

$ cat /etc/group

ユーザー1000がグループ50000に所属していることが確認できるでしょう。

...
user-defined-in-image:x:1000:
group-defined-in-image:x:50000:user-defined-in-image

shellから抜けましょう。

exit

Podにfine-grained(きめ細かい) SupplementalGroups controlを設定する

FEATURE STATE: Kubernetes v1.31 [alpha]

この機能はkubeletとkube-apiseverにSupplementalGroupsPolicy フィーチャーゲートを設定し、Podの.spec.securityContext.supplementalGroupsPolicyフィールドを指定することで利用できます。

supplementalGroupsPolicyフィールドは、Pod内のコンテナプロセスに付与される補助グループを、どのように決定するかを定義します。有効な値は次の2つです。

  • Merge: /etc/groupで定義されている、コンテナのプライマリユーザーが所属するグループをマージします。指定されていない場合、このポリシーがデフォルトです。

  • Strict: fsGroupsupplementalGroupsrunAsGroupフィールドで指定されたグループのみ補助グループに指定されます。つまり、/etc/groupで定義された、コンテナのプライマリユーザーのグループ情報はマージされません。

この機能が有効な場合、.status.containerStatuses[].user.linuxフィールドで、コンテナの最初のプロセスに付与されたユーザー、グループ情報が確認出来ます。暗黙的なグループIDが付与されているかどうかを確認するのに便利でしょう。

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    supplementalGroups: [4000]
    supplementalGroupsPolicy: Strict
  containers:
  - name: sec-ctx-demo
    image: registry.k8s.io/e2e-test-images/agnhost:2.45
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext:
      allowPrivilegeEscalation: false

このPodマニフェストはsupplementalGroupsPolicy=Strictを指定しています。/etc/groupに定義されているグループ情報が、コンテナ内のプロセスの補助グループにマージされないことが確認できるでしょう。

Podを作成してみましょう。

kubectl apply -f https://k8s.io/examples/pods/security/security-context-6.yaml

Podのコンテナが実行されていることを確認します。

kubectl get pod security-context-demo

プロセスのユーザー、グループ情報を確認します。

kubectl exec -it security-context-demo -- id

出力はこのようになります。

uid=1000 gid=3000 groups=3000,4000

Podのステータスを確認します。

kubectl get pod security-context-demo -o yaml

status.containerStatuses[].user.linuxフィールドでコンテナの最初のプロセスに付与されたユーザー、グループ情報が確認出来ます。

...
status:
  containerStatuses:
  - name: sec-ctx-demo
    user:
      linux:
        gid: 3000
        supplementalGroups:
        - 3000
        - 4000
        uid: 1000
...

利用可能な実装

下記のコンテナランタイムがfine-grained(きめ細かい) SupplementalGroups controlを実装しています。

CRI実装:

ノードのステータスでこの機能が利用可能かどうか確認出来ます。

apiVersion: v1
kind: Node
...
status:
  features:
    supplementalGroupsPolicy: true

Podのボリュームパーミッションと所有権変更ポリシーを設定する

FEATURE STATE: Kubernetes v1.23 [stable]

デフォルトでは、Kubernetesはボリュームがマウントされたときに、PodのsecurityContextで指定されたfsGroupに合わせて再帰的に各ボリュームの中の所有権とパーミッションを変更します。 大きなボリュームでは所有権の確認と変更に時間がかかり、Podの起動が遅くなります。 securityContextの中のfsGroupChangePolicyフィールドを設定することで、Kubernetesがボリュームの所有権・パーミッションの確認と変更をどう行うかを管理することができます。

fsGroupChangePolicy - fsGroupChangePolicyは、ボリュームがPod内部で公開される前に所有権とパーミッションを変更するための動作を定義します。 このフィールドはfsGroupで所有権とパーミッションを制御することができるボリュームタイプにのみ適用されます。このフィールドは以下の2つの値を取ります。

  • OnRootMismatch: ルートディレクトリのパーミッションと所有権がボリュームに設定したパーミッションと一致しない場合のみ、パーミッションと所有権を変更します。ボリュームの所有権とパーミッションを変更するのにかかる時間が短くなる可能性があります。
  • Always: ボリュームがマウントされたときに必ずパーミッションと所有権を変更します。

例:

securityContext:
  runAsUser: 1000
  runAsGroup: 3000
  fsGroup: 2000
  fsGroupChangePolicy: "OnRootMismatch"

CSIドライバーにボリュームパーミッションと所有権を移譲する

FEATURE STATE: Kubernetes v1.26 [stable]

VOLUME_MOUNT_GROUP NodeServiceCapabilityをサポートしているContainer Storage Interface (CSI)ドライバーをデプロイした場合、securityContextfsGroupで指定された値に基づいてKubernetesの代わりにCSIドライバーがファイルの所有権とパーミッションの設定処理を行います。 この場合Kubernetesは所有権とパーミッションの設定を行わないためfsGroupChangePolicyは無効となり、CSIで指定されている通りドライバーはfsGroupに従ってボリュームをマウントすると考えられるため、ボリュームはfsGroupに従って読み取り・書き込み可能になります。

コンテナにセキュリティコンテキストを設定する

コンテナに対してセキュリティ設定を行うには、コンテナマニフェストにsecurityContextフィールドを含めてください。securityContextフィールドにはSecurityContextオブジェクトが入ります。 コンテナに指定したセキュリティ設定は個々のコンテナに対してのみ反映され、Podレベルの設定を上書きします。コンテナの設定はPodのボリュームに対しては影響しません。

こちらは一つのコンテナを持つPodの設定ファイルです。PodもコンテナもsecurityContextフィールドを含んでいます。

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-2
spec:
  securityContext:
    runAsUser: 1000
  containers:
  - name: sec-ctx-demo-2
    image: gcr.io/google-samples/node-hello:1.0
    securityContext:
      runAsUser: 2000
      allowPrivilegeEscalation: false

Podを作成します。

kubectl apply -f https://k8s.io/examples/pods/security/security-context-2.yaml

Podのコンテナが実行されていることを確認します。

kubectl get pod security-context-demo-2

実行中のコンテナでshellを取ります。

kubectl exec -it security-context-demo-2 -- sh

shellの中で、実行中のプロセスの一覧を表示します。

ps aux

ユーザー2000として実行されているプロセスが表示されました。これはコンテナのrunAsUserで指定された値です。Podで指定された値である1000を上書きしています。

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
2000         1  0.0  0.0   4336   764 ?        Ss   20:36   0:00 /bin/sh -c node server.js
2000         8  0.1  0.5 772124 22604 ?        Sl   20:36   0:00 node server.js
...

shellから抜けます。

exit

コンテナにケーパビリティを設定する

Linuxケーパビリティを用いると、プロセスに対してrootユーザーの全権を渡すことなく特定の権限を与えることができます。 コンテナに対してLinuxケーパビリティを追加したり削除したりするには、コンテナマニフェストのsecurityContextセクションのcapabilitiesフィールドに追加してください。

まず、capabilitiesフィールドを含まない場合どうなるかを見てみましょう。 こちらはコンテナに対してケーパビリティを渡していない設定ファイルです。

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-3
spec:
  containers:
  - name: sec-ctx-3
    image: gcr.io/google-samples/node-hello:1.0

Podを作成します。

kubectl apply -f https://k8s.io/examples/pods/security/security-context-3.yaml

Podが実行されていることを確認します。

kubectl get pod security-context-demo-3

実行中のコンテナでshellを取ります。

kubectl exec -it security-context-demo-3 -- sh

shellの中で、実行中のプロセスの一覧を表示します。

ps aux

コンテナのプロセスID(PID)が出力されます。

USER  PID %CPU %MEM    VSZ   RSS TTY   STAT START   TIME COMMAND
root    1  0.0  0.0   4336   796 ?     Ss   18:17   0:00 /bin/sh -c node server.js
root    5  0.1  0.5 772124 22700 ?     Sl   18:17   0:00 node server.js

shellの中で、プロセス1のステータスを確認します。

cd /proc/1
cat status

プロセスのケーパビリティビットマップが表示されます。

...
CapPrm:	00000000a80425fb
CapEff:	00000000a80425fb
...

ケーパビリティビットマップのメモを取った後、shellから抜けます。

exit

次に、追加のケーパビリティを除いて上と同じ設定のコンテナを実行します。

こちらは1つのコンテナを実行するPodの設定ファイルです。 CAP_NET_ADMINCAP_SYS_TIMEケーパビリティを設定に追加しました。

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-4
spec:
  containers:
  - name: sec-ctx-4
    image: gcr.io/google-samples/node-hello:1.0
    securityContext:
      capabilities:
        add: ["NET_ADMIN", "SYS_TIME"]

Podを作成します。

kubectl apply -f https://k8s.io/examples/pods/security/security-context-4.yaml

実行中のコンテナでshellを取ります。

kubectl exec -it security-context-demo-4 -- sh

shellの中で、プロセス1のケーパビリティを確認します。

cd /proc/1
cat status

プロセスのケーパビリティビットマップが表示されます。

...
CapPrm:	00000000aa0435fb
CapEff:	00000000aa0435fb
...

2つのコンテナのケーパビリティを比較します。

00000000a80425fb
00000000aa0435fb

1つ目のコンテナのケーパビリティビットマップでは、12, 25ビット目がクリアされています。2つ目のコンテナでは12, 25ビット目がセットされています。12ビット目はCAP_NET_ADMIN、25ビット目はCAP_SYS_TIMEです。 ケーパビリティの定数の定義はcapability.hを確認してください。

コンテナにSeccompプロフィールを設定する

コンテナにSeccompプロフィールを設定するには、Pod・コンテナマニフェストのsecurityContextseccompProfileフィールドを追加してください。 seccompProfileフィールドはSeccompProfileオブジェクトで、typelocalhostProfileで構成されています。 typeではRuntimeDefaultUnconfinedLocalhostが有効です。 localhostProfiletype: Localhostのときのみ指定可能です。こちらはノード上で事前に設定されたプロファイルのパスを示していて、kubeletのSeccompプロファイルの場所(--root-dirフラグで設定したもの)からの相対パスです。

こちらはノードのコンテナランタイムのデフォルトプロフィールをSeccompプロフィールとして設定した例です。

...
securityContext:
  seccompProfile:
    type: RuntimeDefault

こちらは<kubelet-root-dir>/seccomp/my-profiles/profile-allow.jsonで事前に設定したファイルをSeccompプロフィールに設定した例です。

...
securityContext:
  seccompProfile:
    type: Localhost
    localhostProfile: my-profiles/profile-allow.json

コンテナにAppArmorプロフィールを設定する

コンテナにAppArmorプロフィールを設定するには、securityContextセクションにappAormorProfileフィールドを含めてください。 appAormerProfileフィールドには、typeフィールドとlocalhostProfileフィールドから構成されるAppArmorProfileオブジェクトが入ります。typeフィールドの有効なオプションはRuntimeDefault(デフォルト)、UnconfinedLocalhostです。 localhostProfiletypeLocalhostのときには必ず設定しなければなりません。この値はノードで事前に設定されたプロフィール名を示します。Podは事前にどのノードにスケジュールされるかわからないため、指定されたプロフィールはPodがスケジュールされ得る全てのノードにロードされている必要があります。カスタムプロフィールをセットアップする方法はSetting up nodes with profilesを参照してください。

注意: containers[*].securityContext.appArmorProfile.typeが明示的にRuntimeDefaultに設定されている場合は、もしノードでAppArmorが有効化されていなければ、Podの作成は許可されません。 しかし、containers[*].securityContext.appArmorProfile.typeが設定されていない場合、AppArmorが有効化されていれば、デフォルト(RuntimeDefault)が適用されます。もし、AppArmorが無効化されている場合は、Podの作成は許可されますが、コンテナにはRuntimeDefaultプロフィールの制限は適用されません。

これは、AppArmorプロフィールとして、ノードのコンテナランタイムのデフォルトプロフィールを設定する例です。

...
containers:
- name: container-1
  securityContext:
    appArmorProfile:
      type: RuntimeDefault

これは、AppArmorプロフィールとして、k8s-apparmor-example-deny-writeという名前で事前に設定されたプロフィールを設定する例です。

...
containers:
- name: container-1
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: k8s-apparmor-example-deny-write

より詳細な内容についてはRestrict a Container's Access to Resources with AppArmorを参照してください。

コンテナにSELinuxラベルをつける

コンテナにSELinuxラベルをつけるには、Pod・コンテナマニフェストのsecurityContextセクションにseLinuxOptionsフィールドを追加してください。 seLinuxOptionsフィールドはSELinuxOptionsオブジェクトが入ります。 こちらはSELinuxレベルを適用する例です。

...
securityContext:
  seLinuxOptions:
    level: "s0:c123,c456"

効率的なSELinuxのボリューム再ラベル付け

FEATURE STATE: Kubernetes v1.28 [beta]

デフォルトでは、コンテナランタイムは全てのPodのボリュームの全てのファイルに再帰的にSELinuxラベルを付与します。処理速度を上げるために、Kubernetesはマウントオプションで-o context=<label>を使うことでボリュームのSELinuxラベルを即座に変更することができます。

この高速化の恩恵を受けるには、以下の全ての条件を満たす必要があります。

  • AlphaフィーチャーゲートのReadWriteOncePodSELinuxMountReadWriteOncePodを有効にすること
  • Podが適用可能なaccessModesでPersistentVolumeClaimを使うこと
    • ボリュームがaccessModes: ["ReadWriteOncePod"]を持ち、フィーチャーゲートSELinuxMountReadWriteOncePodが有効であること
    • または、ボリュームが他のアクセスモードを使用し、フィーチャーゲートSELinuxMountReadWriteOncePodSELinuxMountの両方が有効であること
  • Pod(またはPersistentVolumeClaimを使っている全てのコンテナ)にseLinuxOptionsが設定されていること
  • 対応するPersistentVolumeが以下のいずれかであること
    • レガシーのin-treeボリュームの場合、iscsrbdfcボリュームタイプであること
    • または、CSIドライバーを使用するボリュームで、そのCSIドライバーがCSIドライバーインスタンスでspec.seLinuxMount: trueを指定したときに-o contextでマウントを行うとアナウンスしていること

それ以外のボリュームタイプでは、コンテナランタイムはボリュームに含まれる全てのinode(ファイルやディレクトリ)に対してSELinuxラベルを再帰的に変更します。 ボリューム内のファイルやディレクトリが増えるほど、ラベリングにかかる時間は増加します。

/procファイルシステムへのアクセスを管理する

FEATURE STATE: Kubernetes v1.12 [alpha]

OCI runtime specificationに準拠するランタイムでは、コンテナはデフォルトで、いくつかの複数のパスはマスクされ、かつ、読み取り専用のモードで実行されます。 その結果、コンテナのマウントネームスペース内にはこれらのパスが存在し、あたかもコンテナが隔離されたホストであるかのように機能しますが、コンテナプロセスはそれらのパスに書き込むことはできません。 マスクされるパスおよび読み取り専用のパスのリストは次のとおりです。

  • マスクされるパス:

    • /proc/asound
    • /proc/acpi
    • /proc/kcore
    • /proc/keys
    • /proc/latency_stats
    • /proc/timer_list
    • /proc/timer_stats
    • /proc/sched_debug
    • /proc/scsi
    • /sys/firmware
    • /sys/devices/virtual/powercap
  • 読み取り専用のパス:

    • /proc/bus
    • /proc/fs
    • /proc/irq
    • /proc/sys
    • /proc/sysrq-trigger

一部のPodでは、デフォルトでパスがマスクされるのを回避したい場合があります。このようなケースで最も一般的なのは、Kubernetesコンテナ(Pod内のコンテナ)内でコンテナを実行しようとする場合です。

securityContextprocMountフィールドを使用すると、コンテナの/procUnmaskedにしたり、コンテナプロセスによって読み書き可能な状態でマウントすることができます。この設定は、/proc以外の/sys/firmwareにも適用されます。

...
securityContext:
  procMount: Unmasked

議論

PodのセキュリティコンテキストはPodのコンテナや、適用可能であればPodのボリュームに対しても適用されます。 特にfsGroupseLinuxOptionsは以下のようにボリュームに対して適用されます。

  • fsGroup: 所有権管理をサポートしているボリュームはfsGroupで指定されているGIDで所有権・書き込み権限が設定されます。詳しくはOwnership Management design documentを確認してください。

  • seLinuxOptions: SELinuxラベリングをサポートしているボリュームではseLinuxOptionsで指定されているラベルでアクセス可能になるように貼り直されます。通常、levelセクションのみ設定する必要があります。 これはPod内の全てのボリュームとコンテナに対しMulti-Category Security (MCS)ラベルを設定します。

クリーンアップ

Podを削除します。

kubectl delete pod security-context-demo
kubectl delete pod security-context-demo-2
kubectl delete pod security-context-demo-3
kubectl delete pod security-context-demo-4

次の項目

このページの項目は、Kubernetesが必要とする機能を提供するサードパーティー製品またはプロジェクトです。Kubernetesプロジェクトの作者は、それらのサードパーティー製品またはプロジェクトに責任を負いません。詳しくは、CNCFウェブサイトのガイドラインをご覧ください。第三者のリンクを追加するような変更を提案する前に、コンテンツガイドを読むべきです。