Context
flowintel is an open-source threat intelligence management platform that supports case/event tracking, MISP/OpenCTI integration, and a REST API. Adding it to the catalog makes it available as a ready-to-deploy service for range exercises requiring a collaborative threat intel workflow (e.g. SOC analyst tracks, DFIR scenarios).
Proposed Solution
Deploy flowintel as a Docker Compose stack in the catalog, with an Ansible role that:
- Brings up the stack (app + DB + optional Redis)
- Provisions initial users (trainee / admin accounts) via the flowintel API at deploy time
- Provisions API keys per user and writes them to Ansible facts / group vars so downstream roles can consume them (e.g. for MISP connector config)
Technology
flowintel ships its own docker-compose.yml. We wrap it in the catalog's 03_container_layer/docker/ pattern and drive provisioning via Ansible + the flowintel REST API.
| Property |
Value |
| Upstream image |
flowintel/flowintel (Docker Hub / ghcr.io) |
| Database |
PostgreSQL (bundled in upstream compose) |
| Auth |
Session + API key (REST /api/user, /api/key) |
| MISP integration |
Optional — configurable via env |
| Air-gapped support |
✅ with pre-pulled image |
| Complexity |
Low-Medium |
Architecture
[Proxmox Host]
├── VM: flowintel-server (admin tier)
│ └── Docker Compose
│ ├── flowintel app :5000
│ └── postgresql :5432
│
├── VM: trainee-01 → flowintel REST API (API key auth)
└── VM: admin-01 → flowintel web UI (user/pass)
Implementation Plan
This touches 3 repos.
range42-catalog (primary)
New Docker stack:
03_container_layer/docker/flowintel/
docker-compose.yml # flowintel app + postgresql
.env.example # FLOWINTEL_SECRET_KEY, DB_PASSWORD, ADMIN_EMAIL, PORT
New Ansible role — server (02_ansible_layer/admin/roles/flowintel_deploy/):
tasks/
main.yml # orchestrates sub-tasks
01_install.yml # docker compose up -d, wait for /api/ping
02_provision_users.yml # POST /api/user for each item in flowintel_users[]
03_provision_keys.yml # POST /api/key per user, store in hostvars
04_verify.yml # GET /api/ping + auth smoke test
vars/
main.yml # default port, image tag, retry intervals
defaults/
main.yml # flowintel_users: [], flowintel_admin_email, flowintel_image_tag
flowintel_users default shape (override in group_vars):
flowintel_users:
- username: admin
email: "{{ flowintel_admin_email }}"
role: admin
generate_api_key: true
- username: trainee
email: trainee@range42.local
role: analyst
generate_api_key: true
Provisioned API keys are registered as hostvars['flowintel-server']['flowintel_api_keys'] (dict keyed by username) so downstream roles (MISP connector, scenario setup) can reference them without manual copy-paste.
Secrets (DB password, secret key) go through Ansible Vault — no plaintext in group_vars.
range42-playbooks
New bundle:
bundles/infra/flowintel/main.yml # applies flowintel_deploy to [flowintel] group
range42
Inventory changes:
# hosts.yml
[flowintel]
flowintel.range42.local ansible_host=<TBD>
# group_vars/flowintel/vars.yml
flowintel_image_tag: "latest"
flowintel_port: 5000
# group_vars/flowintel/vault.yml (Ansible Vault)
flowintel_db_password: !vault |...
flowintel_secret_key: !vault |...
Risks & Mitigations
| Risk |
Severity |
Mitigation |
| flowintel API changes between image versions |
Medium |
Pin flowintel_image_tag in group_vars; add assert task that checks /api/version >= expected |
| User provisioning is not idempotent if flowintel has no PUT /user |
Medium |
Check user existence with GET /api/user?username= before POST; use changed_when properly |
| API key values not persisted — lost on re-run |
High |
Store generated keys in a vault-encrypted file on the deploy host on first run; skip re-generation if file exists |
| DB password in compose env file |
High |
.env generated from Vault at deploy time; never committed; added to .gitignore in Docker stack dir |
| Port 5000 conflicts with other services |
Low |
flowintel_port var; default 5000, override per host |
Open Questions for the Team
- Image source — use upstream
flowintel/flowintel directly, or mirror to a private registry for air-gapped exercises?
- User seed list — should
flowintel_users come from a shared group_vars/all/users.yml so the same user list feeds MISP, flowintel, and future tools from one source?
- Persistence between exercises — should the DB volume persist across scenario resets (continuous intel timeline) or be wiped and re-seeded each time?
- MISP sync — is MISP already in scope for the next exercise track? If yes, the flowintel → MISP connector config should be wired in this role.
- Deployer UI node — add an
InfraNodeFlowintel.vue node type now, or defer to when the UI topology designer is used for CTI exercises?
Affected Repos
/cc team — input on questions 2 (shared user seed) and 3 (DB persistence) is needed before implementation starts.
Context
flowintel is an open-source threat intelligence management platform that supports case/event tracking, MISP/OpenCTI integration, and a REST API. Adding it to the catalog makes it available as a ready-to-deploy service for range exercises requiring a collaborative threat intel workflow (e.g. SOC analyst tracks, DFIR scenarios).
Proposed Solution
Deploy flowintel as a Docker Compose stack in the catalog, with an Ansible role that:
Technology
flowintel ships its own
docker-compose.yml. We wrap it in the catalog's03_container_layer/docker/pattern and drive provisioning via Ansible + the flowintel REST API.flowintel/flowintel(Docker Hub / ghcr.io)/api/user,/api/key)Architecture
Implementation Plan
This touches 3 repos.
range42-catalog(primary)New Docker stack:
New Ansible role — server (
02_ansible_layer/admin/roles/flowintel_deploy/):flowintel_usersdefault shape (override in group_vars):Provisioned API keys are registered as
hostvars['flowintel-server']['flowintel_api_keys'](dict keyed by username) so downstream roles (MISP connector, scenario setup) can reference them without manual copy-paste.Secrets (DB password, secret key) go through Ansible Vault — no plaintext in
group_vars.range42-playbooksNew bundle:
range42Inventory changes:
Risks & Mitigations
flowintel_image_tagin group_vars; addasserttask that checks/api/version>= expectedGET /api/user?username=before POST; usechanged_whenproperly.envgenerated from Vault at deploy time; never committed; added to.gitignorein Docker stack dirflowintel_portvar; default 5000, override per hostOpen Questions for the Team
flowintel/flowinteldirectly, or mirror to a private registry for air-gapped exercises?flowintel_userscome from a sharedgroup_vars/all/users.ymlso the same user list feeds MISP, flowintel, and future tools from one source?InfraNodeFlowintel.vuenode type now, or defer to when the UI topology designer is used for CTI exercises?Affected Repos
range42-catalog— Docker stack +flowintel_deployAnsible rolerange42-playbooks— newbundles/infra/flowintel/bundlerange42— inventory host group + group vars + vault entries/cc team — input on questions 2 (shared user seed) and 3 (DB persistence) is needed before implementation starts.