专栏首页CNCF新手指南之 Kubernetes 准入控制器

新手指南之 Kubernetes 准入控制器

以下文章来源于K8sMeetup社区 ,作者才云 Caicloud

Kubernetes 准入控制器在安全性方面具有明显优势。为了增进各位读者对它的了解,今天 K8sMeetup 中国社区翻译了工程师 Malte Isberner 的技术博客,以两个生动的演示和相关代码引导更多人使用这些强大功能。

作者:Malte Isberner(StackRox) 翻译:bot(才云) 校对:星空下的文仔(才云)

Kubernetes 正在使企业生产环境中的后端集群变得更高效和更易于管理。由于其灵活性、可扩展性和易用性,它也已经成为容器编排领域的事实标准。

为了确保生产工作负载的安全,Kubernetes 提供了很多安全功能,其中有一项新功能是“准入控制器”( Admission Controllers)。在使用其他高级安全功能之前,如用 Pod 安全策略跨整个命名空间执行安全配置基线,请求需要先通过 Kubernetes 准入控制器。

在下文中,StackRox 工程师 Malte Isberner 将详细介绍准入控制器,并指导读者如何充分利用 Kubernetes 中的这些安全功能。

K8sMeetup

什么是 Kubernetes 准入控制器?

Kubernetes 准入控制器是控制和强制使用集群的一种插件。我们可以把它看作是拦截(已认证)API 请求的拦截器,它可以更改请求对象,甚至完全拒绝请求。

Kubernetes 准入控制器的“准入控制链”分为两阶段:变更(Mutating)准入控制,修改请求的对象验证(Validating)准入控制,验证请求的对象。因此准入控制器即可以被用作变更和验证,也可以两者结合起来使用。

例如 LimitRanger 准入控制器可以为未设置资源限制的 Pod 按照命名空间的 LimitRange 进行默认设置,同时验证已有明确资源限制的 Pod 的资源没有超过 LimitRange

值得注意的是,工程师眼中 Kubernetes 的很多“内置”操作实际上是由准入控制器控制的。比如当一个 Pod 被删除并进入 Terminating 状态时,NamespaceLifecycle 准入控制器会禁止在这个命令空间中创建任何新对象

而在 Kubernetes 自带的 30 多个准入控制器中,有两个因其灵活性扮演着特殊角色(它们在 1.13 版本中都是 beta):

  • ValidatingAdmissionWebhooks
  • MutatingAdmissionWebhooks

虽然本身并不实现任何决策,但它们将准入控制器的逻辑与 Kubernetes API Server 解耦,使工程师当在 Kubernetes 集群中创建、更新或删除资源时,能够实现要执行的自定义逻辑

  • ValidatingAdmissionWebhook:该准入控制器调用与请求匹配的任何验证 webhook。匹配的 webhooks 是并行调用的;如果其中任何一个拒绝请求,则请求失败。
  • MutatingAdmissionWebhook:该准入控制器调用与请求匹配的任何变更 webhook。匹配的 webhook 是串行调用的;如果需要,每个人都可以修改对象。

这两种准入控制器 webhooks 之间的区别很明显:变更准入 Webhook 可以修改对象,而验证准入 Webhook 不能。

即便是 MutatingAdmissionWebhook,它也可以通过拒绝请求进行验证。

ValidatingAdmissionWebhooks 则有两个突出优点:

  • 其一,出于安全性考虑,它可能需要禁用 MutatingAdmissionWebhook 准入控制器(或用更严格的 RBAC 限制用户创建 MutatingWebhookConfiguration),因为这会导致混淆;
  • 其二,如前图所示,验证准入控制器(及 webhook)是在变更准入控制器之后运行的,所以验证准入控制器接收的请求对象都是被持久化到 etcd 的最终版本

配置一组启用的准入控制器需要在 Kubernetes API Server 中设置参数。

*请注意,旧的 –admission-control 参数在 v1.10 中已被弃用,并由 –enable-admission-plugins 取代。

--enable-admission-plugins=ValidatingAdmissionWebhook,MutatingAdmissionWebhook

Kubernetes 建议默认启用以下准入控制器:

--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,Priority,ResourceQuota,PodSecurityPolicy

详细准入控制器列表,请见:

https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do

K8sMeetup

为什么我们需要准入控制器?

安全性通过在整个命名空间和集群中强制设置合理的安全基线,准入控制器可以帮助提高整体安全性

如内置的 PodSecurityPolicy 准入控制器可以禁止容器以特权身份运行或确保容器的根文件系统始终以只读方式安装。基于 webhooks 的准入控制器也可以实现其他的安全功能,如:

  • 只允许从企业已知的特定镜像仓库提取镜像,拒绝未知镜像仓库;
  • 拒绝不符合安全标准的部署,如可以通过拒绝请求和用 false 覆盖 privileged 参数,降低容器使用特权身份绕过安全检查的风险。

IT 治理准入控制器可以帮助遵守某些规范,例如使用标签、注释、资源限制或其他设置,一些常见方案包括:

  • 对不同对象强制执行标签验证,确保始终将正确的标签用于各种对象;
  • 自动向对象添加注释,例如为“dev”部署资源指定正确的 cost center。

配置管理准入控制器可以帮助工程师验证集群中运行时对象的配置,防止错误配置影响集群。它对检测和修复不带语义标签的镜像很有用,例如:

  • 自动添加资源限制或验证资源限制;
  • 确保将合理的标签添加到 Pod;
  • 确保在生产部署中使用的镜像引用不使用 latest 标签,或带有 -dev 后缀的标签。

通过以上方式,准入控制器和策略管理有助于确保应用程序在不断变化的控制环境中保持一致

K8sMeetup

示例:编写和部署准入控制器 Webhook

Kubernetes 的很多默认设置是为了便于使用,有时它们会牺牲一定的安全性。因为按照默认设置,容器一般是以 root 身份运行的(即便没有进一步配置,Dockerfile 中也没有指令)。

尽管容器在一定程度上与底层主机隔离,但这样确实会增加部署风险,比如去年曝光的 runC 漏洞(CVE-2019-5736),它被利用的原因之一就是容器内进程都是以 root 权限运行的。

下面我们以这个问题为例,一起利用准入控制器 webhook 建立自定义安全策略。

为了解决上述问题,工程师可以使用自定义的变更准入控制器 Webhook 使默认设置变得更安全:除非明确要求,否则 webhook 将强制要求 Pod 以非 root 身份运行(示例中为分配 ID 1234)

请注意,这个设置不会影响到集群中的工作负载,包括那些明确需要 root 权限的工作负载。

下文只对部分代码做详细说明,完整代码请见以下 repo:

https://github.com/stackrox/admission-controller-webhook-demo

K8sMeetup

变更 Webhook 配置

创建一个 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

当向 /mutate URL 发出 HTTP POST 请求创建 Pod 时,Kubernetes API Server 需要在命名空间 webhook-demo 中查询服务 webhook-server。要使这个配置生效,必须满足几个先决条件。

K8sMeetup

Webhook REST API

Kubernetes API Server 用 JSON 编码的 AdmissionReview(设置了请求字段)向指定服务和 URL 路径发出 HTTPS POST 请求。由于设置了字段,返回的响应也是 JSON 编码的 AdmissionReview

demo repo 里有一个处理序列化/反序列化样板代码的函数,它允许我们把重点放在实现 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

K8sMeetup

对象修改逻辑

在变更准入控制器 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,
})

K8sMeetup

TLS 证书

因为 webhook 必须通过 HTTPS 提供服务,我们还需要为服务器提供适当的证书。这些证书可以是自签名的(而是由自签名的 CA 签名的),但是我们需要 Kubernetes 在与 webhook 服务器通信时通知各自的 CA 证书。

此外,证书的 CN 必须与 Kubernetes API Server 使用的服务器名称匹配,对于内部服务,这个服务器的名称是 <service-name>.<namespace>.svc,例如示例中的是 webhook-server.webhook-demo.svc

由于自签名 TLS 证书的生成在 Internet 上有很好的文档记录,所以示例中我们只引用相应的 Shell 脚本。

先前的 webhook 配置包含一个占位符 ${CA_PEM_B64}。在创建这个配置之前,执行 openssl base64 -A 命令,用 CA 的 Base64 编码 PEM 证书替换这一部分。

K8sMeetup

测试 Webhook

部署完 webhook 服务器并完成配置之后,我们还需要对它进行测试和验证,repo 中提供了三个示例:

  • 没有指定安全上下文的 Pod(带默认设置的 Pod)。我们希望这个 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)

实践是检验真理的唯一方法。欢迎读者在自己的工作负载中测试 repo 中的代码,你也可以通过更改 webhook 的逻辑进行进一步的实验,看看这些改动会怎么影响对象的创建。

https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/

参考文献

https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/

https://docs.okd.io/latest/architecture/additional_concepts/dynamic_admission_controllers.html

https://kubernetes.io/blog/2018/01/extensible-admission-is-beta/

https://medium.com/ibm-cloud/diving-into-kubernetes-mutatingadmissionwebhook-6ef3c5695f74

https://github.com/kubernetes/kubernetes/blob/v1.10.0-beta.1/test/images/webhook/main.go

https://github.com/istio/istio

https://www.stackrox.com/post/2019/02/the-runc-vulnerability-a-deep-dive-on-protecting-yourself/

本文分享自微信公众号 - CNCF(lf_cncf)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 携手发展,共同前行——SIG Cloud Provider

    今天为大家介绍一下SIG Cloud Provider,即云供应商特别兴趣小组。其两位代表分别是来自 Google 的 Jago Macleod 和来自 Dig...

    CNCF
  • Kubernetes准入控制器指南

    Kubernetes极大地提高了当今生产中后端群集的速度和可管理性。由于其灵活性、可扩展性和易用性,Kubernetes已成为容器编排器的事实标准。Kubern...

    CNCF
  • Cluster API简介

    Cluster API是一个Kubernetes项目,它将声明式Kubernetes风格的API用于集群的创建、配置和管理。它在核心Kubernetes之上,提...

    CNCF
  • linux | 下载文件

    努力在北京混出人样
  • SAP 自动编号维护SNRO

    在开发中经常会遇到生成编号的需求,SAP提供了自动编号工具,能根用户需求设定并自动生成一组唯一的编号

    用户5495712
  • 微信Tinker的一切都在这里,包括源码(一)

    Tinker顺利完成了公司的审核,并非常荣幸的成为github.com/Tencent上第一个正式公开的项目。我希望通过分享微信在这历程中的思考与经验,能帮助大...

    张绍文
  • 微信 Tinker 的一切都在这里,包括源码 ( 一 )

    作者希望通过分享在这历程中的思考与经验,能帮助大家更容易的决定是否在自己的项目中使用热补丁技术,以及选择什么样方案。

    微信终端开发团队
  • DPOS 共识算法 - 缺失的白皮书

    这篇“缺失的白皮书”是对委托权益证明(Delegated Proof of Stake, DPOS)的分析,旨在分析 DPOS 的工作原理及其鲁棒性(robus...

    用户1558438
  • 云堆栈三大模式解析

    如果准备使用云计算或者要在自己的IT环境中部署云计算,首先要确定合适的服务模式。这就需要我们对每种服务模式含义和特点都有所了解,很清楚从中能够获得哪些有益东西。...

    静一
  • 逆向分析神器BinNavi开源了

    得益于IDA pro十分开放的架构,Gergely Erdelyi和Ero Carrera在2004年基于IDA pro发布了IDAPython,逆向工程师能够...

    FB客服

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动