以下文章来源于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 集群中创建、更新或删除资源时,能够实现要执行的自定义逻辑。
这两种准入控制器 webhooks 之间的区别很明显:变更准入 Webhook 可以修改对象,而验证准入 Webhook 不能。
即便是 MutatingAdmissionWebhook
,它也可以通过拒绝请求进行验证。
而 ValidatingAdmissionWebhooks
则有两个突出优点:
MutatingAdmissionWebhook
准入控制器(或用更严格的 RBAC 限制用户创建 MutatingWebhookConfiguration
),因为这会导致混淆;配置一组启用的准入控制器需要在 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 治理:准入控制器可以帮助遵守某些规范,例如使用标签、注释、资源限制或其他设置,一些常见方案包括:
配置管理:准入控制器可以帮助工程师验证集群中运行时对象的配置,防止错误配置影响集群。它对检测和修复不带语义标签的镜像很有用,例如:
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-with-override
)运行;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/