Launching Oso Migrate (Beta)
Oso Migrate is a suite of developer tools built into the Oso Dev Server to help you migrate your legacy authorization system over to Oso Cloud as quickly as possible.
Oso Migrate will help you to:
- Capture authorization decisions made in your legacy system through logging.
- Compare them to decisions that have been made by Oso Cloud during the migration.
- Debug exactly why Oso Cloud evaluated the way that it did.
- Iterate quickly on your policy with a fast feedback loop.
Initialization
You'll need to be using version 1.14.0
of the Oso Dev Server or higher to
use Oso Migrate.
Below are the minimal commands to fire up Oso Migrate from the Oso Dev Server.
Note that there are additional pieces of configuration recommended if you're making use of the Snapshotting or Request Replay features (these are explained later on).
Docker
If you're using the Oso Dev Server via Docker, you'll need to provide the
-it
flag and invoke the migrate-ui
subcommand at the end too.
-it
is necessary to attach a tty
to the container and to be able to
interact with it.
docker run -it -p '8080:8080' public.ecr.aws/osohq/dev-server:latest migrate-ui
Standalone Binary
If you're using the Oso Dev Server via the Standalone Binary, then the tool can be invoked in a terminal like so:
./standalone migrate-ui
Features
Oso Migrate provides tooling to help you migrate incrementally, safely and quickly by observing and understanding authorization behavior as it happens in your application, whilst offering the ability to compare results against your legacy authorization system.
Logging
Any authorize
and authorize_local
requests are logged and displayed. In addition to each log providing standard information
(such as the timestamp, query and result), Oso Migrate offers you functionality to help you to
identify unexpected results, understand why they occurred and debug them.
Parity checking
When migrating to Oso Cloud, it's best practice to perform dual reads: check authorization decisons using both your legacy authorization system and Oso Cloud, then compare the results.
In the Logs tab, the Actual
column represents Oso Cloud's decision, based on the policy and facts.
You can supply the expected result from your legacy authorization system using the ParityHandle
, which enables
you to compare the two and surface mismatches in the log output via Oso Migrate.
This allows you to identify inconsistencies and fix them before fully switching your enforcement over to Oso Cloud.
Here’s an example of using ParityHandle
with a legacy authorization system:
import { Oso, ParityHandle, Value } from "oso-cloud";const oso = new Oso( "http://localhost:8080", "e_0123456789_12345_osotesttoken01xiIn");/** * Checks authorization in both Oso and the legacy authorization system in parallel. * Compares results using ParityHandle. */async function authorizeWithParityCheck( userId: String, action: String, resource: Value): Promise<boolean> { const parityHandle = new ParityHandle(); const user = { type: "User", id: userId }; const osoPromise = oso.authorize(user, action, resource, { parityHandle }); const legacyPromise = legacyAuthorize(userId, action, resource); const [_, legacyResult] = await Promise.allSettled([ osoPromise, legacyPromise, ]); // Record the expected result. This will show up in the `Expected` column. parityHandle.expect(legacyResult); // Enforce the legacy result until you're confident things are consistent. return legacyResult;}
In practice, it's possible that your legacy authorization logic is spread in different places throughout your endpoint, in a manner
which isn't easy to centralize in the way the snippet above shows. Therefore, we've designed the ParityHandle
such that
you can call expect
from anywhere in a given request, as long as it is only called once per request.
Snapshotting
Oso Cloud offers you the flexibility to feed in Authorization Data in multiple ways, whether that be context facts (as part of the request to Oso Cloud), centralized in Oso Cloud, or even left in your own database if you're using Local Authorization.
Each log is coupled with a complete snapshot of any data that Oso Cloud used as part of the evaluation for the final result. This means you can
get a centralized view of all of the data used as part of the evaluation, without having to actually centralize all of your data. Simply press Enter
on any Log to see the
Details page and view a full snapshot that was used for the request.
If you're using Local Authorization, then you'll need to supply Oso Migrate with a Connection URI to your local database and a path to your Local Authorization Config. When used in this way, you'll also see fact snapshots for any data that lives inside your database in Oso Cloud. You can do this as follows:
./standalone migrate-ui --local_authorization_config [path_to_local_authz_config] --connection-string [db_connection_string]
Note: Currently, only Postgres is supported for the local data snapshotting feature.
Copy to test
You can copy any request in the Logs tab into a test, which will use the fact snapshot as setup
data and use the
Expected
result as part of the assertion.
Request replay
When you update your policy while using Oso Migrate, it will replay any authorize
/ authorize_local
request that was made to it and recompute
what the result would have been with the new policy. This gives you a fast feedback loop to understand how the result of a particular decision
would change, without you having to manually make the same request or re-run an integration test, making it faster to drive to parity in logic.
To make the most of the Request Replay functionality, it's recommended that you can initialize Oso Migrate with policies
and the --watch-for-changes
flag. When invoked like this, the Replay functionality will re-run every time you save your policy.
./standalone migrate-ui policy1.polar policy2.polar --watch-for-changes
Test runner
Writing tests to capture the intended behaviour of your policy is an important part of the migration, to build confidence that your policy logic functions the way that you think it does. Oso Migrate includes a built-in test runner to view all of your Polar policy tests. The test runner will automatically re-run on each policy update. You can use the Policy debugger to debug any failed assertions.
Policy debugger
It's common for a decision made by Oso Cloud to be different from the decision that you expected during the process of migrating across. When that happens, the Policy Debugger will help you to identify the "root cause" of this discrepancy. It does this by allowing you to visualize how any query was evaluated against the existing facts and policy as a tree, and allowing you to step through the evaluation to find out if it was an issue with the policy or data.
Currently, the Policy Debugger doesn’t support negation or comparisons. This will be supported soon!
Visualizing a query
When a query is run, the Policy Debugger presents it as a tree. Each node in the tree shows:
- All the different "ways" the query could have been satisfied (like an "OR" statement).
- The current way that you are inspecting for that query.
- The subconditions required for the current way you are inspecting to succeed (like an "AND" statement).
At the top level, the query is initially collapsed and looks something like this:
▶ allow(User{"alice"}, "view", Content{"foo"}) ← ● ○ →
Here’s how to read this:
The arrow on the left (▶ or ▼) lets you expand/collapse the query. If it is colored green, then the query succeeded. If it's red, then it failed.
The dots on the right show how many possible "ways" the query could have succeeded. For example, in the above, there are 2 possible ways that the allow query might have been satisfied. You're currently viewing the first one, shown by the "solid" circle.
If a dot is colored green, then it means that particular "way" succeeded. If it's red, then that particular way failed. Only one way has to succeed for the entire query to succeed.
You can scroll through each possible resolution path and inspect them one by one.
Stepping through the logic
Expanding the query reveals the full reasoning behind this result:
▼ allow(User{"alice"}, "view", Content{"foo"}) ← ● ○ → ▼ has_permission(User{"alice"}, "view", Content{"foo"}) ← ○ ● ○ ○ → ▼ has_role(User{"alice"}, "CanView", Folder{"foo-folder"}) ● ♦ (fact) ▼ has_relation(Content{"foo"}, "folder", Folder{"foo-folder"}) ● ♦ (fact)
Here's how to read this:
- The
allow
query succeeds if the shownhas_permission
query succeeds. - The
has_permission
expression succeeds if thehas_role
query succeeds AND thehas_relation
query succeeds. There are also 3 other "ways" thathas_permission
could have succeeded. - The only "way" the
has_role
orhas_relation
queries could be true is if they existed in the database. There are no other ways based on the policy.
When there are multiple ways for a query to succeed (allow
and has_permission
above), you will see a scrollable widget which allows
you to see how many possible ways there are, and the current way you are inspecting. The debugger only shows you
one way at a time to avoid overwhelming you, but lets you explore them all.
Feedback
We are actively iterating on developer experience and would appreciate all feedback on the Oso Migrate and the broader development experience with Oso Cloud. Please do not hesitate to reach out on Slack (opens in a new tab) and/or submit ideas via the feature request portal (opens in a new tab) in the Oso Cloud UI.