Open Policy Agent: Top 5 Kubernetes 准入控制策略

173 阅读5分钟

security

如何使用 Open Policy Agent 实现准入策略控制,可以参考这里

本文翻译自 Open Policy Agent: The Top 5 Kubernetes Admission Control Policies


Kubernetes 开发人员和平台工程师通常承受着非常大的压力,以保持应用程序部署的快速进行,并且总是为了速度和进度而做出妥协。平台团队越来越有责任确保这些妥协(例如管理 Ingress)不会导致客户数据暴露在整个互联网上等后果。

幸运的是,Kubernetes 提供了设置策略的能力,通过检查并防止部署错误将其投入生产,从而避免这些后果。为了确保团队的应用程序不会比信心更重要,以下是现在应该在集群中运行的前五个 Kubernetes 准入控制策略。

1. 可信镜像仓库

此策略很简单,但功能强大:仅允许从受信任的镜像仓库中拉取的容器映像,并且可以选择仅拉取与允许的仓库镜像地址列表匹配的那些镜像。

当然,从互联网(或可信镜像仓库库以外的任何地方)拉取未知镜像会带来风险——例如恶意软件。但是还有其他很好的理由来维护单一的可信来源,例如在企业中实现可支持性。通过确保镜像仅来自受信任的镜像仓库,可以密切控制镜像库存,降低软件熵和蔓延的风险,并提高集群的整体安全性。

相关策略:

  • 禁止所有带有“latest” tag 的镜像
  • 仅允许签名镜像或匹配特定哈希/SHA 的镜像

策略示例:

package kubernetes.validating.images
 
deny[msg] {
    some i
    input.request.kind.kind == "Pod"
    image := input.request.object.spec.containers[i].image
    not startswith(image, "hooli.com/")
    msg := sprintf("Image '%v' comes from untrusted registry", [image])
}

2. 标签安全

此策略要求所有 Kubernetes 资源都包含指定的标签并使用适当的格式。由于标签决定了 Kubernetes 对象和策略的分组,包括工作负载可以运行的位置——前端、后端、数据层——以及哪些资源可以发送流量,标签错误会导致生产中无法解释的部署和可支持性问题。此外,如果没有对标签应用方式的访问控制,集群就缺乏基本的安全性。最后,手动输入标签的危险在于错误会蔓延,特别是因为标签在 Kubernetes 中既灵活又强大。应用此策略并确保标签配置正确且一致。

相关政策:

  • 确保每个工作负载都需要特定的注解(annotations)
  • 指定污点和容忍度以限制可以部署映像的位置

策略示例:

package kubernetes.validating.existence
 
deny[msg] {
    not input.request.object.metadata.labels.costcenter
    msg := "Every resource must have a costcenter label"
}
 
deny[msg] {
    value := input.request.object.metadata.labels.costcenter
    not startswith(value, "cccode-")
    msg := sprintf("Costcenter code must start with `cccode-`; found `%v`", [value])
}

3. 禁止(或指定)特权模式

此策略确保默认情况下容器不能在特权模式下运行 - 除非在允许的情况下排除特定情况(通常很少见)。

通常,希望避免在特权模式下运行容器,因为它提供对主机资源和内核功能的访问——包括禁用主机级保护的能力。虽然容器在某种程度上是隔离的,但它们最终共享相同的内核。这意味着如果特权容器遭到入侵,它可能会成为入侵整个系统的起点。尽管如此,在特权模式下运行还是有正当理由的——只要确保这些时间是例外,而不是规则。

相关政策:

  • 禁止不安全的能力(capabilities)
  • 禁止容器以 root 身份运行(以非 root 身份运行)
  • 设置 userID

策略示例:

package kubernetes.validating.privileged
 
deny[msg] {
    some c
    input_container[c]
    c.securityContext.privileged
    msg := sprintf("Container '%v' should not run in privileged mode.", [c.name])
}
 
input_container[container] {
    container := input.request.object.spec.containers[_]
}
 
input_container[container] {
    container := input.request.object.spec.initContainers[_]
}

4. 定义和控制入口

Ingress 策略允许根据需要公开特定服务(允许 Ingress),或者根据需要不公开任何服务。在 Kubernetes 中,很容易意外启动与公共互联网通信的服务(Kubernetes 故障故事中有很多这样的例子)。同时,过于宽松的 Ingress 会导致启动不必要的外部 LoadBalancer,这也可能会变得非常昂贵(如每月预算支出)非常快!此外,当两个服务尝试共享同一个 Ingress 时,它可能会破坏应用程序。

下面的策略示例防止不同命名空间中的 Ingress 对象共享相同的主机名。这个常见问题意味着新工作负载会从现有工作负载“窃取”互联网流量,这会产生一系列负面后果——从服务中断到数据暴露等等。

相关政策:

  • 需要 TLS
  • 禁止/允许特定端口

策略示例:

package kubernetes.validating.ingress
 
deny[msg] {
    is_ingress
    input_host := input.request.object.spec.rules[_].host
    some other_ns, other_name
    other_host :=
    data.kubernetes.ingresses[other_ns][other_name].spec.rules[_].host
    [input_ns, input_name] != [other_ns, other_name]
 
    input_host == other_host
 
    msg := sprintf("Ingress host conflicts with ingress %v/%v", [other_ns, other_name])
}
 
input_ns = input.request.object.metadata.namespace
 
input_name = input.request.object.metadata.name
 
is_ingress {
    input.request.kind.kind == "Ingress"
    input.request.kind.group == "extensions"
    input.request.kind.version == "v1beta1"
}

5. 定义和控制出口

每个应用程序都需要防护栏来控制出口流量的流动方式,此策略允许你指定集群内和集群外的通信。与 Ingress 一样,默认情况下很容易意外地“允许 Egress”到全世界的每个 IP。有时这甚至不是意外——完全的放开通常是确保可以访问新部署的应用程序的最后努力,即使它过于宽松或引入风险。在集群内级别,还有可能无意中将数据发送到不应该拥有的服务。如果服务受到损害,这两种情况都存在数据泄露和盗窃的风险。另一方面:过于严格,使用 Egress 有时会导致配置错误,从而破坏应用程序。实现两全其美意味着使用此策略选择和指定允许 Egress 发生的时间和服务。

相关政策。

  • 请参阅上面的入口策略

策略示例:

package kubernetes.validating.egress
 
allow_list := {
    "10.10.0.0/16",
    "192.168.100.1/32"
}
 
deny[reason] {
    network_policy_allows_all_egress
    reason := "Network policy allows access to any IP address."
}
 
deny[reason] {
    count(allow_list) > 0
    input.request.kind.kind == "NetworkPolicy"
    input.request.object.spec.policyTypes[_] == "Egress"
    ipBlock := input.request.object.spec.egress[_].to[_].ipBlock
    not any({t | t := net.cidr_contains(allow_list[_], ipBlock.cidr)})
    reason := "Network policy allows egress traffic outside of allowed IP ranges."
}
 
network_policy_allows_all_egress {
    input.request.kind.kind == "NetworkPolicy"
    input.request.object.spec.policyTypes[_] == "Egress"
    egress := input.request.object.spec.egress[_]
    not egress.to
}

有了这些政策,你就可以专注于构建一个世界级的平台。当然,如果你想为 Kubernetes 添加更多基本策略,请查看 openpolicyagent.org

文章统一发布在公众号云原生指北