Skip to content

feat(flowintel): add Dockerized flowintel deployment with user & API key provisioning #163

Description

@t0kubetsu

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:

  1. Brings up the stack (app + DB + optional Redis)
  2. Provisions initial users (trainee / admin accounts) via the flowintel API at deploy time
  3. 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

  1. Image source — use upstream flowintel/flowintel directly, or mirror to a private registry for air-gapped exercises?
  2. 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?
  3. Persistence between exercises — should the DB volume persist across scenario resets (continuous intel timeline) or be wiped and re-seeded each time?
  4. 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.
  5. 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

  • range42-catalog — Docker stack + flowintel_deploy Ansible role
  • range42-playbooks — new bundles/infra/flowintel/ bundle
  • range42 — 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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions