【51CTO.com原创稿件】自 Kubernetes 项目启动以来,安全性已得到了长足提高,但仍存在几个问题。文章从控制平面入手,然后介绍工作负载和网络安全,最后预测安全未来。本文列出的几个要点有助于加固集群并提高集群应对威胁的能力。
第一部分:控制平面
控制平面是 Kubernetes 的大脑。它全面洞察集群上运行的每一个容器和 pod,可以调度新的 pod (pod 包含对父节点拥有 root 访问权限的容器),读取存储在集群中的所有私密信息。这种宝贵资产需要防范意外泄漏和恶意威胁:被访问时、处于静态时以及在网络上传输时都要受到保护。
TLS Everywhere
凡是支持 TLS 以防止流量嗅探、验证服务器身份以及(对于相互 TLS 而言)验证客户端身份的每个组件,都应该启用 TLS。
请注意:某些组件和安装方法可能会启用基于 HTTP 的本地端口,管理员应该熟悉每个组件的设置,以识别可能不安全的流量。
LucasK?ldstr?m 绘制的这个网络图演示了理想情况下运用 TLS 的一些地方:在主节点上的每个组件之间以及 Kubelet 和 API 服务器之间。
Kelsey Hightower堪称典范的《Kubernetes The Hard Way》(https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/1.9.0/docs/04-certificate-authority.md)提供了详细的手册说明,etcd的安全模型(https://coreos.com/etcd/docs/latest/op-guide/security.html)文档也是如此。
图1
自动扩展 Kubernetes 节点向来很难,因为每个节点都需要 TLS 密钥才能连接到主节点,而将私密信息嵌入到基本镜像不是好的做法。Kubelet TLS 引导(https://medium.com/@toddrosner/kubernetes-tls-bootstrapping-cf203776abc7)让新的 kubelet 能够创建证书签名请求,以便引导时生成证书。
图2
启用最小权限的 RBAC,禁用 ABAC
并监控日志
基于角色的访问控制(RBAC)为用户访问资源提供了细粒度的策略管理,比如访问命名空间。
图3
自版本 1.6 以来,Kubernetes 的 ABAC(基于属性的访问控制)已被 RBAC 取代,不应在 API 服务器上启用。请改用 RBAC:
--authorization-mode=RBAC
或者使用该标志在 GKE 中禁用它:
--no-enable-legacy-authorization
有好多关于为集群服务设置 RBAC 策略的典型案例以及好多文档。此外,还可以使用 audit2rbac,从审计日志提取细粒度的 RBAC 策略。
万一 pod 受到危及,不正确或过于宽松的 RBAC 策略是一种安全威胁。保持最小权限,不断审查和改进 RBAC 规则,应被视为是“技术债务卫生”的一部分,团队应纳入到开发生命周期中。
Audit Logging(1.10 版中的 beta)在有效载荷(比如请求和响应)和元数据级别提供了可定制的 API 日志。日志级别可根据贵企业的安全策略加以调整——GKE 提供了合理的默认设置帮助你上手。
对于 get、list 和 watch 等读取请求,只有请求对象保存在审计日志中,响应对象不保存。对于涉及 Secret 和 ConfigMap 等敏感数据的请求,只导出元数据。对于其他所有请求,请求对象和响应对象都保存在审计日志中。
不要忘记:万一受到危及,将这些日志保存在集群中是一种安全威胁。与其他所有安全敏感日志一样,应将这些日志传输到集群外面,以防万一泄密时被人篡改。
对 API 服务器使用第三方验证
在整个企业集中验证和授权机制(即单次登录)有助于为用户配置和取消资源,并授予一致的权限。
将 Kubernetes 与第三方验证提供商(比如谷歌或 Github)整合起来使用远程平台的身份保证(辅以双因子验证即 2FA 等机制),因而管理员无需重新配置 Kubernetes API 服务器以添加或删除用户。
Dex(https://github.com/coreos/dex)是 OpenID Connect Identity(OIDC)和 OAuth 2.0 提供商,带可插入式连接件。
Pusher 借助一些自定义工具使这更进了一步,另外有一些帮助程序(https://github.com/micahhausler/k8s-oidc-helper)可使用,但用例稍有不同。
分离你的 etcd 集群
etcd 存储关于状态和私密数据的信息,它是 Kubernetes 的一个关键组件,保护它的方式应该有别于集群的其他部分。
对 API 服务器的 etcd 的写访问相当于获得整个集群上的 root 权限,连读取访问都可以用来很轻松地提升权限。
Kubernetes 调度程序将在 etcd 中搜索没有节点的 pod 定义。然后,它将找到的 pod 发送到可用的 kubelet 进行调度。API 服务器将已提交的 pod 写到 etcd 之前,负责对这些 pod 进行验证,所以直接写到 etcd 的恶意用户可以绕过许多安全机制,比如 PodSecurityPolicies。
应为 etcd 配置对等体(peer)和客户端 TLS 证书,部署在专用节点上。为了防范 worker 节点上的私钥被窃取和使用,集群也可以通过防火墙与 API 服务器隔开。
轮换加密密钥
一个安全最佳实践是定期轮换加密密钥和证书,以便限制密钥泄密的“影响范围”。
Kubernetes 将通过现有登录信息到期失效时创建新的 CSR 来自动轮换一些证书(尤其是 kubelet 客户端和服务器的证书)。
然而,API 服务器用于加密 etcd 值的对称加密密钥并不自动轮换,它们得手动轮换。为此需要访问主节点,所以托管服务(比如 GKE 或 AKS)让操作员无需操心该问题。
第二部分:工作负载
由于控制平面上的最小可行安全,集群得以安全地运行。但是就像轮船载有可能危险的货物一样,轮船集装箱必须受到保护,以便发生不测时里面的货物完好无损。
对于 Kubernetes 工作负载(pod、部署、作业和集合等)来说,也是如此,它们可能在部署时受到信任,但如果它们面向互联网,总是存在后来被利用的风险。以最小权限运行工作负载并加强运行时配置有助于降低这一风险。
使用Linux安全功能
和PodSecurityPolicies
Linux 内核有许多重叠的安全扩展(功能、SELinux、AppArmor 和 seccomp-bpf),它们经配置后可为应用程序提供最小权限。
bane(https://github.com/genuinetools/bane)之类的工具有助于生成 AppArmor 配置文件,docker-slim(https://github.com/docker-slim/docker-slim#quick-seccomp-example)有助于生成 seccomp 配置文件,但要注意:验证运用这些策略的副作用时,需要一套全面的测试来测试应用程序中的所有代码路径。
PodSecurityPolicies(https://kubernetes.io/docs/concepts/policy/pod-security-policy/)可用于强制使用安全扩展及其他 Kubernetes 安全指令。它们提供了 pod 必须履行的最基本合约才能提交到 API 服务器,包括安全配置文件、特权标志以及主机网络、进程或 IPC 命名空间的共享。
这些指令很重要,因为它们有助于防止容器化进程脱离隔离边界,Tim Allclair 的示例 PodSecurityPolicy(https://gist.github.com/tallclair/11981031b6bfa829bb1fb9dcb7e026b0)是个综合资源,你可以根据自己的用例来定制。
静态分析 YAML
在 PodSecurityPolicies 拒绝访问 API 服务器的情况下,静态分析也可用于开发工作流程,以模拟企业组织的合规需求或风险偏好。
敏感信息不应存储在 pod 类型的 YAML 资源(部署、pod 和集合)中,敏感的配置映射和私密信息应使用 vault、git-crypt、sealed secrets 或云提供商 KMS 来进行加密。
YAML 配置的静态分析可用于为运行时安全性建立一条基线。kubesec(https://kubesec.io/)为资源生成风险评分:
- {
- "score": -30,
- "scoring": {
- "critical": [{
- "selector": "containers[] .securityContext .privileged == true",
- "reason": "Privileged containers can allow almost completely unrestricted host access"
- }],
- "advise": [{
- "selector": "containers[] .securityContext .runAsNonRoot == true",
- "reason": "Force the running image to run as a non-root user to ensure least privilege"
- }, {
- "selector": "containers[] .securityContext .capabilities .drop",
- "reason": "Reducing kernel capabilities available to a container limits its attack surface",
- "href": "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/"
- }]
- }
- }
而 kubetest(https://github.com/garethr/kubetest)是 Kubernetes 配置的单元测试框架:
- #// vim: set ft=python:
- deftest_for_team_label():
- if spec["kind"] =="Deployment":
- labels = spec["spec"]["template"]["metadata"]["labels"]
- assert_contains(labels, "team", "should indicate which team owns the deployment")
- test_for_team_label()
这些工具“向左移”(在开发周期的早期移动检查和验证)。开发阶段的安全测试为用户提供了可能被后来的手动或自动检查拒绝的代码和配置方面的快速反馈,可以减小引入更安全的实践带来的阻力。
以非 root 用户的身份运行容器
经常以 root 的身份运行的容器通常拥有比工作负载实际要求多得多的权限,万一受到危及,会帮助攻击者进一步发动攻击。
容器仍依赖传统的 Unix 安全模型(名为自主访问控制或 DAC),一切都是文件,权限授给用户和用户组。
用户命名空间在 Kubernetes 中并未启用。这意味着容器的用户 ID 表映射到主机的用户表,以 root 用户的身份在容器内运行进程就是在主机上以 root 身份运行它。虽然我们有分层安全机制来防止容器突破(container breakout),但仍不推荐以 root 的身份在容器内运行。
许多容器镜像使用 root 用户来运行 PID 1,如果该进程受到危及,攻击者拥有容器的 root 权限,任何错误配置会变得极容易被利用。
Bitnami 已做了大量的工作将容器镜像移动到非 root 用户(尤其是由于 OpenShift 默认需要这样),这可以简化迁移到非 root 容器镜像。
这个 PodSecurityPolicy 代码片段可防止以 root 的身份在容器内运行进程,并防止权限提升到 root:
- # Required to prevent escalations to root.
- allowPrivilegeEscalation:false
- runAsUser:
- # Require the container to run without root privileges.
- rule:'MustRunAsNonRoot'
非 root 容器无法绑定到 1024 以下的特权端口(这由 CAP_NET_BIND_SERVICE 内核功能控制),但可以使用服务来掩盖这个事实。在该示例中,虚构的 MyApp 应用程序绑定到容器中的端口 8443,但是该服务通过代理请求到 targetPort,在端口 443 上提供它:
- kind:Service
- apiVersion:v1
- metadata:
- name:my-service
- spec:
- selector:
- app:MyApp
- ports:
- -protocol:TCP
- port:443
- targetPort:8443
必须以非 root 用户的身份运行工作负载这一点在用户命名空间可用之前不会改变。
使用网络策略
默认情况下,Kubernetes 网络允许所有 pod 到 pod 的流量,可以使用网络策略(https://kubernetes.io/docs/concepts/services-networking/network-policies/)对此进行限制。
图4
传统服务用防火墙加以限制,防火墙为每个服务使用静态 IP 和端口范围。由于这些 IP 很少变化,因此历来用作一种身份。容器很少有静态 IP,它们是为了快速失效(fail fast)、快速重新调度,并使用服务发现而不是静态 IP 地址。这些属性意味着,防火墙配置和检查起来难多了。
由于 Kubernetes 将其所有系统状态存储在 etcd 中,它可以配置动态防火墙,前提是它得到 CNI 网络插件的支持。Calico、Cilium、kube-router、Romana 和 Weave Net 都支持网络策略。
值得一提的是,这些策略一失效就关闭,所以这里缺少 podSelector 默认情况下用通配符:
- apiVersion:networking.k8s.io/v1
- kind:NetworkPolicy
- metadata:
- name:default-deny
- spec:
- podSelector:
下面这个示例 NetworkPolicy 拒绝除 UDP 53(DNS)之外的所有出站流量,这还阻止入站连接到你的应用程序。NetworkPolicies 是有状态的(https://www.weave.works/blog/securing-microservices-kubernetes/),所以出站请求的回复仍抵达应用程序。
- apiVersion:networking.k8s.io/v1
- kind:NetworkPolicy
- metadata:
- name:myapp-deny-external-egress
- spec:
- podSelector:
- matchLabels:
- app:myapp
- policyTypes:
- -Egress
- egress:
- -ports:
- -port:53
- protocol:UDP
- -to:
- -namespaceSelector:{}
Kubernetes 网络策略无法应用于 DNS 名称。这是由于 DNS 可解析针对多个 IP 的轮询,或者基于呼叫 IP 动态解析,所以网络策略只能应用于固定 IP 或 podSelector(针对动态 Kubernetes IP)。
最佳实践是先拒绝命名空间的所有流量,然后逐渐添加路由,允许应用程序通过其许可测试套件。这可能变得很复杂,于是 ControlPlane 结合了 netassert(https://github.com/controlplaneio/netassert),这是面向 DevSecOps 工作流程的网络安全测试框架,拥有高度并行化的 nmap:
- k8s:# used for Kubernetes pods
- deployment:# only deployments currently supported
- test-frontend:# pod name, defaults to `default` namespace
- test-microservice:80# `test-microservice` is the DNS name of the target service
- test-database:-80# `test-frontend` should not be able to access test-database’s port 80
- 169.254.169.254:-80,-443# AWS metadata API
- metadata.google.internal:-80,-443# GCP metadata API
- new-namespace:test-microservice:# `new-namespace` is the namespace name
- test-database.new-namespace:80# longer DNS names can be used for other namespaces
- test-frontend.default:80
- 169.254.169.254:-80,-443# AWS metadata API
- metadata.google.internal:-80,-443# GCP metadata API
云提供商元数据 API 历来是提升权限的来源(最近的 Shopify 漏洞悬赏表明了这点),因此证实 API 在容器网络上被阻止的特定测试有助于防止意外的错误配置。
扫描镜像,运行 IDS
Web 服务器给它们连接的网络带来了攻击面:扫描镜像的已安装文件可确保没有已知的漏洞,因而攻击者无法钻漏洞的空子、远程访问容器。IDS(入侵检测系统)可检测攻击者是否在钻空子。
Kubernetes通过一系列准入控制器(https://kubernetes.io/docs/admin/admission-controllers/)门卡允许pod进入集群,这些门卡应用于 pod 及其他资源(比如部署)。这些门卡可以验证每个 pod,决定是否准入或更改内容,现在它们支持后端 web 钩子(webhook)。
图5
容器镜像扫描工具可以使用这些 Web 钩子在镜像部署到集群之前验证镜像,可以拒绝未通过检查的镜像准入。
扫描容器镜像查找已知漏洞可以缩短攻击者钻已披露的 CVE 空子的时间窗口。应该在部署流水线中使用免费工具,比如 CoreOS 的 Clair(https://github.com/coreos/clair)和 Aqua 的 Micro Scanner(https://github.com/aquasecurity/microscanner),防止部署存在严重漏洞的镜像。
Grafeas(https://grafeas.io/)等工具可以存储镜像元数据,针对容器的独特签名(内容可寻址哈希)不断进行合规和漏洞检查。这意味着扫描拥有该哈希的容器镜像与扫描生产环境中部署的镜像一样,可以持续地执行,不需要访问生产环境。
未知的零日漏洞将始终存在,所以应在 Kubernetes 中部署入侵检测工具,比如 Twistlock(https://www.twistlock.com/)、Aqua(https://www.aquasec.com/)和 Sysdig Secure(https://sysdig.com/product/secure/)。IDS 可检测容器中的异常行为,暂停或终止容器。Sysdig 的 Falco 是一种开源规则引擎(https://github.com/draios/falco),是这个生态系统的入口点。
展望未来
安全界“云原生演化”的下一个阶段看起来是服务网格(service mesh),不过可能一段时间后才会采用。迁移需要将复杂性从应用程序转移到网格基础设施,企业组织热衷于了解最佳实践。
图6
运行服务网络
服务网格是加密持久的连接组成的网络,是在 Envoy 和 Linkerd 等高性能“跨斗”(sidecar)代理服务器之间建立起来的。它增加了流量管理、监控和策略,这一切无需更改微服务。
有了 Linkerd(https://linkerd.io/),已经可以将微服务的安全和网络代码卸载到一组共享的、久经测试的库,谷歌、IBM 和 Lyft 推出的 Istio(https://istio.io/)在这个领域增加了另一种方案。
由于添加了面向每个pod加密身份的 SPIFFE(https://spiffe.io/)以及其他众多功能(https://istio.io/docs/concepts/what-is-istio/overview.html),Istio 有望简化部署下一代网络安全的工作。
在“零信任”网络中,不需要传统的防火墙或 Kubernetes 网络策略,因为每一次交互都基于 mTLS(相互TLS)进行,确保双方不仅安全通信,而且两种服务的身份都已知道。
对于奉行传统安全理念的人来说,从传统网络到云原生安全原则的这种转变并不容易,而SPIFFE 的 Evan Gilman 撰写的《零信任网络》(https://amzn.to/2Gg6Pav)一书清楚地介绍了这个崭新的领域,强烈推荐读一读。
Istio 0.8 LTS 已过时,该项目正迅速接近 1.0 版本。其稳定版本控制与 Kubernetes 模型一样:一个稳定的核心,各个 API 在各自的 alpha/beta 稳定命名空间下自报身份。预计 Istio 的采用率在今后几个月会有所上升。
结束语
云原生应用程序有一组更精细的轻量级安全基元,为工作负载和基础设施确保安全。这些工具的强大功能和灵活性有利也有弊;由于自动化程度不足,更容易暴露允许突破容器或其隔离模型的不安全的工作负载。
现在可供使用的防御工具比以往更多,但须谨慎行事,减小攻击面和错误配置的可能性。
然而,如果安全减慢了企业组织交付功能的步伐,安全永远不会成为“一等公民”。将持续交付原则运用于软件供应链让企业组织得以实现合规、持续审计和强制治理,又不影响公司的账本底线。
如果得到全面测试套件的支持,安全方面快速迭代最容易。这可以通过持续安全(Continuous Security)来实现——这是时间点渗透测试之外的替代方案,持续的流水线验证确保企业组织的攻击面已知,风险不断被理解和管理。
【51CTO原创稿件,合作站点转载请注明原文作者和出处为51CTO.com】