1 - 使用 Seccomp 限制容器的系统调用

FEATURE STATE: Kubernetes v1.19 [stable]

Seccomp 代表安全计算模式,自 2.6.12 版本以来一直是 Linux 内核的功能。 它可以用来对进程的特权进行沙盒处理,从而限制了它可以从用户空间向内核进行的调用。 Kubernetes 允许你将加载到节点上的 seccomp 配置文件自动应用于 Pod 和容器。

确定工作负载所需的特权可能很困难。在本教程中,你将了解如何将 seccomp 配置文件 加载到本地 Kubernetes 集群中,如何将它们应用到 Pod,以及如何开始制作仅向容器 进程提供必要特权的配置文件。

教程目标

  • 了解如何在节点上加载 seccomp 配置文件
  • 了解如何将 seccomp 配置文件应用于容器
  • 观察由容器进程进行的系统调用的审核
  • 观察当指定了一个不存在的配置文件时的行为
  • 观察违反 seccomp 配置的情况
  • 了解如何创建精确的 seccomp 配置文件
  • 了解如何应用容器运行时默认 seccomp 配置文件

准备开始

为了完成本教程中的所有步骤,你必须安装 kindkubectl。本教程将显示同时具有 alpha(v1.19 之前的版本) 和通常可用的 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"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

使用 Kind 创建一个本地 Kubernetes 集群

为简单起见,可以使用 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.yaml。然后使用这个配置创建集群。

kind create cluster --config=kind.yaml

一旦这个集群已经就绪,找到作为单节点集群运行的容器:

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 中运行命令:

docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles
audit.json  fine-grained.json  violation.json

使用 Seccomp 配置文件创建 Pod 以进行系统调用审核

首先,将 audit.json 配置文件应用到新的 Pod 中,该配置文件将记录该进程的所有系统调用。

为你的 Kubernetes 版本下载正确的清单:

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:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

apiVersion: v1
kind: Pod
metadata:
  name: audit-pod
  labels:
    app: audit-pod
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: localhost/profiles/audit.json
spec:
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

在集群中创建 Pod:

kubectl apply -f audit-pod.yaml

这个配置文件并不限制任何系统调用,所以这个 Pod 应该会成功启动。

kubectl get pod/audit-pod
NAME        READY   STATUS    RESTARTS   AGE
audit-pod   1/1     Running   0          30s

为了能够与该容器公开的端点进行交互,请创建一个 NodePort 服务, 该服务允许从 kind 控制平面容器内部访问该端点。

kubectl expose pod/audit-pod --type NodePort --port 5678

检查这个服务在这个节点上被分配了什么端口。

kubectl get svc/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 -it 6a96207fed4b curl localhost:32373
just made some syscalls!

你可以看到该进程正在运行,但是实际上执行了哪些系统调用?因为该 Pod 是在本地集群中运行的, 你应该可以在 /var/log/syslog 日志中看到这些。打开一个新的终端窗口,使用 tail 命令来 查看来自 http-echo 的调用输出:

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 配置文件的基础。

开始下一节之前,请清理该 Pod 和 Service:

kubectl delete pod/audit-pod
kubectl delete svc/audit-pod

使用导致违规的 Seccomp 配置文件创建 Pod

为了进行演示,请将不允许任何系统调用的配置文件应用于 Pod。

为你的 Kubernetes 版本下载正确的清单:

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:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

apiVersion: v1
kind: Pod
metadata:
  name: violation-pod
  labels:
    app: violation-pod
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: localhost/profiles/violation.json
spec:
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

在集群中创建 Pod:

kubectl apply -f 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 和 Service:

kubectl delete pod/violation-pod
kubectl delete svc/violation-pod

使用设置仅允许需要的系统调用的配置文件来创建 Pod

如果你看一下 fine-pod.json 文件,你会注意到在第一个示例中配置文件设置为 "defaultAction": "SCMP_ACT_LOG" 的一些系统调用。 现在,配置文件设置为 "defaultAction": "SCMP_ACT_ERRNO",但是在 "action": "SCMP_ACT_ALLOW" 块中明确允许一组系统调用。 理想情况下,容器将成功运行,并且你将不会看到任何发送到 syslog 的消息。

为你的 Kubernetes 版本下载正确的清单:

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:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

apiVersion: v1
kind: Pod
metadata:
  name: fine-pod
  labels:
    app: fine-pod
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: localhost/profiles/fine-grained.json
spec:
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

在你的集群上创建Pod:

kubectl apply -f fine-pod.yaml

Pod 应该被成功启动。

kubectl get pod/fine-pod
NAME        READY   STATUS    RESTARTS   AGE
fine-pod   1/1     Running   0          30s

打开一个新的终端窗口,使用 tail 命令查看来自 http-echo 的调用的输出:

tail -f /var/log/syslog | grep 'http-echo'

使用 NodePort 服务为该 Pod 开一个端口:

kubectl expose pod/fine-pod --type NodePort --port 5678

检查服务在该节点被分配了什么端口:

kubectl get svc/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 控制面板容器内部请求这个端点:

docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

你会看到 syslog 中没有任何输出,因为这个配置文件允许了所有需要的系统调用, 并指定如果有发生列表之外的系统调用将发生错误。从安全角度来看,这是理想的情况, 但是在分析程序时需要多付出一些努力。如果有一种简单的方法无需花费太多精力就能更接近此安全性,那就太好了。

开始下一节之前,请清理该 Pod 和 Service:

kubectl delete pod/fine-pod
kubectl delete svc/fine-pod

使用容器运行时默认的 Seccomp 配置文件创建 Pod

大多数容器运行时都提供一组允许或不允许的默认系统调用。通过使用 runtime/default 注释 或将 Pod 或容器的安全上下文中的 seccomp 类型设置为 RuntimeDefault,可以轻松地在 Kubernetes 中应用默认值。

为你的 Kubernetes 版本下载正确的清单:

apiVersion: v1
kind: Pod
metadata:
  name: audit-pod
  labels:
    app: audit-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

apiVersion: v1
kind: Pod
metadata:
  name: default-pod
  labels:
    app: default-pod
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: runtime/default
spec:
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

默认的 seccomp 配置文件应该为大多数工作负载提供足够的权限。

接下来

额外的资源:

2 - AppArmor

FEATURE STATE: Kubernetes v1.4 [beta]

Apparmor 是一个 Linux 内核安全模块,它补充了标准的基于 Linux 用户和组的安全模块将程序限制为有限资源集的权限。AppArmor 可以配置为任何应用程序减少潜在的攻击面,并且提供更加深入的防御。AppArmor 是通过配置文件进行配置的,这些配置文件被调整为报名单,列出了特定程序或者容器所需要的访问权限,如 Linux 功能、网络访问、文件权限等。每个配置文件都可以在强制模式(阻止访问不允许的资源)或投诉模式(仅报告冲突)下运行。

教程目标

  • 查看如何在节点上加载配置文件示例
  • 了解如何在 Pod 上强制执行配置文件
  • 了解如何检查配置文件是否已加载
  • 查看违反配置文件时会发生什么情况
  • 查看无法加载配置文件时会发生什么情况

准备开始

务必:

  1. Kubernetes 版本至少是 v1.4 -- AppArmor 在 Kubernetes v1.4 版本中才添加了对 AppArmor 的支持。早于 v1.4 版本的 Kubernetes 组件不知道新的 AppArmor 注释,并且将会 默认忽略 提供的任何 AppArmor 设置。为了确保您的 Pods 能够得到预期的保护,必须验证节点的 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
  1. 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 只在上游版本中测试过,不承诺支持其他特性。
  1. Docker 作为容器运行环境 -- 目前,支持 Kubernetes 运行的容器中只有 Docker 也支持 AppArmor。随着更多的运行时添加 AppArmor 的支持,可选项将会增多。您可以使用以下命令验证节点是否正在运行 Docker:

    kubectl get nodes -o=jsonpath=$'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.containerRuntimeVersion}\n{end}'
    
    gke-test-default-pool-239f5d02-gyn2: docker://1.11.2
    gke-test-default-pool-239f5d02-x1kf: docker://1.11.2
    gke-test-default-pool-239f5d02-xwux: docker://1.11.2
    

    如果 Kubelet 包含 AppArmor 支持(>=v1.4),如果运行环境不是 Docker,它将拒绝运行带有 AppArmor 选项的 Pod。

  1. 配置文件已加载 -- 通过指定每个容器都应使用 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 目前处于测试阶段,因此选项被指定为注释。一旦 AppArmor 被授予支持通用,注释将替换为首要的字段(更多详情参见升级到 GA 的途径)。

AppArmor 配置文件被指定为 per-container。要指定要用其运行 Pod 容器的 AppArmor 配置文件,请向 Pod 的元数据添加注释:

container.apparmor.security.beta.kubernetes.io/<container_name>: <profile_ref>

<container_name> 的名称是容器的简称,用以描述简介,并且简称为 <profile_ref><profile_ref> 可以作为其中之一:

  • runtime/default 应用运行时的默认配置
  • localhost/<profile_name> 应用在名为 <profile_name> 的主机上加载的配置文件
  • unconfined 表示不加载配置文件

有关注释和配置文件名称格式的详细信息,请参阅API 参考

Kubernetes AppArmor 强制执行方式首先通过检查所有先决条件都已满足,然后将配置文件选择转发到容器运行时进行强制执行。如果未满足先决条件, 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]

您还可以通过检查容器的 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 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 <<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
  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
    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

我们可以通过检查该配置文件的 proc attr 来验证容器是否实际使用该配置文件运行:

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
  annotations:
    container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-allow-write
spec:
  containers:
  - name: hello
    image: busybox
    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:        <none>
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:   <none>
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:        <none>
    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: <none>
Tolerations:    <none>
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 呈现失败状态,并且显示一条有用的错误信息:Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" 未加载。还用相同的消息记录了一个事件。

管理

使用配置文件设置节点

Kubernetes 目前不提供任何本地机制来将 AppArmor 配置文件加载到节点上。有很多方法可以设置配置文件,例如:

  • 通过在每个节点上运行 Pod 的DaemonSet确保加载了正确的配置文件。可以找到一个示例实现这里
  • 在节点初始化时,使用节点初始化脚本(例如 Salt 、Ansible 等)或镜像。
  • 通过将配置文件复制到每个节点并通过 SSH 加载它们,如示例

调度程序不知道哪些配置文件加载到哪个节点上,因此必须将全套配置文件加载到每个节点上。另一种方法是为节点上的每个配置文件(或配置文件类)添加节点标签,并使用[节点选择器](/zh/docs/concepts/configuration/assign pod node/)确保 Pod 在具有所需配置文件的节点上运行。

使用 PodSecurityPolicy 限制配置文件

如果启用了 PodSecurityPolicy 扩展,则可以应用群集范围的 AppArmor 限制。要启用 PodSecurityPolicy,必须在“apiserver”上设置以下标志:

--enable-admission-plugins=PodSecurityPolicy[,others...]

AppArmor 选项可以指定为 PodSecurityPolicy 上的注释:

apparmor.security.beta.kubernetes.io/defaultProfileName: <profile_ref>
apparmor.security.beta.kubernetes.io/allowedProfileNames: <profile_ref>[,others...]

默认配置文件名选项指定默认情况下在未指定任何配置文件时应用于容器的配置文件。节点允许配置文件名选项指定允许 Pod 容器运行时的配置文件列表。配置文件的指定格式与容器上的相同。完整规范见API 参考

禁用 AppArmor

如果您不希望 AppArmor 在集群上可用,可以通过命令行标志禁用它:

--feature-gates=AppArmor=false

禁用时,任何包含 AppArmor 配置文件的 Pod 都将因 "Forbidden" 错误而导致验证失败。注意,默认情况下,docker 总是在非特权 pods 上启用 "docker-default" 配置文件(如果 AppArmor 内核模块已启用),并且即使功能门已禁用,也将继续启用该配置文件。当 AppArmor 应用于通用(GA)时,禁用 Apparmor 的选项将被删除。

使用 AppArmor 升级到 Kubernetes v1.4

不需要对 AppArmor 执行任何操作即可将集群升级到 v1.4。但是,如果任何现有的 pods 有一个 AppArmor 注释,它们将不会通过验证(或 PodSecurityPolicy 认证)。如果节点上加载了许可配置文件,恶意用户可以预先应用许可配置文件,将 pod 权限提升到 docker-default 权限之上。如果存在这个问题,建议清除包含 apparmor.security.beta.kubernetes.io 注释的任何 pods 的集群。

升级到一般可用性的途径

当 Apparmor 准备升级到通用(GA)时,当前指定的选项通过注释将转换为字段。通过转换支持所有升级和降级路径是非常微妙的,并将在转换发生时详细解释。我们将承诺在至少两个版本中同时支持字段和注释,并在之后的至少两个版本中显式拒绝注释。

编写配置文件

获得正确指定的 AppArmor 配置文件可能是一件棘手的事情。幸运的是,有一些工具可以帮助您做到这一点:

  • aa-genprof and aa-logprof 通过监视应用程序的活动和日志并承认它所采取的操作来生成配置文件规则。更多说明由AppArmor 文档提供。
  • bane是一个用于 Docker的 AppArmor 档案生成器,它使用简化的档案语言。

建议在开发工作站上通过 Docker 运行应用程序以生成配置文件,但是没有什么可以阻止在运行 Pod 的 Kubernetes 节点上运行工具。

想要调试 AppArmor 的问题,您可以检查系统日志,查看具体拒绝了什么。AppArmor 将详细消息记录到 dmesg ,错误通常可以在系统日志中或通过 journalctl 找到。更多详细信息见AppArmor 失败

API 参考

Pod 注释

指定容器将使用的配置文件:

  • key: container.apparmor.security.beta.kubernetes.io/<container_name> 中的 <container_name> 匹配 Pod 中的容器名称。 可以为 Pod 中的每个容器指定单独的配置文件。
  • value: 配置文件参考,如下所述

配置文件参考

  • runtime/default: 指默认运行时配置文件。
    • 等同于不指定配置文件(没有 PodSecurityPolicy 默认值),除非它仍然需要启用 AppArmor。
    • 对于 Docker,这将解析为非特权容器的Docker default配置文件,特权容器的配置文件为未定义(无配置文件)。
  • localhost/<profile_name>: 指按名称加载到节点(localhost)上的配置文件。
  • unconfined: 这有效地禁用了容器上的 AppArmor 。

任何其他配置文件引用格式无效。

PodSecurityPolicy 注解

指定在未提供容器时应用于容器的默认配置文件:

  • key: apparmor.security.beta.kubernetes.io/defaultProfileName
  • value: 如上述文件参考所述

上面描述的指定配置文件, Pod 容器列表的配置文件引用允许指定:

  • key: apparmor.security.beta.kubernetes.io/allowedProfileNames
  • value: 配置文件引用的逗号分隔列表(如上所述)
    • 尽管转义逗号是配置文件名中的合法字符,但此处不能显式允许。

接下来

其他资源