Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

Port an existing Rego policy

You can load Rego that you already have. Your logic files and their tests stay as they are. Which edits you need depends on the input shape that your policy reads. There are three cases.

Your policyWhat to do
An Envoy or Istio ext_authz policySet the decision rule. No edits. See Run an Envoy policy.
A policy for another input shape (a Kafka authorizer, your own API)Add one adapter file.
No policy yetWrite package ironflow.authz rules against the native view. See Author a policy set.

This page covers the second case.

Ironflow reads two entry points: the rule data.ironflow.authz.deny and an optional decision rule. Your policy defines neither. The adapter is one file in package ironflow.authz. It calls your policy with with input as and a new input that it builds from the Ironflow input. It turns the answer into a deny reason.

You only write the mapping. Your own files do not change.

This policy decides Kafka requests. It reads input.action and input.requestContext:

package kafka.authz
default allow := false
allow if {
input.action.operation == "READ"
startswith(input.action.resourcePattern.name, "public-")
}
allow if {
"admin" in input.requestContext.principal.groups
}

It has a test, which stays in the set:

package kafka.authz_test
import data.kafka.authz
test_public_read if authz.allow with input as {"action": {"operation": "READ", "resourcePattern": {"name": "public-events"}}, "requestContext": {"principal": {"groups": []}}}
test_private_write_denied if not authz.allow with input as {"action": {"operation": "WRITE", "resourcePattern": {"name": "orders"}}, "requestContext": {"principal": {"groups": []}}}

The adapter maps an Ironflow request to that shape. Actions that end in :read are READ. All others are WRITE. The roles of the subject play the part of the Kafka groups:

package ironflow.authz
import data.kafka.authz as kafka
operation := "READ" if {
endswith(input.request.action, ":read")
} else := "WRITE"
deny contains "kafka policy denies this" if {
not kafka.allow with input as {
"action": {"operation": operation, "resourcePattern": {"name": input.request.resource}},
"requestContext": {"principal": {"groups": input.subject.roles}},
}
}

Put the three files in your policy directory, for example kafka/authz.rego, kafka/authz_test.rego and adapter.rego. Then:

Terminal window
ironflow policy pull ./p
# copy the Rego files into ./p
opa test ./p
ironflow policy dry-run ./p --input request.json
ironflow policy push ./p

An admin who writes runs:delete is allowed. A member who writes runs:delete is denied with the reason kafka policy denies this. A member who reads a resource named public-feed is allowed.

  1. Run your own tests. opa test ./p runs your original tests and your new ones together.
  2. Dry-run with real inputs. Take a few subjects and requests from your audit log. Run ironflow policy dry-run for each. Compare the decision with what you expect.
  3. Start in monitor mode. The first push is in monitor mode. Read the would_deny rows before you switch to enforce. See Roll out with monitor mode.
  • The adapter can only deny. Your policy cannot give a request more access than RBAC gave it.
  • Reason text matters. An adapter reason of kafka policy denies this is a poor audit entry. Add the operation or the resource to the reason when it helps. Do not use request data that varies for every request in a reason in monitor mode. Each different reason is a different audit row, which defeats sampling. See Roll out with monitor mode.
  • Other packages are not type-checked. Only package ironflow.authz is checked against the input schema. Your own packages can read any input shape.
  • Banned builtins apply to every file. If your policy calls http.send, the write fails. See Author a policy set.
  • Import rego.v1 is not needed. Ironflow reads every file as Rego v1. A v0 file fails with policy_set_v0_syntax. Run opa fmt --v0-v1 -w.