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.
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.
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
idmatches, the current instance is updated. - The SDK implements a client that sends a POST request to
/api/discovery/registerwith 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 Robinwrr— Weighted Round Robinrand— 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:
| Key | Description | Example |
|---|---|---|
is_active | "Permission active" flag | false | true |
service | Service name | core |
resource | Resource type | collections |
action | Action | read, create |
object_conditions | Object conditions | Slug must be in the list |
subject_type | Subject type | user |
subject_conditions | User conditions | User role |
created | Creation date | |
modified | Modification 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/$oroperators.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.