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内の全てのコンテナに適用されます。こちらはsecurityContextとemptyDirボリュームを持ったPodの設定ファイルです。
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
fsGroup: 2000
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: gcr.io/google-samples/node-hello:1.0
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になります。
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
出力からrunAsGroupフィールドと同じくgidが3000になっていることが確認できるでしょう。runAsGroupが省略された場合、gidは0(root)になり、そのプロセスはグループroot(0)とグループroot(0)に必要なグループパーミッションを持つグループが所有しているファイルを操作することができるようになります。
shellから抜けましょう。
exit
Podのボリュームパーミッションと所有権変更ポリシーを設定する
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ドライバーにボリュームパーミッションと所有権を移譲する
Kubernetes v1.26 [stable]VOLUME_MOUNT_GROUP NodeServiceCapabilityをサポートしているContainer Storage Interface (CSI)ドライバーをデプロイした場合、securityContextのfsGroupで指定された値に基づいて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_ADMINとCAP_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を確認してください。
CAP_XXX形式です。
ただしコンテナのマニフェストでケーパビリティを記述する際は、定数のCAP_の部分を省いてください。
例えば、CAP_SYS_TIMEを追加したい場合はケーパビリティにSYS_TIMEを追加してください。コンテナにSeccompプロフィールを設定する
コンテナにSeccompプロフィールを設定するには、Pod・コンテナマニフェストのsecurityContextにseccompProfileフィールドを追加してください。
seccompProfileフィールドはSeccompProfileオブジェクトで、typeとlocalhostProfileで構成されています。
typeではRuntimeDefault、Unconfined、Localhostが有効です。
localhostProfileはtype: 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
コンテナにSELinuxラベルをつける
コンテナにSELinuxラベルをつけるには、Pod・コンテナマニフェストのsecurityContextセクションにseLinuxOptionsフィールドを追加してください。
seLinuxOptionsフィールドはSELinuxOptionsオブジェクトが入ります。
こちらはSELinuxレベルを適用する例です。
...
securityContext:
seLinuxOptions:
level: "s0:c123,c456"
効率的なSELinuxのボリューム再ラベル付け
Kubernetes v1.25 [alpha]デフォルトでは、コンテナランタイムは全てのPodのボリュームの全てのファイルに再帰的にSELinuxラベルを付与します。処理速度を上げるために、Kubernetesはマウントオプションで-o context=<label>を使うことでボリュームのSELinuxラベルを即座に変更することができます。
この高速化の恩恵を受けるには、以下の全ての条件を満たす必要があります。
- Alphaフィーチャーゲートの
ReadWriteOncePodとSELinuxMountReadWriteOncePodを有効にすること - Podが
accessModes: ["ReadWriteOncePod"]でPersistentVolumeClaimを使うこと - Pod(またはPersistentVolumeClaimを使っている全てのコンテナ)に
seLinuxOptionsが設定されていること - 対応するPersistentVolumeがCSIドライバーを利用するボリュームか、レガシー
iscsiボリュームタイプを利用するボリュームであること- CSIドライバーを利用するボリュームを利用している場合、そのCSIドライバーがCSIドライバーインスタンスで
spec.seLinuxMount: trueを指定したときに-o contextでマウントを行うとアナウンスしていること
- CSIドライバーを利用するボリュームを利用している場合、そのCSIドライバーがCSIドライバーインスタンスで
それ以外のボリュームタイプでは、コンテナランタイムはボリュームに含まれる全てのinode(ファイルやディレクトリ)に対してSELinuxラベルを再帰的に変更します。 ボリューム内のファイルやディレクトリが増えるほど、ラベリングにかかる時間は増加します。
議論
PodのセキュリティコンテキストはPodのコンテナや、適用可能であればPodのボリュームに対しても適用されます。
特にfsGroupとseLinuxOptionsは以下のようにボリュームに対して適用されます。
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