From ff2ee3a138ce40bfaf9ac05334492c94d212f904 Mon Sep 17 00:00:00 2001 From: Alexis Zubiolo Date: Wed, 27 May 2026 14:16:27 +0200 Subject: [PATCH 1/2] feat: bundle XTM One in the default stack Adds XTM One alongside OpenCTI in the default compose: - New pgsql-copilot service (pgvector/pgvector:pg17) for XTM One's vector store, with dedicated credentials. - New xtm-one + xtm-one-worker services on port 4000, sharing the existing redis and minio. - PLATFORM_REGISTRATION_TOKEN shared secret plumbed into the opencti service (XTM__XTM_ONE_URL / XTM__XTM_ONE_TOKEN) and into XTM One (OPENCTI_* federation env vars). - .env.sample documents the new XTM ONE block. Refs XTM-One-Platform/xtm-one#1011 --- .env.sample | 29 ++++++++++++++- docker-compose.yml | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 7407c044..966495ac 100644 --- a/.env.sample +++ b/.env.sample @@ -48,4 +48,31 @@ 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 # +########################### + +# Shared secret used by OpenCTI and XTM One to authenticate registration. +# Both platforms MUST use the same value. +PLATFORM_REGISTRATION_TOKEN=xtm-default-registration-token + +XTM_ONE_HOST=localhost +XTM_ONE_PORT=4000 +XTM_ONE_EXTERNAL_SCHEME=http +# Image tag for filigran/xtm-one and filigran/xtm-one-worker (e.g. rolling, 1.x.y) +XTM_ONE_VERSION=rolling +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 +# Credentials for the dedicated pgsql-copilot Postgres instance. +XTM_ONE_POSTGRES_USER=copilot +XTM_ONE_POSTGRES_PASSWORD=ChangeMe +# Optional: bucket name in MinIO (auto-created on first boot) +XTM_ONE_S3_BUCKET=copilot-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..51367ed2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -87,6 +87,21 @@ services: interval: 30s timeout: 30s retries: 3 + pgsql-copilot: + # 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: copilot + volumes: + - pgsqlcopilotdata:/var/lib/postgresql/data + restart: always + healthcheck: + test: [ "CMD", "pg_isready", "-U", "${XTM_ONE_POSTGRES_USER}", "-d", "copilot" ] + interval: 10s + timeout: 5s + retries: 5 ########################### # COMMON # @@ -149,6 +164,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 +346,79 @@ services: opencti: condition: service_healthy + ########################### + # XTM ONE # + ########################### + + 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-copilot:5432/copilot + - 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:-copilot-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:-} + # OpenCTI federation + - OPENCTI_ENABLE=true + - OPENCTI_URL=${OPENCTI_EXTERNAL_SCHEME}://${OPENCTI_HOST}:${OPENCTI_PORT} + - OPENCTI_API_URL=http://opencti:8080 + - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN} + ports: + - "${XTM_ONE_PORT}:4000" + depends_on: + pgsql-copilot: + 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-copilot:5432/copilot + - 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:-copilot-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: + pgsqlcopilotdata: From 00bb357f47c6436cb496d40648bfe11280cd7f6b Mon Sep 17 00:00:00 2001 From: Alexis Zubiolo Date: Thu, 28 May 2026 14:20:11 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20align=20BASE=5FURL=20and=20admin=20e?= =?UTF-8?q?mail=20for=20XTM=20One=20=E2=86=94=20OpenCTI=20JWT=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Set APP__BASE_URL=http://opencti:8080 so OpenCTI validates the JWT audience claim correctly (was using the external URL which doesn't match what XTM One puts in the token). - Set BASE_URL=http://xtm-one:4000 so the JWT issuer claim points to the internal Docker hostname (OpenCTI fetches JWKS from {iss}/xtm/auth/jwks). - Align XTM_ONE_ADMIN_EMAIL with OPENCTI_ADMIN_EMAIL so the JWT email claim resolves to an existing user in OpenCTI. - Use lowercase 'changeme' for XTM_ONE_ADMIN_PASSWORD to match OpenCTI convention. --- .env.sample | 5 +++-- docker-compose.yml | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index 966495ac..a4ca89b0 100644 --- a/.env.sample +++ b/.env.sample @@ -63,8 +63,9 @@ XTM_ONE_PORT=4000 XTM_ONE_EXTERNAL_SCHEME=http # Image tag for filigran/xtm-one and filigran/xtm-one-worker (e.g. rolling, 1.x.y) XTM_ONE_VERSION=rolling -XTM_ONE_ADMIN_EMAIL=admin@filigran.io -XTM_ONE_ADMIN_PASSWORD=ChangeMe +# Must match OPENCTI_ADMIN_EMAIL so XTM One's JWT is accepted by OpenCTI. +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-copilot Postgres instance. diff --git a/docker-compose.yml b/docker-compose.yml index 51367ed2..91683105 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -140,7 +140,9 @@ services: environment: - NODE_OPTIONS=--max-old-space-size=8096 - APP__PORT=8080 - - APP__BASE_URL=${OPENCTI_EXTERNAL_SCHEME}://${OPENCTI_HOST}:${OPENCTI_PORT} + # APP__BASE_URL is used as the JWT ``aud`` validation target. It MUST + # match what XTM One puts in the JWT audience claim (the internal URL). + - APP__BASE_URL=http://opencti:8080 - APP__ADMIN__EMAIL=${OPENCTI_ADMIN_EMAIL} - APP__ADMIN__PASSWORD=${OPENCTI_ADMIN_PASSWORD} - APP__ADMIN__TOKEN=${OPENCTI_ADMIN_TOKEN} @@ -355,7 +357,10 @@ services: environment: - PLATFORM_MODE=xtm_one - PLATFORM_REGISTRATION_TOKEN=${PLATFORM_REGISTRATION_TOKEN} - - BASE_URL=${XTM_ONE_EXTERNAL_SCHEME}://${XTM_ONE_HOST}:${XTM_ONE_PORT} + # BASE_URL is used as the JWT ``iss`` claim AND as the JWKS host that + # peer platforms (OpenCTI) fetch keys from. It MUST be the internal + # Docker hostname so peers can verify tokens. + - BASE_URL=http://xtm-one:4000 - FRONTEND_URL=${XTM_ONE_EXTERNAL_SCHEME}://${XTM_ONE_HOST}:${XTM_ONE_PORT} - ADMIN_EMAIL=${XTM_ONE_ADMIN_EMAIL} - ADMIN_PASSWORD=${XTM_ONE_ADMIN_PASSWORD}