Gateway service bridging GakuNin RDM and Flowable workflow engine. Flowable's open-source REST API supports only Basic authentication; this gateway accepts JWT-authenticated requests from RDM and securely proxies workflow access to GakuNin RDM APIs without exposing delegation tokens to Flowable.
- Copy
.env.sampleto.envand adjust the credentials that bootstrap Flowable REST. Set eitherRDM_KEYSET_PATH(local JSON file) orRDM_KEYSET_URL(RDM endpoint) so the gateway can load the RDM service's public keys used to sign workflow tokens. ConfigureGATEWAY_SIGNING_KEY_ID,GATEWAY_SIGNING_PRIVATE_KEY_PATH,DATABASE_URL, andENCRYPTION_KEYso the gateway can connect to PostgreSQL, sign outbound tokens, and encrypt stored delegation tokens. DefineRDM_ALLOWED_DOMAINS,RDM_ALLOWED_API_DOMAINS, andRDM_ALLOWED_WATERBUTLER_URLSwith comma-separatedscheme://host[:port]entries limiting which upstream hosts the delegation proxy will contact. - For local testing, copy
config/keyset.example.jsontoconfig/keyset.jsonand replace the sample key with the PEM-encoded RDM public key (asymmetric algorithms only: RS*/ES*). In integrated environments preferRDM_KEYSET_URLpointing at/api/v1/workflow/keyset/on the RDM backend. - Copy
config/gateway-dev-v1.key.exampletoconfig/gateway-dev-v1.key(or supply your own private key) so the gateway can sign requests back to RDM. - Build and start the stack:
docker compose up --build
- Flowable REST will be exposed on
http://127.0.0.1:8090/flowable-rest/. The gateway FastAPI stub listens onhttp://127.0.0.1:8088/.
Both services share the same network, so the gateway can reach Flowable at the internal URL defined by FLOWABLE_REST_BASE_URL (defaults to http://flowable:8080/flowable-rest).
Stop the stack with docker compose down when you are done.
Every gateway endpoint except /healthz expects an Authorization: Bearer <token> header. The token must be signed with a key whose kid is present in the keyset and satisfy any optional issuer/audience/claim constraints configured via environment variables. Operations that modify Flowable state require the scope workflow::delegate.
The gateway signs tokens with its own key pair before calling the RDM backend. Register the corresponding public key with RDM (see /api/v1/workflow/engine-keys/). With the environment variables above in place you can issue a token via the helper:
python - <<'PYTHON'
import datetime as dt
from gateway.signing import issue_gateway_token
payload = {
"sub": "gateway-dev",
"scope": "workflow::delegate",
"iat": dt.datetime.utcnow(),
"exp": dt.datetime.utcnow() + dt.timedelta(minutes=5),
}
token = issue_gateway_token(payload)
print(token)
PYTHONpython - <<'PYTHON'
import datetime as dt
import jwt
private_key_path = "/path/to/rdm-service.key" # RDM service private key used for local signing
with open(private_key_path, "r", encoding="utf-8") as fh:
private_key = fh.read()
payload = {
"sub": "local-dev",
"scope": "gateway:read",
"iat": dt.datetime.utcnow(),
"exp": dt.datetime.utcnow() + dt.timedelta(minutes=5),
}
headers = {"kid": "rdm-service-v1"}
print(jwt.encode(payload, private_key, algorithm="RS256", headers=headers))
PYTHONUse the printed token when calling e.g. GET http://127.0.0.1:8088/config. The matching public key must appear in config/keyset.json (or be published by the RDM backend for the same kid).
The gateway exposes GET /keyset, which returns the PEM for its configured signing key inside a JWKS-like document. Ensure .env sets GATEWAY_SIGNING_KEY_ID and that config/gateway-dev-v1.key (or your chosen file) holds a valid RSA/EC private key before starting the stack. With the containers up you can inspect the payload:
docker compose up -d
curl -s http://127.0.0.1:8088/keyset | python -m json.tool
docker compose downSupply this JSON to RDM when registering the engine to avoid copying PEM material manually.
Authenticated requests can proxy a small subset of Flowable's REST API. The gateway signs into Flowable with FLOWABLE_REST_APP_ADMIN_USER_ID / FLOWABLE_REST_APP_ADMIN_PASSWORD.
GET /flowable/process-definitions: returns Flowable'sservice/repository/process-definitionspayload (query parameters are forwarded verbatim).GET /flowable/process-instances: proxiesservice/runtime/process-instances.POST /flowable/process-instances: starts a process instance. Body mirrors Flowable's request schema:{ "processDefinitionId": "test:1:25", "name": "example", "businessKey": "rdm:node:abc123", "variables": [ {"name": "key", "value": "value", "type": "string"} ] }GET /flowable/tasks: proxiesservice/runtime/tasks. SupplyingbusinessKeyfilters tasks to process instances with that key.GET /flowable/history/historic-process-instances/{instance_id}: proxies Flowable history lookups for a specific process instance.POST /flowable/tasks/{task_id}: forwards task operations (default{"action": "complete"}mirrors Flowable).
Example start and complete cycle:
TOKEN="$(python - <<'PY'
import datetime as dt, jwt
with open('config/rdm-service.key') as fh:
key = fh.read()
now = dt.datetime.utcnow()
print(jwt.encode(
{
'sub': 'gateway-dev',
'scope': 'workflow::delegate',
'iat': now,
'exp': now + dt.timedelta(minutes=5),
},
key,
algorithm='RS256',
headers={'kid': 'rdm-service-v1'},
))
PY)"
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"processDefinitionId": "example:1:1001", "businessKey": "rdm:node:abc123"}' \
http://127.0.0.1:8088/flowable/process-instances
curl -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:8088/flowable/tasks?businessKey=rdm:node:abc123"
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"variables": [{"name": "approval", "value": true}]}' \
http://127.0.0.1:8088/flowable/tasks/<task-id>config/rdm-publication-approval.bpmn20.xml embeds the reviewer form as flowable:formProperty entries on the reviewTask. Flowable therefore exposes the same information through service/form/form-data, which the gateway translates into the field payload Ember expects.
Deploy the definition with the CLI helper:
python3 cli/deploy_workflow.py \
--asset RDM-flowable-gateway/config/rdm-publication-approval.bpmn20.xmlEnvironment defaults mirror the Docker stack. Override --flowable-url, --username, or --password to target a different Flowable instance. The script requires the httpx package; install the gateway requirements locally with pip install -r RDM-flowable-gateway/requirements.txt before running the command. Successful execution prints the deployment id and registered resources.
The gateway provides a secure proxy mechanism for Flowable workflows to access RDM APIs without exposing actual tokens.
-
Generate Encryption Key:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" -
Update your
.envfile: SetENCRYPTION_KEYto the value you generated in the previous step so the gateway can decrypt stored delegation tokens. -
Initialize Database:
docker compose run gateway python -m gateway.init_db
-
Token Storage: When RDM starts a workflow process, it sends delegation tokens to the gateway in the
delegationTokensfield. The gateway encrypts and stores these tokens in PostgreSQL. -
Proxy URL Generation: The gateway generates proxy URLs and adds them as Flowable process variables:
RDM_CREATOR_API_URL→http://gateway:8088/rdm/{process_instance_id}/creator/apiRDM_CREATOR_WEB_URL→http://gateway:8088/rdm/{process_instance_id}/creator/webRDM_CREATOR_WATERBUTLER_URL→http://gateway:8088/rdm/{process_instance_id}/creator/waterbutler
-
Workflow Access: Flowable workflows use these proxy URLs to access RDM services. The gateway automatically retrieves the appropriate token, decrypts it, and forwards the request with the
Authorizationheader.
<serviceTask id="fetchNode" name="Fetch Node Data">
<extensionElements>
<flowable:field name="url">
<flowable:expression>${RDM_CREATOR_API_URL}/v2/nodes/${RDM_NODE_ID}/</flowable:expression>
</flowable:field>
</extensionElements>
</serviceTask>No Authorization header is needed; the gateway adds it automatically.
- Encryption at Rest: All token values are encrypted using Fernet before storage
- Token Isolation: Actual tokens never appear in Flowable variables or history
- Role-Based Access: Separate tokens for creator, manager, and executor roles
- Strict Allowlist Enforcement: Process variables
RDM_DOMAIN,RDM_API_DOMAIN, andRDM_WATERBUTLER_URLare validated againstRDM_ALLOWED_DOMAINS,RDM_ALLOWED_API_DOMAINS, andRDM_ALLOWED_WATERBUTLER_URLSrespectively, preventing workflows from proxying tokens to untrusted hosts