Skip to main content
Version: Next

Salt.Box Gateway

The Salt.Box Gateway is a component of the Salt.Box platform that implements an API Gateway for a microservices architecture.
It provides request routing, load balancing, service health checking, caching, and centralized access control based on OPA (Open Policy Agent) policies and an application permissions system.

The Salt.Box Gateway runs over the FastAPI framework and is integrated with Redis for keeping its state and caching, as well as OPA for permission checking.

tip

Permission is a security mechanism designed to protect user privacy.

It restricts application access to various functions and data on the device.

Architecture

Salt.Box Gateway components:

  • ServiceDAO — service data access layer (Redis)
  • ProxyService — main class for request proxying, load balancing, and access checking
  • BalancingStrategy — balancing strategy factory
  • AsyncOpaClient — client for checking OPA policies
  • CustomRedisCache — response caching

Key features

  • Dynamic service discovery and management (Service Discovery)
  • Load balancing (Round Robin, Weighted RR, Random)
  • Service instance health check
  • Response caching
  • Static file proxying
  • Access control via OPA and permissions
  • API for managing services and service instances
  • Swagger/OpenAPI documentation for services

Dynamic service discovery and management

Service management functions implemented by the Salt.Box Gateway:

  • Register/disable/delete services and their instances via API
  • Automatic status updates (health check)
  • Support for multiple instances of the same service

Registering a service in the Service Discovery

To register a new service (e.g., core), the Service Discovery mechanism is used.
When a service starts, it sends information about itself (name, endpoints, type, load balancing strategy, developer, etc.) to the Salt.Box Gateway via the API or directly to Redis.

tip

Endpoint is a specific network address, a URL, that defines the access point to a resource or service on the server.

The client application accesses the endpoint to perform certain operations or retrieve data.

Service registration is carried out according to the following algorithm:

  • If a service with the same name has not yet been registered, a single instance of the service is added.
  • If the service already exists, a new instance of this service is added; if the service id matches, the current instance is updated.
  • The SDK implements a client that sends a POST request to /api/discovery/register with a description of the service and its instance.
  • When registering, the service passes the Salt.Box Gateway a full list of its endpoints with their settings: access policy (OPA policy), caching parameters (cache_ttl), partial-query, action, etc.
  • After registration, the service appears in the gateway's list of available services and becomes available for proxying.
  • Based on the registered endpoints, the gateway performs access checks (via OPA) and caches responses according to the endpoint settings.

Load Balancing

The Salt.Box Gateway supports the following load balancing strategies:

  • rr — Round Robin
  • wrr — Weighted Round Robin
  • rand — Random strategy

Each service must have its own strategy defined, which can be changed via the API.

Caching

  • Only successful responses (200 OK) are cached if the endpoint supports TTL.
  • The cache is stored in Redis, namespace: gate_cache.
  • The cache is disabled for Swagger/OpenAPI and private endpoints.

Static file proxying

For services configured with static_host, requests to static files are proxied directly.

Authorization and access control

The Salt.Box Gateway implements a centralized access control system based on OPA (Open Policy Agent) and permissions, supporting the ABAC (Attribute-Based Access Control) model.

Solution benefits

  • ABAC: Access rights are determined based on user, resource, action, and service attributes.
  • Flexibility: Policies are grouped by services, such as core, scheduler, resources, such as tasks, jobs, collections, and actions, such as read, create.
  • Scalability: It's easy to add new services, resources, roles, and conditions.
  • Partial Query: OPA can return a filter to restrict data at the query level.

Integration with OPA (Open Policy Agent)

For each service endpoint, an OPA configuration is defined: policy, action, partial-query, unknowns.
The Salt.Box Gateway generates an input structure for OPA: subject (user), action, resource (service, path, query, body).
OPA returns permission (allow) and, if partial, a query for filtering data.

Example input structure for OPA:

{
"subject": {
"sub": "user_id",
"email": "user@example.com",
"roles": ["tasks_admin"]
},
"action": {
"method": "GET",
"name": "read"
},
"resource": {
"service_name": "core",
"path": ["tasks", "123"],
"query_params": {},
"body": null
}
}

Example policy (Rego):

package core.tasks.read

import data.utils

default allow := false

allow if utils.base.is_admin
allow if utils.base.is_tasks_admin
allow if utils.conditions.check_user_permissions(
data.permissions,
input.resource.service_name,
"tasks",
"read",
input.subject,
object_from_api
)

User object format

"subject": {
"sub": "26b9a3e6-2e80-40c7-8f84-a993a1282169",
"email": "master@example.com",
"email_verified": false,
"name": "Peter Johnson",
"roles": [
"test_common"
]
}

Permissions

Example of a permission

{
"is_active": true,
"service": "core",
"resource": "collections",
"action": "read",
"object_conditions": {
"slug": { "$in": ["linux", "alt"] }
},
"subject_type": "user",
"subject_conditions": {
"roles": { "$in": ["test_common"] }
},
"created": "2024-07-28T12:00:00Z",
"modified": "2024-07-28T12:00:00Z"
}

Permission structure:

KeyDescriptionExample
is_active"Permission active" flagfalse | true
serviceService namecore
resourceResource typecollections
actionActionread, create
object_conditionsObject conditionsSlug must be in the list
subject_typeSubject typeuser
subject_conditionsUser conditionsUser role
createdCreation date
modifiedModification date

Conditions format:

  • Conditions are specified as objects with operators ($in, $eq, $lt, etc.).
  • It is possible to combine different fields in conditions and use nested $and / $or operators.

    Example #1:
    "roles": { "$in": ["test_common"] } — the user must have one of the specified roles.

    Example #2:
    "slug": { "$in": ["linux", "alt"] } — the object must have a slug from the list.

Benefits of using permissions:

  • Flexibility: It is possible to define complex access rules without changing the code.
  • Scalability: It's easy to add new conditions and operators.
  • ABAC support: The conditions can take into account any attributes of the user and the object.
  • Centralized management: All permissions are described in a single format and location.

Partial Query

For some endpoints, OPA returns a partial query to filter data (for example, a task list).

The Salt.Box Gateway adds a filter to the query parameters of the service request.

OPA policies update

OPA policies are updated manually: you must edit the rego files and restart the OPA container for the changes to take effect.