对于运行 Kubernetes 集群的大多数组织和人员来说,安全是一个重要问题。 你可以在 Kubernetes 文档的其他地方找到基本的安全检查清单。
要了解如何部署和管理 Kubernetes 的安全的方方面面,你可以按照本部分中的教程进行操作。
对于运行 Kubernetes 集群的大多数组织和人员来说,安全是一个重要问题。 你可以在 Kubernetes 文档的其他地方找到基本的安全检查清单。
要了解如何部署和管理 Kubernetes 的安全的方方面面,你可以按照本部分中的教程进行操作。
本教程仅适用于新集群。
Pod 安全是一个准入控制器,当新的 Pod 被创建时,它会根据 Kubernetes Pod 安全标准
进行检查。这是在 v1.25 中达到正式发布(GA)的功能。
本教程将向你展示如何在集群级别实施 baseline
Pod 安全标准,
该标准将标准配置应用于集群中的所有名字空间。
要将 Pod 安全标准应用于特定名字空间, 请参阅在名字空间级别应用 Pod 安全标准。
如果你正在运行 v1.32 以外的 Kubernetes 版本, 请查阅该版本的文档。
在你的工作站中安装以下内容:
本教程演示了你可以对完全由你控制的 Kubernetes 集群所配置的内容。 如果你正在学习如何为一个无法配置控制平面的托管集群配置 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.32.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 上下文设置为新集群:
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
使用 --dry-run=server
来了解应用不同的 Pod 安全标准时会发生什么:
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
和 restricted
标准都有警告,特别是在 kube-system
名字空间中。
在本节中,你将以下 Pod 安全标准应用于最新(latest
)版本:
enforce
模式下的 baseline
标准。warn
和 audit
模式下的 restricted
标准。baseline
Pod 安全标准提供了一个方便的中间立场,能够保持豁免列表简短并防止已知的特权升级。
此外,为了防止 kube-system
中的 Pod 失败,你将免除该名字空间应用 Pod 安全标准。
在你自己的环境中实施 Pod 安全准入时,请考虑以下事项:
根据应用于集群的风险状况,更严格的 Pod 安全标准(如 restricted
)可能是更好的选择。
对 kube-system
名字空间进行赦免会允许 Pod 在其中以 privileged
模式运行。
对于实际使用,Kubernetes 项目强烈建议你应用严格的 RBAC 策略来限制对 kube-system
的访问,
遵循最小特权原则。
创建一个配置文件,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
下添加 /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.32.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'.
在 default 名字空间下创建一个 Pod:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
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 Security Admission 是一个准入控制器,在创建 Pod 时应用 Pod 安全标准。
这是在 v1.25 中达到正式发布(GA)的功能。
在本教程中,你将应用 baseline
Pod 安全标准,每次一个名字空间。
你还可以在集群级别一次将 Pod 安全标准应用于多个名称空间。 有关说明,请参阅在集群级别应用 Pod 安全标准。
在你的工作站中安装以下内容:
按照如下方式创建一个 kind
集群:
kind create cluster --name psa-ns-level
输出类似于:
Creating cluster "psa-ns-level" ...
✓ Ensuring node image (kindest/node:v1.32.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 安全标准。 在这一步中,我们将根据最新版本(默认值)对基线 Pod 安全标准发出警告。
kubectl label --overwrite ns example \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/warn-version=latest
你可以使用标签在任何名字空间上配置多个 Pod 安全标准检查。
以下命令将强制(enforce
) 执行基线(baseline
)Pod 安全标准,
但根据最新版本(默认值)对受限(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
在 example
名字空间中创建一个基线 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
名字空间中创建一个基线 Pod:
kubectl apply -n default -f https://k8s.io/examples/security/example-baseline-pod.yaml
输出类似于:
pod/nginx created
Pod 安全标准实施和警告设置仅被应用到 example
名字空间。
以上 Pod 安全标准仅被应用到 example
名字空间。
你可以在没有警告的情况下在 default
名字空间中创建相同的 Pod。
现在通过运行以下命令删除你上面创建的集群:
kind delete cluster --name psa-ns-level
运行一个 shell 脚本 一次执行所有前面的步骤。
enforce
模式下应用 baseline
Pod 安全标准,
同时在 warn
和 audit
模式下应用 restricted
Pod 安全标准。Kubernetes v1.31 [stable]
(enabled by default: true)本页面向你展示如何在节点上加载 AppArmor 配置文件并在 Pod 中强制应用这些配置文件。 要了解有关 Kubernetes 如何使用 AppArmor 限制 Pod 的更多信息,请参阅 Pod 和容器的 Linux 内核安全约束。
AppArmor 是一个可选的内核模块和 Kubernetes 特性,因此请在继续之前验证你的节点是否支持它:
AppArmor 内核模块已启用 —— 要使 Linux 内核强制执行 AppArmor 配置文件,
必须安装并且启动 AppArmor 内核模块。默认情况下,有几个发行版支持该模块,
如 Ubuntu 和 SUSE,还有许多发行版提供可选支持。要检查模块是否已启用,请检查
/sys/module/apparmor/parameters/enabled
文件:
cat /sys/module/apparmor/parameters/enabled
Y
kubelet 会先验证主机上是否已启用 AppArmor,然后再接纳显式配置了 AppArmor 的 Pod。
配置文件已加载 —— 通过指定每个容器应使用的 AppArmor 配置文件,
AppArmor 会被应用到 Pod 上。如果所指定的配置文件未加载到内核,
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)
有关在节点上加载配置文件的详细信息,请参见使用配置文件设置节点。
在 Kubernetes v1.30 之前,AppArmor 是通过注解指定的。 使用文档版本选择器查看包含此已弃用 API 的文档。
AppArmor 配置文件可以在 Pod 级别或容器级别指定。容器 AppArmor 配置文件优先于 Pod 配置文件。
securityContext:
appArmorProfile:
type: <profile_type>
其中 <profile_type>
是以下之一:
RuntimeDefault
使用运行时的默认配置文件Localhost
使用主机上加载的配置文件(见下文)Unconfined
无需 AppArmor 即可运行有关 AppArmor 配置文件 API 的完整详细信息,请参阅指定 AppArmor 限制。
要验证是否应用了配置文件, 你可以通过检查容器根进程的进程属性来检查该进程是否正在使用正确的配置文件运行:
kubectl exec <pod_name> -- cat /proc/1/attr/current
输出应如下所示:
cri-containerd.apparmor.d (enforce)
你还可以通过检查容器的 proc attr,直接验证容器的根进程是否以正确的配置文件运行:
kubectl exec <pod_name> -- cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)
本例假设你已经设置了一个集群使用 AppArmor 支持。
首先,将要使用的配置文件加载到节点上,该配置文件阻止所有文件写入操作:
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# 拒绝所有文件写入
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
接下来,运行一个带有拒绝写入配置文件的简单 “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 创建成功,但进一步检查会发现它陷入 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.32 目前不提供任何本地机制来将 AppArmor 配置文件加载到节点上。 可以通过自定义基础设施或工具(例如 Kubernetes Security Profiles Operator) 加载配置文件。
调度程序不知道哪些配置文件加载到哪个节点上,因此必须将全套配置文件加载到每个节点上。 另一种方法是为节点上的每个配置文件(或配置文件类)添加节点标签, 并使用节点选择器确保 Pod 在具有所需配置文件的节点上运行。
获得正确指定的 AppArmor 配置文件可能是一件棘手的事情。幸运的是,有一些工具可以帮助你做到这一点:
aa-genprof
和 aa-logprof
通过监视应用程序的活动和日志并准许它所执行的操作来生成配置文件规则。
AppArmor 文档提供了进一步的指导。想要调试 AppArmor 的问题,你可以检查系统日志,查看具体拒绝了什么。
AppArmor 将详细消息记录到 dmesg
,
错误通常可以在系统日志中或通过 journalctl
找到。
更多详细信息参见 AppArmor 失败。
在 Kubernetes v1.30 之前,AppArmor 是通过注解指定的。使用文档版本选择器查看包含此已弃用 API 的文档。
你可以在容器的 securityContext
或 Pod 的 securityContext
中设置 appArmorProfile
。
如果在 Pod 级别设置配置文件,该配置将被用作 Pod 中所有容器(包括 Init、Sidecar 和临时容器)的默认配置文件。
如果同时设置了 Pod 和容器 AppArmor 配置文件,则将使用容器的配置文件。
AppArmor 配置文件有 2 个字段:
type
(必需) - 指示将应用哪种 AppArmor 配置文件。有效选项是:
Localhost
localhostProfile
指定)。RuntimeDefault
Unconfined
localhostProfile
- 在节点上加载的、应被使用的配置文件的名称。
该配置文件必须在节点上预先配置才能工作。
当且仅当 type
是 Localhost
时,必须提供此选项。
其他资源:
Kubernetes v1.19 [stable]
Seccomp 代表安全计算(Secure Computing)模式,自 2.6.12 版本以来,一直是 Linux 内核的一个特性。 它可以用来沙箱化进程的权限,限制进程从用户态到内核态的调用。 Kubernetes 能使你自动将加载到节点上的 seccomp 配置文件应用到你的 Pod 和容器。
识别你的工作负载所需要的权限是很困难的。在本篇教程中, 你将了解如何将 seccomp 配置文件加载到本地的 Kubernetes 集群中, 如何将它们应用到 Pod,以及如何开始制作只为容器进程提供必要的权限的配置文件。
为了完成本篇教程中的所有步骤,你必须安装 kind 和 kubectl。
本教程中使用的命令假设你使用 Docker 作为容器运行时。
(kind
创建的集群可以在内部使用不同的容器运行时)。
你也可以使用 Podman,但如果使用了 Podman,
你必须执行特定的指令才能顺利完成任务。
本篇教程演示的某些示例仍然是 Beta 状态(自 v1.25 起),另一些示例则仅使用 seccomp 正式发布的功能。 你应该确保,针对你使用的版本, 正确配置了集群。
本篇教程也使用了 curl
工具来下载示例到你的计算机上。
你可以使用其他自己偏好的工具来自适应这些步骤。
无法将 seccomp 配置文件应用于在容器的 securityContext
中设置了 privileged: true
的容器。
特权容器始终以 Unconfined
的方式运行。
这些配置文件的内容将在稍后进行分析,
现在先将它们下载到名为 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"
],
"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
你应该看到在最后一步的末尾列出有三个配置文件:
audit.json fine-grained.json violation.json
为简单起见,kind 可用来创建加载了 seccomp 配置文件的单节点集群。 Kind 在 Docker 中运行 Kubernetes,因此集群的每个节点都是一个容器。 这允许将文件挂载到每个容器的文件系统中,类似于将文件加载到节点上。
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.32。
作为 Beta 特性,你可以将 Kubernetes
配置为使用容器运行时默认首选的配置文件,
而不是回退到 Unconfined
。
如果你想尝试,请在继续之前参阅
启用使用 RuntimeDefault
作为所有工作负载的默认 seccomp 配置文件。
有了 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 路径中。
使用 docker exec
在 Pod 中运行命令:
# 将 6a96207fed4b 更改为你从 “docker ps” 看到的容器 ID
docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles
audit.json fine-grained.json violation.json
你已验证这些 seccomp 配置文件可用于在 kind 中运行的 kubelet。
大多数容器运行时都提供了一组合理的、默认被允许或默认被禁止的系统调用。
你可以通过将 Pod 或容器的安全上下文中的 seccomp 类型设置为 RuntimeDefault
来为你的工作负载采用这些默认值。
如果你已经启用了 seccompDefault
配置,
只要没有指定其他 seccomp 配置文件,那么 Pod 就会使用 RuntimeDefault
seccomp 配置文件。
否则,默认值为 Unconfined
。
这是一个 Pod 的清单,它要求其所有容器使用 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
首先,将 audit.json
配置文件应用到新的 Pod 上,该配置文件将记录进程的所有系统调用。
这是该 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.32 仅支持使用位于 .spec.securityContext
内的字段来配置 seccomp。本教程将阐述这个方法。
在集群中创建 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
为了能够与容器暴露的端点交互, 创建一个 NodePort 类型的 Service, 允许从 kind 控制平面容器内部访问端点。
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
现在,你可以使用 curl
从 kind 控制平面容器内部访问该端点,位于该服务所公开的端口上。
使用 docker exec
在属于该控制平面容器的容器中运行 curl
命令:
# 将 6a96207fed4b 更改为你从 “docker ps” 看到的控制平面容器 ID 和端口号 32373
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!
你可以看到该进程正在运行,但它实际上进行了哪些系统调用?
因为这个 Pod 在本地集群中运行,你应该能够在本地系统的 /var/log/syslog
中看到它们。
打开一个新的终端窗口并 tail
来自 http-echo
的调用的输出:
# 在你的计算机上,日志路径可能不是 "/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
出于演示目的,将配置文件应用于不允许任何系统调用的 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
进程需要相当多的系统调用。
这里 seccomp 已通过设置 "defaultAction": "SCMP_ACT_ERRNO"
被指示为在发生任何系统调用时报错。
这是非常安全的,但消除了做任何有意义的事情的能力。
你真正想要的是只给工作负载它们所需要的权限。
在进入下一节之前删除该 Pod:
kubectl delete pod violation-pod --wait --now
如果你看一看 fine-grained.json
配置文件,
你会注意到第一个示例的 syslog 中看到的一些系统调用,
其中配置文件设置为 "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
打开一个新的终端窗口并使用 tail
来监视提到来自 http-echo
的调用的日志条目:
# 你计算机上的日志路径可能与 “/var/log/syslog” 不同
tail -f /var/log/syslog | grep 'http-echo'
接着,使用 NodePort Service 公开 Pod:
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
使用 curl
从 kind 控制平面容器内部访问端点:
# 将 6a96207fed4b 更改为你从 “docker ps” 看到的控制平面容器 ID 和端口号 32373
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!
你应该在 syslog
中看不到任何输出。
这是因为配置文件允许所有必要的系统调用,并指定如果调用列表之外的系统调用应发生错误。
从安全角度来看,这是一种理想的情况,但需要在分析程序时付出一些努力。
如果有一种简单的方法可以在不需要太多努力的情况下更接近这种安全性,那就太好了。
在进入下一节之前删除该 Service 和 Pod:
kubectl delete service fine-pod --wait
kubectl delete pod fine-pod --wait --now
RuntimeDefault
作为所有工作负载的默认 seccomp 配置文件Kubernetes v1.27 [stable]
要采用为 Seccomp(安全计算模式)设置默认配置文件这一行为,你必须使用在想要启用此行为的每个节点上启用
--seccomp-default
命令行标志来运行 kubelet。
如果启用,kubelet 将会默认使用 RuntimeDefault
seccomp 配置文件,
(这一配置文件是由容器运行时定义的),而不是使用 Unconfined
(禁用 seccomp)模式。
默认的配置文件旨在提供一组限制性较强且能保留工作负载功能的安全默认值。
不同容器运行时及其不同发布版本之间的默认配置文件可能有所不同,
例如在比较来自 CRI-O 和 containerd 的配置文件时。
启用该功能既不会更改 Kubernetes securityContext.seccompProfile
API 字段,
也不会添加已弃用的工作负载注解。
这样用户可以随时回滚,而且无需实际更改工作负载配置。
crictl inspect
之类的工具可用于检查容器正在使用哪个 seccomp 配置文件。
与其他工作负载相比,某些工作负载可能需要更少的系统调用限制。
这意味着即使使用 RuntimeDefault
配置文件,它们也可能在运行时失败。
要应对此类故障,你可以:
Unconfined
模式运行工作负载。SeccompDefault
特性。同时,确保工作负载被调度到禁用该特性的节点上。如果你将此特性引入到类似的生产集群中, Kubernetes 项目建议你在部分节点上启用此特性门控, 然后在整个集群范围内推出更改之前,测试工作负载执行情况。
你可以在相关的 Kubernetes 增强提案(KEP) 中找到可能的升级和降级策略的更详细信息: 默认启用 Seccomp。
Kubernetes 1.32 允许你配置 Seccomp 配置文件, 当 Pod 的规约未定义特定的 Seccomp 配置文件时应用该配置文件。 但是,你仍然需要为合适的节点启用这种设置默认配置的能力。
如果你正在运行 Kubernetes 1.32
集群并希望启用该特性,请使用 --seccomp-default
命令行参数运行 kubelet,
或通过 kubelet 配置文件启用。
要在 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
为 kind 上的容器运行 crictl inspect
来验证:
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 的更多信息: