Technical — OPA Data
RAGA's policy data synchronization service, built on Bun + Express 5. It pulls user-role and role-grant data from user-service, merges it into a shared JSON file, then pushes that data to OPA (Open Policy Agent) via its data API. Called by user-service whenever a role or policy changes.
Repository
| Key | Value |
|---|---|
| Git Remote | https://git.tlab.co.id/tarantula/tarantula-v2/opa-bun/opa-data.git |
| Active Branch | main |
git clone https://git.tlab.co.id/tarantula/tarantula-v2/opa-bun/opa-data.git
cd opa-dataTech Stack
| Layer | Technology |
|---|---|
| Runtime | Bun 1 |
| Framework | Express 5 (TypeScript) |
| HTTP Client | Axios |
| File I/O | Bun native (file, write) |
| Secret Management | Infisical SDK v5 |
| Logging | Morgan (HTTP request log) |
| Build | bun build --compile → single binary |
Environment Variables
There is no .env file — all variables are injected as container env vars.
Bootstrap (container env vars)
| Variable | Default | Description |
|---|---|---|
NODE_PORT | 3666 | Express server port |
INFISICAL_SITE_URL | https://app.infisical.com | Infisical server URL |
INFISICAL_CLIENT_ID | — | Universal Auth Client ID |
INFISICAL_CLIENT_SECRET | — | Universal Auth Client Secret |
INFISICAL_ENVIRONMENT | — | Target environment (dev / staging / prod) |
INFISICAL_PROJECT_ID | — | Infisical project ID |
INFISICAL_PATH | — | Secrets path in Infisical |
Secrets via Infisical
| Variable | Description |
|---|---|
URL_USER | user-service base URL; used to fetch /open-api/role-users and /open-api/role-grants |
URL_OPA | OPA server base URL; used to PUT data to {URL_OPA}/v1/data |
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /role-users | Syncs the user → role mapping from user-service to OPA |
GET | /role-grants | Syncs the role → policy grants mapping from user-service to OPA |
GET | /health | Health check → { status: "UP" } |
If the response from user-service isn't a 200, opa-data replies 403 { info: "Conflict in timetable (blocked by OPA)" } (that message is from the code as-is, not an actual OPA conflict). If an exception occurs (e.g. user-service or OPA is unreachable), opa-data replies 500 { info: "Internal Server Error" }.
Sync Flow
Data Structure in OPA
The data pushed to OPA (PUT /v1/data) is JSON shaped as:
{
"user_roles": {
"john_doe": ["admin"],
"jane_smith": ["viewer"]
},
"role_grants": {
"admin": [
{ "id": 1, "name": "read", "label": "Read Workspace", "method": "GET", "path": "/workspace", "status": true },
{ "id": 2, "name": "write", "label": "Write Workspace", "method": "POST", "path": "/workspace", "status": true }
],
"viewer": [
{ "id": 1, "name": "read", "label": "Read Workspace", "method": "GET", "path": "/workspace", "status": true }
]
}
}Each grant in role_grants[role] isn't transformed by opa-data — it's copied as-is from the policies field of user-service's GET /open-api/role-grants response (id, name, label, method, path, status). The method, path, and status fields are the ones actually consumed by the Rego policy in OPA Config (grant.method, grant.path with * wildcards, grant.status == true); id, name, and label just come along as reference metadata. The PUT /v1/data request to OPA is sent with header Content-Type: application/json-patch+json.
This data is used by the Rego policy in OPA Config to make authorization decisions, which are then consumed by OPA Auth.
The /share/data.json File
This file lives on a shared volume between the opa-data container and OPA. On every sync:
- The file is read (created fresh with
{}if missing) - New data is merged (
Object.assign) into the existing data - The file is rewritten
- The same file content is PUT to the OPA data API
Position in the Architecture
Build & Run
# Install dependencies
bun install
# Run development
bun run index.ts
# Build a single binary (used in Docker)
bun build --compile --minify --sourcemap --target=bun-linux-x64 ./index.ts --outfile opa-data
# Run the binary
./opa-dataDocker
docker build -t opa-data .
docker run -p 3666:3666 \
-v /host/share:/share \
-e INFISICAL_SITE_URL=http://... \
-e INFISICAL_CLIENT_ID=... \
-e INFISICAL_CLIENT_SECRET=... \
-e INFISICAL_ENVIRONMENT=dev \
-e INFISICAL_PROJECT_ID=... \
-e INFISICAL_PATH=... \
opa-dataThe
-v /host/share:/sharevolume is required so thedata.jsonfile can be read by the OPA container.