From 96bac1d7b5fafbe67ff16b14e0a7422345b7543f Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sat, 30 May 2026 12:36:12 +0200 Subject: [PATCH 1/6] [docker] Bundle XTM One in the default stack Adds XTM One alongside OpenCTI in the default compose stack: - New pgsql-xtm-one service (pgvector/pgvector:pg17) with dedicated credentials and volume (pgsqlxtmonedata). - New xtm-one + xtm-one-worker services (published filigran images), reusing the existing redis and minio. - xtm-one exposed on host port 8090 (after OpenCTI 8080); BASE_URL and FRONTEND_URL both resolve to the templated ${XTM_ONE_EXTERNAL_SCHEME}://${XTM_ONE_HOST}:${XTM_ONE_PORT}. - OpenCTI wired to XTM One via XTM__XTM_ONE_URL / XTM__XTM_ONE_TOKEN and the shared PLATFORM_REGISTRATION_TOKEN. - .env.sample documents the XTM ONE block and the mandatory-to-rotate PLATFORM_REGISTRATION_TOKEN. --- .env.sample | 31 ++++++++++++++++- docker-compose.yml | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 7407c044..87f040a1 100644 --- a/.env.sample +++ b/.env.sample @@ -17,6 +17,10 @@ ELASTIC_MEMORY_SIZE=4G XTM_COMPOSER_ID=8215614c-7139-422e-b825-b20fd2a13a23 COMPOSE_PROJECT_NAME=xtm +# Shared secret used by OpenCTI to register itself with XTM One. +# The platforms sharing an XTM One instance MUST use the same value. +PLATFORM_REGISTRATION_TOKEN=ChangeMeWithGeneratedRandomString # [MANDATORY] Replace with a long random string (e.g. `openssl rand -hex 32`) + ########################### # OPENCTI # ########################### @@ -48,4 +52,29 @@ CONNECTOR_ANALYSIS_ID=4dffd77c-ec11-4abe-bca7-fd997f79fa36 ########################### CONNECTOR_OPENCTI_ID=dd010812-9027-4726-bf7b-4936979955ae -CONNECTOR_MITRE_ID=8307ea1e-9356-408c-a510-2d7f8b28a0e2 \ No newline at end of file +CONNECTOR_MITRE_ID=8307ea1e-9356-408c-a510-2d7f8b28a0e2 + +########################### +# XTM ONE # +########################### + +XTM_ONE_HOST=localhost +XTM_ONE_PORT=8090 +XTM_ONE_EXTERNAL_SCHEME=http +# Image tag for filigran/xtm-one and filigran/xtm-one-worker (e.g. rolling, testing-xtm-one, 1.260513.0) +XTM_ONE_VERSION=rolling +# Must match OPENCTI_ADMIN_EMAIL so XTM One's JWT email claim resolves to an +# existing user on the platform. +XTM_ONE_ADMIN_EMAIL=admin@opencti.io +XTM_ONE_ADMIN_PASSWORD=changeme +# Long random string (e.g. `openssl rand -hex 32`). Used to sign sessions/tokens. +XTM_ONE_SECRET_KEY=ChangeMeWithGeneratedRandomString +# Credentials for the dedicated pgsql-xtm-one Postgres instance. +XTM_ONE_POSTGRES_USER=xtmone +XTM_ONE_POSTGRES_PASSWORD=ChangeMe +# Optional: bucket name in MinIO (auto-created on first boot) +XTM_ONE_S3_BUCKET=xtm-one-files +# Optional: enterprise license PEM (leave empty in xtm_one mode) +XTM_ONE_ENTERPRISE_LICENSE= +XTM_ONE_LOG_LEVEL=info +XTM_ONE_LOG_FORMAT=json \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 244f4688..57b668a3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -149,6 +149,9 @@ services: - SMTP__PORT=25 - PROVIDERS__LOCAL__STRATEGY=LocalStrategy - APP__HEALTH_ACCESS_KEY=${OPENCTI_HEALTHCHECK_ACCESS_KEY} + # XTM One + - XTM__XTM_ONE_URL=http://xtm-one:4000 + - XTM__XTM_ONE_TOKEN=${PLATFORM_REGISTRATION_TOKEN} ports: - "${OPENCTI_PORT}:8080" depends_on: @@ -328,9 +331,92 @@ services: opencti: condition: service_healthy + ########################### + # XTM ONE # + ########################### + + pgsql-xtm-one: + # Dedicated pgvector-enabled instance for XTM One. + image: pgvector/pgvector:pg17 + environment: + POSTGRES_USER: ${XTM_ONE_POSTGRES_USER} + POSTGRES_PASSWORD: ${XTM_ONE_POSTGRES_PASSWORD} + POSTGRES_DB: xtm_one + volumes: + - pgsqlxtmonedata:/var/lib/postgresql/data + restart: always + healthcheck: + test: [ "CMD", "pg_isready", "-U", "${XTM_ONE_POSTGRES_USER}", "-d", "xtm_one" ] + interval: 10s + timeout: 5s + retries: 5 + + xtm-one: + image: filigran/xtm-one:${XTM_ONE_VERSION:-rolling} + environment: + - PLATFORM_MODE=xtm_one + - PLATFORM_REGISTRATION_TOKEN=${PLATFORM_REGISTRATION_TOKEN} + - BASE_URL=${XTM_ONE_EXTERNAL_SCHEME}://${XTM_ONE_HOST}:${XTM_ONE_PORT} + - FRONTEND_URL=${XTM_ONE_EXTERNAL_SCHEME}://${XTM_ONE_HOST}:${XTM_ONE_PORT} + - ADMIN_EMAIL=${XTM_ONE_ADMIN_EMAIL} + - ADMIN_PASSWORD=${XTM_ONE_ADMIN_PASSWORD} + - SECRET_KEY=${XTM_ONE_SECRET_KEY} + - DATABASE_URL=postgresql+asyncpg://${XTM_ONE_POSTGRES_USER}:${XTM_ONE_POSTGRES_PASSWORD}@pgsql-xtm-one:5432/xtm_one + - REDIS_URL=redis://redis:6379 + - S3_ENDPOINT=minio:9000 + - S3_ACCESS_KEY=${MINIO_ROOT_USER} + - S3_SECRET_KEY=${MINIO_ROOT_PASSWORD} + - S3_BUCKET=${XTM_ONE_S3_BUCKET:-xtm-one-files} + - S3_USE_SSL=false + - LOG_LEVEL=${XTM_ONE_LOG_LEVEL:-info} + - LOG_FORMAT=${XTM_ONE_LOG_FORMAT:-json} + - ENTERPRISE_LICENSE=${XTM_ONE_ENTERPRISE_LICENSE:-} + # Internal API endpoint for the OpenCTI integration (Docker hostname) + - OPENCTI_API_URL=http://opencti:8080 + ports: + - "${XTM_ONE_PORT}:4000" + depends_on: + pgsql-xtm-one: + condition: service_healthy + redis: + condition: service_healthy + minio: + condition: service_healthy + restart: always + healthcheck: + test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:4000/api/health')\""] + interval: 15s + timeout: 10s + retries: 5 + start_period: 60s + + xtm-one-worker: + image: filigran/xtm-one-worker:${XTM_ONE_VERSION:-rolling} + environment: + - PLATFORM_MODE=xtm_one + - PLATFORM_REGISTRATION_TOKEN=${PLATFORM_REGISTRATION_TOKEN} + - ADMIN_EMAIL=${XTM_ONE_ADMIN_EMAIL} + - ADMIN_PASSWORD=${XTM_ONE_ADMIN_PASSWORD} + - SECRET_KEY=${XTM_ONE_SECRET_KEY} + - DATABASE_URL=postgresql+asyncpg://${XTM_ONE_POSTGRES_USER}:${XTM_ONE_POSTGRES_PASSWORD}@pgsql-xtm-one:5432/xtm_one + - REDIS_URL=redis://redis:6379 + - S3_ENDPOINT=minio:9000 + - S3_ACCESS_KEY=${MINIO_ROOT_USER} + - S3_SECRET_KEY=${MINIO_ROOT_PASSWORD} + - S3_BUCKET=${XTM_ONE_S3_BUCKET:-xtm-one-files} + - S3_USE_SSL=false + - LOG_LEVEL=${XTM_ONE_LOG_LEVEL:-info} + - LOG_FORMAT=${XTM_ONE_LOG_FORMAT:-json} + - ENTERPRISE_LICENSE=${XTM_ONE_ENTERPRISE_LICENSE:-} + depends_on: + xtm-one: + condition: service_healthy + restart: always + volumes: esdata: s3data: redisdata: amqpdata: rsakeys: + pgsqlxtmonedata: From 935953ac26bf590d9974c5bb0223359805aca563 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sat, 30 May 2026 12:41:27 +0200 Subject: [PATCH 2/6] [docker] Use published xtmone/platform and xtmone/worker images The XTM One images are published on Docker Hub as xtmone/platform and xtmone/worker, not filigran/xtm-one(-worker). --- .env.sample | 2 +- docker-compose.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.sample b/.env.sample index 87f040a1..ab777de3 100644 --- a/.env.sample +++ b/.env.sample @@ -61,7 +61,7 @@ CONNECTOR_MITRE_ID=8307ea1e-9356-408c-a510-2d7f8b28a0e2 XTM_ONE_HOST=localhost XTM_ONE_PORT=8090 XTM_ONE_EXTERNAL_SCHEME=http -# Image tag for filigran/xtm-one and filigran/xtm-one-worker (e.g. rolling, testing-xtm-one, 1.260513.0) +# Image tag for xtmone/platform and xtmone/worker (e.g. rolling, testing-xtm-one, 1.260513.0) XTM_ONE_VERSION=rolling # Must match OPENCTI_ADMIN_EMAIL so XTM One's JWT email claim resolves to an # existing user on the platform. diff --git a/docker-compose.yml b/docker-compose.yml index 57b668a3..5207b775 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -352,7 +352,7 @@ services: retries: 5 xtm-one: - image: filigran/xtm-one:${XTM_ONE_VERSION:-rolling} + image: xtmone/platform:${XTM_ONE_VERSION:-rolling} environment: - PLATFORM_MODE=xtm_one - PLATFORM_REGISTRATION_TOKEN=${PLATFORM_REGISTRATION_TOKEN} @@ -391,7 +391,7 @@ services: start_period: 60s xtm-one-worker: - image: filigran/xtm-one-worker:${XTM_ONE_VERSION:-rolling} + image: xtmone/worker:${XTM_ONE_VERSION:-rolling} environment: - PLATFORM_MODE=xtm_one - PLATFORM_REGISTRATION_TOKEN=${PLATFORM_REGISTRATION_TOKEN} From bc753ba841ae633466f8c02ecd19a238360ff0fb Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sat, 30 May 2026 13:00:36 +0200 Subject: [PATCH 3/6] [docker] Align admin emails to admin@filigran.io Use admin@filigran.io as the default admin email for OpenCTI and XTM One in .env.sample so the shared JWT email claim resolves on the platform. Admin emails live in .env.sample only; none are hard-coded in the compose file. Unify the XTM One comments to match the other docker repos. --- .env.sample | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.env.sample b/.env.sample index ab777de3..dcaaaaa2 100644 --- a/.env.sample +++ b/.env.sample @@ -17,8 +17,8 @@ ELASTIC_MEMORY_SIZE=4G XTM_COMPOSER_ID=8215614c-7139-422e-b825-b20fd2a13a23 COMPOSE_PROJECT_NAME=xtm -# Shared secret used by OpenCTI to register itself with XTM One. -# The platforms sharing an XTM One instance MUST use the same value. +# Shared secret used to register the platforms with XTM One. +# All platforms sharing an XTM One instance MUST use the same value. PLATFORM_REGISTRATION_TOKEN=ChangeMeWithGeneratedRandomString # [MANDATORY] Replace with a long random string (e.g. `openssl rand -hex 32`) ########################### @@ -28,7 +28,7 @@ PLATFORM_REGISTRATION_TOKEN=ChangeMeWithGeneratedRandomString # [MANDATORY] Repl OPENCTI_HOST=localhost OPENCTI_PORT=8080 OPENCTI_EXTERNAL_SCHEME=http -OPENCTI_ADMIN_EMAIL=admin@opencti.io +OPENCTI_ADMIN_EMAIL=admin@filigran.io OPENCTI_ADMIN_PASSWORD=changeme OPENCTI_ADMIN_TOKEN=ChangeMe_UUIDv4 OPENCTI_HEALTHCHECK_ACCESS_KEY=changeme @@ -63,9 +63,9 @@ XTM_ONE_PORT=8090 XTM_ONE_EXTERNAL_SCHEME=http # Image tag for xtmone/platform and xtmone/worker (e.g. rolling, testing-xtm-one, 1.260513.0) XTM_ONE_VERSION=rolling -# Must match OPENCTI_ADMIN_EMAIL so XTM One's JWT email claim resolves to an -# existing user on the platform. -XTM_ONE_ADMIN_EMAIL=admin@opencti.io +# Must match the admin email of the connected platform(s) so XTM One's JWT +# email claim resolves to an existing user. +XTM_ONE_ADMIN_EMAIL=admin@filigran.io XTM_ONE_ADMIN_PASSWORD=changeme # Long random string (e.g. `openssl rand -hex 32`). Used to sign sessions/tokens. XTM_ONE_SECRET_KEY=ChangeMeWithGeneratedRandomString From 83495172e92acc409c20f9bbd95a62dcee5bdff1 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sat, 30 May 2026 13:05:45 +0200 Subject: [PATCH 4/6] [docker] Pin XTM One images to latest Use xtmone/platform:latest and xtmone/worker:latest directly, matching the opencti/platform:latest convention, and drop the XTM_ONE_VERSION variable from .env.sample. --- .env.sample | 2 -- docker-compose.yml | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index dcaaaaa2..cc0d7d32 100644 --- a/.env.sample +++ b/.env.sample @@ -61,8 +61,6 @@ CONNECTOR_MITRE_ID=8307ea1e-9356-408c-a510-2d7f8b28a0e2 XTM_ONE_HOST=localhost XTM_ONE_PORT=8090 XTM_ONE_EXTERNAL_SCHEME=http -# Image tag for xtmone/platform and xtmone/worker (e.g. rolling, testing-xtm-one, 1.260513.0) -XTM_ONE_VERSION=rolling # Must match the admin email of the connected platform(s) so XTM One's JWT # email claim resolves to an existing user. XTM_ONE_ADMIN_EMAIL=admin@filigran.io diff --git a/docker-compose.yml b/docker-compose.yml index 5207b775..da5d84f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -352,7 +352,7 @@ services: retries: 5 xtm-one: - image: xtmone/platform:${XTM_ONE_VERSION:-rolling} + image: xtmone/platform:latest environment: - PLATFORM_MODE=xtm_one - PLATFORM_REGISTRATION_TOKEN=${PLATFORM_REGISTRATION_TOKEN} @@ -391,7 +391,7 @@ services: start_period: 60s xtm-one-worker: - image: xtmone/worker:${XTM_ONE_VERSION:-rolling} + image: xtmone/worker:latest environment: - PLATFORM_MODE=xtm_one - PLATFORM_REGISTRATION_TOKEN=${PLATFORM_REGISTRATION_TOKEN} From cafe609c429a6465a36159c923542c22b1bf6e6a Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sat, 30 May 2026 13:12:13 +0200 Subject: [PATCH 5/6] [docker] Address review: curl healthcheck, mirror XTM One to opensearch - Use curl for the XTM One healthcheck (the xtmone/platform image ships curl, not wget). - Mirror the XTM One services and OpenCTI XTM__XTM_ONE_* wiring into docker-compose.opensearch.yml so both stacks bundle XTM One and the shared .env.sample variables take effect there too. --- docker-compose.opensearch.yml | 86 +++++++++++++++++++++++++++++++++++ docker-compose.yml | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/docker-compose.opensearch.yml b/docker-compose.opensearch.yml index 4828f5e7..8f3ab4db 100644 --- a/docker-compose.opensearch.yml +++ b/docker-compose.opensearch.yml @@ -150,6 +150,9 @@ services: - SMTP__PORT=25 - PROVIDERS__LOCAL__STRATEGY=LocalStrategy - APP__HEALTH_ACCESS_KEY=${OPENCTI_HEALTHCHECK_ACCESS_KEY} + # XTM One + - XTM__XTM_ONE_URL=http://xtm-one:4000 + - XTM__XTM_ONE_TOKEN=${PLATFORM_REGISTRATION_TOKEN} ports: - "${OPENCTI_PORT}:8080" depends_on: @@ -327,7 +330,89 @@ services: restart: always depends_on: opencti: + condition: service_healthy + + ########################### + # XTM ONE # + ########################### + + pgsql-xtm-one: + # Dedicated pgvector-enabled instance for XTM One. + image: pgvector/pgvector:pg17 + environment: + POSTGRES_USER: ${XTM_ONE_POSTGRES_USER} + POSTGRES_PASSWORD: ${XTM_ONE_POSTGRES_PASSWORD} + POSTGRES_DB: xtm_one + volumes: + - pgsqlxtmonedata:/var/lib/postgresql/data + restart: always + healthcheck: + test: [ "CMD", "pg_isready", "-U", "${XTM_ONE_POSTGRES_USER}", "-d", "xtm_one" ] + interval: 10s + timeout: 5s + retries: 5 + + xtm-one: + image: xtmone/platform:latest + environment: + - PLATFORM_MODE=xtm_one + - PLATFORM_REGISTRATION_TOKEN=${PLATFORM_REGISTRATION_TOKEN} + - BASE_URL=${XTM_ONE_EXTERNAL_SCHEME}://${XTM_ONE_HOST}:${XTM_ONE_PORT} + - FRONTEND_URL=${XTM_ONE_EXTERNAL_SCHEME}://${XTM_ONE_HOST}:${XTM_ONE_PORT} + - ADMIN_EMAIL=${XTM_ONE_ADMIN_EMAIL} + - ADMIN_PASSWORD=${XTM_ONE_ADMIN_PASSWORD} + - SECRET_KEY=${XTM_ONE_SECRET_KEY} + - DATABASE_URL=postgresql+asyncpg://${XTM_ONE_POSTGRES_USER}:${XTM_ONE_POSTGRES_PASSWORD}@pgsql-xtm-one:5432/xtm_one + - REDIS_URL=redis://redis:6379 + - S3_ENDPOINT=minio:9000 + - S3_ACCESS_KEY=${MINIO_ROOT_USER} + - S3_SECRET_KEY=${MINIO_ROOT_PASSWORD} + - S3_BUCKET=${XTM_ONE_S3_BUCKET:-xtm-one-files} + - S3_USE_SSL=false + - LOG_LEVEL=${XTM_ONE_LOG_LEVEL:-info} + - LOG_FORMAT=${XTM_ONE_LOG_FORMAT:-json} + - ENTERPRISE_LICENSE=${XTM_ONE_ENTERPRISE_LICENSE:-} + # Internal API endpoint for the OpenCTI integration (Docker hostname) + - OPENCTI_API_URL=http://opencti:8080 + ports: + - "${XTM_ONE_PORT}:4000" + depends_on: + pgsql-xtm-one: + condition: service_healthy + redis: condition: service_healthy + minio: + condition: service_healthy + restart: always + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost:4000/api/health || exit 1"] + interval: 15s + timeout: 10s + retries: 5 + start_period: 60s + + xtm-one-worker: + image: xtmone/worker:latest + environment: + - PLATFORM_MODE=xtm_one + - PLATFORM_REGISTRATION_TOKEN=${PLATFORM_REGISTRATION_TOKEN} + - ADMIN_EMAIL=${XTM_ONE_ADMIN_EMAIL} + - ADMIN_PASSWORD=${XTM_ONE_ADMIN_PASSWORD} + - SECRET_KEY=${XTM_ONE_SECRET_KEY} + - DATABASE_URL=postgresql+asyncpg://${XTM_ONE_POSTGRES_USER}:${XTM_ONE_POSTGRES_PASSWORD}@pgsql-xtm-one:5432/xtm_one + - REDIS_URL=redis://redis:6379 + - S3_ENDPOINT=minio:9000 + - S3_ACCESS_KEY=${MINIO_ROOT_USER} + - S3_SECRET_KEY=${MINIO_ROOT_PASSWORD} + - S3_BUCKET=${XTM_ONE_S3_BUCKET:-xtm-one-files} + - S3_USE_SSL=false + - LOG_LEVEL=${XTM_ONE_LOG_LEVEL:-info} + - LOG_FORMAT=${XTM_ONE_LOG_FORMAT:-json} + - ENTERPRISE_LICENSE=${XTM_ONE_ENTERPRISE_LICENSE:-} + depends_on: + xtm-one: + condition: service_healthy + restart: always volumes: esdata: @@ -335,3 +420,4 @@ volumes: redisdata: amqpdata: rsakeys: + pgsqlxtmonedata: diff --git a/docker-compose.yml b/docker-compose.yml index da5d84f1..f00987cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -384,7 +384,7 @@ services: condition: service_healthy restart: always healthcheck: - test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:4000/api/health')\""] + test: ["CMD-SHELL", "curl -fsS http://localhost:4000/api/health || exit 1"] interval: 15s timeout: 10s retries: 5 From cc36d7410d20718929382f87de0760d6cbcc8d42 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sat, 30 May 2026 13:23:02 +0200 Subject: [PATCH 6/6] [docker] Remove trailing whitespace at the XTM One insertion boundary Strip the trailing whitespace on the connector-mitre depends_on line that precedes the new XTM ONE section in both docker-compose.yml and docker-compose.opensearch.yml. --- docker-compose.opensearch.yml | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.opensearch.yml b/docker-compose.opensearch.yml index 8f3ab4db..20b52580 100644 --- a/docker-compose.opensearch.yml +++ b/docker-compose.opensearch.yml @@ -330,7 +330,7 @@ services: restart: always depends_on: opencti: - condition: service_healthy + condition: service_healthy ########################### # XTM ONE # diff --git a/docker-compose.yml b/docker-compose.yml index f00987cc..41535077 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -329,7 +329,7 @@ services: restart: always depends_on: opencti: - condition: service_healthy + condition: service_healthy ########################### # XTM ONE #