Local Authorization
Oso can make decisions based on data stored in your own database, which is known as local authorization. In these cases, Oso never needs to read the data, only understand its structure.
How local authorization affects authorization decisions
When Oso receives an authorization request, it evaluates the policy to aggregate sets of facts (typically represented in authorization data) which, if true, allow the request.
When using one of Oso's local check APIs (which rely on local authorization), the Oso client returns a SQL expression which you can use to perform the authorization decision locally. Depending on which type of authorization you're performing, the expression can either be a whole query or a fragment that you use inside another query.
Local authorization vs. other authorization data
Centralized authorization data and context facts affect local authorization decisions. That is to say that when evaluating a request sent via local authorization, Oso evaluates all available authorization data before determining the SQL expression to generate.
For example, if your environment contains centralized auth data that would
authorize a request, local authorization returns a query equivalent to SELECT true
because there's no need to further evaluate your data to allow the
request.
When to use local authorization
In general, Oso recommends using local authorization unless:
- Data can only be known by the requestor's environment. In these cases, use Context facts.
- Some services that require authorization don't have access to the database containing the authorization data. In these cases, centralize the data in Oso.
- The impact of making authorization decisions is too great for the database containing the authorization data. In these cases, centralize the data in Oso.
For details about local authorization versus other strategies, see Authorization data.
Local authorization overview
To use local authorization, at a high level:
- Configure how facts in your policy correlate to tables in your database using a local authorization config file.
- Call the local check API in your application's authorization
enforcement code. This API returns a SQL expression––either a full query or a
fragment to use in another query's
WHERE
clause. - Use the returned SQL fragment when querying the database storing the data that affects authorization.
For a more thorough description of using local authorization, see the guide on filtering lists with local data.
Local authorization config
When Oso evaluates authorization requests based on your policy, it aggregates sets of facts which, if true, allow the request.
In the context of local authorization, this means that Oso needs to understand
how to translate facts (e.g. has_role(User{"alice"}, "admin", Organization{"acme"})
) into expressions your database can evaluate.
To describe this translation, you provide Oso a "local authorization config," which is a YAML file with the following structure:
# One entry per fact signature in your databasefacts: # Ex: # has_relation(Issue:_, parent, Repository:_): <predicate> ([<type>:]{_, <id>} )*: # Ex: # query: SELECT id, repository FROM issues query: <sql_query># Optional map of resource <type>s to their data type in your application databasesql_types: # Ex: # Issue: UUID <type>: <sql_data_type>
facts
The facts
section is required and is where the majority of your configuration
logic lives. It consists of one key for each fact signature stored in your
database. Fact signatures are specified with a predicate (e.g. has_role
,
has_relation
etc.) followed by a parenthesized list of comma-separated
arguments (e.g. User:_
, String:admin
etc.).
Each fact signature must have a query
value, which specifies the SQL query
that will be used to look up facts matching this signature.
The _
symbol in a fact signature indicates that the <id>
of the entity is a
variable returned by the query
. For example, the signature
has_relation(Issue:_, String:parent, Repository:_):
says: "this SQL query returns one row for every Issue
that has a parent
Repository
".
The query
value for a signature must return the same number of columns as
there are wildcards in the signature. This means that for the signature above,
this
SELECT issue.id, 'parent', issue.parent_repoFROM issue
is invalid. A valid query would be
SELECT issue.id, issue.parent_repoFROM issue
Additional Restrictions
-
Fact signatures must be used by rules in your policy (they wouldn't do much if they didn't).
-
Fact signatures also must not "overlap", meaning they must all be mutually exclusive. Consider this example:
facts:is_protected (Repository:_, Boolean:true):query: |-select idfrom repositorywhere is_protectedis_protected (Repository:_, Boolean:false):query: |-select idfrom repositorywhere !is_protectedThese facts don't overlap, so this is valid! On the other hand:
facts:is_protected (Repository:_, Boolean:_):query: |-select id, is_protectedfrom repositoryis_protected (Repository:_, Boolean:false):query: |-select idfrom repositorywhere !is_protectedThese facts do overlap. Slightly more formally the set of facts matching the signature
is_protected (Repository:_, Boolean:_)
contains the set of facts matching the signatureis_protected (Repository:_, Boolean:false)
. -
Each
query
must be aSELECT
statement (Technically a<query specification>
per SQl-92 (opens in a new tab)). Common table expressions (CTEs), subqueries, and set expressions likeUNION
are allowed, but each of these must also be aSELECT
. The following is not valid, because the CTE is anUPDATE
statement:WITH inserted as (UPDATE issueSET parent_repo = 1RETURNING id, parent_repo)SELECT id, parent_repoFROM inserted
sql_types
sql_types
maps resources in Oso Cloud (e.g. Issue
, Repository
) to their
data types in your application database. This allows authorization queries
returned by the local check API to more effectively use indexes, improving
query performance.
The sql_types
section is optional, but strongly recommended.
Example
facts: has_relation(Issue:_, parent, Repository:_): query: SELECT id, repository FROM issuessql_types: Issue: UUID Repository: UUID
This example tells Oso that has_relation
facts that associate issues with
their parent repositories can be resolved by running a query (SELECT id, repository FROM issues
) against your application's database.
Validate local authorization config
You can validate the contents of the local authorization configuration using the oso-cloud CLI.
Note that you should run this check as part of your CI/CD workflow. For more guidance, see CI and Testing.
Guides
Local Check API
After configuring local authorization for your application, the local check API allows you to perform authorization using data that's distributed across Oso Cloud and your own database.
The methods are documented under the appropriate Client SDK:
Note that centralized authorization data still affects local authorization decisions.
Limitations
Oso Local Authorization currently has the following limitations:
- Only supports PostgreSQL. The minimum tested version is PostgreSQL 14.10.
- Does not support:
- Context facts
- Recursive logic involving data in your database
- Logic that may allow all resources depending on data in your database
- Inequality comparisons between data stored in Oso Cloud and data stored in your database
Related content
- For a comprehensive example of local authorization, see Ruby on Rails list filtering sample app (opens in a new tab)
- Facts: The Oso Cloud data model