使用OPA增强Kubernetes安全

二叶草 2020年2月14日09:24:55函数代码评论阅读模式
 Kubernetes的安全性生态体系将会令人费解。2019年7月的Sysdig文章内容简述了Kubernetes的33种安全工器具。这一大数字仅仅 提升了。现如今有利于维护Kubernetes群集的专用工具能够分成三类别。例如Clair和SonarQube之类的工具会在容器映像中扫描代码以查找漏洞。

诸如StackRox,Dynatrace和Sysdig之类的平台专注于保护管道,以确保您验证的代码就是部署到您环境中的代码。最后,Kubernetes和您的容器运行时会利用SELinux,AppArmor和POSIX等低级工具来防止不良行为者进入您的集群。

即使有所有这些可用产品,我们也会在此门户文章中讨论这种安全模型中的漏洞。即使上述工具协同工作,但如果将错误类型的工作负载部署到敏感区域,仍然会出现安全问题。例如,您不希望允许在Kubernetes集群中部署其他负载均衡器,因为它们可能以意外或不安全的方式路由流量。您也不想将未经验证的开发代码部署到生产环境中。为了防止这些与部署相关的安全问题,您可以使用称为“准入控制器”的Kubernetes组件创建策略。

Kubernetes Admission Controllers可以分析API请求,以在实际创建对象之前在集群中创建对象。准入控制器有两种。

突变准入控制器在将其部署到您的Kubernetes集群之前,会接收传入的API请求并对其进行规定的更改。如果您想对请求的各个部分进行通用更改,这些功能将非常有用。设置配额的默认值,默认存储类,甚至将Pod设置为始终拉取映像的新副本等常见操作均由Mutation Admission Controllers处理。可以在Kubernetes文档中找到默认启用的突变入场控制器列表。

验证控制器不会对API请求进行更改,但是如果该请求违反Admission Controller使用的策略,则他们可以拒绝该请求。

如果这两种类型的Admission Controller拒绝API请求,则该对象永远不会实际部署,并且该请求将报告工作流在该点失败。今天,我们正在研究一个基于Open Policy Agent(OPA)在Kubernetes社区中迅速普及的Validation Admission Controller。

使用开放式政策代理

OPA(发音为“ oh-pa”)是Cloud Native Computing Foundation(CNCF)的孵化项目。通过其文档网站,OPA是

一个开源的通用策略引擎,可以统一整个堆栈中的策略。OPA提供了一种高级声明性语言,使您可以将策略指定为代码和简单的API,以减轻软件决策的负担。您可以使用OPA在微服务,Kubernetes,CI / CD管道,API网关等中实施策略。”

OPA在Kubernetes中作为验证准入控制器进行部署。OPA网站上有很棒的教程,可以帮助您在群集中启动和运行它。尽管本教程使用Minikube作为开发平台,但是可以使用任何功能性的Kubernetes集群。本教程还使用自签名TLS证书在OPA和Kubernetes之间进行通信。如果要使用自己的证书,只需提供它们作为参考文件即可,而不是使用OpenSSL生成自签名证书。

在Kubernetes中部署时,OPA充当验证准入控制器。

使用OPA增强Kubernetes安全

使用Rego验证请求

当请求进入API服务器时,OPA会使用Rego(一种可支持JSON的结构化查询语言)编写的规则集对它进行验证。Rego基于InfoSec和其他社区已经存在数十年的Datalog等格式。OPA教程页面将引导您完成设置OPA并将其配置为允许特定名称空间的特定Ingress域。本教程中创建的策略通过为同一域创建另一个Ingress并将其指向其他服务来确保不会劫持绑定到一个域的流量。如果没有OPA之类的工具,可能会成为攻击的载体。

使用Rego编写的政策是您如何与Kubernetes集群中的OPA进行交互。在以下各节中,我们将深入研究OPA政策。此示例的代码来自OPA网站。
研究Rego和OPA的交叉路口

OPA策略作为ConfigMap加载到OPA中。

package kubernetes.admission
import data.kubernetes.namespaces
operations = {"CREATE", "UPDATE"}
deny[msg] {  input.request.kind.kind == "Ingress"  operations[input.request.operation]  host := input.request.object.spec.rules[_].host  not fqdn_matches_any(host, valid_ingress_hosts)  msg := sprintf("invalid ingress host %q", [host])}
valid_ingress_hosts = {host |  whitelist := namespaces[input.request.namespace].metadata.annotations["ingress-whitelist"]  hosts := split(whitelist, ",")  host := hosts[_]}
fqdn_matches_any(str, patterns) {  fqdn_matches(str, patterns[_])}
fqdn_matches(str, pattern) {  pattern_parts := split(pattern, ".")  pattern_parts[0] == "*"  str_parts := split(str, ".")  n_pattern_parts := count(pattern_parts)  n_str_parts := count(str_parts)  suffix := trim(pattern, "*.")  endswith(str, suffix)}
fqdn_matches(str, pattern) {    not contains(pattern, "*")    str == pattern}
第一行,程序包kubernetes.admission定义了文件文件其余部分中策略的层次结构名称。OPA中策略的默认位置是kubernetes.admission。

import参数import data.kubernetes.namespaces提供了kubernetes中部署的所有当前名称空间的列表。部署Pod时,OPA会收集此数据,并在激活策略时更新这些数据。

操作= {“创建”,“更新”}定义将触发该操作的操作。在这种情况下,将在创建或更新API对象时运行该策略。

之后,使用Rego编写的OPA政策可能会有点违反直觉,直到您习惯了它们的运作方式。

剖析Rego撰写的OPA政策

Rego中最常见的模式是定义一组要测试的条件。如果满足所有条件,则拒绝该请求,并通过Kubernetes API服务器将正确的原因提供给请求该操作的用户。这些条件在deny函数中定义。

deny[msg] {  input.request.kind.kind == "Ingress"  operations[input.request.operation]  host := input.request.object.spec.rules[_].hostnot fqdn_matches_any(host, valid_ingress_hosts)  msg := sprintf("invalid ingress host %q", [host])}
让我们看看这些情况。

input.request.kind.kind ==“ Ingress”告诉OPA仅对正在创建Ingress对象的API请求进行操作。

使用OPA增强Kubernetes安全

operation [input.request.operation]确认请求类型在operation变量中。如果API请求的操作类型为UPDATE或CREATE,则返回true。当添加到之前的测试中时,此策略仅在创建或更新Ingress对象时起作用。

host:= input.request.object.spec.rules [_]。host使用来自API请求.spec.rules.host值的数据定义一个名为host的变量。

_字符是一个特殊的匿名变量。不必显式命名每个变量,可以使用_字符快速遍历值列表。在此变量定义中,_变量遍历API请求.spec.rules中的所有规则,并根据策略条件测试主机的每个值。

使用OPA增强Kubernetes安全

最终测试

最终测试是Rego可能会引起混乱的地方。默认方法以及本教程中使用的方法是创建一个拒绝API请求的策略。这意味着我们一直在执行的拒绝政策中的所有条件都必须评估为是。如果满足“拒绝策略”中的所有条件,则OPA将拒绝该请求。

如果API请求中的域位于其名称空间的入口白名单注释中,则策略中的最终测试将返回True。但是,如果请求不在名称空间的ingress-whitelist批注中,我们想拒绝它们。为此,最终测试使用not运算符。即使fqdn_matches_any如果应允许使用域也返回True,则not运算符也会告诉deny策略寻找该结果的反函数。

代码不是fqdn_matches_any(host,valid_ingress_hosts)调用策略中定义的fqdn_matches_any函数。此外,它还传递策略中定义的valid_ingress_hosts参数。

valid_ingress_hosts的值定义如下:

valid_ingress_hosts = {host |  whitelist := namespaces[input.request.namespace].metadata.annotations["ingress-whitelist"]  hosts := split(whitelist, ",")  host := hosts[_]}
大括号将valid_ingress_hosts定义为具有名为whitelist和host的键的数组。

白名单值是通过查看传入API请求的名称空间注释来计算的。

如果名称空间具有名为ingress-whitelist的注释,则该注释的关联主机名模式将另存为数组内的主机值。

在本教程中,将创建带有ingress-whitelist批注的名称空间来测试该策略。

apiVersion: v1kind: Namespacemetadata:annotations:ingress-whitelist: "*.qa.example.com,*.internal.example.com"name: qa
在此命名空间中,valid_ingress_hosts的计算方式如下:

{"host": "*.qa.example.com", "host": "*.internal.example.com"}
该数组传递到fqdn_matches_any中。

fqdn_matches_any(str, patterns) {  fqdn_matches(str, patterns[_])}
该函数调用两个名为fqdn_matches的函数。

  fqdn_matches(str, pattern) {  pattern_parts := split(pattern, ".")  pattern_parts[0] == "*"  str_parts := split(str, ".")  n_pattern_parts := count(pattern_parts)  n_str_parts := count(str_parts)  suffix := trim(pattern, "*.")  endswith(str, suffix)}
fqdn_matches(str, pattern) {    not contains(pattern, "*")    str == pattern}
这两个功能都是采用* .qa.example.com之类的域的功能

干净地修剪*。从域的最前面(如果存在)返回,如果主机变量与valid_ingress_hosts中的一个域匹配,则返回true。

如果没有*,则返回true。在域的最前面,主机字符串与valid_ingress_hosts中的域之一匹配。

包起来

让我们以简单的语言总结一下此OPA政策拒绝:

当传入Kubernetes的API请求创建或更新Ingress对象时,将运行该策略。

将传入Ingress对象的主机值与有效的入口主机进行比较,该入口主机作为作用于名称空间上的注释进行维护。

如果传入请求的主机值与其名称空间的有效入口域不匹配,则拒绝该请求,并将“无效入口主机”传递回尝试创建入口对象的用户。

这种编程逻辑可以快速测试任何Kubernetes API请求,并通过Kubernetes API甚至外部数据源将其与OPA pod可用的任何数据进行评估。除了上面使用的功能之外,Rego还具有内置的库来发送HTTP或HTTPS请求并评估响应数据。

OPA策略和Rego在Kubernetes社区中迅速受到关注,这是因为其强大的功能以及以编程方式定义用于管理任何API对象创建的策略的能力。

本文来源于:使用OPA增强Kubernetes安全-变化吧门户
特别声明:以上文章内容仅代表作者本人观点,不代表变化吧门户观点或立场。如有关于作品内容、版权或其它问题请于作品发表后的30日内与变化吧联系。

  • 赞助本站
  • 微信扫一扫
  • weinxin
  • 加入Q群
  • QQ扫一扫
  • weinxin
二叶草
Go语言中的常量 函数代码

Go语言中的常量

1 概述 常量,一经定义不可更改的量。功能角度看,当出现不需要被更改的数据时,应该使用常量进行存储,例如圆周率。从语法的角度看,使用常量可以保证数据,在整个运行期间内,不会被更改。例如当前处理器的架构...
Go语言的接口 函数代码

Go语言的接口

Go语言-接口 在Go语言中,一个接口类型总是代表着某一种类型(即所有实现它的类型)的行为。一个接口类型的声明通常会包含关键字type、类型名称、关键字interface以及由花括号包裹的若干方法声明...
Go语言支持的正则语法 函数代码

Go语言支持的正则语法

1 字符 语法 说明 . 任意字符,在单行模式(s标志)下,也可以匹配换行 字符类 否定字符类 d Perl 字符类 D 否定 Perl 字符类 ASCII 字符类 否定 ASCII 字符类 pN U...
Go语言的包管理 函数代码

Go语言的包管理

1 概述 Go 语言的源码复用建立在包(package)基础之上。包通过 package, import, GOPATH 操作完成。 2 main包 Go 语言的入口 main() 函数所在的包(pa...

发表评论