A production-ready integration that continuously synchronizes threat intelligence from MISP (Malware Information Sharing Platform) to Google Security Operations (SecOps), enabling real-time threat detection and correlation.
- Overview
- Platform Introduction
- Architecture
- Prerequisites
- Installation
- Configuration
- Deployment
- Runtime Management
- Monitoring
- Troubleshooting
This forwarder bridges the gap between MISP threat intelligence and Google SecOps SIEM, automatically ingesting Indicators of Compromise (IoCs) as Entity Context for real-time detection and alerting.
-
Continuous Synchronization: Polls MISP at configurable intervals
-
Entity Context Ingestion: IoCs stored as searchable entities in SecOps
-
Runtime Configuration: Update settings without container restart
-
Narrative Logging: Human-readable status updates
-
State Persistence: Tracks progress to avoid duplicate ingestion
-
Docker-First: Production-ready containerized deployment
MISP (Malware Information Sharing Platform) is an open-source threat intelligence platform designed for:
- Collecting, storing, and sharing threat indicators
- Collaborative threat intelligence sharing across organizations
- Automated correlation and enrichment of IoCs
- Integration with security tools via REST API
Common Use Cases:
- Centralized threat intelligence repository
- Information sharing communities (ISACs, CERTs)
- Threat hunting and incident response
Google SecOps (formerly Chronicle) is a cloud-native SIEM platform that provides:
- Petabyte-scale log ingestion and retention
- Real-time threat detection and correlation
- Entity-based threat intelligence matching
- Advanced threat hunting with YARA-L rules
Entity Context in SecOps: When IoCs are ingested as Entity Context, they:
- Remain active for a configurable time window (default: 90 days)
- Trigger instant "IoC Match" alerts when seen in telemetry
- Enable retroactive hunting in historical data
- Correlate across all ingested security logs
- Fetch: Poll MISP
/attributes/restSearchfor new IoCs - Transform: Convert MISP attributes to SecOps Entity format
- Ingest: Send batches to SecOps Entity API
- Track: Update
state.jsonwith last processed timestamp - Repeat: Sleep for configured interval, then loop
-
MISP Instance
- Obtaining an instance (if needed):
- MISP Docker (Recommended for quick setup)
- Manual Installation Guide
- Access Requirements (Once instance is ready):
- URL and API key with read access
- Network connectivity from deployment environment
- Obtaining an instance (if needed):
-
Google Cloud Project
- SecOps instance provisioned
- Service Account with
Chronicle APIrole - Customer ID from SecOps admin console
-
Deployment Environment
- Docker and Docker Compose installed
- Linux host (tested on Ubuntu 20.04+)
- Network access to both MISP and Google APIs
cd /path/to/your/workspace
# If using git:
git clone <repository-url> misp-secops-forwarder
cd misp-secops-forwarder
# Or extract from archive:
unzip misp-secops-forwarder.zip
cd misp-secops-forwarder- Go to Google Cloud Console
- Navigate to IAM & Admin > Service Accounts
- Create or select a service account with SecOps permissions
- Click Keys > Add Key > Create New Key
- Choose JSON format
- Save as
credentials.jsonin the project root
- Log into Google SecOps Console
- Navigate to Settings > SIEM Settings
- Copy your Customer ID (format:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
- Log into your MISP instance
- Navigate to Administration > List Auth Keys
- Copy your API key or create a new one
Create your local .env file by copying the template and filling in your specific details:
# Linux / macOS / PowerShell
cp .env.example .env
# Windows (Command Prompt)
copy .env.example .envEdit the .env file with your specific values:
# MISP Configuration
MISP_URL=https://your-misp-instance.com
MISP_API_KEY=your_misp_api_key_here
MISP_VERIFY_SSL=false
# Google SecOps Configuration
GOOGLE_SA_CREDENTIALS=/app/credentials.json
GOOGLE_CUSTOMER_ID=your-customer-id-here
GOOGLE_PROJECT_ID=your-gcp-project-id
# SecOps API URLs (required)
SECOPS_ENTITY_API_URL=https://malachiteingestion-pa.googleapis.com/v2/entities:batchCreate
SECOPS_UDM_API_URL=https://malachiteingestion-pa.googleapis.com/v2/udmevents:batchCreate
The repository includes a config.json file with default settings. You can review and modify it as needed:
{
"FETCH_INTERVAL": 3600,
"FETCH_PAGE_SIZE": 100,
"FORWARDER_BATCH_SIZE": 100, // Max recommended 500 (API Limit: 4MB)
"IOC_EXPIRATION_DAYS": 30,
"TEST_MODE": false,
"MAX_TEST_EVENTS": 3,
"HISTORICAL_POLLING_DATE": "0000-00-00",
"LOG_LEVEL": "INFO"
}Configuration Priority:
- CLI arguments (highest)
config.jsonfile- Built-in defaults (lowest)
-
Build the image:
docker compose build
-
Start the forwarder:
docker compose up
-
View logs:
docker compose logs -f
-
Stop the forwarder:
docker compose down
After starting, check logs for:
✓ Metron Security banner display
✓ "Successfully established a secure connection to MISP"
✓ "Configuration loaded from /app/config.json"
✓ "Entering routine monitoring loop"
List all current settings:
docker exec misp-secops-forwarder python manage.py listGet a specific value:
docker exec misp-secops-forwarder python manage.py get FORWARDER_BATCH_SIZEUpdate configuration values:
# Change batch size to 200 (Safety limit: 500)
docker exec misp-secops-forwarder python manage.py set FORWARDER_BATCH_SIZE 200
# Change fetch interval to 30 minutes (1800 seconds)
docker exec misp-secops-forwarder python manage.py set FETCH_INTERVAL 1800
# Change page size to 50
docker exec misp-secops-forwarder python manage.py set FETCH_PAGE_SIZE 50
# Set historical polling
docker exec misp-secops-forwarder python manage.py set HISTORICAL_POLLING_DATE 2025-01-01 (format: YYYY-MM-DD)
# Enable test mode
docker exec misp-secops-forwarder python manage.py set TEST_MODE true
# Set max test events
docker exec misp-secops-forwarder python manage.py set MAX_TEST_EVENTS 5
# Set IoC expiration days
docker exec misp-secops-forwarder python manage.py set IOC_EXPIRATION_DAYS 90
# Change log level to DEBUG
docker exec misp-secops-forwarder python manage.py set LOG_LEVEL DEBUG
# Change log level back to INFO
docker exec misp-secops-forwarder python manage.py set LOG_LEVEL INFO
How it works:
- The
manage.pycommand updatesconfig.json - The running application detects the file change immediately
- The current process is gracefully terminated
- A new process is automatically started with the new configuration
- No container restart required!
When a configuration change is detected (via manage.py or direct file edit):
- Detection: The system monitors
config.jsonfor modification events. - Termination: The active worker loop is interrupted, and the current process state is cleared.
- Restart: The application re-initializes all client connections and reloads settings.
- Logging: You will see specific logs indicating this lifecycle transition:
Configuration updated Previous process terminated Application restarted with new configuration
You can reset the synchronization start time to a specific point in the past by updating HISTORICAL_POLLING_DATE.
Using Runtime Configuration:
Updating this value via manage.py will automatically trigger a resync from the new date, resetting the current progress.
# Reset sync to a specific date
docker exec misp-secops-forwarder python manage.py set HISTORICAL_POLLING_DATE 2024-01-01Note: This will overwrite the current progress in state.json and start fetching from the new timestamp forward.
Check if container is running:
docker ps | grep misp-secops-forwarderView real-time logs:
docker logs -f misp-secops-forwarderCheck last 100 lines:
docker logs --tail=100 misp-secops-forwarderSuccessful sync:
The application successfully retrieved X new attributes from MISP
The application successfully transformed X of them into valid threat entities
The threat data has been successfully delivered and ingested into Google SecOps
Configuration reload:
Configuration file has been modified. Reloading settings...
Configuration reloaded successfully. New batch size: 200, Fetch interval: 1800s
No new data:
No new threat indicators were found during this check
Step 1: Enable DEBUG logging
docker exec misp-secops-forwarder python manage.py set LOG_LEVEL DEBUGStep 2: View real-time logs (all levels)
# View all logs in real-time
docker logs -f misp-secops-forwarder
# View last 100 lines
docker logs --tail=100 misp-secops-forwarder
# View logs since a specific time
docker logs --since 10m misp-secops-forwarder
# View logs with timestamps
docker logs -f --timestamps misp-secops-forwarderStep 3: Filter logs by level
# Show only INFO messages
docker logs misp-secops-forwarder | grep "INFO"
# Show only DEBUG messages
docker logs misp-secops-forwarder | grep "DEBUG"
# Show ERROR and WARNING messages
docker logs misp-secops-forwarder | grep -E "ERROR|WARNING"Step 4: Save logs to file
# Save all logs to file
docker logs misp-secops-forwarder > forwarder_logs.txt
# Save last 1000 lines
docker logs --tail=1000 misp-secops-forwarder > recent_logs.txt- Log into SecOps Console
- Navigate to Investigation > SIEM Search
- Query for recent MISP entities:
graph.entity.hostname = "hostname" graph.metadata.product_name = "MISP"
Note
It can take some time (typically 2 to 4 hours) for new indicators to be fully processed and for matches to appear in the IOC Matches view. Google SecOps periodically correlates security telemetry against ingested threat intelligence.
The simulate_match.py script generates a UDM event to test if your IoCs are triggering matches in SecOps.
What it does:
- Prompts you to enter an IoC value (IP, domain, URL, or file hash)
- Generates a realistic UDM event containing that IoC
- Sends the event to SecOps UDM API
- The event should trigger an IoC match if the entity exists in SecOps
Run the test:
# Run interactively
docker exec -it misp-secops-forwarder python tests/simulate_match.pyExample session:
UDM Event Generator for IoC Match Testing
==================================================
Enter IoC details to generate a matching UDM event:
Supported types:
1. IP address (e.g., 192.168.1.100)
2. Domain/Hostname (e.g., malicious.com)
3. URL (e.g., http://malicious.com/path)
4. File hash (MD5/SHA1/SHA256)
Select type (1-4): 2
Enter IoC value: malicious.com
Generating event for domain: malicious.com
Sending UDM event to Google SecOps...
✓ UDM Event Sent Successfully!
- Log into Google SecOps Console
- Navigate to Alerts > IoC Matches
- Look for a recent match with:
- IoC Value: The domain/IP from the test
- Source: MISP
- Timestamp: Within the last few minutes
Note: IoC matching in Google SecOps may take 5-15 minutes to appear after ingestion due to indexing and correlation processing. For large batches, allow up to 30 minutes. This is normal behavior for the platform's distributed architecture. For more information, refer to the Google SecOps Entity API documentation.
Test configuration loading:
docker exec misp-secops-forwarder python manage.py listTest MISP connectivity:
docker exec misp-secops-forwarder python -c "
from src.misp.client import MispClient
misp = MispClient()
print('MISP Connection:', 'OK' if misp.test_connection() else 'FAILED')
"Test config reload:
# Change a value
docker exec misp-secops-forwarder python manage.py set FETCH_INTERVAL 1800
# Wait for next sync cycle and check logs
docker logs misp-secops-forwarder | grep "Configuration reloaded"Problem: "Failed to connect to MISP"
- Verify
MISP_URLis correct and accessible - Check
MISP_API_KEYis valid - If using self-signed certs, ensure
MISP_VERIFY_SSL=false - Test connectivity:
curl -k https://your-misp-url/servers/getVersion
Problem: "Unable to load Google Service Account credentials"
- Verify
credentials.jsonexists and is mounted correctly - Check file permissions:
chmod 644 credentials.json - Validate JSON syntax:
cat credentials.json | jq .
Problem: "Failed to ingest entities" (403/401)
- Verify Service Account has SecOps API permissions
- Check
GOOGLE_CUSTOMER_IDis correct - Ensure
SECOPS_ENTITY_API_URLis correct
Problem: Container exits immediately
- Check logs:
docker compose logs misp-secops-forwarder - Verify all required env variables are set
- Test config:
docker exec misp-secops-forwarder python manage.py list
Problem: Configuration changes not applying
- Verify
config.jsonis mounted as a volume - Check file modification time:
docker exec misp-secops-forwarder ls -l /app/config.json - Wait for next sync cycle (check
FETCH_INTERVAL)
Enable verbose logging using the runtime configuration:
# Enable DEBUG logging
docker exec misp-secops-forwarder python manage.py set LOG_LEVEL DEBUG
# View logs to see debug output
docker logs -f misp-secops-forwarder
# Switch back to INFO when done
docker exec misp-secops-forwarder python manage.py set LOG_LEVEL INFOValid log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
To start fresh (re-process all data):
# Stop container
docker compose down
# Remove state file
rm -rf misp_data/state.json
# Restart
docker compose up -dRecommendations:
- Set
TEST_MODE=false - Use
FETCH_INTERVAL=3600(1 hour) or higher - Enable SSL verification:
MISP_VERIFY_SSL=true - Monitor disk usage for
misp_data/volume - Set up log rotation for Docker logs
- Use Docker health checks in production orchestration
- Batch Size: Increase
FORWARDER_BATCH_SIZEfor high-volume environments (Strictly limited/capped at 500 to prevent 4MB API overflow). - Fetch Interval: Decrease for near-real-time sync (minimum: 300s)
- Page Size: Increase
FETCH_PAGE_SIZEif MISP has many attributes per sync
For issues, questions, or contributions, please refer to the project repository: [email protected]
