前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rego的第二个设计原则:接受分层数据

Rego的第二个设计原则:接受分层数据

作者头像
CNCF
发布2020-03-27 11:02:59
2.5K0
发布2020-03-27 11:02:59
举报
文章被收录于专栏:CNCF

这是关于Open Policy Agent(OPA)策略语言Rego背后的设计原则的博客系列的第二部分。前面我们描述了如何将Rego的语法设计为反映真实策略的结构。在本系列的这一部分中,我们将了解Rego为什么以及如何专门使用分层数据(例如JSON和YAML)来表示它用于决策和表示决策本身的原始信息。

快速复习一下OPA

OPA的设计目的是将策略决策从广泛的软件服务中剥离出来。你通常在需要策略决策的软件所在的服务器上运行OPA,并诱使该软件在需要时向OPA请求策略决策。如下图所示,OPA利用以下信息进行决策:

  • 策略查询。需要策略决策的服务提供的任意JSON文档。将策略查询看作OPA需要作出决策的具体信息(例如用户-行为-资源)。
  • 外部数据。策略查询之外的OPA中注入的任意数量的JSON文档,这些文档表示现实世界中正在发生的事情(例如,K8s集群中的当前资源或资源属性,如所有者、大小等),并且随着世界的变化保持最新。
  • Rego策略。一个或多个Rego策略。Rego是一种专门为跨任何域表达策略而构建的定制语言。
OPA决策互动
OPA决策互动

这篇博客文章的重点是解释我们为什么以及如何选择使用JSON来表示策略查询、外部数据,甚至策略决策本身。

JSON是无处不在

JSON(或者更普遍的层次结构数据)在云原生生态系统中无处不在。公有云、kubernetes集群、NOSQL(甚至SQL)数据库、服务网格、微服务API和应用程序配置都以JSON的形式获取和导出它们的状态。分层数据(相对于存储在经典SQL数据库中的关系数据)将会继续存在,这可能是因为它非常适合对软件应用程序的许多不同方面以及它们所运行的基础设施进行建模。此外,HTTP/JSON API的流行使JSON成为交换信息的普遍格式。

对于OPA来说,这意味着当一个服务向OPA请求一个策略决策时,它将拥有一些OPA做出决策所需的层次数据,这几乎是确定无疑的。

例如,它可能是一个JSON Web Token(JWT),表示用户和她的属性:

代码语言:javascript
复制
{
  "sub": "1234567890",
  "name": "Alice Smithsonian",
  "iat": 1516239022,
  "groups": ["employee", "billing-manager"]
}

或者,它可能是关于宠物商店里宠物的属性信息:

代码语言:javascript
复制
{
    "id": "i0779921",
    "name": "Lassie",
    "breed": "collie",
    "owners": [{
        "first": "Rudd",
        "last": "Weatherwax"
    }]
}

它也可以是Kubernetes上运行的应用程序的配置描述(这里显示的是常见的K8s YAML,它可以很容易地转换成JSON):

代码语言:javascript
复制
apiVersion: admission.k8s.io/v1beta1
kind: AdmissionReview
request:
  kind:
    group: extensions
    kind: Ingress
    version: v1beta1
  object:
    metadata:
      name: prod
      labels:
        costcenter: retail
    spec:
      rules:
      - host: initech.com
        http:
          paths:
          - path: /finance
            backend:
              serviceName: banking
              servicePort: 443
          - path: /retail
            backend:
              serviceName: storefront
              servicePort: 8080

在整个堆栈中,从基础设施到微服务,再到应用程序存储的业务数据,JSON无处不在地表示信息。此外,即使在JSON数据不像SQL数据库那样普遍存在的领域,也可以直接将平面的、非层次结构的数据转换为JSON;然而,将JSON转换为非分层数据格式会带来很多可用性挑战。

OPA如何与外界互动

请记住,OPA可以使用两个数据源来进行决策:

  • 服务作为策略查询提供的数据
  • 被注入OPA的外部数据代表外部世界的状态

这两个都是任意JSON。OPA不将任何模式或数据模型强加于这些JSON文档。OPA只知道它是一个JSON块;策略作者需要理解JSON在世界上代表什么,并编写策略来做出适当的决策。

我们可以设计一个不同的OPA。我们可以设计OPA来为每个域(例如K8s、服务网格、数据库、应用程序)提供模式或数据模型,并要求外部世界根据OPA的模型调整其数据。

例如,假设OPA要求每个策略查询有三个字段:

  • username:表示用户正在进行操作的字符串
  • action:指定用户要执行的操作的字符串
  • resource:标识被操作的资源的字符串

这意味着每个请求OPA进行授权决策的应用程序都需要提供这三个字段。如果应用程序将如下所示的用户信息存储在JWT中,它不能直接将JWT交给OPA—-它需要提取sub(subject)值并将其包含为username值。

代码语言:javascript
复制
{
  "sub": "1234567890",
  "name": "Alice Smithsonian",
  "iat": 1516239022,
  "groups": ["employee", "billing-manager"]
}

强加一个模式或数据模型会使构建OPA变得更容易,因为它将集成的负担转移到了外部世界。世界上每个希望与OPA集成的系统都需要包含特定于OPA的代码来转换数据以满足OPA的需求。

此外,OPA用于决策的外部数据也是如此。如果OPA将数据模型强加于所有外部数据,那么将数据推入OPA的系统将需要理解OPA的数据模型,并将来自外部世界的数据转换为与该模型匹配的数据。

相反,OPA旨在为策略查询和外部数据获取任意JSON数据。这使得与OPA的集成非常简单;只需将信息转换为JSON(每种编程语言都有相应的标准库)并将其发送出去。不需要ETL你的数据得到它到OPA--任何webhook将足以集成OPA。总之……

OPA应该适应外部世界的数据,而不是相反

对于外部世界来说,以任何自然的形式获取JSON数据都很容易,但这确实意味着策略语言Rego需要足够灵活,以便人们能够编写适应这种格式的策略。例如,策略语言不能依赖于用户名或操作的固定位置。它必须具有足够的表达能力,以便人们能够编写策略来弥补世界数据模型和最适合表达策略的格式之间的差距。

Rego对JSON的支持

Rego策略的起点是(i)表示外部软件提供的策略查询(又称input)的任意JSON对象(例如API调用、配置文件、数据元素等)和(ii)表示世界状态的任意JSON对象。OPA和Rego都不明白这些数据在现实世界中意味着什么,但策略作者却明白。策略作者编写Rego对浏览这些JSON文档的逻辑进行编码,并将其与硬编码的值或其他JSON位进行比较,以便做出决策。

例如,对于一个简单的HTTP API,输入JSON对象可以是:

代码语言:javascript
复制
{
    "method": "GET",  
    "path": "/dogs/dog123",
    "user": "alice",
    "roles": ["customer", "guest"]
}

作为一个策略作者,我知道这个JSON对象代表一个HTTP API,但是Rego不知道。如果我想允许所有到根路径的GET请求,我对input文档写一个简单的规则与条件(input在Rego是一个全局变量,代表提供给OPA的策略查询):

代码语言:javascript
复制
allow {  
  input.method == "GET"    
  input.path == "/"
}

这个例子显示了对字符串的简单相等性检查,但是通常你可能需要将/dogs/dog123这样的路径拆分成多个块,操作数字,检查JWT的内部等等。JSON中的标量值通常包含需要提取或操作的信息。

Rego必须操作JSON标量类型:布尔值、数字、字符串和null

为此,Rego在openpolicyagent.org上提供了50多个内置函数,这些函数提供了检查和构造标量JSON类型所需的各种基本功能。

当然,支持JSON的重点不是标量类型,而是复合类型:数组和对象。没有这些,就根本没有等级制度。

支持JSON数组和对象有两个关键需求:能够钻取层次结构(你已经通过点表示法了解了)和能够迭代集合元素(数组元素或对象的键/值对)。

Rego必须应对深度嵌套的数组和对象

在Rego中,当你知道确切的路径时,在数组和对象中穿梭是很简单的。它使用与许多编程语言相同的语法:点表示法和括号表示法。

例如,假设下面的JSON对象是input。

代码语言:javascript
复制
{
    "id": "i0779921",
    "name": "Lassie",
    "breed": "collie",
    "owners": [{
        "first": "Rudd",
        "last": "Weatherwax"
    }]
}

你可以编写以下所有表达式来浏览这个JSON文档。

代码语言:javascript
复制
input.name            # "Lassie"
input["name"]         # "Lassie"   x.y is syntactic sugar for x["y"]
input.owners[0]       # First element of owner's array
input.owners[0].first # "Rudd"

更有趣的是迭代。99%的Rego语句都是简单的if语句,而迭代主要用作其中一个if语句的条件。

例如,假设你希望允许admin执行任何操作,并向你提供了一个列出所有用户角色的input。

代码语言:javascript
复制
{
    "method": "GET",  
    "path": "/dogs/dog123",
    "user": "alice",
    "roles": ["customer", "guest"]
}

你需要编写一个策略,指出如果roles数组中有某个元素等于“admin”,则应该允许该请求。

Rego中的迭代使用关键字some。你可以编写一个表达式来测试某个条件是否为真,并对要遍历的表达式中的变量应用some。

在admin示例中,编写下面的Rego来检查输入的roles数组是否有some索引i,input.roles[i]等于“admin”。

代码语言:javascript
复制
allow {  
  some i    
  input.roles[i] == "admin"
}

你可以一次将some应用到多个变量上。在Kubernetes的策略中,这种情况经常发生。这是Kubernetes提交给许可控制的一个对象--注意数据嵌套的深度。

代码语言:javascript
复制
kind:
  kind: Ingress
  group: extensions
metadata:
  name: prod
  labels:
    costcenter: retail
spec:
  rules:
  - host: initech.com
    http:
      paths:
      - path: /finance
        backend:
          serviceName: banking
          servicePort: 443
      - path: /retail
        backend:
          serviceName: storefront
          servicePort: 8080

如果你想在servicePort不是443的情况下拒绝创建这个资源,你可以编写下面的Rego。

代码语言:javascript
复制
deny {
    input.kind.kind == "Ingress"
    some i,j
    input.spec.rules[i].http.paths[j].backend.servicePort != 443
}

虽然到servicePort的路径有点长,但这只是数据的性质。看到路径被写在一行中,使得将其映射回实际数据变得相对容易,这有助于读者理解规则的意图。

相反,在传统编程语言中,你需要将JSON路径分解为块,并准确地规定希望一次迭代一个变量的范围。在Python中也有相同的例子。

代码语言:javascript
复制
function deny():
    return input.kind.kind == “Ingress” and deny_aux()
function deny_aux():
    for rule in input.spec.rules:
        for path in rule.http.paths:
            if path.backend.servicePort != 443:
                return true

作为一名读者,要理解Python在数据方面的内容,你需要通过组合for循环和if语句中的路径来重构JSON路径。Python中显示的分解路径方法更接近于策略的实现,而不是策略本身。

当然,Rego具有足够的灵活性,你可以根据需要分解路径。

代码语言:javascript
复制
deny {
    input.kind.kind == "Ingress"
    some i, j
    rule := input.spec.rules[i]
    path := rule.http.paths[j]
    path.backend.servicePort != 443
}

在过去几年里,Rego能够以不同的方式进行迭代,我们发现有时分解路径,有时不分解。就我个人而言,我通常会避免分解路径,因为我发现几周甚至几天后返回时更容易阅读它们,因为我可以更直接地将策略语句与JSON数据的文档进行比较;通常我甚至不需要文档,因为路径本身是不言自明的。

总结

Rego的设计初衷是通过JSON数据来表达策略。

  • JSON的原因吗?JSON在云原生环境中无处不在,这意味着OPA用于决策的外部数据和输入很容易获得。
  • Rego的设计是为了适应它周围的世界--而不是反过来。这导致与OPA集成的障碍很低,通常不需要特定于OPA的代码。
  • Rego对检查JSON值提供了一流的支持。它有50+内置的字符串操作、JWT操作、网络CIDR数学等功能。Rego对通过深度嵌套的数组和字典进行浏览提供了一流的支持。

OPA被设计成集成到广泛的软件系统中,因此这种集成的方便性是至关重要的。Rego的灵活性使它适用于各种各样的用例,而且使它很容易跨云原生堆栈集成OPA。

感谢Eileen Kemp、Chris Webber和Ash Narkar。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CNCF 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档