使用AppArmor限制容器对资源的访问
特性状态:Kubernetesv1.4[beta]
AppArmor是一个Linux内核安全模块,它补充了基于标准Linux用户和组的权限,将程序限制在一组有限的资源中。AppArmor可以配置为任何应用程序减少潜在的攻击面,并且提供更加深入的防御。它通过调整配置文件进行配置,以允许特定程序或容器所需的访问,如Linux权能字、网络访问、文件权限等。每个配置文件都可以在强制(enforcing)模式(阻止访问不允许的资源)或投诉(complain)模式(仅报告冲突)下运行。
AppArmor可以通过限制允许容器执行的操作,和/或通过系统日志提供更好的审计来帮助你运行更安全的部署。但是,重要的是要记住AppArmor不是灵丹妙药,只能做部分事情来防止应用程序代码中的漏洞。提供良好的限制性配置文件,并从其他角度强化你的应用程序和集群非常重要。
教程目标在开始之前
确保:
Kubernetes版本至少是v1.4——AppArmor在Kubernetesv1.4版本中才添加了对AppArmor的支持。早于v1.4版本的Kubernetes组件不知道新的AppArmor注解并且将会默认忽略提供的任何AppArmor设置。为了确保你的Pod能够得到预期的保护,必须验证节点的Kubelet版本:
kubectl get nodes -o=jsonpath={range .items[*]}{@.metadata.name}: {@.status.nodeInfo.kubeletVersion}\n{end}'
gke-test-default-pool-239f5d02-gyn2: v1.4.0
gke-test-default-pool-239f5d02-x1kf: v1.4.0
gke-test-default-pool-239f5d02-xwux: v1.4.0
AppArmor内核模块已启用——要使Linux内核强制执行AppArmor配置文件,必须安装并且启动AppArmor内核模块。默认情况下,有几个发行版支持该模块,如Ubuntu和SUSE,还有许多发行版提供可选支持。要检查模块是否已启用,请检查/sys/module/apparmor/parameters/enabled文件:
cat /sys/module/apparmor/parameters/enabled
Y
如果Kubelet包含AppArmor支持(>=v1.4),但是内核模块未启用,它将拒绝运行带有AppArmor选项的Pod。
说明:Ubuntu携带了许多没有合并到上游Linux内核中的AppArmor补丁,包括添加附加钩子和特性的补丁。Kubernetes只在上游版本中测试过,不承诺支持其他特性。
容器运行时支持AppArmor——目前所有常见的Kubernetes支持的容器运行时都应该支持AppArmor,像Docker,CRI-O或containerd。请参考相应的运行时文档并验证集群是否满足使用AppArmor的要求。配置文件已加载——通过指定每个容器都应使用的AppArmor配置文件,AppArmor会被应用到Pod上。如果指定的任何配置文件尚未加载到内核,Kubelet(>=v1.4)将拒绝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)
只要Kubelet版本包含AppArmor支持(>=v1.4),如果不满足这些先决条件,Kubelet将拒绝带有AppArmor选项的Pod。你还可以通过检查节点就绪状况消息来验证节点上的AppArmor支持(尽管这可能会在以后的版本中删除):
kubectl get nodes -o=jsonpath={range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}\n{end}'
gke-test-default-pool-239f5d02-gyn2: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-x1kf: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-xwux: kubelet is posting ready status. AppArmor enabled
保护Pod
说明:
AppArmor目前处于Beta阶段,因此选项以注解形式设定。一旦AppArmor支持进入正式发布阶段,注解将被替换为一阶的资源字段。
AppArmor配置文件是按逐个容器的形式来设置的。要指定用来运行Pod容器的AppArmor配置文件,请向Pod的metadata添加注解:
container.apparmor.security.beta.kubernetes.io/:
的名称是配置文件所针对的容器的名称,
则设置要应用的配置文件。
可以是以下取值之一:
KubernetesAppArmor强制执行机制首先检查所有先决条件都已满足,然后将所选的配置文件转发到容器运行时进行强制执行。如果未满足先决条件,Pod将被拒绝,并且不会运行。
要验证是否应用了配置文件,可以在容器创建事件中查找所列出的AppArmor安全选项:
kubectl get events | grep Created
22s 22s 1 hello-apparmor Pod spec.containers{hello} Normal Created {kubelet e2e-test-stclair-node-pool-31nt} Created container with docker id 269a53b202d3; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]
你还可以通过检查容器的procattr,直接验证容器的根进程是否以正确的配置文件运行:
kubectl exec cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)
举例
本例假设你已经设置了一个集群使用AppArmor支持。
首先,我们需要将要使用的配置文件加载到节点上。配置文件拒绝所有文件写入:
#include
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include
file,
# Deny all file writes.
deny /** w,
}
由于我们不知道Pod将被调度到哪里,我们需要在所有节点上加载配置文件。在本例中,我们将使用SSH来安装概要文件。
NODES=(
# The SSH-accessible domain names of your nodes
gke-test-default-pool-239f5d02-gyn2.us-central1-a.my-k8s
gke-test-default-pool-239f5d02-x1kf.us-central1-a.my-k8s
gke-test-default-pool-239f5d02-xwux.us-central1-a.my-k8s)
for NODE in ${NODES[*]}; do ssh $NODE 'sudo apparmor_parser -q <
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include
file,
# Deny all file writes.
deny /** w,
}
EOF'
done
接下来,我们将运行一个带有拒绝写入配置文件的简单“HelloAppArmor”Pod:
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor
annotations:
# Tell Kubernetes to apply the AppArmor profile "k8s-apparmor-example-deny-write".
# Note that this is ignored if the Kubernetes node is not running version 1.4 or greater.
container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write
spec:
containers:
- name: hello
image: busybox:1.28
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
kubectl create -f ./hello-apparmor.yaml
如果我们查看Pod事件,我们可以看到Pod容器是用AppArmor配置文件“k8s-apparmor-example-deny-write”所创建的:
kubectl get events | grep hello-apparmor
14s 14s 1 hello-apparmor Pod Normal Scheduled {default-scheduler } Successfully assigned hello-apparmor to gke-test-default-pool-239f5d02-gyn2
14s 14s 1 hello-apparmor Pod spec.containers{hello} Normal Pulling {kubelet gke-test-default-pool-239f5d02-gyn2} pulling image "busybox"
13s 13s 1 hello-apparmor Pod spec.containers{hello} Normal Pulled {kubelet gke-test-default-pool-239f5d02-gyn2} Successfully pulled image "busybox"
13s 13s 1 hello-apparmor Pod spec.containers{hello} Normal Created {kubelet gke-test-default-pool-239f5d02-gyn2} Created container with docker id 06b6cd1c0989; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]
13s 13s 1 hello-apparmor Pod spec.containers{hello} Normal Started {kubelet gke-test-default-pool-239f5d02-gyn2} Started container with docker id 06b6cd1c0989
我们可以通过检查该配置文件的procattr来验证容器是否实际使用该配置文件运行:
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 <
apiVersion: v1
kind: Pod
metadata:
name: hello-apparmor-2
annotations:
container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-allow-write
spec:
containers:
- name: hello
image: busybox:1.28
command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created
kubectl describe pod hello-apparmor-2
Name: hello-apparmor-2
Namespace: default
Node: gke-test-default-pool-239f5d02-x1kf/
Start Time: Tue, 30 Aug 2016 17:58:56 -0700
Labels:
Annotations: container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write
Status: Pending
Reason: AppArmor
Message: Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
IP:
Controllers:
Containers:
hello:
Container ID:
Image: busybox
Image ID:
Port:
Command:
sh
-c
echo 'Hello AppArmor!' && sleep 1h
State: Waiting
Reason: Blocked
Ready: False
Restart Count: 0
Environment:
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-dnz7v (ro)
Conditions:
Type Status
Initialized True
Ready False
PodScheduled True
Volumes:
default-token-dnz7v:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-dnz7v
Optional: false
QoS Class: BestEffort
Node-Selectors:
Tolerations:
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
23s 23s 1 {default-scheduler } Normal Scheduled Successfully assigned hello-apparmor-2 to e2e-test-stclair-node-pool-t1f5
23s 23s 1 {kubelet e2e-test-stclair-node-pool-t1f5} Warning AppArmor Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
注意Pod呈现Pending状态,并且显示一条有用的错误信息:PodCannotenforceAppArmor:profile"k8s-apparmor-example-allow-write"isnotloaded。还用相同的消息记录了一个事件。
管理 使用配置文件设置节点
Kubernetes目前不提供任何本地机制来将AppArmor配置文件加载到节点上。有很多方法可以设置配置文件,例如:
调度程序不知道哪些配置文件加载到哪个节点上,因此必须将全套配置文件加载到每个节点上。另一种方法是为节点上的每个配置文件(或配置文件类)添加节点标签,并使用节点选择器确保Pod在具有所需配置文件的节点上运行。
使用PodSecurityPolicy限制配置文件
说明:
PodSecurityPolicy在Kubernetesv1.21版本中已被废弃,将在v1.25版本移除。
如果启用了PodSecurityPolicy扩展,则可以应用集群范围的AppArmor限制。要启用PodSecurityPolicy,必须在apiserver上设置以下标志:
--enable-admission-plugins=PodSecurityPolicy[,others...]
AppArmor选项可以指定为PodSecurityPolicy上的注解:
apparmor.security.beta.kubernetes.io/defaultProfileName:
apparmor.security.beta.kubernetes.io/allowedProfileNames: [,others...]
默认配置文件名选项指定默认情况下在未指定任何配置文件时应用于容器的配置文件。所允许的配置文件名称选项指定允许Pod容器运行期间所对应的配置文件列表。如果同时提供了这两个选项,则必须允许默认值。配置文件的指定格式与容器上的相同。
禁用AppArmor
如果你不希望AppArmor在集群上可用,可以通过命令行标志禁用它:
--feature-gates=AppArmor=false
禁用时,任何包含AppArmor配置文件的Pod都将导致验证失败,且返回“Forbidden”错误。
说明:
即使此Kubernetes特性被禁用,运行时仍可能强制执行默认配置文件。当AppArmor升级为正式版(GA)时,禁用AppArmor功能的选项将被删除。
编写配置文件
获得正确指定的AppArmor配置文件可能是一件棘手的事情。幸运的是,有一些工具可以帮助你做到这一点:
想要调试AppArmor的问题,你可以检查系统日志,查看具体拒绝了什么。AppArmor将详细消息记录到dmesg,错误通常可以在系统日志中或通过journalctl找到。更多详细信息见AppArmor失败。
API参考 Pod注解
指定容器将使用的配置文件:
配置文件引用 localhost/
:按名称引用加载到节点(localhost)上的配置文件。unconfined:这相当于为容器禁用AppArmor。
任何其他配置文件引用格式无效。
PodSecurityPolicy注解
指定在未提供容器时应用于容器的默认配置文件:
上面描述的指定配置文件,Pod容器列表的配置文件引用允许指定:
尽管转义逗号是配置文件名中的合法字符,但此处不能显式允许。