Decisions

A service describes its decisions as a type: the name of each, what it is asked, and what it answers.

type IdDecisions = {
  authz: {
    input: { user: { name: string | null }; verb: string; resource: { resource: string; owner?: string } }
    result: { allow: boolean; reason?: string }
  }
  release: {
    input: { user: { name: string }; client: { id: string }; scopes: string[] }
    result: { scopes: string[]; reasons?: Record<string, string> }
  }
}

and answers them with createPolicy:

const policy = createPolicy<IdDecisions>({
  package: 'fairgarden/id',
  builtIn: {
    authz: ({ user, resource }) =>
      resource.owner && resource.owner !== user.name
        ? { allow: false, reason: 'That belongs to someone else.' }
        : { allow: user.name !== null },
    release: ({ scopes }) => ({ scopes }),
  },
})

const { allow, reason } = await policy.decide('authz', input)

Built-in rules

Every decision has them. They answer when there is no organization policy, and for any decision the organization's policy has no rule for — so a service behaves the same without one, and works on its own, outside a distribution. The Rego a service ships decides the same way until an organization adds to it.

Inputs

Name inputs after something a policy author already knows. The id service's authz input is a Kubernetes SubjectAccessReview's attributes — user, verb, resource with its group, version, name and owner — so a Rego rule reads the way an RBAC rule does. Put who the decision is about in input.user.name: the decision log files entries under it.

Failing closed

decide throws PolicyUnavailableError when a decision cannot be made: the rule has no value for the input, the bundle cannot be read or was refused, the OPA server does not answer, a built-in rule throws. Treat it as no. A refused bundle decides nothing at all, not even what the built-in rules would: running rules nobody vouched for is worse than running none. The failure is logged like any other decision, with error in place of result.