Dashboard OPA rules
Last updated: 5 minutes read.
# Default OPA rules
package dashboard_users
default request_intent = "write"
request_intent = "read" { input.request.method == "GET" }
request_intent = "read" { input.request.method == "HEAD" }
request_intent = "delete" { input.request.method == "DELETE" }
# Set of rules to define which permission is required for a given request intent.
# read intent requires, at a minimum, the "read" permission
intent_match("read", "read")
intent_match("read", "write")
intent_match("read", "admin")
# write intent requires either the "write" or "admin" permission
intent_match("write", "write")
intent_match("write", "admin")
# delete intent requires either the "write" or "admin permission
intent_match("delete", "write")
intent_match("delete", "admin")
# Helper to check if the user has "admin" permissions
default is_admin = false
is_admin {
input.user.user_permissions["IsAdmin"] == "admin"
}
# Check if the request path matches any of the known permissions.
# input.permissions is an object passed from the Tyk Dashboard containing mapping between user permissions (“read”, “write” and “deny”) and the endpoint associated with the permission.
# (eg. If “deny” is the permission for Analytics, it means the user would be denied the ability to make a request to ‘/api/usage’.)
#
# Example object:
# "permissions": [
# {
# "permission": "analytics",
# "rx": "\\/api\\/usage"
# },
# {
# "permission": "analytics",
# "rx": "\\/api\\/uptime"
# }
# ....
# ]
#
# The input.permissions object can be extended with additional permissions (eg. you could create a permission called ‘Monitoring’ which gives “read” access to the analytics API ‘/analytics’).
# This is can be achieved inside this script using the array.concat function.
request_permission[role] {
perm := input.permissions[_]
regex.match(perm.rx, input.request.path)
role := perm.permission
}
# --------- Start "deny" rules -----------
# A deny object contains a detailed reason behind the denial.
default allow = false
allow { count(deny) == 0 }
deny["User is not active"] {
not input.user.active
}
# If a request to an endpoint does not match any defined permissions, the request will be denied.
deny[x] {
count(request_permission) == 0
x := sprintf("This action is unknown. You do not have permission to access '%v'.", [input.request.path])
}
deny[x] {
perm := request_permission[_]
not is_admin
not input.user.user_permissions[perm]
x := sprintf("You do not have permission to access '%v'.", [input.request.path])
}
# Deny requests for non-admins if the intent does not match or does not exist.
deny[x] {
perm := request_permission[_]
not is_admin
not intent_match(request_intent, input.user.user_permissions[perm])
x := sprintf("You do not have permission to carry out '%v' operation.", [request_intent, input.request.path])
}
# If the "deny" rule is found, deny the operation for admins
deny[x] {
perm := request_permission[_]
is_admin
input.user.user_permissions[perm] == "deny"
x := sprintf("You do not have permission to carry out '%v' operation.", [request_intent, input.request.path])
}
# Do not allow users (excluding admin users) to reset the password of another user.
deny[x] {
request_permission[_] = "ResetPassword"
not is_admin
user_id := split(input.request.path, "/")[3]
user_id != input.user.id
x := sprintf("You do not have permission to reset the password for other users.", [user_id])
}
# Do not allow admin users to reset passwords if it is not allowed in the global config
deny[x] {
request_permission[_] == "ResetPassword"
is_admin
not input.config.allow_admin_reset_password
not input.user.user_permissions["ResetPassword"]
x := "You do not have permission to reset the password for other users. As an admin user, this permission can be modified using OPA rules."
}
# --------- End "deny" rules ----------
##################################################################################################################
# Demo Section: Examples of rule capabilities. #
# The rules below are not executed until additional permissions have been assigned to the user or user group. #
##################################################################################################################
# If you are testing using OPA playground, you can mock Tyk functions like this:
#
# TykAPIGet(path) = {}
# TykDiff(o1,o2) = {}
#
# You can use this pre-built playground: https://play.openpolicyagent.org/p/T1Rcz5Ugnb
# Example: Deny users the ability to change the API status with an additional permission.
# Note: This rule will not be executed unless the additional permission is set.
deny["You do not have permission to change the API status."] {
# Checks the additional user permission enabled with tyk_analytics config: `"additional_permissions":["test_disable_deploy"]`
input.user.user_permissions["test_disable_deploy"]
# Checks the request intent is to update the API
request_permission[_] == "apis"
request_intent == "write"
# Checks if the user is attempting to update the field for API status.
# TykAPIGet accepts API URL as an argument, e.g. to receive API object call: TykAPIGet("/api/apis/<api-id>")
api := TykAPIGet(input.request.path)
# TykDiff performs Object diff and returns JSON Merge Patch document https://tools.ietf.org/html/rfc7396
# eg. If only the state has changed, the diff may look like: {"active": true}
diff := TykDiff(api, input.request.body)
# Checks if API state has changed.
not is_null(diff.api_definition.active)
}
# Using the patch_request helper you can modify the content of the request
# You should respond with JSON merge patch.
# See https://tools.ietf.org/html/rfc7396 for more details
#
# Example: Modify data under a certain condition by enforcing http proxy configuration for all APIs with the #external category.
patch_request[x] {
# Enforce only for users with ["test_patch_request"] permissions.
# Remove the ["test_patch_request"] permission to enforce the proxy configuration for all users instead of those with the permission.
input.user.user_permissions["test_patch_request"]
request_permission[_] == "apis"
request_intent == "write"
contains(input.request.body.api_definition.name, "#external")
x := {"api_definition": {"proxy": {"transport": {"proxy_url": "http://company-proxy:8080"}}}}
}
# You can create additional permissions for not only individual users, but also user groups in your rules.
deny["Only '%v' group has permission to access this API"] {
# Checks for the additional user permission enabled with tyk_analytics config: '"additional_permissions":["test_admin_usergroup"]
input.user.user_permissions["test_admin_usergroup"]
# Checks that the request intent is to access the API.
request_permission[_] == "apis"
api := TykAPIGet(input.request.path)
# Checks that the API being accessed has the category #admin-teamA
contains(input.request.body.api_definition.name, "#admin-teamA")
# Checks for the user group name.
not input.user.group_name == "TeamA-Admin"
}