Kubernetes准入控制器指南

作者:Malte Isberner(StackRox)

Kubernetes极大地提高了当今生产中后端群集的速度和可管理性。由于其灵活性、可扩展性和易用性,Kubernetes已成为容器编排器的事实标准。Kubernetes也提供一系列保护生产工作负载的功能。安全功能的最新引入是一组称为“准入控制器”的插件。必须启用准入控制器才能使用Kubernetes的一些更高级的安全功能,例如,在整个命名空间中强制实施安全配置基线的pod安全政策。以下必须知道的提示和技巧,将帮助你利用准入控制器,在Kubernetes中充分利用这些安全功能。

什么是Kubernetes准入控制器?

简而言之,Kubernetes准入控制器是管理和强制执行集群使用方式的插件。可以将它们视为拦截(经过身份验证的)API请求的网守,并且可以更改请求对象,或完全拒绝请求。准入控制过程有两个阶段:首先执行改变(mutating)阶段,然后是验证(validating)阶段。因此,准入控制器可以充当改变或验证控制器,或两者的组合。例如,LimitRanger准入控制器可以使用默认资源请求和限制(改变阶段)扩充pod,并验证具有设置资源要求的pod,不超过LimitRange对象中指定的每命名空间限制(验证阶段)。

Kubernetes准入控制器指南
准入控制器阶段

值得注意的是,许多用户认为是内置的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准入控制器为安全性提供了显着优势。深入研究两个功能强大的示例,并附带可用的代码,将帮助你开始利用这些强大的功能。

参考文档:


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邀请你加入最终用户社区

相关推荐