6. OPA run(互动式)
OPA包括一个交互式外壳程序或REPL(读取-评估-打印循环)。您可以使用REPL来试验策略并为新策略创建原型。
要启动REPL,只需:
./opa run
当您在REPL中输入语句时,OPA会对它们进行评估并打印结果。
> true true > 3.14 3.14 > ["hello", "world"] [ "hello", "world" ]
大多数REPL允许您定义以后可以引用的变量。OPA允许您执行类似的操作。例如,您可以定义一个pi常量,如下所示:
> pi := 3.14
定义“ pi”后,您将查询该值并根据该值编写表达式:
> pi 3.14 > pi > 3 true
通过按Control-D
或键入exit
以下命令退出REPL :
> exit
您可以通过在命令行上传递策略和数据文件来将它们加载到REPL中。默认情况下,JSON和YAML文件植于下data。
opa run input.json
运行一些查询以查找数据:
> data.server[0].protocols[1] undefined > data.servers[0].protocols[1] "ssh" > data.servers[i].protocols[j] +---+---+------------------------------+ | i | j | data.servers[i].protocols[j] | +---+---+------------------------------+ | 0 | 0 | "https" | | 0 | 1 | "ssh" | | 1 | 0 | "mysql" | | 2 | 0 | "memcache" | | 3 | 0 | "http" | | 4 | 0 | "telnet" | +---+---+------------------------------+ > net := data.networks[_]; net.public +-----------------------------+ | net | +-----------------------------+ | {"id":"net3","public":true} | | {"id":"net4","public":true} | +-----------------------------+
要将数据文件设置为inputREPL中的文档,请在文件路径前添加前缀:
opa run example.rego repl.input:input.json
> data.example.public_server[s] +-------------------------------------------------------------------+-------------------------------------------------------------------+ | s | data.example.public_server[s] | +-------------------------------------------------------------------+-------------------------------------------------------------------+ | {"id":"app","ports":["p1","p2","p3"],"protocols":["https","ssh"]} | {"id":"app","ports":["p1","p2","p3"],"protocols":["https","ssh"]} | | {"id":"ci","ports":["p1","p2"],"protocols":["http"]} | {"id":"ci","ports":["p1","p2"],"protocols":["http"]} | +-------------------------------------------------------------------+-------------------------------------------------------------------+
7. OPA run(服务器)
要与OPA集成,您可以将其作为服务器运行并通过HTTP执行查询。您可以使用-s或将OPA作为服务器启动--server
:
./opa run --server ./example.rego
默认情况下,OPA在上侦听HTTP连接0.0.0.0:8181。请参阅参考资料opa run --help,以获取用于更改侦听地址,启用TLS等的选项的列表。
在另一个终端内部使用curl(或类似的工具)来访问OPA的HTTP API。查询/v1/dataHTTP API时,必须将输入数据包装在JSON对象内:
{ "input": <value> }
创建输入文件的副本,以通过发送curl:
cat <<EOF > v1-data-input.json { "input": $(cat input.json) } EOF
执行一些curl请求并检查输出:
curl localhost:8181/v1/data/example/violation -d @v1-data-input.json -H 'Content-Type: application/json' curl localhost:8181/v1/data/example/allow -d @v1-data-input.json -H 'Content-Type: application/json'
默认情况下data.system.main
,用于不带路径的策略查询。当您在不提供路径的情况下执行查询时,不必包装输入。如果该data.system.main
决定未定义,则将其视为错误:
curl localhost:8181 -i -d @input.json -H 'Content-Type: application/json'
您可以重新启动OPA并将其配置为使用任何决策作为默认决策:
./opa run --server --set=default_decision=example/allow ./example.rego
curl从上面重新运行最后一个命令:
curl localhost:8181 -i -d @input.json -H 'Content-Type: application/json'
8. Rego 语法
OPA策略以称为Rego的高级声明性语言表示。Rego(发音为“ ray-go”)是专门为在复杂的分层数据结构上表达策略而构建的。有关Rego的详细信息,请参阅策略语言文档。
below以下示例是交互式的!如果在包含服务器,网络和端口的上方编辑输入数据,则输出将在下面更改。同样,如果您在下面的示例中编辑查询或规则,则输出将更改。在通读本节时,请尝试更改输入,查询和规则,并观察输出的差异。
也可以使用以下命令在您的计算机上本地运行它们opa eval,这是设置说明。
8.1 参考
当OPA评估策略时,它将查询中提供的数据绑定到名为的全局变量input。您可以使用.(点)运算符在输入中引用数据。
input.servers
[ { "id": "app", "ports": [ "p1", "p2", "p3" ], "protocols": [ "https", "ssh" ] }, { "id": "db", "ports": [ "p3" ], "protocols": [ "mysql" ] }, { "id": "cache", "ports": [ "p3" ], "protocols": [ "memcache" ] }, { "id": "ci", "ports": [ "p1", "p2" ], "protocols": [ "http" ] }, { "id": "busybox", "ports": [ "p1" ], "protocols": [ "telnet" ] } ]
要引用数组元素,可以使用熟悉的方括号语法:
input.servers[0].protocols[0]
"https"
如果引用的值不存在,则OPA返回undefined。未定义表示OPA无法找到任何结果。
input.deadbeef
undefined decision
8.2 表达式(逻辑与)
要在Rego中制定政策决策,您要针对输入和其他数据编写表达式。
input.servers[0].id == "app"
true
OPA包括一组内置函数,可用于执行常见操作,例如字符串操作,正则表达式匹配,算术,聚合等。
count(input.servers[0].protocols) >= 1
true
有关现成的OPA支持的内置功能的完整列表,请参阅“策略参考”页面。
多个表达式通过;(AND)运算符连接在一起。为了使查询产生结果,查询中的所有表达式必须为真或已定义。表达式的顺序无关紧要。
input.servers[0].id == "app"; input.servers[0].protocols[0] == "https"
true
您可以;通过将表达式分成多行来省略(AND)运算符。以下查询与上一个查询具有相同的含义:
input.servers[0].id == "app" input.servers[0].protocols[0] == "https"
true
如果查询中的任何表达式都不为真(或未定义),则结果为未定义。在下面的示例中,第二个表达式为false:
input.servers[0].id == "app" input.servers[0].protocols[0] == "telnet"
undefined decision
8.3 逻辑或
在查询中将多个表达式连接在一起时,您表示的是逻辑与。要在Rego中表达逻辑OR,您可以定义多个具有相同名称的规则。让我们来看一个例子。
想象一下,您想知道是否有任何服务器公开允许客户端外壳访问的协议。为了确定这一点,你可以定义声明了一个完整的规则 shell_accessible是true,如果任何服务器暴露"telnet"或"ssh" 协议:
package example.logical_or default shell_accessible = false shell_accessible = true { input.servers[_].protocols[_] == "telnet" } shell_accessible = true { input.servers[_].protocols[_] == "ssh" }
{ "servers": [ { "id": "busybox", "protocols": ["http", "telnet"] }, { "id": "web", "protocols": ["https"] } ] }
shell_accessible true
defaultkeyword如果未定义具有相同名称的所有其他规则,则该关键字告诉OPA为该变量分配一个值。
当您将逻辑或与部分规则一起使用时,每个规则定义都会影响分配给变量的一组值。例如,可以将上面的示例修改为生成一组公开"telnet"或 的服务器"ssh"。
package example.logical_or shell_accessible[server.id] { server := input.servers[_] server.protocols[_] == "telnet" } shell_accessible[server.id] { server := input.servers[_] server.protocols[_] == "ssh" }
{ "servers": [ { "id": "busybox", "protocols": ["http", "telnet"] }, { "id": "db", "protocols": ["mysql", "ssh"] }, { "id": "web", "protocols": ["https"] } ] }
shell_accessible [ "busybox", "db" ]
8.4 Variables变量
您可以使用:=
(赋值)运算符将值存储在中间变量中。可以像一样引用变量input
s := input.servers[0] s.id == "app" p := s.protocols[0] p == "https"
+---------+-------------------------------------------------------------------+ | p | s | +---------+-------------------------------------------------------------------+ | "https" | {"id":"app","ports":["p1","p2","p3"],"protocols":["https","ssh"]} | +---------+-------------------------------------------------------------------+
当OPA评估表达式时,它将查找使所有表达式都为真的变量的值。如果没有使所有表达式都为真的变量赋值,则结果是不确定的。
s := input.servers[0] s.id == "app" s.protocols[1] == "telnet"
undefined decision
变量是不可变的。如果您尝试两次分配相同的变量,OPA将报告错误。
s := input.servers[0] s := input.servers[1]
1 error occurred: 2:1: rego_compile_error: var s assigned above
OPA必须能够枚举所有表达式中所有变量的值。如果OPA无法枚举任何表达式中变量的值,则OPA将报告错误。
x := 1 x != y # y has not been assigned a value
2 errors occurred: 2:1: rego_unsafe_var_error: var _ is unsafe 2:1: rego_unsafe_var_error: var y is unsafe
8.5 迭代
像其他声明性语言(例如SQL)一样,Rego没有显式的循环或迭代构造。而是将变量注入表达式中时,隐式发生迭代。
要了解Rego中迭代的工作原理,请想象您需要检查是否有任何公共网络。回想一下,网络是在数组中提供的:
input.networks
[ { "id": "net1", "public": false }, { "id": "net2", "public": false }, { "id": "net3", "public": true }, { "id": "net4", "public": true } ]
一种选择是测试输入中的每个网络:
input.networks[0].public == true false input.networks[1].public == true false input.networks[2].public == true true
这种方法是有问题的,因为可能有太多网络无法静态列出,或更重要的是,可能无法事先知道网络的数量。
在Rego中,解决方案是将数组索引替换为变量。
some i; input.networks[i].public == true
+---+ | i | +---+ | 2 | | 3 | +---+
现在,查询将要求该值i使整个表达式为真。当您在引用中替换变量时,OPA会自动查找满足查询中所有表达式的变量分配。就像中间变量一样,OPA返回变量的值。
您可以根据需要替换任意多个变量。例如,要确定是否有服务器公开了不安全的"http"协议,您可以编写:
some i, j; input.servers[i].protocols[j] == "http" +---+---+ | i | j | +---+---+ | 3 | 0 | +---+---+
如果变量出现多次,则分配满足所有表达式。例如,要查找连接到公共网络的端口的ID,可以编写:
some i, j id := input.ports[i].id input.ports[i].network == input.networks[j].id input.networks[j].public
+---+------+---+ | i | id | j | +---+------+---+ | 1 | "p2" | 2 | +---+------+---+
为变量提供好名字可能很难。如果仅引用一次变量,则可以将其替换为特殊的_(通配符变量)运算符。从概念上讲,的每个实例_都是一个唯一变量。
input.servers[_].protocols[_] == "http" true
就像引用不存在的字段或无法匹配的表达式的引用一样,如果OPA无法找到满足所有表达式的任何变量赋值,则结果是不确定的。
some i; input.servers[i].protocols[i] == "ssh" # there is no assignment of i that satisfies the expression undefined decision
8.6 规则
Rego使您可以使用规则封装和重用逻辑。规则只是if-then逻辑语句。规则可以是“完整”或“部分”。
8.6.1 完整规则
完整的规则是if-then语句,这些语句将单个值分配给变量。例如:
package example.rules any_public_networks = true { # is true if... net := input.networks[_] # some network exists and.. net.public # it is public. }
每条规则都由一个头和一个身体组成。在Rego中,如果规则主体对于某些变量分配集为true,则说规则标题为true 。在上面的示例any_public_networks = true中,头是net := input.networks[_]; net.public是身体。
您可以查询规则生成的值,就像其他任何值一样:
any_public_networks true
规则生成的所有值都可以通过全局data变量查询。
data.example.rules.any_public_networks true
您可以通过使用绝对路径引用OPA加载的任何规则来查询其值。规则的路径始终为:
data.<package-path>.<rule-name>
。
如果您省略= <value>
规则标题的一部分,则该值默认为true
。您可以按以下方式重写上面的示例,而无需更改其含义:
package example.rules any_public_networks { net := input.networks[_] net.public }
要定义常量,请省略规则主体。省略规则正文时,默认为true
。由于规则主体为true,因此规则标头始终为true / defined
。
package example.constants pi = 3.14
如果OPA无法找到满足规则主体的变量分配,则可以说该规则是未定义的。例如,如果input提供给OPA的不包括公共网络,any_public_networks
则将是未定义的(与false相同)。下面,为OPA提供了一组不同的输入网络(都不是公共的):
{ "networks": [ {"id": "n1", "public": false}, {"id": "n2", "public": false} ] }
any_public_networks undefined decision