Local Authorization

Local Authorization

Oso can make decisions based on data stored in your own database, which is known as Local Authorization. Oso never needs to read the data, only understand its structure.

By understanding your database's schema, Oso can then produce complete or partial SQL queries to be run against your database. These expressions reflect the authorization decisions set by your policy, such as:

  • Evaluating to a boolean value to express authorization
  • Filtering rows representing unauthorized resources from a query

When to use Local Authorization

  • Your service uses a PostgreSQL database

  • All data you need to make authorization decisions is available:

    For example, in a microservice architecture, services that introduce their own roles on resources (such as file sharing, which might offer reader and writer roles) might use Local Authorization, so long as the tenant-level roles for users (managed by another service) are synchronized to Oso as centralized authorization data.

  • Your database can handle the additional load of making authorization decisions

  • The service's authorization does not rely on any of these currently unsupported features:

    • 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

In other cases, use centralized authorization data.

Overview

To use Local Authorization, at a high level:

  1. Configure how facts in your policy correlate to tables in your database using a Local Authorization config file.
  2. 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.
  3. 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.

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 database
facts:
# 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 database
sql_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_repo
FROM issue

is invalid. A valid query would be


SELECT issue.id, issue.parent_repo
FROM 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 id
    from repository
    where is_protected
    is_protected (Repository:_, Boolean:false):
    query: |-
    select id
    from repository
    where !is_protected

    These facts don't overlap, so this is valid! On the other hand:


    facts:
    is_protected (Repository:_, Boolean:_):
    query: |-
    select id, is_protected
    from repository
    is_protected (Repository:_, Boolean:false):
    query: |-
    select id
    from repository
    where !is_protected

    These facts do overlap. Slightly more formally the set of facts matching the signature is_protected (Repository:_, Boolean:_) contains the set of facts matching the signature is_protected (Repository:_, Boolean:false).

  • Each query must be a SELECT statement (Technically a <query specification> per SQl-92 (opens in a new tab)). Common table expressions (CTEs), subqueries, and set expressions like UNION are allowed, but each of these must also be a SELECT. The following is not valid, because the CTE is an UPDATE statement:


    WITH inserted as (
    UPDATE issue
    SET parent_repo = 1
    RETURNING id, parent_repo
    )
    SELECT id, parent_repo
    FROM 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 issues
sql_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.

Details

Local Authorization vs. other authorization data

  • Centralized authorization data affects the SQL expressions that Local Authorization returns.

    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.

Related content