Check pipelines

You can add check pipelines to request units to perform checks on incoming requests. check pipelines needs only to explicitly green-light the incoming request, otherwise an error is returned to the caller without executing the main pipeline.

check pipelines can also be used to contribute to the rate limiting and access control features of Xapix.

Check Pipeline Protocol

A check pipeline needs to adhere to the following data structure:

result.pass - Boolean, required
result.error.body - String, optional; error body
result.error.status - Integer, optional; http error status

In case result.pass is set to false (or undefined), an error is returned to the user. If result.error.body and/or result.error.status are set, those are used to create the error response. In case multiple check pipelines fail and provide an error body / status, one of them is picked. It's recommended to always define both, error body and status. If no error body or status is defined, a 401 with the body "Access denied." is returned.

In case result.pass is set to true for all check pipelines, the request is allowed to proceed.

Custom Authentication Pipeline

The check pipeline can also be used to authenticate users, and pass details about the user back to the main pipeline. The result can also be used with the rate limiting and access control features of Xapix.

To use the rate limiting and access control features with check pipelines, you first need to set the user management to custom (see examples section below).

And one of the check pipeline needs to return the following data structure, with result.pass set to true in case the authorization passed:

result.pass - Boolean, required
result.user.id - String; user id to be used for rate limiting
result.user.roles - Array of Strings; roles to be used for access control and rate limiting

The result.user.id needs to be unique per user, and will be used to rate limit access as defined in the users' roles.

The result.user.roles needs to list the user's roles, which are used to control access and enforce rate limits as defined by those roles. The roles are looked up by their id. If the user has no roles or no matching roles, access will be denied.

Examples

Smallest Check Pipeline

For an easy example, let's assume you want to require basic authentication to be present, and otherwise deny access. In that case you could use the following check pipeline:

---
version: v1
kind: Pipeline
metadata:
id: simple-check
project: simple-check
definition:
units:
- version: v1
kind: Unit/Entry
metadata:
id: entry
definition:
name: Entry
parameters:
- name: header
type: object
properties:
- name: Authorization
type: string
sample: Basic dGVzdDp0ZXN0
- version: v1
kind: Unit/Result
metadata:
id: result
definition:
name: Result
inputs:
- entry
parameters:
- name: result
type: object
properties:
- name: pass
type: boolean
formula: "entry.header.Authorization = 'Basic dGVzdDp0ZXN0'"

The request pipeline would then look like this:

---
version: v1
kind: Endpoint/REST
metadata:
id: test
project: simple-check
definition:
name: test
path: test
httpMethod: get
pipeline:
units:
- version: v1
kind: Unit/Request
metadata:
id: request
definition:
name: Request
parameters:
- name: header
type: object
properties:
- name: Authorization
type: string
checkPipelines:
- simple-check
- version: v1
kind: Unit/Endpoint
metadata:
id: endpoint
definition:
name: Endpoint
inputs:
- request
parameters:
- name: body
type: object
properties:
- name: status
formula: '"ok"'

Note the checkPipelines in the Unit/Request resource which references the simple-check pipeline defined above.

Check Pipeline for Authentication, Rate Limiting and Access Controls

To switch a project to use a custom check pipeline for authentication, and to use rate limiting and access controls, apply the following ApiPublishing resource definition to switch user management to custom

---
version: v1
kind: ApiPublishing
metadata:
id: <project-id>
definition:
enabled: true
userManagement: custom

Then adjust the check-pipeline above to return result.user.id and result.user.roles.

Custom JWT Check Pipeline

For a more realistic example, let's assume you want to validate your custom JWTs and only permit access for users with valid tokens and valid roles. The check pipeline would look something like this:

---
version: v1
kind: Credential/PublicPrivateKey
metadata:
id: jwt-verification-key
definition:
name: JWT verification key
publicKey: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----
---
version: v1
kind: Pipeline
metadata:
id: jwt-request-check
definition:
units:
- version: v1
kind: Unit/Entry
metadata:
id: entry
definition:
name: Entry
parameters:
- name: header
type: object
properties:
- name: Authorization
type: string
sample: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoxNTgyMjE1ODYwLCJleHAiOjE1ODQ4MDg1NTV9.b_a_hY9svkBuSLwTr25VvGUfAddoYGV8bSG1dxvfHv5scgbVFfmfowOxqBtA4c2D_KmIIRfQsFGsBi9j3JMnKHmU6ABL1KJqqV7OmNlC4MQX-Ai0fySqTcC9kQ47CjrUdGybv38WLCr_iBGlV_FMD6bp51VmoJvqK6KlOu5M_DRmrVpuIoDcaoakZaV-dMI5oPIhDz9g0CCfSyOT1AkVz1kjVsa67afOW3meiynz7oGqsKxs4zpOaw7kUjFKc5aSfcUnsGeYZoWj6qk8mBD8C9mck4fNpOyPLxlHK7R0U7DtD_Kzi373AFVAsptPmM07RlIaOfWfmULArUy0RwExMQ
- version: v1
kind: Unit/SecureStore
metadata:
id: secure-store
definition:
name: Secure Store
credentials:
- jwt-verification-key
- version: v1
kind: Unit/Transform
metadata:
id: extract
definition:
name: Extract
inputs:
- entry
- secure-store
parameters:
- name: auth
type: string
sample:
formula: |
WITH(
jwt, REGEXEXTRACT(IF(entry.header.Authorization, entry.header.Authorization, ""), '^Bearer (.+)'),
key, KEY.PUB(secure-store.jwt-verification-key_public_key),
JWT.VERIFY('RS256', IF(jwt, jwt, ""), key)
)
- version: v1
kind: Unit/Result
metadata:
id: result
definition:
name: Result
inputs:
- extract
parameters:
- name: result
type: object
properties:
- name: pass
type: boolean
formula: |
AND(
NOT(.extract.auth.error),
NOW() >= .extract.auth.payload.nbf,
NOW() < .extract.auth.payload.exp)
sample: "true"
- name: user
type: object
properties:
- name: id
formula: ".extract.auth.payload.sub"
sample: "1234567890"
- name: roles
formula: ".extract.auth.payload.roles"
sample: "default_test_role"
- name: error
type: object
properties:
- name: body
formula: ".extract.auth.error.message"
- name: status
formula: "401"

This assumes that for your JWTs you're using the following format:

{
"sub": "1234567890",
"nbf": 1582215860,
"exp": 1584808555,
"roles": ["some_role"]
}

To check the JWT, only the public key is needed. If the key is valid and not yet expired (checking "nbf" and "exp", see line 82-83), the check pipeline returns the token's subject (line 89) and roles (line 92). The roles need to be created in Xapix, and access is only given if at least one role from the token matches with the list of roles which have access to the endpoint. If you do not want to check access permissions with Xapix, you can simply use a static role instead of taking them from the token.