In this article
Bien démarrer avec Practical Rego
Bien qu’une grande partie des informations présentées dans cet article recoupe la référence Rego officielle, ce guide de prise en main met l’accent sur les paradigmes de programmation (logique ou procédural), ce qui est utile aux développeurs habitués aux langages impératifs tels que Python ou Java.
Les liens classiques (p. ex. Wikipedia) renvoient vers des informations complémentaires, tandis que les liens entre crochets (p. ex. [1]) indiquent les sources des affirmations de cet article.
Tous les extraits de code de cet article ont été exécutés avec OPA v0.54.0.
Merci à Jasper Van der Jeugt d’avoir relu une version préliminaire de cet article et proposé des améliorations.
Présentation de Rego
Le projet Open Policy Agent (« OPA ») a suscité beaucoup d’intérêt depuis qu’il a été accepté comme projet diplômé de la CNCF [0]. OPA est un moteur de politiques généraliste qui permet d’appliquer des cadres d’autorisation. Ses politiques sont définies dans un langage de requête appelé Rego, au cœur de cet article.
Rego repose sur Datalog, un langage déclaratif de requête logique. Il propose des constructions pour la correspondance de motifs, le filtrage et l’itération. Rego est comparable à SQL, car tous deux sont des langages de requête généralistes. Toutefois, SQL est conçu pour traiter des données tabulaires, tandis que Rego fonctionne avec des données au format JSON. En tant que langage déclaratif de requête logique, Rego possède des caractéristiques peu courantes dans les autres langages de programmation. Il est essentiel de les comprendre pour bien maîtriser Rego.
Programmation déclarative
La programmation déclarative exprime la logique et les règles d’un problème sans décrire explicitement les étapes nécessaires à sa résolution. Elle s’intéresse au quoi plutôt qu’au comment. À l’inverse, un paradigme de programmation plus répandu est dit « impératif » : les instructions modifient l’état d’un programme pour produire un résultat. SQL ([1], p. 79) et HTML [2] sont des exemples courants de langages déclaratifs. Les langages d’infrastructure en tant que code, comme HCL, adoptent généralement eux aussi une approche déclarative, souvent mêlée à des éléments impératifs [3]. De nombreux langages de programmation permettent de combiner les deux paradigmes, mais penchent généralement davantage vers la programmation impérative, comme Python [4].
Exemple de programmation impérative (Python) :
def is_even(x):
remainder = x % 2
if remainder == 0:
return True
return FalseExemple de programmation logique déclarative (Rego) :
is_even {
remainder == 0
remainder = input % 2
}
Dans Rego, l’ordre des instructions au sein d’une règle n’a aucune importance.
Programmation logique
Datalog applique le paradigme de la programmation logique, une forme particulière de programmation déclarative fondée sur la logique formelle [5], [6]. Les programmes logiques se composent de faits et de règles qui définissent des relations et des contraintes afin de décrire le domaine du problème. Dans Rego, ces faits et règles s’expriment au moyen de la clause de Horn, une formule logique également utilisée dans Datalog [6].
Un moteur d’inférence déduit ensuite la solution au problème en déterminant l’ordre d’exécution. Autrement dit, le moteur traduit le code en plusieurs formules logiques, puis les résout. Cette architecture limite fortement les effets de bord, renforce la robustesse et se prête mieux à la vérification formelle.
Dans le cas de Rego, OPA joue notamment le rôle de moteur d’inférence. Pour améliorer les garanties de terminaison de Rego, des mesures ont été prises, entre autres, afin d’empêcher certaines formes de récursivité [7], [8]. En outre, grâce aux choix de conception évoqués précédemment, Rego bénéficie de la décidabilité, une autre propriété informatique essentielle [9]. La décidabilité garantit que le processus de décision lors de l’évaluation des politiques produira toujours un résultat. Les politiques Rego ne resteront donc pas bloquées et la complexité temporelle est optimisée. Cette robustesse de Rego est probablement l’une des principales raisons de l’utiliser avec OPA. Il peut intervenir dans n’importe quel chemin critique d’une application métier et remplir ses fonctions de manière fiable.
Comme indiqué précédemment, Rego se compose de faits et de règles. Chaque instruction du code représente une règle logique. Si une règle logique ne peut pas être résolue, il est inutile de poursuivre l’évaluation du code. Par conséquent, chaque ligne de code doit être « truthy ». Rego hérite de ce comportement parce qu’il repose sur Datalog : il ne lui est donc pas propre. Il reste néanmoins important pour les auteurs de politiques Rego. Pour comprendre pourquoi il est essentiel d’en tenir compte, consultez la section « Pièges ».
Quantification existentielle
En programmation, les quantificateurs logiques définissent la portée d’une instruction sur une collection d’éléments.
Rego applique la quantification existentielle (pensez à FOR ANY), qui consiste à vérifier si une condition est vraie pour au moins un élément d’une collection.
Les langages de programmation impératifs n’appliquent pas intrinsèquement la quantification comme le fait la programmation logique. Ils proposent toutefois des constructions permettant de vérifier si une condition est vraie pour tous les éléments d’une collection, ce qui correspond à la quantification universelle (pensez à FOR ALL).
Dans les deux cas, une boucle parcourt une collection et adapte son comportement lorsqu’elle cherche des éléments qui satisfont les conditions (quantification existentielle) ou qu’elle vérifie ces conditions pour tous les éléments (quantification universelle).
La quantification existentielle consiste à identifier « toutes les affectations de variables » qui satisfont une condition d’une requête [10].
Cela peut sembler abstrait, mais nous l’aborderons dans un contexte plus concret dans les sections suivantes.
La quantification existentielle est un concept peu familier pour de nombreux développeurs, ce qui peut entraîner des bogues logiques, comme nous le verrons dans la section « Pièges ».
Point d’entrée
Rego ne comporte pas de fonction « main » qui lance son exécution. OPA est plutôt configuré avec un point d’entrée. Il s’agit d’une chaîne qui désigne des règles Rego, par exemple data.mypolicies.den. Ce point d’entrée demande à OPA d’interroger toutes les règles Rego deny dans l’espace de noms mypolicies, d’unir les résultats de toutes les règles et de les renvoyer. Il est possible de fournir plusieurs points d’entrée. Si les conditions d’une règle ne sont pas remplies, celle-ci est considérée comme « non résoluble » et n’est pas incluse dans le résultat final.
Un point d’entrée seul ne suffit pas pour exécuter OPA. Il faut également un document input qui sert de variable globale dans Rego et contient toutes les données provenant de sources externes.
Optimisation
Il peut sembler prématuré d’aborder les optimisations de Rego à ce stade, mais l’une d’elles mérite d’être signalée, car elle peut entraîner un comportement inattendu.
Cette optimisation s’appelle « arrêt anticipé lors de l’évaluation d’une règle » et peut être à l’origine d’un bogue logique. La documentation officielle décrit bien cette mesure en détail. Il s’agit toutefois d’un sujet avancé auquel on ne pense pas forcément quand on découvre Rego. La section « Pièges » présente ce risque et propose des solutions de contournement.
Voici comment est décrite la partie délicate de cette optimisation :
Lorsqu’un « arrêt anticipé » est possible pour une ou plusieurs règles, les itérations au sein de la règle sont interrompues dès qu’une liaison satisfait le corps de la règle :
package earlyexit.iteration
p {
some p
data.projects[p] == "project-a"
}
Puisqu’aucune possibilité ne pourrait modifier le résultat de data.earlyexit.iteration.p une fois qu’une liaison de variable satisfait les conditions, aucune autre itération n’a lieu. – Documentation OPA
Autrement dit, OPA s’arrête de parcourir data.projects dès qu’il trouve un élément égal à la chaîne project-a, conséquence directe de la quantification existentielle. C’est logique et correct, mais il est important de connaître ce comportement.
Égalité
Rego possède trois types d’opérateurs d’égalité, chacun ayant une signification différente :
opérateur d’égalité | signification |
|---|---|
| l’opérateur de comparaison, qui sert à comparer les valeurs de variables |
| l’opérateur d’affectation, qui sert à attribuer des valeurs aux variables |
| l’opérateur d’unification, qui combine affectation et comparaison |
L’opérateur = effectue d’abord l’affectation, puis la comparaison. Si l’affectation échoue, OPA compare tout de même les valeurs :
# 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
}
Sauf raison particulière d’utiliser l’opérateur `=`, il est recommandé de l’éviter [13]. Utilisez plutôt l’opérateur `:=` pour les affectations et l’opérateur `==` pour les comparaisons.
Politiques, règles et fonctions
Avant d’aborder cette section, il est utile de connaître les structures de données Rego : valeurs scalaires, chaînes, valeurs composites et compréhensions.
Politiques
Une politique est un concept de niveau supérieur dans Rego qui désigne un ensemble de règles définissant le comportement et les contraintes d’un système. Le document suivant est, par exemple, une politique contenant les règles Rego allow et deny :
package mypolicy
allow {
input.name == "bar"
}
deny {
input.name == "foo"
}
Règles
Les règles produisent des « documents virtuels », c’est-à-dire des structures de données calculées à l’exécution. Pour en savoir plus sur les règles, cliquez ici. Exemple :
# 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 aucune valeur de retour n’est définie, les règles Rego renvoient true, comme l’indique la documentation : > Si la valeur est omise, elle est définie par défaut sur true. – documentation
Les deux règles suivantes ont donc le même comportement :
deny := true if { # rule header
true # rule body
}
deny { # optimized rule header
true
}Comme indiqué précédemment, une règle ne s’exécute jusqu’au bout que si toutes les conditions de son corps sont satisfaites. Par conséquent, la règle suivante ne renvoie rien :
allow := true {
false
}Les règles qui renvoient des données de type set ou object sont appelées règles incrémentales et utilisent une syntaxe légèrement différente :
# 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"
]
}
Les règles Rego ne prennent pas en charge les paramètres. Par exemple, return_set(param1 est un code de règle invalide), mais les [fonctions][#functions] peuvent être utilisées à la place.
Quantification existentielle
Le comportement d’OPA lors de l’exécution des règles est défini comme suit : lors de l’évaluation des corps des règles, OPA recherche les liaisons de variables qui rendent toutes les expressions vraies. – documentation
Comment fonctionne la quantification universelle dans les langages impératifs ? Voici un exemple en Python :
for n in numbers: # think "FOR ALL" items `n` in numbers
if n % 2 == 0:
print(n)Cet extrait de code trouve les nombres pairs dans le tableau numbers. La même tâche peut être réalisée avec Rego. Exemple :
even_numbers[n] {
n := input[_]
n % 2 == 0 # think "FOR ANY" item `n` in inputs
}Dans l’exemple ci-dessus, le mot-clé some sert à déclarer la variable n. OPA cherche, dans le tableau input, un nombre n qui satisfait la condition de parité. Si c’est le cas, la liaison de variable n est renvoyée.
En identifiant toutes les liaisons de variables correspondantes, OPA détermine si la quantification existentielle est satisfaite.
Sans être obligatoire, la déclaration explicite des variables à l’aide de some peut améliorer la clarté et la compréhension du code.
Autre exemple : trouver toutes les valeurs d’un objet qui commencent par la chaîne 1 :
# input
{
"a": "1a",
"b": "2b",
"c": "1c"
}
# code
prefixed[item] {
item := input[_]
startswith(item, "1")
}
# output
{
"prefixed": [
"1a",
"1c"
]
}La ligne item := input[_] parcourt le document d’entrée. Le deuxième élément d’entrée, b, ne satisfait pas la condition de règle startswith(l, "1"). OPA n’interrompt pas l’exécution de la boucle, mais ignore le résultat de l’itération et passe à l’élément suivant, c, pour poursuivre la « recherche de toutes les liaisons de variables ».
Ce comportement est utile, mais peut entraîner un bogue logique.
Fonctions
Les fonctions Rego ressemblent aux règles, mais se comportent différemment :
greet(name) := msg {
msg := sprintf("Hello %s!", [name])
}
greeting := greet("Bob") # returns "Hello Bob!"Les fonctions sans paramètres ne sont pas prises en charge, car une telle définition est interprétée comme une règle :
greet() := msg {
msg := "Hello"
}
greet() # creates error "rego_parse_error:
# rule argument list must take at least one argument"Puisque les règles représentent des documents virtuels et que les fonctions n’en représentent pas, elles doivent être utilisées différemment :
# 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
]
Les règles comme les fonctions peuvent servir à partager des fonctionnalités entre les politiques.
Flux de contrôle
Notions de base
Pour construire une logique qui vérifie plusieurs conditions, il suffit d’énumérer chaque condition sur une ligne :
# input
{
"name": "foo"
}
# code
my_rule {
startswith(input.name, "f") # first condition (AND..)
endswith(input.name, "o") # second condition
}
# output
{
"my_rule": true
}
Pour créer un flux de contrôle avec différents cas dans lesquels une règle doit être évaluée à true, il suffit de définir plusieurs fois la règle (l’en-tête doit être identique dans chaque définition). Si l’une des règles est évaluée à true, la valeur renvoyée sera true. Si les règles renvoient des collections, l’ensemble des éléments correspondant aux conditions des règles est renvoyé.
Dans l’exemple suivant, les règles my_rule renvoient une valeur booléenne :
# input
{
"name": "foo"
}
# code
my_rule {
count(input.name) == 3
}
my_rule {
input.name == "foobar" # not true; aborts execution
}
# output
{
"my_rule": true
}La structure if/else peut être mise en œuvre de façon similaire aux langages impératifs en utilisant le mot-clé 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 logique de négation s’implémente à l’aide du mot-clé not :
# input
{
"a": "1a",
"b": "2b",
"c": "1c"
}
# code
prefixed[item] {
item := input[_]
not startswith(item, "1")
}
# output
{
"prefixed": [
"2b"
]
}L’opérateur not sert à la négation logique, tandis que l’opérateur != permet de comparer des valeurs pour vérifier qu’elles sont différentes.
Boucles
Dans Rego, les boucles sont appelées itérations et sont bien documentées. Elles sont toutefois généralement inutiles, car on utilise plutôt la quantification existentielle.
Ambiguïté syntaxique
Rego utilise la même syntaxe pour parcourir et accéder aux valeurs des ensembles, des objets et des tableaux. Il est donc essentiel de connaître les structures de données concernées. Le comportement du flux de contrôle varie selon la structure de données, même si la syntaxe reste la même :
# 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
}Sans contexte, il n’est pas toujours possible de déduire la sémantique du code Rego en lisant une instruction.
Exemple 1 :
var3 := var1[var2]
Si la structure de données de var1 est un…
| alors |
| alors |
Exemple 2 :
var1[var2]
Si la structure de données de var1 est un(e)…
| alors |
| alors le programme vérifie si la clé |
| alors |
Pièges
Les défauts s’infiltrent dans toutes les bases de code. L’automatisation de leur détection et de leur prévention est une mesure efficace qui devrait faire partie de tout processus de développement Rego.
Deux outils utiles pour y parvenir :
OPA inclut la commande intégrée builtin command
check, qui aide à détecter les erreurs de compilation et les problèmes de qualité du code.
Styra, l’entreprise à l’origine d’OPA, a publié
regal, un analyseur statique Rego qui analyse le code source afin d’identifier différents problèmes, du style de code aux bogues.
Optimisation par arrêt anticipé
Comme indiqué dans la section « Optimisation », Rego s’arrête dès qu’il peut déterminer le résultat final sans exécuter l’intégralité du code.
Imaginons qu’un auteur souhaite créer une règle qui n’autorise l’accès que si toutes les connexions de l’utilisateur à l’entreprise proviennent de l’un de ses bureaux régionaux :
# input
[
"on-prem",
"remote",
"on-prem",
"on-prem"
]
# code
default allow := false
allow {
input[_] == "on-prem"
}
# output
{
"allow": true
}Cette règle renvoie true, même si une connexion provient d’une source distante. Ce comportement peut dérouter les programmeurs qui ne connaissent pas la programmation logique.
Par exemple, en Python, le code pourrait ressembler à l’exemple ci-dessous et fonctionner comme prévu :
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())Le bogue logique vient de l’optimisation d’arrêt anticipé de Rego : dès que Rego peut évaluer à true les conditions de la règle allow, il s’arrête. C’est le cas parce que la quantification existentielle est satisfaite lorsque l’origine est associée à on-prem lors de la première itération. C’est très différent de la programmation impérative, où la condition est évaluée pour chaque élément de origins avant qu’une décision soit prise.
Pour corriger ce bogue, il faut appliquer la quantification universelle (FOR ALL).
Une solution consiste à utiliser une comprehension :
default allow := false
allow {
all_allows := [ allowed |
origin := input[_]
origin == "on-prem"
allowed := true
]
count(all_allows) == count(input)
}Une autre solution consiste à utiliser le mot-clé every (introduit dans OPA v0.38.0) [14] [15] :
import future.keywords.every
default allow := false
allow {
every origin in input {
origin == "on-prem"
}
}Une troisième option consiste à inverser la logique de la règle en utilisant la négation :
# input
[
"remote",
"on-prem",
"on-prem"
]
# code
default deny := false
deny {
input[_] != "on-prem"
}
# output
{
"deny": true
}Cette règle deny renverra true, comme prévu, si l’une des connexions de l’utilisateur provient de l’extérieur des bureaux de l’entreprise.
Toutefois, l’utilisation du mot-clé every est la solution la plus appropriée, car elle applique explicitement la quantification universelle et exprime ainsi plus clairement et délibérément l’intention de l’auteur de la règle [14].
Lorsqu’une règle inclut une itération, son auteur doit se demander si la quantification universelle ou existentielle produira le comportement souhaité.
Valeurs non définies
La section « Introduction » ci-dessus explique que l’exécution du code s’arrête si une ligne n’est pas évaluée à true. Plus précisément, lorsqu’une règle logique ne peut pas être résolue parce que sa condition n’est pas remplie, elle ne renvoie pas false, contrairement à ce que les programmeurs habitués aux langages impératifs pourraient attendre : l’exécution du code s’arrête simplement. Il en va de même pour les valeurs ou attributs undefined [17].
Le code Rego renvoie undefined, par exemple, lorsqu’on accède à un espace de noms ou à un attribut d’objet inexistant.
Cela peut entraîner un comportement inattendu et être difficile à déboguer :
# input
{
"message": "world"
}
# code
deny {
input.mesage == "world"
}
# output
{}L’attribut input.mesage comporte une faute de frappe et n’existe pas. Rego ne sait qu’à l’exécution si cet attribut existe ou s’il est undefined ; OPA s’arrête donc simplement sans avertissement lors de l’évaluation de cette ligne.
Pour résoudre ce type de problème, il est généralement utile d’ajouter plusieurs instructions print() dans la base de code afin de repérer la ligne erronée. Pensez également à vérifier les espaces de noms des packages.
Une autre façon de repérer la ligne erronée consiste à désactiver (mettre en commentaire) de grandes parties de la base de code, puis à les réactiver progressivement jusqu’à ce que le problème apparaisse.
« true » en JSON
Le document input transmis à OPA contient des données au format JSON, qui prend en charge les booléens et les chaînes de caractères [18]. Dans la pratique, les booléens sont parfois représentés sous forme de chaînes (par exemple, « true »), ce qui peut entraîner des décisions de politique inattendues. Par exemple :
# input
{
"privileged": "true"
}
# code
deny {
input.privileged == true
}
# output
{}Une solution possible serait d’utiliser une fonction auxiliaire qui gère les chaînes de caractères :
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}Tests
Les tests sont essentiels pour garantir le comportement attendu des règles. OPA fournit une commande test qui permet d’exécuter facilement des tests.
À partir de la règle suivante (fichier policy.rego) :
package mypolicy
deny {
input.privileged == true
}Il est facile d’implémenter des tests (fichier policy_tests.rego) :
package mypolicy
test_deny_privileged {
deny with input as {"privileged": true}
}
test_deny_unprivileged {
not deny with input as {"privileged": false}
}La commande ./opa test policy.rego policy_tests.rego exécute les tests et affiche le résultat : PASS: 2/2.
Débogage
Le débogage est important dans tout projet de programmation. OPA fournit un REPL et une commande eval que l’on peut instrumenter pour déboguer. Cependant, il n’existe pas encore de débogueur classique comme gdb, qui permet de parcourir le code pas à pas et d’inspecter les symboles. Par conséquent, afficher les valeurs des variables reste une méthode de débogage importante.
Dans cet exemple, les tests du chapitre précédent, « Tests », sont complétés par un cas de test qui exécute la règle avec l’entrée « true » :
test_deny_string {
deny with input as {"privileged": "true"}
}Comme prévu, le cas de test échoue :
$ ./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
Pour examiner le problème, vous pouvez afficher la valeur de input.privileged à l’aide de print() :
deny {
print(sprintf("value of `privileged`: %v", [input]))
input.privileged == true
}Lorsque le cas de test est exécuté à nouveau, la valeur s’affiche :
$ ./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
La sortie révèle alors la structure de données de input.privileged : il s’agit d’une chaîne et non d’un booléen. Il est donc possible de corriger le problème.
OPA fournit la fonction print() depuis la version v0.34.0 d’OPA. Heureusement, print() ne s’arrête pas sur les valeurs undefined, mais affiche plutôt <undefined>.
fregot est un outil tiers pratique pour déboguer facilement le code, tester des règles et bien plus encore.
Erreurs courantes
Complete rules must not produce multiple outputs
Cette erreur se produit lorsque plusieurs règles d’une même définition reçoivent la même entrée et produisent des sorties différentes. Par exemple :
# 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 première définition de a_rule renvoie « b », tandis que la deuxième renvoie « c », ce qui entraîne un comportement non déterministe.
Souvent, toutes les valeurs de retour sont valides et le résultat attendu est leur union. Pour cela, vous pouvez renvoyer un ensemble :
# input
-
# code
a_rule[res] {
res := "b"
}
a_rule[res] {
res := "c"
}
# output
{
"a_rule": [
"b",
"c"
]
}Une autre option courante consiste à fusionner les deux règles et à renvoyer une collection. Si aucune de ces options ne permet d’obtenir le résultat souhaité, il peut être nécessaire de revoir la logique de la règle.
OPA pour les chemins critiques
Rego et OPA sont des outils fiables pour créer et évaluer des règles. Les garanties de décidabilité et de terminaison inspirent confiance et permettent de concevoir des frameworks d’autorisation puissants, qui peuvent s’intégrer à tout chemin critique d’une application métier.
L’utilisation de Rego a toutefois un coût : de nombreux programmeurs ne connaissent pas certains concepts de programmation logique, comme la quantification existentielle ou l’optimisation d’arrêt anticipé d’OPA présentée dans la section « Introduction ».
Les programmeurs doivent donc franchir une courbe d’apprentissage initialement abrupte, ce qui constitue un frein à l’adoption. De plus, au vu des pièges possibles, le coût de l’adoption d’OPA doit être évalué et comparé à celui d’autres approches, comme le choix d’un langage de programmation impératif, qui bénéficie d’un écosystème d’outils de développement plus mature et d’un vivier de programmeurs bien plus vaste (les langages impératifs ne sont bien sûr pas à l’abri des pièges non plus).
Par exemple, lorsqu’on crée un logiciel pour lequel le risque d’effets secondaires est acceptable et où la mise sur le marché rapide est cruciale, il convient de choisir Rego en tenant compte de ces conséquences.
Priorisez ce qui compte le plus
Les organisations ont besoin d’une approche globale pour hiérarchiser les risques. Avec Snyk, priorisez les risques selon leur contexte.