Deploying Orbital
Data access policies
Taxi supports defining data access policies against types, which are evaluated at runtime.
This is a powerful capability, as it allows you to define data policies once, and enforce them consistently across your organisation, regardless of where data is served from.
model Employee {
id : EmployeeId
manager : ManagerId
salary : Salary
}
policy SalaryPolicy against Employee {
// policies are either 'read' or 'write'
read {
// An employee can read their own data
case caller.EmployeeId == this.id -> permit
// The employee's manager can read data
case caller.EmployeeId == this.manager -> permit
// HR can read data
case caller.DepartmentName == "HR" -> permit
// For everyone else, filter the salary information
else -> filter(salary)
}
}
Overview
Policies are a first-class citizen within Taxi, and are defined in your taxi project along with your types, models and services.
Policies are enforced when queries are being executed, and can be defined against either a type or a model.
Policies define simple expressions that are evaluated, and inform how the data should be treated:
permit
: allows the data to flow through or for the operation to continuefilter
: replaces the data withnull
filter(attributeA,attributeB)
: replaces specific attributes withnull
, but allows the rest of the data to be read
The "caller"
Policies are defined against the caller
- the person or service requesting the data. The caller
itself is resolved using the auth token presented from your IDP.
(Note - policies are not supported when running without an IDP)
Similar to how you can use claims from your IDP to pass
through to a data source, these same claims are available when evaluating a policy, as the caller
object.
Resolving data for policy evaluation
Policies can be defined against any data that is discoverable using an Orbital query - not just the values present on the inbound claim. A seperate subquery is executed to discover data that is needed to evaluate the policy.
For example:
policy SalaryPolicy against Employee {
read {
// HR can read data
case caller.DepartmentName == "HR" -> permit
}
}
Here, the policy is defined against the caller’s Deperatment Name, which may not be available in the inbound claim, but is disoverable using the user’s Id.
Let’s imagine the users credentials are presented as follows:
model AcmeAuthClaims inherits JtwClaim {
userId : UserId inherits String
}
model UserInformation {
department : DepartmentName
}
service UserService {
operation getUserInformation(UserId):UserInformation
}
In this example, in order to evaluate the policy, the DepartmentName
is required. By executing a subquery
using the information on the AcmeAuthClaims
, Orbital invokes the getUserInformation
operation to discover
the DepartmentName
Policy scopes
When Orbital is executing a query, data is exposed in two places:
- From services to Orbital, in order to execute the query. This is known as
internal
scope - as the data stays within Orbital. - From Orbital back to the initiating user. This is known as
external
scope - as the data leaves Orbital.
Often, it’s ok for data to be used in order to look up values, provided it’s not exposed to the end user.
Policy scopes account for this by allowing policies to restrict either internal
or external
For example:
policy SalaryPolicy against Employee {
// It's ok for Orbital to access all employee data, provided
// the data isn't leaked out to the user.
read internal {
permit
}
// When returning data to the user, apply these policies.
read external {
// An employee can read their own data
case caller.EmployeeId == this.id -> permit
// The employee's manager can read data
case caller.EmployeeId == this.manager -> permit
// HR can read data
case caller.DepartmentName == "HR" -> permit
else -> filter(salary)
}
}
Internal needs