In this article
Primeros pasos con Practical Rego
Aunque gran parte de la información de este artículo coincide con la referencia de Rego oficial, esta guía introductoria destaca los paradigmas de programación (lógico frente a procedimental), lo que resulta útil para quienes programan con lenguajes imperativos como Python o Java.
Los enlaces normales (p. ej., Wikipedia) llevan a información adicional, mientras que los enlaces entre corchetes (p. ej., [1]) son fuentes de las afirmaciones de esta publicación.
Todos los fragmentos de código de esta publicación se ejecutaron con OPA v0.54.0.
Gracias a Jasper Van der Jeugt por leer un borrador de esta publicación del blog y sugerir mejoras.
Introducción a Rego
El proyecto Open Policy Agent (OPA) recibió mucha atención desde que fue aceptado como proyecto graduado de CNCF [0]. OPA es un motor de políticas de propósito general que se usa para aplicar marcos de autorización. Sus políticas se definen en un lenguaje de consulta llamado Rego, el tema central de esta publicación.
Rego se basa en Datalog, un lenguaje declarativo de consulta lógica. Ofrece estructuras para la coincidencia de patrones, el filtrado y la iteración. Rego es comparable con SQL, ya que ambos son lenguajes de consulta de propósito general. Sin embargo, SQL está diseñado para trabajar con datos tabulares, mientras que Rego opera con datos en formato JSON. Al ser un lenguaje declarativo de consulta lógica, Rego tiene algunas características poco comunes en otros lenguajes de programación. Es fundamental comprender estas características para entender Rego.
Programación declarativa
La programación declarativa expresa la lógica y las reglas de un problema sin describir explícitamente los pasos para resolverlo. Se enfoca en el qué en lugar del cómo. En contraste, un paradigma de programación más popular se llama «imperativo», en el que las instrucciones cambian el estado de un programa para producir un resultado. Algunos ejemplos comunes de lenguajes declarativos son SQL ([1], p. 79) y HTML [2]. Los lenguajes de infraestructura como código, como HCL, también suelen adoptar el enfoque declarativo, aunque a menudo lo combinan con elementos imperativos [3]. Muchos lenguajes de programación permiten combinar ambos paradigmas, pero suelen inclinarse más por la programación imperativa, como Python [4].
Ejemplo de programación imperativa (Python):
def is_even(x):
remainder = x % 2
if remainder == 0:
return True
return FalseEjemplo de programación lógica declarativa (Rego):
is_even {
remainder == 0
remainder = input % 2
}
En Rego, el orden de las instrucciones dentro de una regla no importa.
Programación lógica
Datalog aplica el paradigma de programación lógica, una forma específica de programación declarativa basada en la lógica formal [5], [6]. Los programas lógicos constan de los llamados hechos y reglas, que establecen relaciones y restricciones para describir el dominio del problema. En Rego, estos hechos y reglas se expresan mediante la cláusula de Horn, una fórmula lógica que también se usa en Datalog [6].
Luego, un motor de inferencia deduce la solución al problema al determinar el orden de ejecución. En otras palabras, el motor traduce el código en varias fórmulas lógicas que luego resuelve. Esta arquitectura reduce considerablemente los efectos secundarios, demuestra solidez y la hace más adecuada para la verificación formal.
En el caso de Rego, OPA actúa como motor de inferencia, entre otras funciones. Para mejorar las garantías de terminación de Rego, entre otras medidas, se han realizado esfuerzos para prevenir algunas formas de recursión [7], [8]. Además, gracias a las decisiones de diseño mencionadas anteriormente, Rego cuenta con la decidibilidad como otra propiedad computacional crucial [9]. La decidibilidad garantiza que el proceso de toma de decisiones durante la evaluación de políticas siempre produzca un resultado. Por lo tanto, las políticas de Rego no se quedarán bloqueadas y optimizarán la complejidad temporal. Esta solidez de Rego probablemente sea una de las razones principales para usarlo junto con OPA. Puede formar parte de cualquier ruta crítica de una aplicación empresarial y cumplir sus funciones de manera confiable.
Como se mencionó anteriormente, Rego consta de hechos y reglas. Cada instrucción del código representa una regla lógica. Si una regla lógica no se puede resolver, no es necesario seguir evaluando el código. Por consiguiente, cada línea de código debe ser «verdadera». Rego hereda este comportamiento porque tiene sus raíces en Datalog; no es algo exclusivo de Rego. Aun así, es relevante para quienes escriben políticas de Rego. Para entender por qué es importante tener presente este comportamiento, lee la sección «Errores comunes».
Cuantificación existencial
Los cuantificadores lógicos en programación definen el alcance de una instrucción sobre una colección de elementos.
Rego aplica la cuantificación existencial (piensa en FOR ANY), que consiste en comprobar si una condición es verdadera para al menos un elemento de una colección.
Los lenguajes de programación imperativos no aplican de forma inherente la cuantificación como se hace en la programación lógica. Sin embargo, ofrecen estructuras que permiten comprobar si una condición es verdadera para todos los elementos de una colección, lo que funciona como la cuantificación universal (piensa en FOR ALL).
En ambos casos, un ciclo itera sobre una colección y cambia su comportamiento cuando busca elementos que cumplan las condiciones (cuantificación existencial) o intenta verificar las condiciones para todos los elementos (cuantificación universal).
La idea de la cuantificación existencial se implementa al identificar «todas las asignaciones de variables» que satisfacen una condición de una consulta [10].
Aunque esto pueda parecer abstracto, lo analizaremos en un contexto más práctico en las siguientes secciones.
La cuantificación existencial es un concepto poco familiar para muchos programadores, lo que puede ocasionar errores lógicos, como se explica en la sección «Errores comunes».
Punto de entrada
Rego no tiene una función «main» que inicie su ejecución. En su lugar, OPA se configura con un punto de entrada. Un punto de entrada es una cadena que apunta a reglas de Rego, por ejemplo, data.mypolicies.deny. Este punto de entrada le pide a OPA que consulte todas las reglas de Rego deny en el espacio de nombres mypolicies, combine todos los resultados de las reglas y los devuelva. Se pueden proporcionar varios puntos de entrada. Si no se cumplen las condiciones de una regla, esta se considera «no resuelta» y no se incluye en el resultado final.
No basta con proporcionar un punto de entrada para ejecutar OPA. También se requiere un documento input que funcione como una variable global dentro de Rego y contenga todos los datos de fuentes externas.
Optimización
Puede parecer prematuro hablar de las optimizaciones de Rego en este momento, pero vale la pena mencionar una en particular, ya que puede provocar un comportamiento inesperado.
La optimización se llama «Salida anticipada en la evaluación de reglas» y puede ser la causa de un error lógico. La documentación oficial describe bien esta medida y con detalle. Sin embargo, es un tema avanzado que quizá no se tenga en cuenta al comenzar a usar Rego. La sección «Errores comunes» aborda este riesgo y sugiere soluciones alternativas.
La parte delicada de la optimización se describe así:
Cuando es posible aplicar la «salida anticipada» a una regla o conjunto de reglas, las iteraciones dentro de esa regla se cancelan en cuanto una asignación satisface el cuerpo de la regla:
package earlyexit.iteration
p {
some p
data.projects[p] == "project-a"
}
Como ninguna posibilidad podría cambiar el resultado de data.earlyexit.iteration.p una vez que una asignación de variable satisface las condiciones, no se realizarán más iteraciones. – Documentación de OPA
En otras palabras, OPA dejará de recorrer data.projects en cuanto encuentre un elemento igual a la cadena project-a, una consecuencia directa de la cuantificación existencial. Esto es razonable y correcto; sin embargo, es importante tener presente este comportamiento.
Igualdad
Rego tiene tres tipos de operadores de igualdad, cada uno con un significado distinto:
operador de igualdad | significado |
|---|---|
| el operador de comparación, que se usa para comparar valores de variables |
| el operador de asignación, que se usa para asignar valores a variables |
| el operador de unificación, que combina la asignación y la comparación |
El operador = primero realiza la asignación y luego la comparación. Si la asignación falla, OPA compara los valores de todas formas:
# input
[1, 2, 5]
# code
default allow := false
allow {
input[_] = 5 # assignment fails, comparison evaluates to `true`
# (`input[_] := 5` creates error "cannot assign to ref")
}
# output
{
"allow": true
}
A menos que haya una razón para usar el operador `=`, se recomienda evitarlo [13]. En su lugar, usa el operador `:=` para las asignaciones y el operador `==` para las comparaciones.
Políticas, reglas y funciones
Antes de pasar a esta sección, es útil conocer las estructuras de datos de Rego: valores escalares, cadenas, valores compuestos y comprensiones.
Políticas
Una política es un concepto de nivel superior en Rego que se refiere a un conjunto de reglas que definen el comportamiento y las restricciones de un sistema. Por ejemplo, el siguiente documento es una política que contiene las reglas de Rego allow y deny:
package mypolicy
allow {
input.name == "bar"
}
deny {
input.name == "foo"
}
Reglas
Las reglas producen los llamados «documentos virtuales», que son estructuras de datos calculadas en tiempo de ejecución. Lee más sobre las reglas aquí. Por ejemplo:
# input
[
"world-a",
"universe-a",
"world-b",
"universe-b"
]
# code
worlds[world] {
item := input[_] # loop over `input`
startswith(item, "world")
world := item # var `world` is return value
}
universes[universe] {
item := input[_]
startswith(item, "universe")
universe := item
}
# output
{
"universes": [
"universe-a",
"universe-b"
],
"worlds": [
"world-a",
"world-b"
]
}
Si no se define ningún valor de retorno, las reglas de Rego devuelven true, según indica la documentación: > Si se omite el valor, el valor predeterminado es true. – documentación
Por lo tanto, las siguientes dos reglas se comportan igual:
deny := true if { # rule header
true # rule body
}
deny { # optimized rule header
true
}Como se mencionó anteriormente, las reglas solo terminan de ejecutarse si se cumplen todas las condiciones del cuerpo. Por consiguiente, la siguiente regla no devuelve nada:
allow := true {
false
}Las reglas que devuelven datos de tipo set u object se llaman reglas incrementales y usan una sintaxis un poco diferente:
# input
[
"a",
"b",
"c",
"c"
]
# code
return_set[item] {
item := input[_]
}
return_object[key] := value {
value := input[key]
}
# output
{
"return_object": {
"0": "a",
"1": "b",
"2": "c",
"3": "c"
},
"return_set": [
"a",
"b",
"c"
]
}
Las reglas de Rego no admiten parámetros. Por ejemplo, return_set(param1 es código de regla no válido), pero se pueden usar [funciones][#functions] en su lugar.
Cuantificación existencial
El comportamiento de OPA para la ejecución de reglas está definido: al evaluar los cuerpos de las reglas, OPA busca asignaciones de variables que hagan que todas las expresiones sean verdaderas. – documentación
¿Cómo funciona la cuantificación universal en los lenguajes imperativos? Veamos un ejemplo en Python:
for n in numbers: # think "FOR ALL" items `n` in numbers
if n % 2 == 0:
print(n)Este fragmento de código encuentra los números pares en el arreglo numbers. También es posible realizar la misma tarea en Rego. Por ejemplo:
even_numbers[n] {
n := input[_]
n % 2 == 0 # think "FOR ANY" item `n` in inputs
}En el ejemplo anterior, se usa la palabra clave some para declarar la variable n. OPA intenta encontrar cualquier número n del arreglo input que cumpla la condición de ser par. Si se cumple, se devuelve la asignación de variable de n.
Al identificar todas las asignaciones de variables coincidentes, OPA determina si se satisface la cuantificación existencial.
Aunque no es obligatorio, declarar las variables explícitamente con some puede mejorar la claridad y la comprensión.
Otro ejemplo para encontrar todos los valores de un objeto que comienzan con la cadena 1:
# input
{
"a": "1a",
"b": "2b",
"c": "1c"
}
# code
prefixed[item] {
item := input[_]
startswith(item, "1")
}
# output
{
"prefixed": [
"1a",
"1c"
]
}La línea item := input[_] itera sobre el documento de entrada. El segundo elemento de entrada, b, no cumple la condición de la regla startswith(l, "1"). OPA no interrumpe la ejecución del ciclo, sino que ignora el resultado de la iteración y pasa al siguiente elemento, c, para continuar la «búsqueda de todas las asignaciones de variables».
Este comportamiento es útil, pero puede provocar un error lógico.
Funciones
Las funciones de Rego se parecen a las reglas, pero se comportan de manera diferente:
greet(name) := msg {
msg := sprintf("Hello %s!", [name])
}
greeting := greet("Bob") # returns "Hello Bob!"No se admiten las funciones sin parámetros, porque una definición así se interpreta como una regla:
greet() := msg {
msg := "Hello"
}
greet() # creates error "rego_parse_error:
# rule argument list must take at least one argument"Como las reglas representan documentos virtuales y las funciones no, se accede a ellas de manera diferente:
# input
[
[1, 1],
[2, 2],
[3, 3]
]
# code
add_function(inp) := result { # define function
result := [ sum | # create array comprehension
arr := inp[_]
sum := arr[0] + arr[1]
]
}
output := add_function(input) # store result of function
dummy_rule_iterate_function_output[item] {
item := output[index] # work with function result
}
add_rule = result { # define rule
result := [ sum | # create array comprehension
arr := input[_]
sum := arr[0] + arr[1]
]
}
dummy_rule_iterate_rule[item] {
item := add_rule[index] # work with rule result
}
# output
"add_rule": [
2,
4,
6
],
"output": [
2,
4,
6
]
Tanto las reglas como las funciones se pueden usar para compartir funcionalidades entre políticas.
Flujo de control
Conceptos básicos
Para construir una lógica que compruebe varias condiciones, se enumeran todas las condiciones línea por línea:
# input
{
"name": "foo"
}
# code
my_rule {
startswith(input.name, "f") # first condition (AND..)
endswith(input.name, "o") # second condition
}
# output
{
"my_rule": true
}
Para crear un flujo de control con distintos escenarios en los que una regla debe evaluarse como true, basta con definir la regla varias veces (el encabezado debe ser el mismo en todas las definiciones). Si alguna de las reglas se evalúa como true, el valor de retorno será true. Si las reglas devuelven colecciones, se devuelve la unión de todos los elementos para los que se cumplen las condiciones de la regla.
En el siguiente ejemplo, las reglas my_rule devuelven un valor booleano:
# input
{
"name": "foo"
}
# code
my_rule {
count(input.name) == 3
}
my_rule {
input.name == "foobar" # not true; aborts execution
}
# output
{
"my_rule": true
}La estructura if/else se puede implementar de forma similar a los lenguajes imperativos con la palabra clave else:
# input
{
"name": "foo"
}
# code
my_rule := msg {
input.name == "foo"
msg := "Hello foo!"
} else := msg {
msg := "Hello anonymous"
}
# output
{
"my_rule": "Hello foo!"
}La lógica de negación se implementa con la palabra clave not:
# input
{
"a": "1a",
"b": "2b",
"c": "1c"
}
# code
prefixed[item] {
item := input[_]
not startswith(item, "1")
}
# output
{
"prefixed": [
"2b"
]
}El operador not se usa para la negación lógica, mientras que el operador != se usa para comparar valores y comprobar si son distintos.
Bucles
En Rego, los bucles se llaman iteraciones y están bien documentados. Sin embargo, por lo general no se necesitan, ya que se usa la cuantificación existencial.
Ambigüedad sintáctica
Rego usa la misma sintaxis para iterar sobre valores y acceder a ellos en conjuntos, objetos y arreglos. Por eso, es fundamental tener en cuenta las estructuras de datos involucradas. El flujo de control cambia según la estructura de datos, aunque se use la misma sintaxis:
# input
{
"array": [0, 3, 5],
"object": {"foo": "bar"}
}
# code
iterate_array[msg] { # returns array of strings
value := input.array[index] # loop over array
msg := sprintf("%d: %d", [index, value])
}
access_key_in_object := v { # returns string
v := input.object["foo"] # get value by key "foo"
}
create_set := s {
s := { v |
v := input.array[_]
}
}
iterate_set[v] { # returns array of integers
create_set[v] # iterate over set
}
check_key_in_object { # returns `true`
input.object["foo"] # check if key "foo" exists
}No siempre es posible deducir la semántica del código Rego al leer una instrucción fuera de contexto.
Ejemplo 1:
var3 := var1[var2]
Si la estructura de datos de var1 es un…
| entonces se iterará sobre |
| entonces |
Ejemplo 2:
var1[var2]
Si la estructura de datos de var1 es un(a)…
| entonces se iterará sobre |
| entonces se comprobará si existe la clave |
| entonces se iterará sobre |
Errores comunes
Los defectos pueden aparecer en cualquier base de código. La automatización de su detección y prevención es una medida eficaz que debería formar parte de todo proceso de desarrollo con Rego.
Dos herramientas útiles para lograrlo:
OPA incluye el comando integrado
check, que ayuda a identificar errores de compilación y problemas de calidad del código.
Styra, la empresa detrás de OPA, lanzó
regal, un linter para Rego que analiza el código fuente en busca de distintos problemas, desde el estilo de código hasta errores.
Optimización de salida anticipada
Como se explicó en la sección «Optimización», Rego finaliza antes si puede determinar el resultado final sin ejecutar todo el código.
Supongamos que un autor quiere crear una política que solo permita el acceso si todas las conexiones del usuario con la empresa se originan en alguna de sus sucursales:
# input
[
"on-prem",
"remote",
"on-prem",
"on-prem"
]
# code
default allow := false
allow {
input[_] == "on-prem"
}
# output
{
"allow": true
}Esta política devuelve true aunque una de las conexiones provenga de un origen remoto. Este comportamiento puede resultar desconcertante para quienes no conocen la programación lógica.
Por ejemplo, en Python, el código podría ser similar al siguiente y funcionar como se espera:
origins = ['on-prem', 'remote', 'on-prem', 'on-prem']
def allow():
for origin in origins:
if origin != 'on-prem':
return 1
return 0
if __name__ == '__main__':
raise SystemExit(allow())El error lógico se debe a la optimización de salida anticipada de Rego: en cuanto puede evaluar las condiciones de la regla allow como true, se detiene. Esto sucede porque la cuantificación existencial se satisface cuando el origen se vincula a on-prem en la primera iteración. Esto difiere bastante de la programación imperativa, donde se evalúa la condición para cada elemento de origins antes de tomar una decisión.
Para corregir el error, se debe aplicar la cuantificación universal (FOR ALL).
Una forma de corregirlo es usar una comprehension:
default allow := false
allow {
all_allows := [ allowed |
origin := input[_]
origin == "on-prem"
allowed := true
]
count(all_allows) == count(input)
}Otra forma de corregirlo es usar la palabra clave every (incorporada en OPA v0.38.0) [14] [15]:
import future.keywords.every
default allow := false
allow {
every origin in input {
origin == "on-prem"
}
}Una tercera opción es invertir la lógica de la regla mediante la negación:
# input
[
"remote",
"on-prem",
"on-prem"
]
# code
default deny := false
deny {
input[_] != "on-prem"
}
# output
{
"deny": true
}Esta regla deny devuelve true, como se espera, si alguna de las conexiones del usuario se origina fuera de una oficina de la empresa.
Sin embargo, usar la palabra clave every es la opción más adecuada, porque aplica intencionalmente la cuantificación universal y, por lo tanto, expresa de forma más clara y deliberada las intenciones del autor de la política [14].
Al crear una regla que contiene una iteración, el autor de la política debería considerar si la cuantificación universal o la existencial produce el comportamiento esperado.
Valores indefinidos
En la sección «Introducción» anterior se explica que la ejecución del código se detiene si una línea no se evalúa como true. Para ser claros, cuando una regla lógica no se puede resolver porque no se cumple su condición, no devuelve false como esperarían quienes están acostumbrados a los lenguajes imperativos; simplemente se detiene la ejecución del código. Lo mismo ocurre con los valores o atributos undefined [17].
Por ejemplo, el código Rego se evalúa como undefined cuando se accede a un espacio de nombres o atributo de objeto inexistente.
Esto puede provocar un comportamiento inesperado y ser difícil de depurar:
# input
{
"message": "world"
}
# code
deny {
input.mesage == "world"
}
# output
{}El atributo input.mesage tiene un error tipográfico y no existe. Rego solo puede determinar en tiempo de ejecución si este atributo existe o es undefined; por eso, OPA simplemente se detiene sin avisar al evaluar esta línea.
Para diagnosticar este tipo de problema, suele ser útil incluir varias instrucciones print() en la base de código para identificar la línea errónea. Recuerda verificar también los espacios de nombres de los paquetes.
Otra forma de identificar la línea errónea es desactivar (comentar) grandes secciones de la base de código y volver a activarlas paso a paso hasta encontrar el problema.
El «true» de JSON
El documento input de OPA contiene datos con formato JSON, que admite estructuras de datos booleanas y de cadena [18]. En la práctica, los valores booleanos pueden representarse como cadenas (por ejemplo, «true»), lo que puede provocar decisiones de políticas no deseadas. Por ejemplo:
# input
{
"privileged": "true"
}
# code
deny {
input.privileged == true
}
# output
{}Una posible solución es crear una función auxiliar que contemple las cadenas:
1# input
2{
3 "privileged": "true"
4}
5
6# code
7is_true(b) := ret {
8 is_boolean(b)
9 b
10 ret := b
11} else := ret {
12 b == "true"
13 ret := true
14} else = false {
15 true
16}
17
18deny {
19 is_true(input.privileged)
20}
21
22# output
23{
24 "deny": true
25}Pruebas
Las pruebas son fundamentales para garantizar que las políticas se comporten como se espera. OPA incluye el comando test, que permite ejecutar pruebas fácilmente.
Dada la siguiente política (archivo policy.rego):
package mypolicy
deny {
input.privileged == true
}Es sencillo implementar pruebas (archivo policy_tests.rego):
package mypolicy
test_deny_privileged {
deny with input as {"privileged": true}
}
test_deny_unprivileged {
not deny with input as {"privileged": false}
}El comando ./opa test policy.rego policy_tests.rego ejecuta las pruebas y muestra el resultado: PASS: 2/2.
Depuración
La depuración es importante en cualquier proyecto de programación. OPA ofrece un REPL y un comando eval que se pueden instrumentar para depurar. Sin embargo, todavía no existe un depurador típico como gdb que permita recorrer el código paso a paso e inspeccionar símbolos. Por eso, imprimir los valores de las variables sigue siendo una técnica importante de depuración.
En este ejemplo, se amplían las pruebas del capítulo anterior, «Pruebas», con un caso de prueba que ejecuta la política con la entrada «true»:
test_deny_string {
deny with input as {"privileged": "true"}
}Como era de esperar, el caso de prueba falla:
$ ./opa test policy.rego policy_test.rego
policy_test.rego:
data.mypolicy.test_deny_string: FAIL (85.375µs) --------------------------------------------------------------------------------
PASS: 2/3
FAIL: 1/3
Para investigar el problema, se puede mostrar el valor de input.privileged con print():
deny {
print(sprintf("value of `privileged`: %v", [input]))
input.privileged == true
}Al ejecutar de nuevo el caso de prueba, se muestra el valor:
$ ./opa test policy.rego policy_test.rego
policy_test.rego:
data.mypolicy.test_deny_string: FAIL (88.875µs)
value of `privileged`: {"privileged": "true"} --------------------------------------------------------------------------------
PASS: 2/3
FAIL: 1/3
El resultado ahora revela la estructura de datos de input.privileged: es una cadena y no un booleano. Así, se puede corregir el problema.
OPA ofrece la función print() desde la versión v0.34.0 de OPA. Por suerte, print() no se detiene ante valores undefined, sino que muestra <undefined>.
Una herramienta útil de terceros para depurar código, probar políticas y mucho más es fregot.
Errores comunes
Complete rules must not produce multiple outputs
Este error ocurre si dos o más reglas de una misma definición reciben la misma entrada y producen resultados distintos. Por ejemplo:
# input
-
# code
a_rule := res {
res := "b"
}
a_rule := res {
res := "c"
}
# outputpolicy.rego:7: eval_conflict_error: complete rules must not produce multiple outputs
La primera definición de a_rule devuelve «b», mientras que la segunda devuelve «c», lo que produce un comportamiento no determinista.
A menudo, todos los valores de retorno son válidos y se espera obtener la unión. Esto se puede lograr devolviendo un conjunto:
# input
-
# code
a_rule[res] {
res := "b"
}
a_rule[res] {
res := "c"
}
# output
{
"a_rule": [
"b",
"c"
]
}Otra opción común es combinar ambas reglas y devolver una colección. Si ninguna de las dos opciones da el resultado esperado, quizá sea necesario revisar la lógica de la política.
OPA para rutas críticas
Rego y OPA son herramientas confiables para crear y evaluar políticas. Las garantías de decidibilidad y terminación brindan seguridad y permiten crear marcos de autorización eficaces que pueden formar parte de cualquier ruta crítica de las aplicaciones empresariales.
Sin embargo, usar Rego tiene un costo, ya que muchos programadores no están familiarizados con algunos de los conceptos de programación lógica involucrados, como la cuantificación existencial o la optimización de salida anticipada de OPA, que se analiza en la sección «Introducción».
Por consiguiente, los programadores deben superar una curva de aprendizaje inicialmente pronunciada, que representa una barrera de entrada. Además, dado que existen posibles dificultades, se debe dimensionar el costo de adoptar OPA y compararlo con otras opciones, como elegir en su lugar un lenguaje de programación imperativo con un ecosistema de herramientas para desarrolladores más maduro y muchos más programadores disponibles (por supuesto, los lenguajes imperativos tampoco están libres de dificultades).
Por ejemplo, al crear software en el que se acepta el riesgo de efectos secundarios y el tiempo de salida al mercado es crucial, la decisión de elegir Rego debe tomarse teniendo en cuenta sus consecuencias.
Prioriza lo que más importa
Las organizaciones necesitan un enfoque integral para priorizar los riesgos. Usa la priorización contextual basada en riesgos de Snyk.