首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
基础
使用 Minikube 创建集群
使用 kubectl 创建 Deployment
查看 pod 和工作节点
使用 Service 暴露你的应用
运行应用程序的多个实例
执行滚动更新
使用 ConfigMap 来配置 Redis
使用 AppArmor 限制容器对资源的访问
在集群级别应用 Pod 安全标准
在名字空间级别应用 Pod 安全标准
使用 seccomp 限制容器的系统调用
公开外部 IP 地址以访问集群中应用程序
使用 Redis 部署 PHP 留言板应用程序
StatefulSet 基础
使用持久卷部署 WordPress 和 MySQL
使用 StatefulSet 部署 Cassandra
运行 ZooKeeper
服务器端应用API
客户端库
用户认证
使用启动引导令牌
证书签名请求
准入控制器
管理服务账号
使用 RBAC 鉴权
当前位置:
首页>>
技术小册>>
Kubernetes中文教程(六)
小册名称:Kubernetes中文教程(六)
AppArmor 是一个 Linux 内核安全模块, 它补充了基于标准 Linux 用户和组的权限,将程序限制在一组有限的资源中。 AppArmor 可以配置为任何应用程序减少潜在的攻击面,并且提供更加深入的防御。 它通过调整配置文件进行配置,以允许特定程序或容器所需的访问, 如 Linux 权能字、网络访问、文件权限等。 每个配置文件都可以在 **强制(enforcing)** 模式(阻止访问不允许的资源)或 **投诉(complain)** 模式(仅报告冲突)下运行。 AppArmor 可以通过限制允许容器执行的操作, 和/或通过系统日志提供更好的审计来帮助你运行更安全的部署。 但是,重要的是要记住 AppArmor 不是灵丹妙药, 只能做部分事情来防止应用程序代码中的漏洞。 提供良好的限制性配置文件,并从其他角度强化你的应用程序和集群非常重要。 * 查看如何在节点上加载配置文件示例 * 了解如何在 Pod 上强制执行配置文件 * 了解如何检查配置文件是否已加载 * 查看违反配置文件时会发生什么 * 查看无法加载配置文件时会发生什么 确保: 1. Kubernetes 版本至少是 v1.4 —— AppArmor 在 Kubernetes v1.4 版本中才添加了对 AppArmor 的支持。 早于 v1.4 版本的 Kubernetes 组件不知道新的 AppArmor 注解并且将会 **默认忽略** 提供的任何 AppArmor 设置。 为了确保你的 Pod 能够得到预期的保护,必须验证节点的 Kubelet 版本: ```shell 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 ``` 2. AppArmor 内核模块已启用 —— 要使 Linux 内核强制执行 AppArmor 配置文件, 必须安装并且启动 AppArmor 内核模块。默认情况下,有几个发行版支持该模块, 如 Ubuntu 和 SUSE,还有许多发行版提供可选支持。要检查模块是否已启用,请检查 `/sys/module/apparmor/parameters/enabled` 文件: ```shell cat /sys/module/apparmor/parameters/enabled Y ``` 如果 Kubelet 包含 AppArmor 支持(>= v1.4), 但是内核模块未启用,它将拒绝运行带有 AppArmor 选项的 Pod。 Ubuntu 携带了许多没有合并到上游 Linux 内核中的 AppArmor 补丁, 包括添加附加钩子和特性的补丁。Kubernetes 只在上游版本中测试过,不承诺支持其他特性。 3. 容器运行时支持 AppArmor —— 目前所有常见的 Kubernetes 支持的容器运行时都应该支持 AppArmor, 像 、 或 。 请参考相应的运行时文档并验证集群是否满足使用 AppArmor 的要求。 4. 配置文件已加载 —— 通过指定每个容器都应使用的 AppArmor 配置文件, AppArmor 会被应用到 Pod 上。如果指定的任何配置文件尚未加载到内核, Kubelet(>= v1.4)将拒绝 Pod。 通过检查 `/sys/kernel/security/apparmor/profiles` 文件, 可以查看节点加载了哪些配置文件。例如: ```shell ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort" ``` ``` apparmor-test-deny-write apparmor-test-audit-write docker-default k8s-nginx ``` 有关在节点上加载配置文件的详细信息,请参见[使用配置文件设置节点]。 只要 Kubelet 版本包含 AppArmor 支持, 如果不满足这些先决条件,Kubelet 将拒绝带有 AppArmor 选项的 Pod。 你还可以通过检查节点就绪状况消息来验证节点上的 AppArmor 支持(尽管这可能会在以后的版本中删除): ```shell kubectl get nodes -o=jsonpath='{range .items[*]}{@.metadata.name}: {.status.conditions[?].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 支持进入正式发布阶段,注解将被替换为一阶的资源字段 (更多详情参见[升级到 GA 的途径])。 AppArmor 配置文件是按 **逐个容器** 的形式来设置的。 要指定用来运行 Pod 容器的 AppArmor 配置文件,请向 Pod 的 metadata 添加注解: ```yaml container.apparmor.security.beta.kubernetes.io/<container_name>: <profile_ref> ``` `<container_name>` 的名称是配置文件所针对的容器的名称,`<profile_def>` 则设置要应用的配置文件。 `<profile_ref>` 可以是以下取值之一: * `runtime/default` 应用运行时的默认配置 * `localhost/<profile_name>` 应用在主机上加载的名为 `<profile_name>` 的配置文件 * `unconfined` 表示不加载配置文件 有关注解和配置文件名称格式的详细信息,请参阅 [API 参考]。 Kubernetes AppArmor 强制执行机制首先检查所有先决条件都已满足, 然后将所选的配置文件转发到容器运行时进行强制执行。 如果未满足先决条件,Pod 将被拒绝,并且不会运行。 要验证是否应用了配置文件,可以在容器创建事件中查找所列出的 AppArmor 安全选项: ```shell 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,直接验证容器的根进程是否以正确的配置文件运行: ```shell kubectl exec <pod_name> -- cat /proc/1/attr/current ``` ``` k8s-apparmor-example-deny-write ``` ## 举例 **本例假设你已经设置了一个集群使用 AppArmor 支持。** 首先,我们需要将要使用的配置文件加载到节点上。配置文件拒绝所有文件写入: ```shell #include <tunables/global> profile k8s-apparmor-example-deny-write flags= { #include <abstractions/base> file, # 拒绝所有文件写入 deny /** w, } ``` 由于我们不知道 Pod 将被调度到哪里,我们需要在所有节点上加载配置文件。 在本例中,我们将使用 SSH 来安装概要文件, 但是在[使用配置文件设置节点]中讨论了其他方法。 ```shell NODES=( # 你的节点的可通过 SSH 访问的域名 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= { #include <abstractions/base> file, # Deny all file writes. deny /** w, } EOF' done ``` 接下来,我们将运行一个带有拒绝写入配置文件的简单 “Hello AppArmor” Pod: ```shell kubectl create -f ./hello-apparmor.yaml ``` 如果我们查看 Pod 事件,我们可以看到 Pod 容器是用 AppArmor 配置文件 “k8s-apparmor-example-deny-write” 所创建的: ```shell 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 来验证容器是否实际使用该配置文件运行: ```shell kubectl exec hello-apparmor -- cat /proc/1/attr/current ``` ``` k8s-apparmor-example-deny-write ``` 最后,我们可以看到,如果我们尝试通过写入文件来违反配置文件会发生什么: ```shell 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 ``` 最后,让我们看看如果我们试图指定一个尚未加载的配置文件会发生什么: ```shell kubectl create -f /dev/stdin <<EOF ``` ```yaml 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 ``` ```shell 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 Conditions: Type Status Initialized True Ready False PodScheduled True Volumes: default-token-dnz7v: Type: 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 呈现 Pending 状态,并且显示一条有用的错误信息: `Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded`。 还用相同的消息记录了一个事件。 ## 管理 ### 使用配置文件设置节点 Kubernetes 目前不提供任何本地机制来将 AppArmor 配置文件加载到节点上。 有很多方法可以设置配置文件,例如: * 通过在每个节点上运行 Pod 的 [DaemonSet] 来确保加载了正确的配置文件。 可以在[这里]找到实现示例。 * 在节点初始化时,使用节点初始化脚本或镜像。 * 通过将配置文件复制到每个节点并通过 SSH 加载它们,如[示例]。 调度程序不知道哪些配置文件加载到哪个节点上,因此必须将全套配置文件加载到每个节点上。 另一种方法是为节点上的每个配置文件(或配置文件类)添加节点标签, 并使用[节点选择器]确保 Pod 在具有所需配置文件的节点上运行。 ### 禁用 AppArmor 如果你不希望 AppArmor 在集群上可用,可以通过命令行标志禁用它: ``` --feature-gates=AppArmor=false ``` 禁用时,任何包含 AppArmor 配置文件的 Pod 都将导致验证失败,且返回 “Forbidden” 错误。 即使此 Kubernetes 特性被禁用,运行时仍可能强制执行默认配置文件。 当 AppArmor 升级为正式版 时,禁用 AppArmor 功能的选项将被删除。 ## 编写配置文件 获得正确指定的 AppArmor 配置文件可能是一件棘手的事情。幸运的是,有一些工具可以帮助你做到这一点: * `aa-genprof` 和 `aa-logprof` 通过监视应用程序的活动和日志并准许它所执行的操作来生成配置文件规则。 [AppArmor 文档]提供了进一步的指导。 * [bane] 是一个用于 Docker的 AppArmor 配置文件生成器,它使用一种简化的画像语言(profile language)。 想要调试 AppArmor 的问题,你可以检查系统日志,查看具体拒绝了什么。 AppArmor 将详细消息记录到 `dmesg`, 错误通常可以在系统日志中或通过 `journalctl` 找到。 更多详细信息参见 [AppArmor 失败]。 ## API 参考 ### Pod 注解 指定容器将使用的配置文件: - **键名**:`container.apparmor.security.beta.kubernetes.io/<container_name>`, 其中 `<container_name>` 与 Pod 中某容器的名称匹配。 可以为 Pod 中的每个容器指定单独的配置文件。 - **键值**:对配置文件的引用,如下所述 ### 配置文件引用 - `runtime/default`:指默认运行时配置文件。 - 等同于不指定配置文件,只是它仍然需要启用 AppArmor。 - 实际上,许多容器运行时使用相同的 OCI 默认配置文件,在此处定义: https://github.com/containers/common/blob/main/pkg/apparmor/apparmor_linux_template.go - `localhost/<profile_name>`:按名称引用加载到节点(localhost)上的配置文件。 - 可能的配置文件名在[核心策略参考]。 - `unconfined`:这相当于为容器禁用 AppArmor。 任何其他配置文件引用格式无效。 ## 其他资源: * [Apparmor 配置文件语言快速指南] * [Apparmor 核心策略参考]
上一篇:
使用 ConfigMap 来配置 Redis
下一篇:
在集群级别应用 Pod 安全标准
该分类下的相关小册推荐:
Kubernets合辑3-kubernetes介绍
Kubernets合辑7-存储
Kubernetes中文教程(四)
Kubernets合辑6-服务发现
Kubernets合辑12-配置中心
Kubernetes中文教程(二)
Kubernets合辑5-Pod控制器
Kubernetes中文教程(五)
Kubernets合辑11-持续集成
云原生-K8S入门实战
Kubernetes中文教程(一)
Kubernets合辑9-资源约束