写点什么

不遵循这 11 条建议,小心你的 Kubernetes 集群安全

  • 2020-04-15
  • 本文字数:8040 字

    阅读完需:约 26 分钟

不遵循这11条建议,小心你的Kubernetes集群安全

自从 Kubernetes 项目开天辟地以来, 其安全性已经取得了长足的发展, 但它目前依然还有些要点值得注意。 本文列举了 11 条建议来帮助让你的集群在稳定运行时加固安全,以及在受到危害时对抗冲击。这些军规涵盖了对控制面板的配置,对工作负载的防护,乃至对未来的展望。

第一部分:控制面板

作为 Kubernetes 的核心大脑, 控制面板全面展示了在集群中所有容器和 Pod 的运行情况。 它可以直接调度新的 Pod(可能包含对宿主节点有 root 权限的容器), 还可以用来读取集群中所有的 secret。 控制面板中展示的内容非常重要, 需要好好的保护起来以免发生意外泄漏和恶意访问——无论这些内容是被访问时, 在存储状态, 又或者是在网络中传输时。

1. 全面启用 TLS

应该在所有组件上都使用 TLS, 以防止网络嗅探, 验证服务器端的身份,以及在启动双向 TLS 认证时验证客户端的身份。


需要注意的是, 一些组件在安装时可能会有默认设置启用 http 端口。管理员需要熟悉每个组件的设置从而识别出这种潜在会产生不安全流量的情况。


这份来自 Lucas Käldström[1]的网络图展示了几处应该配置 TLS 的地方, 包括组件与主节点的交互, 以及 Kubelet 与 API Server 的交互。 Kelsey Hightower 的大作 Kubernetes The Hard Way[2]中对此提供了详细的手工操作指导。 etcd 的安全模型文档[3]里也有细致描述。



自动伸缩 Kubernetes 的节点在过去是非常困难的操作, 因为每个节点都需要一个 TLS 的密钥来连接主节点, 而将密钥打包放进基础镜像又不是一个好的实践方式。 现在 Kubelet 的 TLS 引导程序提供了让新的 kubelet 在启动时生成证书签名申请的能力。


2. 以最小特权原则设置 RBAC, 关闭 ABAC, 以及监控日志

RBAC 提供了细粒度的规则管理功能, 以限制用户对资源(比如 namespace)的访问。



ABAC 于 Kubernetes 的 1.6 版开始为 RBAC 所取代,已不建议启用。 在普通的 Kubernetes 集群中通过设置以下启动参数在 API Server 中启用 RBAC:


--authorization-mode=RBAC
复制代码


而在 GKE 中通过另一个参数来禁用 ABAC:


--no-enable-legacy-authorization
复制代码


网上有很多不错的例子和文档描述了如何在集群中使用 RBAC 策略[4]。 除此之外, 管理员还可以通过 audit2rbac[5]生成的审计日志来分析调优 RBAC 规则。


如果将 RBAC 规则设置得过大或者不正确, 一旦集群中出现有问题的 Pod 会对整个集群产生安全威胁。 RBAC 规则应该基于最小特权原则进行维护, 并持续地加之以审阅和改善。 这应被团队视为技术负债的清除手段被整合进开发流程之中。


审计日志功能(1.10 中为 beta 阶段)提供了可定制化地对集群各组件的访问流量内容(例如请求和响应)及元数据(例如发起用户和时间戳)记录日志的功能。 日志等级可以根据组织内的安全策略进行调整。 在 GKE 上也有相应的设置来配置这个功能。


对于读取类请求(比如 get, list 和 watch) ,只有请求内容会被记录在审计日志中, 响应内容不会保存。 对于涉及到敏感数据(比如 Configmap 和 Secret)的请求, 只有元数据会被记录。 对于其他类型的请求, 请求和相应对象都会被记录。


切记:如果将审计日志保留在集群内部, 在集群已被入侵时会有安全威胁。 像这样的所有安全相关的日志都应转移到集群之外, 以避免出现安全漏洞时被篡改。

3. 为 API Server 启用第三方认证

在一个组织内部通过集中化的手段管理认证和授权(又被称为单点登录)有助于管理用户的添加, 删除以及一致的权限控制。


通过将 Kubernetes 与第三方的身份验证服务(比如 Google 和 GitHub)整合,这样可以使用第三方平台的身份保证机制(包括双因子验证等机制), 以避免管理员不得不重新配置 API Server 来添加或删除用户。


Dex[6]是个 OpenID Connect(OIDC)和 OAuth2.0 的身份服务组件。 它带有可插拔的连接器。 Pusher 公司通过这个机制将 Dex 作为认证链的中间件将 Kubernetes 与其他第三方认证服务关联。 除此之外还有其他工具可以达成类似目的。

4. 将 etcd 集群隔离出来并加上防火墙控制

etcd 中存储了集群的状态和 secret, 对 Kubernetes 而言是至关重要的组件。 它的防护措施应该与集群的其他部分区别处理。


对 API Server 的 etcd 拥有写权限相当于获取了整个集群的 root 权限。 甚至仅通过对 etcd 的读取操作都可以相当简单地自行提权。


Kubernetes 的调度器会搜索 etcd 找出那些已定义但还没被调度到节点上的 Pod, 然后把这样的 Pod 发送到空闲的 Kubelet 节点进行部署。 在 Pod 信息被写进 etcd 之前, API Server 会检查提交的 Pod 定义。 因此一些有恶意使用者会直接把 Pod 定义写入 etcd, 以跳过许多诸如 PodSecurityPolicy 这样的安全机制。


无论是 etcd 集群的节点与节点, 还是节点与 API Server, 他们的通信都应该配置 TLS 证书, 并且 etcd 集群应部署在专属的节点之上。 etcd 集群与 Kubernetes 集群间也应该设置防火墙, 以避免由于私钥被盗而被从工作节点上发起攻击。

5. 定期替换密钥

定期替换密钥和证书是一条安全方面的最佳实践。 这样能减小当密钥被盗时所遭受的损害范围。


Kubernetes 会在某些现有证书过期时创建新的证书签名申请来自动替换 Kubelet 的客户端和服务器端证书。


但是 API Server 用来加密 etcd 数据的对称密钥是无法自动替换的。 它只能手工替换。 这样的操作需要有对主节点操作的权限, 因此托管 Kubernetes 服务(比如谷歌的 GKE 和微软的 AKS)会自行解决这个问题而无需管理员操心。

第二部分:工作负载

通过对控制面板实施最小化可用安全策略,可以使集群工作得更安全。 但这样还不够。 打个比方, 对于一艘装运了危险货物的船,船上的集装箱也必须加以防护,这样在出现意外事故或破坏时集装箱还能承载货物。对于 Kubernetes 的工作负载对象(Pod, Deployment,Job 和 Set 等)也是一样。 它们在部署时还是可信的,然而当面对外网流量时总还是会有被攻破的风险。 要减轻此类风险,除了针对工作负载对象实施最小特权原则外,还需要加固它们运行时的配置。

6. 使用 Linux 的安全功能以及 Kubernetes 的 Pod 安全规则

Linux 内核提供了许多互相有功能重叠的安全扩展(capabilities, SELinux, AppArmor, seccomp-bpf)。 他们可被配置用来为应用提供最小特权。


像 bane[7]这样的工具可以用来生成 AppArmor 的配置文件和 seccomp 的 docker-slim 配置文件。 然而用户需要详细测试自己应用的所有路径, 验证这些配置对应用是否会造成副作用。


而 Pod 安全规则可以用来授权使用 Kubernetes 的安全扩展和其他安全指令。 这些规则描述了一个 Pod 提交到 API Server 后所必须遵守的最小安全契约。 这些契约包括了安全配置,提权标志,以及共享主机网络,进程或进程间通信的命名空间。


这些安全规则相当重要, 因为他们有助于防止容器内的进程超越其隔离边界。 Tim Allclair 提供的 PodSecurityPolicy 范例[8]相当详尽。 你可以自定义这个范例, 将其用在自己的使用场景里。

7. 静态分析 YAML 文件

在使用 Pod 安全规则来控制 Pod 访问 API Server 的同时, 也可以在开发工作流中使用静态文件分析来构筑组织的合规需求或满足风险偏好。


在 Pod 类型(比如 Deployment, Pod, Set 等)的 YAML 文件中不应该存放敏感数据, 而包含敏感数据的 Configmap 和 Secret 应该由诸如 vault(通过 CoreOS 提供的 operator), git-crypt, sealed secret 或者云厂商提供的密钥管理服务来进行加密处理。


静态分析 YAML 配置可以对运行时的安全情况构建一条基准线。 以下是 kubesec 工具为某个资源生成风险评分的例子:


{  "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 工具是个针对 Kubernetes 的 YAML 文件的单元测试框架。 代码例子如下:


#// vim: set ft=python:def test_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()
复制代码


以上这些工具都通过将检查和验证工作在软件开发周期中提前(shift left])到更早的开发阶段的方式, 使开发人员能更早地获得对于代码和配置的反馈, 以避免提交在之后的人工或自动检查中被退回。这样也可以减少引入更多安全实践的障碍。

8. 使用非 root 的用户运行容器

运行在 root 用户下的容器大多拥有了大大超过其所承载的服务需要的权限。 当集群被侵入时,这样的容器会让攻击者有能力执行更进一步的破坏。


容器技术依然基于传统的 Unix 安全模型, 叫做自主访问控制(简称 DAC)。在这个模型之下,所有东西都是文件, 而权限是可以被赋予用户或用户组。


用户命名空间在 Kubernetes 下并没有启用, 这意味着容器中的用户表会被映射到宿主机的用户表中, 而在容器里由 root 身份运行的进程相当于在宿主机上也是以 root 身份运行。 尽管说我们有层级安全机制来防范容器发生问题, 但在容器中用 root 身份运行进程依然是不推荐的做法。


许多容器镜像使用 root 用户运行一号进程。 如果这个进程被攻破, 那么攻击者在容器中就有了 root 的权限, 从而会轻易放大由于集群误配置造成的安全漏洞。


Bitnami 公司在将容器镜像迁移到非 root 用户这方面做了很多工作[9](主要由于 OpenShift 平台默认要求非 root 身份运行容器)。参考他们提供的文档可以降低管理员实施类似迁移的难度。


下列 Pod 安全规则的代码片段给出了防止容器中的进程以 root 身份运行, 以及防止进程提权到 root 身份的方法:


# Required to prevent escalations to root.allowPrivilegeEscalation: falserunAsUser:  # Require the container to run without root privileges.  rule: 'MustRunAsNonRoot'
复制代码


非 root 的容器无法绑定小于 1024 的端口(可以通过配置内核参数 CAP_NET_BIND_SERVICE 来启用), 但使用 service 的特性可以使对外端口配置为 1024 以下。 在以下例子中,MyApp 这个应用在容器中绑定了 8443 端口,而 service 将相关流量代理到 443 这个端口中对外暴露应用:


kind: ServiceapiVersion: v1metadata:  name: my-servicespec:  selector:    app: MyApp  ports:  - protocol: TCP    port: 443    targetPort: 8443
复制代码


在用户命名空间在 Kubernetes 中可用之前,或者非 root 功能在容器运行时组件中获得直接支持之前, 使用非 root 用户运行容器依然是推荐的做法。

9. 使用网络规则

默认状态下, Kubernetes 中 Pod 与 Pod 之间的网络是互通的。 可以通过使用网络规则来对此加以限制。



传统的服务一般给每个应用配置静态 IP 和端口范围。 这样的静态 IP 一般很少会改变,从而会被当作服务的一种标识来对待。 因此传统服务可以通过防火墙来加以限制和保护。 容器为了能快速失败快速重新调度,一般很少使用固定 IP,而是通过使用服务发现的机制来替代。 容器这样的特性使得防火墙会更难配置和审查。


由于 Kubernetes 将所有系统状态都存储在 etcd 里, 只要 CNI 网络插件支持网络规则, 借助 etcd 中的数据就可以配置动态防火墙。 当前 Calico, Cilium, kube-router, Romana 和 Weave Net 这些插件都能支持网络规则。


值得注意的是,这些网络规则是失效关闭的(fail-closed), 因此在下列 YAML 中缺少 podSelector 配置意味着这个规则会应用到所有容器:


apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:  name: default-denyspec:  podSelector:
复制代码


下列的网络规则的例子描述了如何关闭除了 UDP 53(DNS 端口)之外的所有对外流量。 这样也防止了进入应用的连接——这是因为网络规则是有状态面向连接的, 同一个网络连接中应用对外请求的响应也依旧会返回到应用中。


apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:  name: myapp-deny-external-egressspec:  podSelector:    matchLabels:      app: myapp  policyTypes:  - Egress  egress:  - ports:    - port: 53      protocol: UDP  - to:    - namespaceSelector: {}

复制代码


Kubernetes 的网络规则并不能被应用到 DNS 名字上。 这是因为一个 DNS 名字会可能被轮询调度解析到许多个 IP 地址,或者依据调用 IP 动态被解析到不同的目标 IP 地址, 而网络规则只能被应用到静态的 IP 或 podSelector(也就是 Kubernetes 的动态 IP 机制)上。


安全上有一条最佳实践建议一开始对 Kubernetes 的 namespace 先拒绝所有流量, 然后再逐渐添加路由允许应用能通过测试场景。 这样做是非常复杂的, 可以通过下列 ControlPlane 公司出品的 netassert 这个开源工具来帮助达成最佳实践。netassert[10]是一个安全运维工作流方面的网络安全工具, 通过高度并发运用 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 受攻击的案例[11]), 因此也需要有专门的测试来确定这一类 API 在容器网络中被关闭, 以避免误配置的可能。

10. 扫描容器镜像和运行 IDS(入侵检测系统)

Web 服务器作为它所部署的网络上的一个可被攻击的切入口,它镜像上的文件系统需要被完全扫描,以避免存在已知的漏洞被攻击者利用来取得远程控制容器的权限。使用 IDS 可以检测已知漏洞。


Kubernetes 通过一系列的 Admission Controller 来控制 Pod 类型的资源(如 Deployment 等)是否能被部署进集群。 Admission controller 会检验每个被提交进来的 Pod 的定义,或者有时会修改 Pod 定义的内容。 现在已经支持后台 webhook 挂载外部应用。



Webhook 功能可以和容器镜像扫描工具结合起来。 在容器被部署进集群之前扫描镜像内容。 一旦未通过扫描检查, 容器便会被 Admission Controller 拒绝部署。


使用工具扫描容器镜像中是否包含已有的漏洞, 这样能减小攻击者利用公开漏洞(CVE, Common Vulnerabilities & Exposures)的时间窗口。 像 CoreOS 出品的 Clair 和 Aqua 出品的 Micro Scanner 都应该被整合到部署管道中, 以避免部署的容器中包含致命漏洞。


像 Grafeas 这样的工具可以存储镜像的元数据, 用来针对容器的特有签名(基于内容寻址的散列哈希)进行持续的合规与漏洞检查。 通过这个签名来扫描本地容器镜像等同于扫描部署在生产环境的容器, 这样就可以持续地做本地检查而不需要连接到生产环境中。


而未知的瞬时攻击漏洞(Zero Day)永远会存在。 为了应对这样的威胁, 需要在集群中部署一些诸如 Twistlock,Aqua 和 Sysdig Security 这样的工具。而另外一些 IDS 会检测出容器中发生的不寻常的行为, 暂停或者终止这样的容器。 典型的工具有 Sysdig 出品的开源规则引擎 Falco[12]。

第三部分: 展望未来

目前看来服务网格当属安全在“云原生进化(cloud native evolution)”发展中的下一阶段形态。 当然真正应用服务网格尚需时日。 迁移工作涉及到将应用层中含有的相关复杂逻辑转移为依赖服务网格的基础构件, 而企业也会很想详细理解迁移和运用服务网格相关的最佳实践。


11. 使用服务网格

服务网格是通过类似 Envoy 和 Linkerd 这样的高性能边车(sidecar)代理服务器在集群中组建应用间加密网络的基础设施。 它提供了流量管理, 监控和规则控制的能力而无需微服务为此修改代码。


服务网格意味着微服务中的安全与网络管理相关的代码可以被去除, 而这部分责任将被转移到经过实战验证的公共组件中。 之前通过 Linkerd 就已经可以达成这样的功能。 如今由 Google, IBM 和 Lyft 联合推出了 Istio 在服务网格领域也可以作为一个可选方案。 Istio 通过基于 SPIFFE 规范的 Pod 间相互进行身份识别的能力以及其他一系列的功能, 来简化服务网格这个下一代网络安全的部署。 在“永不信任,始终验证”(Zero Trust)的网络模型中, 每次交互都应在互信 TLS(mTLS, mutual TLS)下发生, 以保证交互双方不仅链路安全,而且身份已知。 这样的话或许就没有必要使用传统的防火墙或者 Kubernetes 的网络规则了。


对于仍怀有传统网络思维的那些人来说,我们预计向云原生安全原则的思维转变不会容易。 此处推荐阅读来自 SPIFFE 项目委员会的 Evan Gilman 撰写的 Zero Trust Networking[13]这本书来了解这一领域的入门知识。


Istio 0.8 LTS 版本已经推出,这个项目正快速接近 1.0 版本的发布。 其版本编号习惯会与 Kubernetes 遵循的模型相同:核心版本稳定增长, 每个 API 都会通过自己版本中的 alpha/beta 来描述其稳定性。 可以期待 Istio 在之后几个月的采用率会有上升。

结语

云原生应用拥有一系列带有细粒度配置能力的轻量级安全组件来控制工作负载与基础架构。 这些工具的威力和灵活性对管理员来说既是祝福又是诅咒。 如果自动化安全能力不足, 这些工具很容易会暴露不安全的网络流量,导致容器或隔离结构爆发问题。


防护工具层出不穷, 但管理员仍需警惕误配置的潜在可能, 并尽量减少对外易受攻击的点。


然而如果安全性要求会减缓组织交付功能的速度, 那么安全就永远无法成为一类需求。通过在软件交付流程中应用持续交付的原则, 可以让组织在不影响业务的情况下也能合规和治理目标,实现持续审计。


在有完整的测试套件的支持之下,想要在安全方面快速迭代是非常容易的。 而这样的迭代并非是用定点运行渗透测试来运作,而是持续安全(Continuous Security)。 持续安全通过持续的管道验证的方式来保证组织的受攻击点清晰, 相关风险明确并能有效地被管理起来。


相关链接:


  1. https://docs.google.com/presentation/d/1Gp-2blk5WExI_QR59EUZdwfO2BWLJqa626mK2ej-huo/edit#slide=id.g1e639c415b_0_56

  2. https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/1.9.0/docs/04-certificate-authority.md

  3. https://coreos.com/etcd/docs/latest/op-guide/security.html

  4. https://github.com/uruddarraju/kubernetes-rbac-policies

  5. https://github.com/liggitt/audit2rbac

  6. https://github.com/coreos/dex

  7. https://github.com/genuinetools/bane

  8. https://gist.github.com/tallclair/11981031b6bfa829bb1fb9dcb7e026b0

  9. https://engineering.bitnami.com/articles/running-non-root-containers-on-openshift.html

  10. https://github.com/controlplaneio/netassert

  11. https://hackerone.com/reports/341876

  12. https://github.com/draios/falco

  13. https://amzn.to/2Gg6Pav


原文链接:https://kubernetes.io/blog/2018/07/18/11-ways-not-to-get-hacked/#1-tls-everywhere


2020-04-15 23:05842

评论

发布
暂无评论
发现更多内容
不遵循这11条建议,小心你的Kubernetes集群安全_文化 & 方法_Rancher_InfoQ精选文章