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 continue
  • filter : replaces the data with null
  • filter(attributeA,attributeB) : replaces specific attributes with null, 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

If an internal policy restricts Orbital from reading data, values cannot be used as inputs into other services. Be careful, as this can cause queries to fail, even though there is sufficient data to correctly execute.
Previous
Authorization within Orbital
Next
Distributing work across a cluster