Python vs Rego for Policy as Code
Beta Content If you've found a problem with this documentation or would like to suggest improvements, please join us in the Styra Community Slack!
Python is a versatile programming language commonly used in domains such as data analysis, web development, automation, and scientific computing. Python applications frequently interact with external data sources, handle sensitive information, and process unstructured and untrusted data.
Being so general-purpose Python can do all of these things, but expressing the related policies and rules can be error-prone and distract from the core logic of applications. This guide presents a series of examples illustrating how a policy can be expressed in Python and the corresponding Rego code for comparison.
Check If a User Is an Admin
Admin status is often encoded in a JWT (JSON Web Token). This example shows how to extract information from a JWT and make an authorization decision based on it. The example uses an insecure secret to verify the JWT for brevity.
Python
import jwt # pip install pyjwt
def allow(token):
try:
payload = jwt.decode(
token, "pa$$w0rd", algorithms=["HS256"])
except jwt.PyJWTError as e:
return False
if not "roles" in payload:
return False
return "admin" in payload["roles"]
Rego
package example
import rego.v1
claims := io.jwt.decode(input.token)[1] if {
io.jwt.verify_hs256(input.token, "pa$$w0rd")
}
default allow := false
allow if "admin" in claims.roles
Note: Verifying JWTs with a hard-coded secret is insecure and is used here for example only, please refer to the OPA documentation on Token Verification for more information on how to securely verify JWTs.
Grant Access Based on Inherited Permissions
Staff roles are used to define permissions that a given
user has within an organization. In this example, we show how
permissions can be inherited from other roles based on
the organization's staff hierarchy. Rego's graph.reachable
built-in function not only
keeps the policy concise but is also safe from infinite recursion
caused by cyclic dependencies in the staff hierarchy.
Python
manages = {
"manager": ["supervisor", "security"],
"supervisor": ["assistant"],
"assistant": [],
"security": [],
}
dataset_permissions = {
"manager": ["salaries"],
"supervisor": ["rotas"],
"security": ["cctv"],
"assistant": ["product_prices"],
}
def reachable_roles_for_role(role):
roles = [role]
for r in manages[role]:
roles.append(r)
roles.extend(reachable_roles_for_role(r))
return roles
def allow(role, dataset):
inheritable_roles = reachable_roles_for_role(role)
for r in inheritable_roles:
if r in dataset_permissions:
if dataset in dataset_permissions[r]:
return True
return False
Rego
package example
import rego.v1
manages := {
"manager": {"supervisor", "security"},
"supervisor": {"assistant"},
"security": set(),
"assistant": set(),
}
dataset_permissions := {
"manager": {"salaries"},
"supervisor": {"rotas"},
"security": {"cctv"},
"assistant": {"product_prices"},
}
default allow := false
allow if {
some inherited_role in graph.reachable(
manages, {input.role}
)
input.dataset in dataset_permissions[inherited_role]
}
Validate User-Generated Content
Policy is often used to guide users when they make mistakes. Validation of user-generated resources can be complicated and often needs to be implemented in many applications. This example compares code for validating a user-submitted blog post and shows how Rego rules can be defined incrementally.
Python
def validations(input):
messages = []
# ensure title and content are set
for field in ["title", "content"]:
if not input.get(field):
messages.append(f"Value missing for field '{field}'")
# ensure title starts with a capital letter
title = input.get("title", "")
if title and not title[0].isupper():
messages.append("Title must start with a capital")
# ensure user identifier is set
user = input.get("user", {})
if not user:
messages.append("User email or id must be set")
else:
if not any(k in user for k in ["email", "id"]):
messages.append("User email or id must be set")
# ensure example.com emails are not allowed
if user.get("email", "").endswith("@example.com"):
messages.append("example.com emails not allowed")
return messages
Rego
package example
import rego.v1
# ensure title and content are set
validations contains message if {
some field in {"title", "content"}
object.get(input, field, "") == ""
message := sprintf("Value missing for field '%s'", [field])
}
# ensure title starts with a capital letter
validations contains "Title must start with a capital" if {
not regex.match(`^[A-Z]`, input.title)
}
# ensure user identifier is set
validations contains "User email or id must be set" if {
not input.user.email
not input.user.id
}
# ensure example.com emails are not allowed
validations contains "example.com emails not allowed" if {
endswith(input.user.email, "@example.com")
}
If you've found a problem with this documentation or would like to suggest improvements, please join us in the Styra Community Slack!