これは、このセクションの複数ページの印刷可能なビューです。 印刷するには、ここをクリックしてください.
セキュリティ
1 - クラスターレベルでのPodセキュリティの標準の適用
Note
このチュートリアルは、新しいクラスターにのみ適用されます。Podセキュリティアドミッション(PSA)は、ベータへ進み、v1.23以降でデフォルトで有効になっています。
Podセキュリティアドミッションは、Podが作成される際に、Podセキュリティの標準の適用の認可を制御するものです。
このチュートリアルでは、クラスター内の全ての名前空間に標準設定を適用することで、クラスターレベルでbaseline
Podセキュリティの標準を強制する方法を示します。
Podセキュリティの標準を特定の名前空間に適用するには、名前空間レベルでのPodセキュリティの標準の適用を参照してください。
v1.33以外のKubernetesバージョンを実行している場合は、そのバージョンのドキュメントを確認してください。
始める前に
ワークステーションに以下をインストールしてください:
このチュートリアルでは、完全な制御下にあるKubernetesクラスターの何を設定できるかをデモンストレーションします。 コントロールプレーンを設定できない管理されたクラスターのPodセキュリティアドミッションに対しての設定方法を知りたいのであれば、名前空間レベルでのPodセキュリティの標準の適用を参照してください。
適用する正しいPodセキュリティの標準の選択
Podのセキュリティアドミッションは、以下のモードでビルトインのPodセキュリティの標準の適用を促します: enforce
、audit
、warn
。
設定に最適なPodセキュリティの標準を選択するにあたって助けになる情報を収集するために、以下を行ってください:
Podセキュリティの標準を適用していないクラスターを作成します:
kind create cluster --name psa-wo-cluster-pss
出力は次のようになります:
Creating cluster "psa-wo-cluster-pss" ... ✓ Ensuring node image (kindest/node:v1.33.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-psa-wo-cluster-pss" You can now use your cluster with: kubectl cluster-info --context kind-psa-wo-cluster-pss Thanks for using kind! 😊
kubectl contextを新しいクラスターにセットします:
kubectl cluster-info --context kind-psa-wo-cluster-pss
出力は次のようになります:
Kubernetes control plane is running at https://127.0.0.1:61350 CoreDNS is running at https://127.0.0.1:61350/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
クラスター内の名前空間の一覧を取得します:
kubectl get ns
出力は次のようになります:
NAME STATUS AGE default Active 9m30s kube-node-lease Active 9m32s kube-public Active 9m32s kube-system Active 9m32s local-path-storage Active 9m26s
異なるPodセキュリティの標準が適用されたときに何が起きるかを理解するために、
-dry-run=server
を使います:privileged
kubectl label --dry-run=server --overwrite ns --all \ pod-security.kubernetes.io/enforce=privileged
出力は次のようになります:
namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled namespace/kube-system labeled namespace/local-path-storage labeled
baseline
kubectl label --dry-run=server --overwrite ns --all \ pod-security.kubernetes.io/enforce=baseline
出力は次のようになります:
namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "baseline:latest" Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged namespace/kube-system labeled namespace/local-path-storage labeled
restricted
kubectl label --dry-run=server --overwrite ns --all \ pod-security.kubernetes.io/enforce=restricted
出力は次のようになります:
namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "restricted:latest" Warning: coredns-7bb9c7b568-hsptc (and 1 other pod): unrestricted capabilities, runAsNonRoot != true, seccompProfile Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile namespace/kube-system labeled Warning: existing pods in namespace "local-path-storage" violate the new PodSecurity enforce level "restricted:latest" Warning: local-path-provisioner-d6d9f7ffc-lw9lh: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile namespace/local-path-storage labeled
この出力から、privileged
Podセキュリティの標準を適用すると、名前空間のどれにも警告が示されないことに気付くでしょう。
これに対し、baseline
とrestrict
の標準ではどちらも、とりわけkube-system
名前空間に対して警告が示されています。
モード、バージョン、標準のセット
このセクションでは、latest
バージョンに以下のPodセキュリティの標準を適用します:
enforce
モードでbaseline
標準。warn
およびaudit
モードでrestricted
標準。
baseline
Podセキュリティの標準は、免除リストを短く保てて、かつ既知の特権昇格を防ぐような、利便性のある中庸を提供します。
加えて、kube-system
内の失敗からPodを守るために、適用されるPodセキュリティの標準の対象から名前空間を免除します。
環境にPodセキュリティアドミッションを実装する際には、以下の点を考慮してください:
クラスターに適用されるリスク状況に基づくと、
restricted
のようにより厳格なPodセキュリティの標準のほうが、より良い選択肢かもしれません。kube-system
名前空間の免除は、Podがその名前空間でprivileged
として実行するのを許容することになります。 実世界で使うにあたっては、以下の最小権限の原則に従ってkube-system
へのアクセスを制限する厳格なRBACポリシーを適用することを、Kubernetesプロジェクトは強く推奨します。 上記の標準を実装するには、次のようにします:目的のPodセキュリティの標準を実装するために、Podセキュリティアドミッションコントローラーで利用可能な設定ファイルを作成します:
mkdir -p /tmp/pss cat <<EOF > /tmp/pss/cluster-level-pss.yaml apiVersion: apiserver.config.k8s.io/v1 kind: AdmissionConfiguration plugins: - name: PodSecurity configuration: apiVersion: pod-security.admission.config.k8s.io/v1 kind: PodSecurityConfiguration defaults: enforce: "baseline" enforce-version: "latest" audit: "restricted" audit-version: "latest" warn: "restricted" warn-version: "latest" exemptions: usernames: [] runtimeClasses: [] namespaces: [kube-system] EOF
クラスターの作成中にこのファイルを取り込むAPIサーバーを設定します:
cat <<EOF > /tmp/pss/cluster-config.yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane kubeadmConfigPatches: - | kind: ClusterConfiguration apiServer: extraArgs: admission-control-config-file: /etc/config/cluster-level-pss.yaml extraVolumes: - name: accf hostPath: /etc/config mountPath: /etc/config readOnly: false pathType: "DirectoryOrCreate" extraMounts: - hostPath: /tmp/pss containerPath: /etc/config # optional: if set, the mount is read-only. # default false readOnly: false # optional: if set, the mount needs SELinux relabeling. # default false selinuxRelabel: false # optional: set propagation mode (None, HostToContainer or Bidirectional) # see https://kubernetes.io/docs/concepts/storage/volumes/#mount-propagation # default None propagation: None EOF
備考:
macOSでDocker Desktopとkindを利用している場合は、Preferences > Resources > File Sharingのメニュー項目からShared Directoryとして/tmp
を追加できます。目的のPodセキュリティの標準を適用するために、Podセキュリティアドミッションを使うクラスターを作成します:
kind create cluster --name psa-with-cluster-pss --config /tmp/pss/cluster-config.yaml
出力は次のようになります:
Creating cluster "psa-with-cluster-pss" ... ✓ Ensuring node image (kindest/node:v1.33.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-psa-with-cluster-pss" You can now use your cluster with: kubectl cluster-info --context kind-psa-with-cluster-pss Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
kubectlをこのクラスターに向けます:
kubectl cluster-info --context kind-psa-with-cluster-pss
出力は次のようになります:
Kubernetes control plane is running at https://127.0.0.1:63855 CoreDNS is running at https://127.0.0.1:63855/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
デフォルトの名前空間にPodを作成します:
kubectl apply -f https://k8s.io/examples/security/example-baseline-pod.yaml
Podは正常に開始されますが、出力には警告が含まれます:
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost") pod/nginx created
後片付け
では、上記で作成したクラスターを、以下のコマンドを実行して削除します:
kind delete cluster --name psa-with-cluster-pss
kind delete cluster --name psa-wo-cluster-pss
次の項目
- 前出の一連の手順を一度に全て行うためにシェルスクリプトを実行します:
- クラスターレベルの設定に基づきPodセキュリティの標準を作成します。
- APIサーバーでこの設定を取り込むようにファイルを作成します。
- この設定のAPIサーバーを立てるクラスターを作成します。
- この新しいクラスターにkubectl contextをセットします。
- 最小限のPod YAMLファイルを作成します。
- 新しいクラスター内でPodを作成するために、このファイルを適用します。
- Podのセキュリティアドミッション
- Podセキュリティの標準
- 名前空間レベルでのPodセキュリティの標準の適用
2 - 名前空間レベルでのPodセキュリティの標準の適用
Note
このチュートリアルは、新しいクラスターにのみ適用されます。Podセキュリティアドミッション(PSA)は、ベータへ進み、v1.23以降でデフォルトで有効になっています。
Podセキュリティアドミッションは、Podが作成される際に、Podセキュリティの標準の適用の認可を制御するものです。
このチュートリアルでは、一度に1つの名前空間でbaseline
Podセキュリティ標準を強制します。
Podセキュリティの標準を複数の名前空間に一度にクラスターレベルで適用することもできます。やり方についてはクラスターレベルでのPodセキュリティの標準の適用を参照してください。
始める前に
ワークステーションに以下をインストールしてください:
クラスターの作成
以下のように
KinD
クラスターを作成します。kind create cluster --name psa-ns-level
出力は次のようになります:
Creating cluster "psa-ns-level" ... ✓ Ensuring node image (kindest/node:v1.33.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-psa-ns-level" You can now use your cluster with: kubectl cluster-info --context kind-psa-ns-level Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/
kubectl のコンテキストを新しいクラスターにセットします:
kubectl cluster-info --context kind-psa-ns-level
出力は次のようになります:
Kubernetes control plane is running at https://127.0.0.1:50996 CoreDNS is running at https://127.0.0.1:50996/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
名前空間の作成
example
と呼ぶ新しい名前空間を作成します:
kubectl create ns example
出力は次のようになります:
namespace/example created
名前空間へのPodセキュリティの標準チェックの有効化
ビルトインのPod Security Admissionでサポートされているラベルを使って、この名前空間のPodセキュリティの標準を有効にします。 このステップでは、baseline Podセキュリティの標準の最新バージョンに合わないPodについて警告するチェックを設定します。
kubectl label --overwrite ns example \ pod-security.kubernetes.io/warn=baseline \ pod-security.kubernetes.io/warn-version=latest
ラベルを使って、任意の名前空間に対して複数のPodセキュリティの標準チェックを設定できます。 以下のコマンドは、
baseline
Podセキュリティの標準をenforce
(強制)としますが、restricted
Podセキュリティの標準には最新バージョンに準じてwarn
(警告)およびaudit
(監査)とします(デフォルト値)。kubectl label --overwrite ns example \ pod-security.kubernetes.io/enforce=baseline \ pod-security.kubernetes.io/enforce-version=latest \ pod-security.kubernetes.io/warn=restricted \ pod-security.kubernetes.io/warn-version=latest \ pod-security.kubernetes.io/audit=restricted \ pod-security.kubernetes.io/audit-version=latest
Podセキュリティの標準の強制の実証
example
名前空間内にbaseline
Podを作成します:kubectl apply -n example -f https://k8s.io/examples/security/example-baseline-pod.yaml
Podは正常に起動しますが、出力には警告が含まれています。例えば:
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost") pod/nginx created
default
名前空間内にbaseline
Podを作成します:kubectl apply -n default -f https://k8s.io/examples/security/example-baseline-pod.yaml
出力は次のようになります:
pod/nginx created
example
名前空間にだけ、Podセキュリティの標準のenforceと警告の設定が適用されました。
default
名前空間内では、警告なしに同じPodを作成できました。
後片付け
では、上記で作成したクラスターを、以下のコマンドを実行して削除します:
kind delete cluster --name psa-ns-level
次の項目
前出の一連の手順を一度に全て行うためにシェルスクリプトを実行します。
- KinDクラスターを作成します。
- 新しい名前空間を作成します。
enforce
モードではbaseline
Podセキュリティの標準を適用し、warn
およびaudit
モードではrestricted
Podセキュリティの標準を適用します。- これらのPodセキュリティの標準を適用した新しいPodを作成します。
3 - AppArmorを使用してコンテナのリソースへのアクセスを制限する
Kubernetes v1.31 [stable]
(enabled by default: true)このページでは、ノードでAppArmorプロファイルを読み込み、それらのプロファイルをPodに適用する方法を説明します。 AppArmorを使用してPodを制限するKubernetesの仕組みについて詳しく知りたい場合は、PodとコンテナのためのLinuxカーネルのセキュリティ制約をご覧ください。
目標
- プロファイルをノードに読み込む方法の例を見る
- Pod上でプロファイルを強制する方法を学ぶ
- プロファイルが読み込まれたかを確認する方法を学ぶ
- プロファイルに違反した場合に何が起こるのかを見る
- プロファイルが読み込めなかった場合に何が起こるのかを見る
始める前に
AppArmorはカーネルモジュールおよびKubernetesのオプション機能です。 そのため、先に進む前にノード上でAppArmorがサポートされていることを確認してください:
AppArmorカーネルモジュールが有効であること。 LinuxカーネルがAppArmorプロファイルを強制するためには、AppArmorカーネルモジュールのインストールと有効化が必須です。 UbuntuやSUSEなどのディストリビューションではデフォルトで有効化されますが、他の多くのディストリビューションでのサポートはオプションです。 モジュールが有効になっているかどうかを確認するためには、
/sys/module/apparmor/parameters/enabled
ファイルを確認します:cat /sys/module/apparmor/parameters/enabled Y
Kubeletは、AppArmorが明示的に設定されたPodを受け入れる前にホスト上でAppArmorが有効になっているかどうかを検証します。
コンテナランタイムがAppArmorをサポートしていること。 KubernetesがサポートするcontainerdやCRI-Oなどのすべての一般的なコンテナランタイムは、AppArmorをサポートしています。 関連するランタイムのドキュメントを参照して、クラスターがAppArmorを利用するための要求を満たしているかどうかを検証してください。
プロファイルが読み込まれていること。 AppArmorがPodに適用されるのは、各コンテナが実行するAppArmorプロファイルを指定したときです。 もし指定されたプロファイルがまだカーネルに読み込まれていなければ、KubeletはPodを拒否します。 どのプロファイルがノードに読み込まれているのかを確かめるには、
/sys/kernel/security/apparmor/profiles
を確認します。 例えば:ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort"
apparmor-test-deny-write (enforce) apparmor-test-audit-write (enforce) docker-default (enforce) k8s-nginx (enforce)
ノード上でのプロファイルの読み込みの詳細については、プロファイルを使用したノードのセットアップを参照してください。
Podをセキュアにする
備考:
Kubernetes v1.30より前では、AppArmorはアノテーションを通じて指定されていました。 この非推奨となったAPIに関するドキュメントを表示するには、ドキュメントのバージョンセレクターを使用してください。AppArmorプロファイルは、Podレベルまたはコンテナレベルで指定することができます。 コンテナのAppArmorプロファイルは、Podのプロファイルよりも優先されます。
securityContext:
appArmorProfile:
type: <profile_type>
ここで、<profile_type>
には次の値のうち1つを指定します:
RuntimeDefault
: ランタイムのデフォルトのプロファイルを適用します。Localhost
: ホストに読み込まれたプロファイルを適用します(下記を参照してください)。unconfined
: AppArmorなしで実行します。
AppArmorプロファイルAPIの詳細については、AppArmorによる制限の指定を参照してください。
プロファイルが適用されたかどうかを確認するには、proc attrを調べることでコンテナのルートプロセスが正しいプロファイルで実行されているかどうかを確認します:
kubectl exec <pod_name> -- cat /proc/1/attr/current
出力は以下のようになるはずです:
cri-containerd.apparmor.d (enforce)
例
この例は、クラスターがすでにAppArmorのサポート付きでセットアップ済みであることを前提としています。
まず、使用したいプロファイルをノード上に読み込む必要があります。 このプロファイルは、すべてのファイル書き込みを拒否します:
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# Deny all file writes.
deny /** w,
}
Podがどのノードにスケジュールされるかは予測できないため、プロファイルはすべてのノードに読み込ませる必要があります。 この例では、単純にSSHを使ってプロファイルをインストールしますが、プロファイルを使用したノードのセットアップでは、他のアプローチについて議論しています。
# この例では、ノード名がホスト名と一致し、SSHで到達可能であることを前提としています。
NODES=($( kubectl get node -o jsonpath='{.items[*].status.addresses[?(.type == "Hostname")].address}' ))
for NODE in ${NODES[*]}; do ssh $NODE 'sudo apparmor_parser -q <<EOF
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# Deny all file writes.
deny /** w,
}
EOF'
done
次に、deny-writeプロファイルを使用した単純な"Hello AppArmor" Podを実行します。
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor
spec:
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: k8s-apparmor-example-deny-write
containers:
- name: hello
image: busybox:1.28
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
kubectl create -f hello-apparmor.yaml
/proc/1/attr/current
を確認することで、コンテナがこのプロファイルで実際に実行されていることを検証できます:
kubectl exec hello-apparmor -- cat /proc/1/attr/current
出力は以下のようになるはずです:
k8s-apparmor-example-deny-write (enforce)
最後に、ファイルへの書き込みを行おうとすることで、プロファイルに違反すると何が起こるか見てみましょう:
kubectl exec hello-apparmor -- touch /tmp/test
touch: /tmp/test: Permission denied
error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1
まとめとして、読み込まれていないプロファイルを指定しようとするとどうなるのか見てみましょう:
kubectl create -f /dev/stdin <<EOF
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor-2
spec:
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: k8s-apparmor-example-allow-write
containers:
- name: hello
image: busybox:1.28
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created
Podは正常に作成されましたが、さらに調査すると、PodがPendingで止まっていることがわかります:
kubectl describe pod hello-apparmor-2
Name: hello-apparmor-2
Namespace: default
Node: gke-test-default-pool-239f5d02-x1kf/10.128.0.27
Start Time: Tue, 30 Aug 2016 17:58:56 -0700
Labels: <none>
Annotations: container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write
Status: Pending
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 10s default-scheduler Successfully assigned default/hello-apparmor to gke-test-default-pool-239f5d02-x1kf
Normal Pulled 8s kubelet Successfully pulled image "busybox:1.28" in 370.157088ms (370.172701ms including waiting)
Normal Pulling 7s (x2 over 9s) kubelet Pulling image "busybox:1.28"
Warning Failed 7s (x2 over 8s) kubelet Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found k8s-apparmor-example-allow-write
Normal Pulled 7s kubelet Successfully pulled image "busybox:1.28" in 90.980331ms (91.005869ms including waiting)
イベントにはエラーメッセージとその理由が表示されます。 具体的な文言はランタイムによって異なります:
Warning Failed 7s (x2 over 8s) kubelet Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found
管理
プロファイルを使用したノードのセットアップ
Kubernetes 1.33は、AppArmorプロファイルをノードに読み込むネイティブの仕組みは提供していません。 プロファイルは、カスタムインフラストラクチャーやKubernetes Security Profiles Operatorなどのツールを通じて読み込むことができます。
スケジューラーはどのプロファイルがどのノードに読み込まれているのかがわからないため、すべてのプロファイルがすべてのノードに読み込まれていなければなりません。 もう1つのアプローチとしては、各プロファイル(あるいはプロファイルのクラス)ごとにノードラベルを追加し、ノードセレクターを用いてPodが必要なプロファイルを読み込んだノードで実行されるようにする方法もあります。
Profilesの作成
AppArmorプロファイルを正しく指定するのはやっかいな作業です。 幸い、その作業を補助するツールがいくつかあります:
aa-genprof
およびaa-logprof
は、アプリケーションの動作とログを監視し、実行されるアクションを許可することで、プロファイルのルールを生成します。 詳しい説明については、AppArmor documentationを参照してください。- baneは、Docker向けのAppArmorプロファイル・ジェネレーターです。簡略化されたプロファイル言語を使用しています。
AppArmorに関する問題をデバッグするには、システムログを確認して、特に何が拒否されたのかを確認できます。
AppArmorのログはdmesg
にverboseメッセージを送り、エラーは通常システムログまたはjournalctl
で確認できます。
詳しい情報は、AppArmor failuresで提供されています。
AppArmorによる制限の指定
注意:
Kubernetes v1.30より前では、AppArmorはアノテーションを通じて指定されていました。 この非推奨となったAPIに関するドキュメントを表示するには、ドキュメントのバージョンセレクターを使用してください。セキュリティコンテキスト内のAppArmorプロファイル
appArmorProfile
は、コンテナのsecurityContext
またはPodのsecurityContext
のいずれかで指定できます。
プロファイルがPodレベルで設定されている場合、Pod内のすべてのコンテナ(initコンテナ、サイドカーコンテナ、およびエフェメラルコンテナを含む)のデフォルトのプロファイルとして使用されます。
Podとコンテナの両方でAppArmorプロファイルが設定されている場合は、コンテナのプロファイルが使用されます。
AppArmorプロファイルには2つのフィールドがあります:
type
(必須) - 適用されるAppArmorプロファイルの種類を示します。
有効なオプションは以下の通りです:
Localhost
- ノードに事前に読み込まれているプロファイル(
localhostProfile
で指定します)。 RuntimeDefault
- コンテナランタイムのデフォルトのプロファイル。
Unconfined
- AppArmorによる制限を適用しません。
localhostProfile
- ノード上に読み込まれている、使用すべきプロファイルの名前。
このプロファイルは、ノード上であらかじめ設定されている必要があります。
このオプションは、type
がLocalhost
の場合にのみ指定する必要があります。
次の項目
追加のリソースとしては以下のものがあります:
4 - seccompを使用してコンテナのシステムコールを制限する
Kubernetes v1.19 [stable]
seccomp(SECure COMPuting mode)はLinuxカーネル2.6.12以降の機能です。 この機能を用いると、ユーザー空間からカーネルに対して発行できるシステムコールを制限することで、プロセス権限のサンドボックスを構築することができます。 Kubernetesでは、ノード上で読み込んだseccompプロファイルをPodやコンテナに対して自動で適用することができます。
あなたのワークロードが必要とする権限を特定するのは、実際には難しい作業になるかもしれません。 このチュートリアルでは、ローカルのKubernetesクラスターでseccompプロファイルを読み込むための方法を説明し、seccompプロファイルのPodへの適用方法について学んだ上で、コンテナプロセスに対して必要な権限のみを付与するためのseccompプロファイルを作成する方法を概観していきます。
目標
- ノードでseccompプロファイルを読み込む方法を学ぶ。
- seccompプロファイルをコンテナに適用する方法を学ぶ。
- コンテナプロセスが生成するシステムコールの監査出力を確認する。
- seccompプロファイルが指定されない場合の挙動を確認する。
- seccompプロファイルの違反を確認する。
- きめ細やかなseccompプロファイルの作成方法を学ぶ。
- コンテナランタイムの標準seccompプロファイルを適用する方法を学ぶ。
始める前に
このチュートリアルのステップを完了するためには、kindとkubectlをインストールしておく必要があります。
このチュートリアルで利用するコマンドは、Dockerをコンテナランタイムとして利用していることを前提としています。
(kind
が作成するクラスターは内部的に異なるコンテナランタイムを利用する可能性があります)。
Podmanを使うこともできますが、チュートリアルを完了するためには、所定の手順に従う必要があります。
このチュートリアルでは、現時点(v1.25以降)でのベータ機能を利用する設定例をいくつか示しますが、その他の例についてはGAなseccomp関連機能を用いています。 利用するKubernetesバージョンを対象としたクラスターの正しい設定がなされていることを確認してください。
チュートリアル内では、サンプルをダウンロードするためにcurl
を利用します。
この手順については、ほかの好きなツールを用いて実施してもかまいません。
備考:
securityContext
にprivileged: true
が設定されているContainerに対しては、seccompプロファイルを適用することができません。
特権コンテナは常にUnconfined
な状態で動作します。サンプルのseccompプロファイルをダウンロードする
プロファイルの内容については後で解説しますので、まずはクラスターで読み込むためのseccompプロファイルをprofiles/
ディレクトリ内にダウンロードしましょう。
{
"defaultAction": "SCMP_ACT_LOG"
}
{
"defaultAction": "SCMP_ACT_ERRNO"
}
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"accept4",
"epoll_wait",
"pselect6",
"futex",
"madvise",
"epoll_ctl",
"getsockname",
"setsockopt",
"vfork",
"mmap",
"read",
"write",
"close",
"arch_prctl",
"sched_getaffinity",
"munmap",
"brk",
"rt_sigaction",
"rt_sigprocmask",
"sigaltstack",
"gettid",
"clone",
"bind",
"socket",
"openat",
"readlinkat",
"exit_group",
"epoll_create1",
"listen",
"rt_sigreturn",
"sched_yield",
"clock_gettime",
"connect",
"dup2",
"epoll_pwait",
"execve",
"exit",
"fcntl",
"getpid",
"getuid",
"ioctl",
"mprotect",
"nanosleep",
"open",
"poll",
"recvfrom",
"sendto",
"set_tid_address",
"setitimer",
"writev",
"fstatfs",
"getdents64",
"pipe2",
"getrlimit"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
次のコマンドを実行してください:
mkdir ./profiles
curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json
curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json
curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json
ls profiles
最終的に3つのプロファイルが確認できるはずです:
audit.json fine-grained.json violation.json
kindでローカルKubernetesクラスターを構築する
手軽な方法として、kindを利用することで、seccompプロファイルを読み込んだ単一ノードクラスターを構築できます。 kindはKubernetesをDocker内で稼働させるため、クラスターの各ノードはコンテナとなります。 これにより、ノード上にファイルを展開するのと同じように、各コンテナのファイルシステムに対してファイルをマウントすることが可能です。
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
extraMounts:
- hostPath: "./profiles"
containerPath: "/var/lib/kubelet/seccomp/profiles"
kindの設定サンプルをダウンロードして、kind.yaml
の名前で保存してください:
curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml
ノードのコンテナイメージを設定する際には、特定のKubernetesバージョンを指定することもできます。 この設定方法の詳細については、kindのドキュメント内の、ノードの項目を参照してください。 このチュートリアルではKubernetes v1.33を使用することを前提とします。
ベータ機能として、Unconfined
にフォールバックするのではなく、コンテナランタイムがデフォルトで推奨するプロファイルを利用するようにKubernetesを設定することもできます。
この機能を試したい場合、これ以降の手順に進む前に、全ワークロードに対する標準seccompプロファイルとしてRuntimeDefault
を使用するを参照してください。
kindの設定ファイルを設置したら、kindクラスターを作成します:
kind create cluster --config=kind.yaml
Kubernetesクラスターが準備できたら、単一ノードクラスターが稼働しているDockerコンテナを特定してください:
docker ps
kind-control-plane
という名前のコンテナが稼働していることが確認できるはずです。
出力は次のようになるでしょう:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6a96207fed4b kindest/node:v1.18.2 "/usr/local/bin/entr…" 27 seconds ago Up 24 seconds 127.0.0.1:42223->6443/tcp kind-control-plane
このコンテナのファイルシステムを観察すると、profiles/
ディレクトリがkubeletのデフォルトのseccompパスとして正しく読み込まれていることを確認できるはずです。
Pod内でコマンドを実行するためにdocker exec
を使います:
# 6a96207fed4bを"docker ps"で確認したコンテナIDに変更してください。
docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles
audit.json fine-grained.json violation.json
kind内で稼働しているkubeletがseccompプロファイルを利用可能であることを確認しました。
コンテナランタイムの標準seccompプロファイルを利用してPodを作成する
ほとんどのコンテナランタイムは、許可・拒否の対象とする標準的なシステムコールの集合を提供しています。
PodやContainerのセキュリティコンテキストでseccompタイプをRuntimeDefault
に設定すると、コンテナランタイムが提供するデフォルトのプロファイルをワークロードに適用することができます。
備考:
seccompDefault
の設定を有効化している場合、他にseccompプロファイルを定義しなくても、PodはRuntimeDefault
seccompプロファイルを使用します。
seccompDefault
が無効の場合のデフォルトはUnconfined
です。Pod内の全てのContainerに対してRuntimeDefault
seccompプロファイルを要求するマニフェストは次のようなものです:
apiVersion: v1
kind: Pod
metadata:
name: default-pod
labels:
app: default-pod
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: test-container
image: hashicorp/http-echo:1.0
args:
- "-text=just made some more syscalls!"
securityContext:
allowPrivilegeEscalation: false
このPodを作成してみます:
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml
kubectl get pod default-pod
Podが正常に起動できていることを確認できるはずです:
NAME READY STATUS RESTARTS AGE
default-pod 1/1 Running 0 20s
次のセクションに進む前に、Podを削除します:
kubectl delete pod default-pod --wait --now
システムコール監査のためのseccompプロファイルを利用してPodを作成する
最初に、新しいPodにプロセスの全システムコールを記録するためのaudit.json
プロファイルを適用します。
このPodのためのマニフェストは次の通りです:
apiVersion: v1
kind: Pod
metadata:
name: audit-pod
labels:
app: audit-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/audit.json
containers:
- name: test-container
image: hashicorp/http-echo:1.0
args:
- "-text=just made some syscalls!"
securityContext:
allowPrivilegeEscalation: false
備考:
過去のバージョンのKubernetesでは、アノテーションを用いてseccompの挙動を制御することができました。 Kubernetes 1.33におけるseccompの設定では.spec.securityContext
フィールドのみをサポートしており、このチュートリアルではKubernetes 1.33における手順を解説しています。クラスター内にPodを作成します:
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml
このプロファイルは、いかなるシステムコールも制限しないため、Podは正常に起動するはずです。
kubectl get pod audit-pod
NAME READY STATUS RESTARTS AGE
audit-pod 1/1 Running 0 30s
このコンテナが公開するエンドポイントとやりとりするために、kindのコントロールプレーンコンテナの内部からこのエンドポイントにアクセスできるように、NodePort Serviceを作成します。
kubectl expose pod audit-pod --type NodePort --port 5678
どのポートがノード上のServiceに割り当てられたのかを確認しましょう。
kubectl get service audit-pod
次のような出力が得られます:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
audit-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s
ここまで来れば、kindのコントロールプレーンコンテナの内部からエンドポイントに対して、Serviceが公開するポートを通じてcurl
で接続することができます。
docker exec
を使って、コントロールプレーンコンテナに属するコンテナの中からcurl
を実行しましょう:
# 6a96207fed4bと32373を、それぞれ"docker ps"で確認したコンテナIDとポート番号に変更してください。
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!
プロセスが実行されていることは確認できましたが、実際にどんなシステムコールが発行されているのでしょうか?
このPodはローカルクラスターで稼働しているため、発行されたシステムコールを/var/log/syslog
で確認することができるはずです。
新しいターミナルを開いて、http-echo
が発行するシステムコールをtail
してみましょう:
# あなたのマシンのログは"/var/log/syslog"以外の場所にあるかもしれません。
tail -f /var/log/syslog | grep 'http-echo'
すでにhttp-echo
が発行したいくつかのシステムコールのログが見えているはずです。
コントロールプレーンコンテナ内から再度curl
を実行すると、新たにログに追記された内容が出力されます。
例えば、次のような出力が得られるでしょう:
Jul 6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul 6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul 6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000
各行のsyscall=
エントリに着目することで、http-echo
プロセスが必要とするシステムコールを理解していくことができるでしょう。
このプロセスが利用する全てのシステムコールを網羅するものではないかもしれませんが、このコンテナのseccompプロファイルを作成する上での基礎とすることは可能です。
次のセクションに進む前にServiceとPodを削除します:
kubectl delete service audit-pod --wait
kubectl delete pod audit-pod --wait --now
違反が発生するseccompプロファイルでPodを作成する
デモとして、いかなるシステムコールも許可しないseccompプロファイルをPodに適用してみましょう。
マニフェストは次の通りです:
apiVersion: v1
kind: Pod
metadata:
name: violation-pod
labels:
app: violation-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/violation.json
containers:
- name: test-container
image: hashicorp/http-echo:1.0
args:
- "-text=just made some syscalls!"
securityContext:
allowPrivilegeEscalation: false
クラスターにPodを作成してみます:
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml
Podを作成しても問題が発生します。 Podの状態を確認すると、起動に失敗していることが確認できるはずです。
kubectl get pod violation-pod
NAME READY STATUS RESTARTS AGE
violation-pod 0/1 CrashLoopBackOff 1 6s
直前の事例で見てきたように、http-echo
プロセスは多くのシステムコールを必要とします。
ここでは"defaultAction": "SCMP_ACT_ERRNO"
が設定されているため、あらゆるシステムコールに対してseccompがエラーを発生させました。
この構成はとてもセキュアですが、有意義な処理は何もできないことを意味します。 私たちが実際にやりたいのは、ワークロードが必要とする権限のみを付与することです。
次のセクションに進む前にPodを削除します。
kubectl delete pod violation-pod --wait --now
必要なシステムコールのみを許可するseccompプロファイルを用いてPodを作成する
fine-grained.json
プロファイルの内容を見れば、最初の例で"defaultAction":"SCMP_ACT_LOG"
を設定していた際に、ログに表示されたシステムコールが含まれていることに気づくでしょう。
今回のプロファイルでも"defaultAction": "SCMP_ACT_ERRNO"
を設定しているものの、"action": "SCMP_ACT_ALLOW"
ブロックで明示的に一連のシステムコールを許可しています。
理想的な状況下であれば、コンテナが正常に稼働することに加え、syslog
へのメッセージ送信を見ることはなくなるでしょう。
この事例のマニフェストは次の通りです:
apiVersion: v1
kind: Pod
metadata:
name: fine-pod
labels:
app: fine-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/fine-grained.json
containers:
- name: test-container
image: hashicorp/http-echo:1.0
args:
- "-text=just made some syscalls!"
securityContext:
allowPrivilegeEscalation: false
クラスター内にPodを作成します:
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml
kubectl get pod fine-pod
Podは正常に起動しているはずです:
NAME READY STATUS RESTARTS AGE
fine-pod 1/1 Running 0 30s
新しいターミナルを開いて、http-echo
からのシステムコールに関するログエントリをtail
で監視しましょう:
# あなたのマシンのログは"/var/log/syslog"以外の場所にあるかもしれません。
tail -f /var/log/syslog | grep 'http-echo'
次のPodをNodePort Serviceで公開します:
kubectl expose pod fine-pod --type NodePort --port 5678
ノード上のServiceに割り当てられたポートを確認します:
kubectl get service fine-pod
出力は次のようになるでしょう:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
fine-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s
kindのコントロールプレーンコンテナの内部から、curl
を用いてエンドポイントにアクセスします:
# 6a96207fed4bと32373を、それぞれ"docker ps"で確認したコンテナIDとポート番号に変更してください。
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!
syslog
には何も出力されないはずです。
なぜなら、このプロファイルは必要なシステムコールを全て許可しており、一覧にないシステムコールが呼び出された時にのみエラーを発生させるように構成しているためです。
これはセキュリティの観点からすると理想的なシチュエーションといえますが、seccompプロファイルを作成するためのプログラムの解析には多少の労力が必要です。
多くの労力を割かなくてもこれに近いセキュリティが得られるシンプルな手法があったなら、きっと素晴らしいものになるでしょう。
次のセクションに進む前にServiceとPodを削除します:
kubectl delete service fine-pod --wait
kubectl delete pod fine-pod --wait --now
全ワークロードに対する標準seccompプロファイルとしてRuntimeDefault
を使用する
Kubernetes v1.27 [stable]
標準seccompプロファイルを指定するためには、この機能を利用したい全てのノードで--seccomp-default
コマンドラインフラグを用いてkubeletを起動する必要があります。
この機能を有効化すると、kubeletはコンテナランタイムが定義するRuntimeDefault
のseccompプロファイルをデフォルトで使用するようになり、Unconfined
モード(seccomp無効化)になることはありません。
コンテナランタイムが用意する標準プロファイルは、ワークロードの機能性を維持しつつ、強力な標準セキュリティルールの一式を用意することを目指しています。
標準のプロファイルはコンテナランタイムやリリースバージョンによって異なる可能性があります。
例えば、CRI-Oとcontainerdの標準プロファイルを比較してみるとよいでしょう。
備考:
この機能を有効化しても、PodやContainerなどのsecurityContext.seccompProfile
フィールドは変更されず、非推奨のアノテーションがワークロードに追加されることもありません。
したがって、ユーザーはワークロードの設定を変更せずにロールバックすることが可能です。
crictl inspect
のようなツールを使うことで、コンテナがどのseccompプロファイルを利用しているのかを確認できます。いくつかのワークロードでは、他のワークロードよりもシステムコール制限を少なくすることが必要な場合があります。
つまり、RuntimeDefault
を適用する場合、こうしたワークロードの実行が失敗する可能性があります。
このような障害を緩和するために、次のような対策を講じることができます:
- ワークロードを明示的に
Unconfined
として稼働させる。 SeccompDefault
機能をノードで無効化する。 また、機能を無効化したノードに対してワークロードが配置されていることを確認しておく。- ワークロードを対象とするカスタムseccompプロファイルを作成する。
実運用環境に近いクラスターに対してこの機能を展開する場合、クラスター全体に対して変更をロールアウトする前に、一部ノードのみを対象にしてこのフィーチャーゲートを有効化し、ワークロードが実行できることを検証しておくことをお勧めします。
クラスターに対してとりうるアップグレード・ダウングレード戦略について更に詳細な情報を知りたい場合は、関連するKubernetes Enhancement Proposal (KEP)であるEnable seccomp by defaultを参照してください。
Kubernetes 1.33では、specで特定のseccompプロファイルを指定していないPodに対して、デフォルトで適用するseccompプロファイルを設定することができます。 ただし、この標準seccompプロファイルを利用する場合、対象とする全ノードでこの機能を有効化しておく必要があります。
稼働中のKubernetes 1.33クラスターでこの機能を有効化する場合、kubeletに--seccomp-default
コマンドラインフラグを付与して起動するか、Kubernetesの設定ファイルでこの機能を有効化する必要があります。
このフィーチャーゲートをkindで有効化する場合、kind
が最低限必要なKubernetesバージョンを満たしていて、かつkindの設定ファイルでSeccompDefault
を有効化してください:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
seccomp-default: "true"
- role: worker
image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
seccomp-default: "true"
クラスターの準備ができたら、Podを実行します:
kubectl run --rm -it --restart=Never --image=alpine alpine -- sh
このコマンドで標準のseccompプロファイルを紐付けられるはずです。
この結果を確認するには、docker exec
経由でcrictl inspect
を実行することで、kindワーカー上のコンテナを確認します:
docker exec -it kind-worker bash -c \
'crictl inspect $(crictl ps --name=alpine -q) | jq .info.runtimeSpec.linux.seccomp'
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
"syscalls": [
{
"names": ["..."]
}
]
}
次の項目
Linuxのseccompについて更に学びたい場合は、次の記事を参考にすると良いでしょう: