乐趣区

Kubernetes准入控制器指南

作者:Malte Isberner(StackRox)
Kubernetes 极大地提高了当今生产中后端群集的速度和可管理性。由于其灵活性、可扩展性和易用性,Kubernetes 已成为容器编排器的事实标准。Kubernetes 也提供一系列保护生产工作负载的功能。安全功能的最新引入是一组称为“准入控制器”的插件。必须启用准入控制器才能使用 Kubernetes 的一些更高级的安全功能,例如,在整个命名空间中强制实施安全配置基线的 pod 安全政策。以下必须知道的提示和技巧,将帮助你利用准入控制器,在 Kubernetes 中充分利用这些安全功能。
什么是 Kubernetes 准入控制器?
简而言之,Kubernetes 准入控制器是管理和强制执行集群使用方式的插件。可以将它们视为拦截(经过身份验证的)API 请求的网守,并且可以更改请求对象,或完全拒绝请求。准入控制过程有两个阶段:首先执行改变(mutating)阶段,然后是验证(validating)阶段。因此,准入控制器可以充当改变或验证控制器,或两者的组合。例如,LimitRanger 准入控制器可以使用默认资源请求和限制(改变阶段)扩充 pod,并验证具有设置资源要求的 pod,不超过 LimitRange 对象中指定的每命名空间限制(验证阶段)。
准入控制器阶段
值得注意的是,许多用户认为是内置的 Kubernetes 操作的某些方面,实际上由准入控制器管理。例如,当删除命名空间并随后进入 Terminating 状态时,NamespaceLifecycle 准入控制器将阻止在此命名空间中创建任何新对象。
在 Kubernetes 附带的 30 多个准入控制器中,有两个因其几乎无限的灵活性而发挥特殊作用 – ValidatingAdmissionWebhooks 和 MutatingAdmissionWebhooks,两者在 Kubernetes 1.13 都处于 beta 状态。我们将仔细研究这两个准入控制器,因为它们本身并没有实现任何政策决策逻辑。相反,相应的操作是从集群内运行的服务的 REST 端点(webhook)获得的。这种方法将准入控制器逻辑与 Kubernetes API 服务器分离,从而允许用户在 Kubernetes 集群中创建、更新或删除资源时实现自定义逻辑。
两种准入控制器 webhooks 之间的差异几乎是不言自明的:改变(mutating)准入 webhooks 可能会改变对象,而验证(validating)准入 webhooks 则不会。然而,即使是改变准入 webhook 也可以拒绝请求,从而以验证的方式行事。验证入场 webhooks 比改变 webhooks 有两个主要优点:首先,出于安全原因,可能需要禁用 MutatingAdmissionWebhook 准入控制器(或对谁可能创建 MutatingWebhookConfiguration 对象应用更严格的 RBAC 限制),因为它可能会产生混淆,甚至危险的副作用。其次,如上图所示,验证准入控制器(以及 webhooks)在改变控制器之后运行。因此,验证 webhook 看到的任何请求对象都是将持久保存到 etcd 的最终版本。
通过将标志传递给 Kubernetes API 服务器来配置启用的准入控制器集。请注意,旧的 -admission-control 标志在 1.10 中已弃用,并替换为 -enable-admission-plugins。
–enable-admission-plugins=ValidatingAdmissionWebhook,MutatingAdmissionWebhook

Kubernetes 建议默认启用以下准入控制器。
–enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,Priority,ResourceQuota,PodSecurityPolicy

可以在 Kubernetes 官方参考中找到完整的准入控制器列表及其说明。本讨论将仅关注基于 webhook 的准入控制器。
为什么我需要准入控制器?

安全性:准入控制器可以通过在整个命名空间或集群中,强制使用合理的安全基准来提高安全性。内置的 PodSecurityPolicy 准入控制器可能是最突出的例子;例如,它可以用于禁止容器以 root 身份运行,或者确保容器的根文件系统始终以只读方式挂载。可通过基于 webhook 的自定义准入控制器实现的其他用例包括:

允许仅从企业已知的特定仓库中提取镜像,同时拒绝未知的镜像仓库。
拒绝不符合安全标准的部署。例如,使用特权(privileged)标志的容器可以规避许多安全检查。基于 webhook 的准入控制器可以减轻此风险,该准入控制器拒绝此类部署(验证)或覆盖特权(privileged)标志,将其设置为 false。

治理:准入控制器允许你强制遵守某些做法,例如具有良好的标签、注释、资源限制或其他设置。一些常见的场景包括:

对不同对象强制执行标签验证,以确保将正确的标签用于各种对象,例如分配给团队或项目的每个对象,或指定应用程序标签的每个部署。
自动向对象添加注释,例如为“dev”部署资源分配正确的成本中心。

配置管理:准入控制器允许你验证群集中运行对象的配置,并防止群集中任何明显的错误配置。准入控制器可用于检测和修复没有语义标签的部署镜像,例如:

自动添加资源限制或验证资源限制,
确保合理的标签被添加到 pod,或
确保生产部署中使用的镜像引用不使用最新的(latest)标记或带有 -dev 后缀的标记。

通过这种方式,准入控制器和政策管理有助于确保应用程序在不断变化的控制环境中保持合规。
示例:编写和部署准入控制器 Webhook
为了说明如何利用准入控制器 webhook 来建立自定义安全政策,让我们考虑一个解决 Kubernetes 缺点之一的例子:它的许多默认值都经过优化,易于使用并减少摩擦,有时以牺牲安全性为代价。其中一个设置是默认允许容器以 root 身份运行(并且,如果没有进一步的配置,Dockerfile 中也没有 USER 指令,也会这样)。尽管容器在一定程度上与底层主机隔离,但以 root 身份运行容器确实会增加部署的风险级别 – 作为许多安全性最佳实践之一,这应该避免。例如,最近暴露的 runC 漏洞(CVE-2019-5736)只有在容器以 root 身份运行时才能被利用。
你可以使用自定义改变准入控制器 webhook 来应用更安全的默认值:除非明确请求,否则我们的 webhook 将确保 pod 作为非 root 用户运行(如果未进行明确分配,我们将分配用户 ID 1234)。请注意,此设置不会阻止你在群集中部署任何工作负载,包括那些合法需要以 root 身份运行的工作负载。它只要求你在部署配置中,明确启用此风险程序操作模式,而对所有其他工作负载默认为非 root 模式。
完整的代码以及部署说明可以在我们随附的 GitHub 存储库中找到。在这里,我们将重点介绍 webhook 如何工作的一些更微妙的方面。
改变(Mutating)Webhook 配置
通过在 Kubernetes 中创建 MutatingWebhookConfiguration 对象来定义改变准入控制器 webhook。在我们的示例中,我们使用以下配置:
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: demo-webhook
webhooks:
– name: webhook-server.webhook-demo.svc
clientConfig:
service:
name: webhook-server
namespace: webhook-demo
path: “/mutate”
caBundle: ${CA_PEM_B64}
rules:
– operations: [“CREATE”]
apiGroups: [“”]
apiVersions: [“v1”]
resources: [“pods”]

此配置定义 webhook webhook-server.webhook-demo.svc,并指示 Kubernetes API 服务器在通过向 /mutate URL 发出 HTTP POST 请求创建 pod 时,在命名空间 webhook-demo 中查询服务 webhook-server。要使此配置生效,必须满足几个先决条件。
Webhook REST API
Kubernetes API 服务器向给定服务和 URL 路径发出 HTTPS POST 请求,并在请求正文中使用 JSON 编码的 AdmissionReview(设置了 Request 字段)。响应应该是 JSON 编码的 AdmissionReview,这次设置了 Response 字段。
我们的演示存储库包含一个处理序列化 / 反序列化样板代码的函数,并允许你专注于实现在 Kubernetes API 对象上运行的逻辑。在我们的示例中,实现准入控制器逻辑的函数称为 applySecurityDefaults,在 /mutate URL 下提供此功能的 HTTPS 服务器可以设置如下:
mux := http.NewServeMux()
mux.Handle(“/mutate”, admitFuncHandler(applySecurityDefaults))
server := &http.Server{
Addr: “:8443”,
Handler: mux,
}
log.Fatal(server.ListenAndServeTLS(certPath, keyPath))

请注意,要使服务器在没有提升权限的情况下运行,我们让 HTTP 服务器侦听端口 8443。Kubernetes 不允许在 webhook 配置中指定端口;它始终采用 HTTPS 端口 443。但是,由于无论如何都需要服务对象,我们可以轻松地将服务的端口 443 映射到容器上的端口 8443:
apiVersion: v1
kind: Service
metadata:
name: webhook-server
namespace: webhook-demo
spec:
selector:
app: webhook-server # specified by the deployment/pod
ports:
– port: 443
targetPort: webhook-api # name of port 8443 of the container

对象修改逻辑
在改变准入控制器 webhook 中,通过 JSON 补丁执行改变。虽然 JSON 补丁标准包含许多复杂性,远远超出了本讨论的范围,但我们的示例中的 Go 数据结构,及其用法应该为用户提供有关 JSON 补丁如何工作的良好初步概述:
type patchOperation struct {
Op string `json:”op”`
Path string `json:”path”`
Value interface{} `json:”value,omitempty”`
}

要将 pod 的字段.spec.securityContext.runAsNonRoot 设置为 true,我们构造以下 patchOperation 对象:
patches = append(patches, patchOperation{
Op: “add”,
Path: “/spec/securityContext/runAsNonRoot”,
Value: true,
})

TLS 证书
由于必须通过 HTTPS 提供 webhook,因此我们需要适当的服务器证书。这些证书可以是自签名的(由自签名 CA 签名),但我们需要 Kubernetes 在与 webhook 服务器通信时指示相应的 CA 证书。此外,证书的公用名(CN)必须与 Kubernetes API 服务器使用的服务器名称匹配,内部服务的名称是 <service-name>.<namespace>.svc,在我们的案例中即 webhook-server.webhook-demo.svc。由于自签名 TLS 证书的生成在 Internet 上有详细记录,因此我们只需在示例中引用相应的 shell 脚本。
之前显示的 webhook 配置包含占位符 ${CA_PEM_B64}。在我们创建此配置之前,我们需要将此部分替换为 CA 的 Base64 编码的 PEM 证书。openssl base64 - A 命令可用于此目的。
测试 Webhook
在部署 webhook 服务器并对其进行配置之后(可以通过从存储库调用./deploy.sh 脚本来完成),现在是时候测试并验证 webhook 是否确实完成它的工作。存储库包含三个示例:

未指定安全上下文的 pod(pod-with-defaults)。我们希望此 pod 以非 root 身份运行,用户 ID 为 1234。
一个指定安全上下文的 pod,明确允许它以 root 身份运行(pod-with-override)。
具有冲突配置的 pod,指定它必须以非 root 用户身份运行,但用户 ID 为 0(pod-with-conflict)。为了展示拒绝对象创建请求,我们增加了我们的准入控制器逻辑,以拒绝这些明显的错误配置。

通过运行 kubectl create -f examples/<name>.yaml 创建其中一个 pod。在前两个示例中,你可以通过检查日志来验证 pod 运行的用户 ID,例如:
$ kubectl create -f examples/pod-with-defaults.yaml
$ kubectl logs pod-with-defaults
I am running as user 1234

在第三个示例中,应该拒绝对象创建并提供适当的错误消息:
$ kubectl create -f examples/pod-with-conflict.yaml
Error from server (InternalError): error when creating “examples/pod-with-conflict.yaml”: Internal error occurred: admission webhook “webhook-server.webhook-demo.svc” denied the request: runAsNonRoot specified, but runAsUser set to 0 (the root user)

你也可以使用自己的工作负载进行测试。当然,你还可以通过更改 webhook 的逻辑,并查看更改如何影响对象创建来进一步实验。有关如何进行此类更改实验的更多信息,请参阅存储库的自述文件。
摘要
Kubernetes 准入控制器为安全性提供了显着优势。深入研究两个功能强大的示例,并附带可用的代码,将帮助你开始利用这些强大的功能。
参考文档:

https://kubernetes.io/docs/re…
https://docs.okd.io/latest/ar…
https://kubernetes.io/blog/20…
https://medium.com/ibm-cloud/…
https://github.com/kubernetes…
https://github.com/istio/istio
https://www.stackrox.com/post…

KubeCon + CloudNativeCon + Open Source Summit 大会日期:

会议日程通告日期:2019 年 4 月 10 日
会议活动举办日期:2019 年 6 月 24 至 26 日

KubeCon + CloudNativeCon + Open Source Summit 赞助方案 KubeCon + CloudNativeCon + Open Source Summit 多元化奖学金现正接受申请 KubeCon + CloudNativeCon 和 Open Source Summit 即将首次合体落地中国 KubeCon + CloudNativeCon + Open Source Summit 购票窗口,立即购票!CNCF 邀请你加入最终用户社区

退出移动版