OPA将从外部加载的数据成为基本文档(base documents),有规则产生的值成为虚拟文档(virtual documents),此处"虚拟"的意思表示文档由策略进行了计算,且不是外部加载的。Rego中可以使用名为data的全局变量访问这两种数据。
异步加载的基本文档可以通过data全局变量进行访问。另一方面,如果软件需要查询OPA来获取策略决策时,也可以将基础文档同步推入或拉入OPA,此时需要通过input全局变量来引用同步推送的基本文档。同步加载的数据保存在data之外,防止命名冲突。
{ "servers": [ {"id": "app", "protocols": ["https", "ssh"], "ports": ["p1", "p2", "p3"]}, {"id": "db", "protocols": ["mysql"], "ports": ["p3"]}, {"id": "cache", "protocols": ["memcache"], "ports": ["p3"]}, {"id": "ci", "protocols": ["http"], "ports": ["p1", "p2"]}, {"id": "busybox", "protocols": ["telnet"], "ports": ["p1"]} ], "networks": [ {"id": "net1", "public": false}, {"id": "net2", "public": false}, {"id": "net3", "public": true}, {"id": "net4", "public": true} ], "ports": [ {"id": "p1", "network": "net1"}, {"id": "p2", "network": "net3"}, {"id": "p3", "network": "net2"} ]} OPA使用;来表示逻辑AND
input.servers[0].id == "app"; input.servers[0].protocols[0] == "https"$ true也可以使用多行来忽略;
input.servers[0].id == "app"input.servers[0].protocols[0] == "https"$ rue如果引用的内容不存在或匹配失败,则返回的结果为undefined
s := input.servers[0]s.id == "app1"$undefined decisions := input.servers[1110]s.id == "app1"$ undefined decisionOPA的变量一旦赋值之后就是不可变的:
s := input.servers[0]s := input.servers[1]$ 1 error occurred: 2:1: rego_compile_error: var s assigned above找出连接到public网络的ports的id,下面使用some关键字定义循环的变量,使用id变量提取符合要求的ports的id,最后一行input.networks[j].public也可以写为input.networks[j].public==true,同时注意条件之间是逻辑与的关系
some i, jid := input.ports[i].idinput.ports[i].network == input.networks[j].idinput.networks[j].public$ +---+------+---+ | i | id | j | +---+------+---+ | 1 | "p2" | 2 | +---+------+---+可以使用下划线_(通配符)进行遍历,使用下划线来表示实例的单独变量。如使用如下方式找出input.servers中协议为http的id
s := input.servers[_]id := s.ids.protocols[_] == "http"$ true使用规则可以重用判定逻辑,可以看作是一种函数实现。规则可以是"完整(complete)"或"部分(partial)"的。
每个规则都包含一个head和一个body,如下head为any_public_networks = true ,body为net := input.networks[_]; net.public。如果忽略= <value>部分(= <value>用于使用右值赋予左边的变量,如apps_by_hostname[hostname] = app,key为hostname,value为app),则默认为true。
package example.rulesany_public_networks = true { # is true if... net := input.networks[_] # some network exists and.. net.public # it is public.}可以为规则定义默认值,这样在结果返回"undefined decision"时,会将any_public_networks置为false
package example.rulesdefault any_public_networks = falseany_public_networks = true { # is true if... net := input.no_exist_networks[_] # some network exists and.. net.public # it is public.}any_public_networks$ false可以使用如下方式进行访问:
any_public_networks$ true也可以使用全局变量data进行访问,访问方式为data.<package-path>.<rule-name>
data.example.rules.any_public_networks$ true可以使用如下方式定义常量
package example.constantspi := 3.14head为public_network[net.id],body为net := input.networks[_]; net.public,可以看作是带返回值的函数
package example.rulespublic_network[net.id] { # net.id is in the public_network set if... net := input.networks[_] # some network exists and... net.public # it is public.}可以遍历返回值
public_network[_]$ +-------------------+ | public_network[_] | +-------------------+ | "net3" | | "net4" | +-------------------+使用逻辑或时,要求多条规则的名称相同。如下用于校验servers是否暴露了telnet或ssh协议:
package example.logical_ordefault shell_accessible = falseshell_accessible = true { input.servers[_].protocols[_] == "telnet"}shell_accessible = true { input.servers[_].protocols[_] == "ssh"}如下规则用于找出协议为telnet或ssh的server的id:
package example.logical_orshell_accessible[server.id] { server := input.servers[_] server.protocols[_] == "telnet"}shell_accessible[server.id] { server := input.servers[_] server.protocols[_] == "ssh"}rego语法
rego支持字符串、数字、布尔和null
greeting := "Hello"max_height := 42pi := 3.14159allowed := truelocation := null支持两种类型的字符串:双引号包围的字符串和原始字符串,后者一般用于正则表达式。
定义了数值集合
cube := {"width": 3, "height": 4, "depth":true}cube.depth$ true可以看作golang的map
ips_by_port := { 80: ["1.1.1.1", "1.1.1.2"], 443: ["2.2.2.1"],}ips_by_port[80]$ [ "1.1.1.1", "1.1.1.2" ]数组使用下标进行索引
s1 := [1, 2, 3]s2 := [2, 1, 3]s1[1]$ 2s1 == s2$ false它是唯一值的无序集合。它没有key,且无法使用下标进行索引。注意在解析为JSON格式时,集合体现为数组格式。
s1 := {1, 2, 3}s2 := {2, 1, 3}s1 == s2$ true变量位于规则的head和body,规则head中的变量可以认为是输入或输出,如果提供了确定的值,则认为是输入,否则认为是输出。例如:
sites := [ {"name": "prod"}, {"name": "smoke1"}, {"name": "dev"}]q[name] { name := sites[_].name }如下x并没有绑定到某个值,则返回所有x和q[x]的值
q[x]$ +----------+----------+ | x | q[x] | +----------+----------+ | "dev" | "dev" | | "prod" | "prod" | | "smoke1" | "smoke1" | +----------+----------+如下"dev"是一个确定的值,作为输入,用于判断name中是否存在该值
q["dev"]$ "dev"有两种方式访问嵌套文档:点访问方式和方括号访问方式,如下:
sites[0].servers[1].hostnamesites[0]["servers"][1]["hostname"]引用可以使用变量作为键,这种方式用于选择所有元素的值
sites[i].servers[j].hostname$ +---+---+------------------------------+ | i | j | sites[i].servers[j].hostname | +---+---+------------------------------+ | 0 | 0 | "hydrogen" | | 0 | 1 | "helium" | | 0 | 2 | "lithium" | | 1 | 0 | "beryllium" | | 1 | 1 | "boron" | | 1 | 2 | "carbon" | | 2 | 0 | "nitrogen" | | 2 | 1 | "oxygen" | +---+---+------------------------------+如果迭代时不需要用到变量,则可以使用下划线_
sites[_].servers[_].hostname$ +------------------------------+ | sites[_].servers[_].hostname | +------------------------------+ | "hydrogen" | | "helium" | | "lithium" | | "beryllium" | | "boron" | | "carbon" | | "nitrogen" | | "oxygen" | +------------------------------+s := {[1, 2], [1, 4], [2, 6]}s[[1, 2]]$ [ 1, 2 ]s[[1, x]]$ +---+-----------+ | x | s[[1, x]] | +---+-----------+ | 2 | [1,2] | | 4 | [1,4] | +---+-----------+规则通常是多表达式的,包含到documents的引用。下面定义了一个数组,每个数组包含一个服务的应用名称和主机名称
apps_and_hostnames[[name, hostname]] { some i, j, k name := apps[i].name server := apps[i].servers[_] sites[j].servers[k].name == server hostname := sites[j].servers[k].hostname}apps_and_hostnames[x]$ +----------------------+-----------------------+ | x | apps_and_hostnames[x] | +----------------------+-----------------------+ | ["mongodb","oxygen"] | ["mongodb","oxygen"] | | ["mysql","carbon"] | ["mysql","carbon"] | | ["mysql","lithium"] | ["mysql","lithium"] | | ["web","beryllium"] | ["web","beryllium"] | | ["web","boron"] | ["web","boron"] | | ["web","helium"] | ["web","helium"] | | ["web","hydrogen"] | ["web","hydrogen"] | | ["web","nitrogen"] | ["web","nitrogen"] | +----------------------+-----------------------+与规则类似,推导式有一个head和一个body。
region := "west"names := [name | sites[i].region == region; name := sites[i].name]$ +-----------------+--------+ | names | region | +-----------------+--------+ | ["smoke","dev"] | "west" | +-----------------+--------+这与python中的推导式类似
# Python equivalent of Rego comprehension shown above.names = [site.name for site in sites if site.region == "west"]格式如下:
[ <term> | <body> ]app_to_hostnames[app_name] = hostnames { app := apps[_] app_name := app.name hostnames := [hostname | name := app.servers[_] s := sites[_].servers[_] s.name == name hostname := s.hostname]}app_to_hostnames[app]$ +-----------+------------------------------------------------------+ | app | app_to_hostnames[app] | +-----------+------------------------------------------------------+ | "mongodb" | ["oxygen"] | | "mysql" | ["lithium","carbon"] | | "web" | ["hydrogen","helium","beryllium","boron","nitrogen"] | +-----------+------------------------------------------------------+格式如下:
{ <key>: <term> | <body> }注意key不能有冲突
app_to_hostnames := {app.name: hostnames | app := apps[_] hostnames := [hostname | name := app.servers[_] s := sites[_].servers[_] s.name == name hostname := s.hostname]}app_to_hostnames[app]$ +-----------+------------------------------------------------------+ | app | app_to_hostnames[app] | +-----------+------------------------------------------------------+ | "mongodb" | ["oxygen"] | | "mysql" | ["lithium","carbon"] | | "web" | ["hydrogen","helium","beryllium","boron","nitrogen"] | +-----------+------------------------------------------------------+格式如下:
{ <term> | <body> }a := [1, 2, 3, 4, 3, 4, 3, 4, 5]b := {x | x = a[_]}$ +---------------------+-------------+ | a | b | +---------------------+-------------+ | [1,2,3,4,3,4,3,4,5] | [1,2,3,4,5] | +---------------------+-------------+返回结果是一个集合
hostnames[name] { name := sites[_].servers[_].hostname }hostnames[name]$ +-------------+-----------------+ | name | hostnames[name] | +-------------+-----------------+ | "beryllium" | "beryllium" | | "boron" | "boron" | | "carbon" | "carbon" | +-------------+-----------------+返回结果是一个可检索的对象
apps_by_hostname[hostname] = app { some i server := sites[_].servers[_] hostname := server.hostname apps[i].servers[_] == server.name app := apps[i].name}apps_by_hostname["helium"]$ "web"增量定义实际就是逻辑或
如下,将servers 和containers 数据抽象为 instances:
instances[instance] { server := sites[_].servers[_] instance := {"address": server.hostname, "name": server.name}}instances[instance] { container := containers[_] instance := {"address": container.ipaddress, "name": container.name}}除了使用部分规则定义集合和对象,还可以使用完整规则,完整规则忽略了head中的key,通常用于表示常量
pi := 3.14159完整定义一次性赋予一个值,如下将32和4赋值给max_memory就会发生错误
# Power users get 32GB memory.max_memory = 32 # Restricted users get 4GB memory.max_memory = 4 $ module.rego:8: eval_conflict_error: complete rules must not produce multiple outputs使用:=时,每个包中只能声明一个相同名称的完整定义:
package examplepi := 3.14# some other rules...pi := 3.14156 # Redeclaration error because 'pi' already declared above.Rego支持自定义函数,这些函数可以与内置函数一样调用。函数可以有任意多个输入,但只能有一个输出
trim_and_split(s) = x { t := trim(s, " ") x := split(t, ".")}trim_and_split(" foo.bar ")$ [ "foo", "bar" ]一个函数可以定义多次,用于实现通过条件来选择所要执行的函数:
q(1, x) = y { y := x}q(2, x) = y { y := x*4}q(1, 2)$ 2q(2, 2)$ 8但在调用时需要注意,入参不能匹配多个函数
r(1, x) = y { y := x}r(x, 2) = y { y := x*4}r(1, 2)$ module.rego:3: eval_conflict_error: functions must not produce multiple outputs for same inputs注意,如果无法匹配到函数,则结果是未定义的:
s(x, 2) = y { y := x * 4}s(5, 3)$ undefined decisiont { greeting := "hello" not greeting == "goodbye"}t$ true下面用于分别找出在和不在prod环境的app:
prod_servers[name] { site := sites[_] site.name == "prod" name := site.servers[_].name}apps_in_prod[name] { app := apps[_] server := app.servers[_] prod_servers[server] #过滤出在prod的app,行与行之间是与的关系,如果不存在则不会执行下一个语句,即name不会被赋值 name := app.name}apps_not_in_prod[name] { name := apps[_].name not apps_in_prod[name]}Rego没有直接的方式来表示全量("FOR ALL")。例如需要找出名称非"bitcoin-miner"的app时,使用如下方式是错误的,无论apps中是否存在名为"bitcoin-miner"的app,最终都会返回true
no_bitcoin_miners { app := apps[_] app.name != "bitcoin-miner" # THIS IS NOT CORRECT.}可以使用如下方式来实现上述目的:
no_bitcoin_miners_using_negation { not any_bitcoin_miners}any_bitcoin_miners { some i app := apps[i] app.name == "bitcoin-miner"}此外还可以使用推导式实现:
no_bitcoin_miners_using_comprehension { bitcoin_miners := {app | app := apps[_]; app.name == "bitcoin-miner"} count(bitcoin_miners) == 0}在rego中,策略被定义在模块中,一个模块需要包含:
使用#进行注释
包可以将一个或多个模块中的规则打包到特定的命名空间中。
模块中可以使用data和input引用文本
如在 kubernetes.admission中定义了一个规则 deny:
package kubernetes.admissiondeny[msg] { input.request.kind.kind == "Pod" some i image := input.request.object.spec.containers[i].image not startswith(image, "hooli.com/") msg := sprintf("image '%v' comes from untrusted registry", [image])} 在另一个包中可以通过如下方式引用deny规则:
{ "user": "alice", "action": "read", "object": "id123", "type": "dog"}package app.rbacimport data.kubernetes.admissiondeny[input.user] with关键字允许查询以编程方式指定嵌套在input 文档 和data 文档下的值。with关键字充当表达式的修饰符。一个表达式可以有零或多个with修饰符。
格式如下,data.foo.bar,如果策略试图替换data.foo.bar.baz,那么编译器将产生错误)。
<expr> with <target-1> as <value-1> [with <target-2> as <value-2> [...]]举例如下:
allow with input as {"user": "charlie", "method": "GET"} with data.roles as {"dev": ["charlie"]}with关键字仅影响连接表达符,后续表达式将看到未修改的值。下面是一种例外(input.foo=1,input.bar=2),outer中的输入在middle中进行了计算
inner := [x, y] { x := input.foo y := input.bar}middle := [a, b] { a := inner with input.foo as 100 b := input}outer := result { result := middle with input as {"foo": 200, "bar": 300} #middle中修改了a}{ "inner": [ 1, 2 ], "middle": [ [ 100, 2 ], { "bar": 2, "foo": 1 } ], "outer": [ [ 100, 300 ], { "bar": 300, "foo": 200 } ]}default关键字允许策略为具有完整定义的规则生成的文档定义默认值。格式如下:
default <name> = <term>用法如下,如果没有default,则会返回undefined
default allow = falseallow { input.user == "bodddb" input.method == "GEdddT"}allow { input.user == "aliddce"}与编程语言中的else类似
authorize = "allow" { input.user == "superuser" # allow 'superuser' to perform any operation.} else = "deny" { input.path[0] == "admin" # disallow 'admin' operations... input.source_network == "external" # from external networks.} # ... more rules需要import future.keywords。成员操作符in用于检查一个元素是否存在于array, set, 或 object中,返回true或false。
import future.keywords.inp = [x, y, z] { x := 3 in [1, 2, 3] # array y := 3 in {1, 2, 3} # set z := 3 in {"foo": 1, "bar": 3} # object}{ "p": [ true, true, true ]}当在in操作符左侧提供两个参数,且右侧为object或array,则第一个参数作为key(object)或index(array):
import future.keywords.inp := [ x, y ] { x := "foo", "bar" in {"foo": "bar"} # key, val with object y := 2, "baz" in ["foo", "bar", "baz"] # key, val with array}{ "p": [ true, true ]}注意在列表(如集合或数组以及函数参数)上下文中需要使用圆括号来让两侧参数一一对应
import future.keywords.inp := x { x := { 0, 2 in [2] } #这是一个集合,表示0和2 in [2]}q := x { x := { (0, 2 in [2]) }#这是一个集合,但计算的是0, 2 in [2]}w := x { x := g((0, 2 in [2]))#g(x)只有一个参数,需要使用圆括号括起来}z := x { x := f(0, 2 in [2])#f(x)有两个参数,第一个参数是0,第二个参数是2 in [2]}f(x, y) = sprintf("two function arguments: %v, %v", [x, y])g(x) = sprintf("one function argument: %v", [x])与not结合使用,可以很方便地断言一个元素是否是数组的成员:
import future.keywords.indeny { not "admin" in input.user.roles}test_deny { deny with input.user.roles as ["operator", "user"]}{ "test_deny": true}使用some,可以根据不同的类型引入新的变量
import future.keywords.inp[x] { some x in ["a", "r", "r", "a", "y"]}q[x] { some x in {"s", "e", "t"}}r[x] { some x in {"foo": "bar", "baz": "quz"}}{ "p": [ "a", "r", "y" ], "q": [ "e", "s", "t" ], "r": [ "bar", "quz" ]}使用两个参数可以检索object的关键字和array的索引
import future.keywords.inp[x] { some x, "r" in ["a", "r", "r", "a", "y"] # key variable, value constant}q[x] = y { some x, y in ["a", "r", "r", "a", "y"] # both variables}r[y] = x { some x, y in {"foo": "bar", "baz": "quz"}}{ "p": [ 1, 2 ], "q": { "0": "a", "1": "r", "2": "r", "3": "a", "4": "y" }, "r": { "bar": "foo", "quz": "baz" }}some变量的任何参数都可以是复合的非基础值:
import future.keywords.inp[x] = y { some x, {"foo": y} in [{"foo": 100}, {"bar": 200}]#x为key为foo的数组索引,y为key为foo的值}p[x] = y { some {"bar": x}, {"foo": y} in {{"bar": "b"}: {"foo": "f"}} # x为bar的值,y为foo的值}{ "p": { "0": 100, "b": "f" }}Rego支持三种等式:赋值(:=),比较()和联合(=)。建议使用赋值(:=)和比较()。
可以使用一种简单的解构形式将数组中的值解包并将其分配给变量
address := ["3 Abbey Road", "NW8 9AY", "London", "England"]in_london { [_, _, city, country] := address city == "London" country == "England"}{ "address": [ "3 Abbey Road", "NW8 9AY", "London", "England" ], "in_london": true}Rego会将比较为真的值赋于变量。联合可以赋予变量使表达式为true的值。
sites[i].servers[j].name = apps[k].servers[m]+---+---+---+---+| i | j | k | m |+---+---+---+---+| 0 | 0 | 0 | 0 || 0 | 1 | 0 | 1 || 0 | 2 | 1 | 0 || 1 | 0 | 0 | 2 || 1 | 1 | 0 | 3 || 1 | 2 | 1 | 1 || 2 | 0 | 0 | 4 || 2 | 1 | 2 | 0 |+---+---+---+---+a == b # `a` is equal to `b`.a != b # `a` is not equal to `b`.a < b # `a` is less than `b`.a <= b # `a` is less than or equal to `b`.a > b # `a` is greater than `b`.a >= b # `a` is greater than or equal to `b`.内置函数的格式如下:
<name>(<arg-1>, <arg-2>, ..., <arg-n>)默认情况下,遇到运行时错误的内置函数调用会将结果设为undefined (通常可以被视为false),且不会停止策略计算。这种方式可以保证在使用调用内置函数时,输入无效参数不会导致整个策略停止计算。
# assign variable x to value of field foo.bar.baz in inputx := input.foo.bar.baz# check if variable x has same value as variable yx == y# check if variable x is a set containing "foo" and "bar"x == {"foo", "bar"}# OR{"foo", "bar"} == x# lookup value at index 0val := arr[0] # check if value at index 0 is "foo""foo" == arr[0]# find all indices i that have value "foo""foo" == arr[i]# lookup last valueval := arr[count(arr)-1]# with `import future.keywords.in`some 0, val in arr # lookup value at index 00, "foo" in arr # check if value at index 0 is "foo"some i, "foo" in arr # find all indices i that have value "foo"# lookup value for key "foo"val := obj["foo"]# check if value for key "foo" is "bar""bar" == obj["foo"]# OR"bar" == obj.foo# check if key "foo" exists and is not falseobj.foo# check if key assigned to variable k existsk := "foo"obj[k]# check if path foo.bar.baz exists and is not falseobj.foo.bar.baz# check if path foo.bar.baz, foo.bar, or foo does not exist or is falsenot obj.foo.bar.baz# with `import future.keywords.in`o := {"foo": false}# check if value exists: the expression will be truefalse in o# check if value for key "foo" is false"foo", false in o# check if "foo" belongs to the seta_set["foo"]# check if "foo" DOES NOT belong to the setnot a_set["foo"]# check if the array ["a", "b", "c"] belongs to the seta_set[["a", "b", "c"]]# find all arrays of the form [x, "b", z] in the seta_set[[x, "b", z]]# with `import future.keywords.in`"foo" in a_setnot "foo" in a_setsome ["a", "b", "c"] in a_setsome [x, "b", z] in a_set# iterate over indices iarr[i]# iterate over valuesval := arr[_]# iterate over index/value pairsval := arr[i]# with `import future.keywords.in`some val in arr # iterate over valuessome i, _ in arr # iterate over indicessome i, val in arr # iterate over index/value pairs# iterate over keysobj[key]# iterate over valuesval := obj[_]# iterate over key/value pairsval := obj[key]# with `import future.keywords.in`some val in obj # iterate over valuessome key, _ in obj # iterate over keyssome key, val in obj # key/value pairs# iterate over valuesset[val]# with `import future.keywords.in`some val in set# nested: find key k whose bar.baz array index i is 7foo[k].bar.baz[i] == 7# simultaneous: find keys in objects foo and bar with same valuefoo[k1] == bar[k2]# simultaneous self: find 2 keys in object foo with same valuefoo[k1] == foo[k2]; k1 != k2# multiple conditions: k has same value in both conditionsfoo[k].bar.baz[i] == 7; foo[k].qux > 3# assert no values in set match predicatecount({x | set[x]; f(x)}) == 0# assert all values in set make function f truecount({x | set[x]; f(x)}) == count(set)# assert no values in set make function f true (using negation and helper rule)not any_match# assert all values in set make function f true (using negation and helper rule)not any_not_matchany_match { set[x] f(x)}any_not_match { set[x] not f(x)}In the examples below ... represents one or more conditions.
a = {1, 2, 3}b = {4, 5, 6}c = a | b# p is true if ...p = true { ... }# ORp { ... }default a = 1a = 5 { ... }a = 100 { ... }# a_set will contain values of x and values of ya_set[x] { ... }a_set[y] { ... }# a_map will contain key->value pairs x->y and w->za_map[x] = y { ... }a_map[w] = z { ... }default a = 1a = 5 { ... }else = 10 { ... }f(x, y) { ...}# ORf(x, y) = true { ...}f(x) = "A" { x >= 90 }f(x) = "B" { x >= 80; x < 90 }f(x) = "C" { x >= 70; x < 80 }本文来自博客园,作者:charlieroro,转载请注明原文链接:https://www.cnblogs.com/charlieroro/p/15876732.html