XTM Composer architecture
Overview
XTM Composer is a micro-orchestration tool that manages connectors/collectors/injectors for Filigran platforms (OpenCTI and OpenAEV). Written in Rust for performance and reliability, it acts as a bridge between the platforms and container orchestration systems.
Global mechanism
Core architecture flow
βββββββββββββββ     REST/GraphQL     ββββββββββββββββ
β   OpenCTI   βββββββββββββββββββββββΊβ              β
β   Platform  β                      β  XTM         β     Container API
βββββββββββββββ                      β  Composer    ββββββββββββββββββββββββΊβββββββββββββββ
                                     β              β                       β Orchestratorβ
βββββββββββββββ                      β              β                       β (K8s/Docker)β
β   OpenAEV   βββββββββββββββββββββββΊβ              β                       βββββββββββββββ
β   Platform  β     REST/GraphQL     ββββββββββββββββ
βββββββββββββββ
Main components
- Engine Module (
src/engine/) - Manages lifecycle of orchestration and health monitoring
 - Creates separate async tasks for each platform (OpenCTI/OpenAEV)
 - 
Handles graceful shutdown via system signals
 - 
API Module (
src/api/) - Abstracts platform communication through 
ComposerApitrait - Handles GraphQL queries/mutations for OpenCTI
 - 
Manages container configuration retrieval and status updates
 - 
Orchestrator Module (
src/orchestrator/) - Implements 
Orchestratortrait for different container systems - Supports Kubernetes, Docker, and Portainer
 - 
Handles container lifecycle operations
 - 
Config Module (
src/config/) - Manages application settings from YAML files
 - Supports environment-specific configurations
 - Handles credentials and encryption keys
 
Terminology
XTM Composer orchestrates different types of containerized components depending on the platform:
- OpenCTI: Deploys Connectors as containers (import, export, stream)
 - OpenAEV: Deploys Collectors and Injectors as containers
 
Each component is deployed and managed as a container:
| Platform | Component Types | Deployed As | 
|---|---|---|
| OpenCTI | Connectors | Container | 
| OpenAEV | Collectors/Injectors | Container | 
This abstraction allows the composer to use the same orchestration logic regardless of the platform-specific terminology.
Execution flow
- Initialization
 - Load configuration from 
config/default.yamland environment - Initialize RSA private key for configuration decryption
 - 
Set up logging system
 - 
Platform Registration
 - Connect to platform (OpenCTI/OpenAEV)
 - Register composer instance with unique manager ID
 - 
Verify API compatibility
 - 
Main Orchestration Loop
 - Execute every 
execute_scheduleseconds (default: 30s) - Pull connector configurations
 - Reconcile desired vs actual state
 - Apply necessary changes
 
Pull mechanism
Configuration retrieval (Connectors/Collectors/Injectors)
The composer periodically pulls container configurations from the platform:
// Executed every 30 seconds by default
async fn orchestrate() {
    // 1. Fetch all container configurations from platform
    // (connectors for OpenCTI, collectors/injectors for OpenAEV)
    let connectors = api.connectors().await; // Generic method name
    // 2. For each container configuration
    for connector in connectors {
        // 3. Check if container exists in orchestrator
        let container = orchestrator.get(connector).await;
        // 4. Reconcile state
        match container {
            Some(c) => orchestrate_existing(c, connector),
            None => orchestrate_missing(connector)
        }
    }
}
Container configuration structure
Each container configuration (running a connector/collector/injector) contains: - ID: Unique identifier - Name: Human-readable name - Image: Docker image to deploy - Contract Hash: Version identifier for configuration - Status: Current and requested states - Configuration: Key-value pairs (potentially encrypted)
State synchronization
The composer maintains two-way synchronization:
- Platform β Composer (Pull)
 - Fetch latest component definitions (connectors/collectors/injectors)
 - Retrieve requested status changes
 - 
Get configuration updates
 - 
Composer β Platform (Push)
 - Report actual container status
 - Send container logs
 - Update health metrics
 
Update mechanism
Container lifecycle management
The update mechanism handles several scenarios:
1. Deployment (Missing Container)
// Container doesn't exist but component is defined in platform
// (connector for OpenCTI, collector/injector for OpenAEV)
async fn orchestrate_missing(connector) {
    orchestrator.deploy(connector).await;
    api.patch_status(connector.id, ConnectorStatus::Stopped).await;
}
2. Status Alignment
// Align requested status with actual state
match (requested_status, current_status) {
    (RequestedStatus::Starting, ConnectorStatus::Stopped) => {
        orchestrator.start(container).await;
    }
    (RequestedStatus::Stopping, ConnectorStatus::Started) => {
        orchestrator.stop(container).await;
    }
}
3. Configuration Updates
// Check if configuration hash changed
if requested_hash != current_hash {
    orchestrator.refresh(connector).await;  // Recreate with new config
}
4. Health Monitoring
- Tracks container restart count
 - Detects reboot loops (>3 restarts in <5 minutes)
 - Reports health metrics every 30 seconds for running containers
 
5. Log Collection
- Collects container logs every 
logs_scheduleminutes - Sends logs to platform for centralized monitoring
 - Maintains log history for debugging
 
Cleanup process
The composer automatically removes orphaned containers:
// Remove containers not defined in platform
// (connectors for OpenCTI, collectors/injectors for OpenAEV)
for container in orchestrator.list().await {
    if !platform_connectors.contains(container.id) {
        orchestrator.remove(container).await;
    }
}
Configuration encryption
Hybrid encryption scheme
XTM Composer uses RSA/AES hybrid encryption for sensitive configuration:
Platform                    Composer
ββββββββ                    ββββββββ
1. Generate AES key
2. Encrypt config with AES
3. Encrypt AES key with RSA public key
4. Send: [Version|Encrypted AES Key|Encrypted Data]
                            β
                            βΌ
                    5. Decrypt AES key with RSA private key
                    6. Decrypt config with AES key
                    7. Use plaintext configuration
Encryption format
Encrypted values are Base64-encoded with structure:
Key management
- RSA Private Key
 - Loaded at startup from file or environment variable
 - PKCS#8 PEM format required
 - 
Used to decrypt AES keys
 - 
AES Encryption
 - AES-256-GCM for data encryption
 - Random key and IV per configuration value
 - Provides authenticated encryption
 
Security properties
- Confidentiality: Sensitive data encrypted at rest and in transit
 - Key Isolation: Each configuration value has unique AES key
 - Forward Secrecy: Compromised AES key doesn't affect other values
 - Authentication: GCM mode provides data integrity
 
Performance considerations
- Async/Await: All I/O operations are asynchronous
 - Periodic Execution: Configurable intervals prevent API flooding
 - Batch Processing: Multiple connectors processed in single cycle
 - Resource Efficiency: Rust's zero-cost abstractions minimize overhead
 - Error Recovery: Graceful handling of failures with retry logic
 
Fault tolerance
- Health Checks: Regular ping to platform ensures connectivity
 - Version Detection: Re-registers on platform upgrade
 - Reboot Loop Detection: Prevents infinite restart cycles
 - Graceful Shutdown: Handles system signals properly
 - State Persistence: Platform maintains desired state