Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

The Content Manager plugin operates within the Wazuh Indexer environment. It is composed of several components that handle REST API requests, background job scheduling, content synchronization, user-generated content management, and Engine communication.

Components

REST Layer

Exposes HTTP endpoints under /_plugins/_content_manager/ for:

  • Subscription management (store CTI access token)
  • Manual content sync trigger
  • CUD operations on rules, decoders, integrations, and KVDBs
  • Policy management
  • Promotion preview and execution
  • Logtest execution
  • Content validation and promotion

Credentials Store

Manages the CTI access token used for all CTI API requests. The token is submitted via POST /subscription, persisted in the .wazuh-cti-credentials hidden index, and cached in PluginSettings.accessToken (a volatile String field). On node startup, the token is loaded from the index into memory. Without a registered token, sync and update operations are rejected.

All HTTP clients that communicate with CTI services send a custom User-Agent header in the format Wazuh Indexer <version> (e.g., Wazuh Indexer 5.0.0). This applies to the Catalog API client, Snapshot client, and Telemetry client.

Job Scheduler (CatalogSyncJob)

Implements the OpenSearch JobSchedulerExtension interface. Registers a periodic job (wazuh-catalog-sync-job) that triggers content synchronization at a configurable interval (default: 60 minutes). The job metadata is stored in .wazuh-content-manager-jobs.

Update Check Service (TelemetryPingJob)

Implements a daily heartbeat job (wazuh-telemetry-ping-job) that calls the CTI Update check API endpoint (/ping).

  • Enabled by default through plugins.content_manager.telemetry.enabled.
  • Can be toggled at runtime because it is a dynamic setting.
  • Sends deployment metadata required for update checks (cluster UUID, deployed Wazuh version, and user-agent).
  • Job metadata is stored in .wazuh-content-manager-jobs.
  • The first ping is dispatched immediately after the job is registered in the scheduler; subsequent runs follow the 1-day interval.

Consumer Service

Orchestrates synchronization for each catalog consumer type (ruleset, iocs, vulnerabilities). Compares local offsets (from .wazuh-cti-consumers) with remote offsets from the CTI API, then delegates to either the Snapshot Service or Update Service. Tracks the sync lifecycle through the status field in .wazuh-cti-consumers: set to updating at the start of synchronize() and back to idle only once all post-sync work (hash recalculation, Security Analytics sync, Engine notification) is complete.

Snapshot Service

Handles initial content loading. Initializes from either a remote CTI snapshot (when a custom consumer URL is configured) or a local packaged snapshot, then extracts and bulk-indexes content into the appropriate system indices. Performs data enrichment (e.g., converting JSON payloads to YAML for decoders).

Update Service

Handles incremental updates. Fetches change batches from the CTI API based on offset differences and applies create, update, and delete operations to content indices.

Security Analytics Service

Interfaces with the OpenSearch Security Analytics plugin. Creates, updates, and deletes Security Analytics rules, integrations, and detectors to keep them in sync with CTI content.

Dynamic Configuration: Instead of using hardcoded defaults, the service extracts enabled, interval, and source (index patterns) directly from the CTI integration payload. This allows CTI to control detector behavior dynamically.

Document ID model: SAP documents use their own auto-generated UUIDs as primary IDs, independent of the CTI document UUIDs. Each SAP document stores:

  • document.id — the UUID of the original CTI document in the Content Manager.
  • source — the space the document belongs to (e.g., “draft”, “test”, “custom”, or “standard”).

This design allows the same CTI resource to exist across multiple spaces without ID collisions. Association and lookup between CTI and SAP documents is performed by querying document.id + source.

Note: SAP enforces a maximum of 100 rules per detector. If an integration has more than 100 enabled rules, the detector creation or update request will be rejected. See Security Analytics — Detector constraints for details.

Space Service

Manages the four content spaces (standard, draft, test, custom). Routes CUD operations to the correct space partitions within system indices. Handles promotion by computing diffs between spaces in the promotion chain (Draft → Test → Custom).

Engine Client

Communicates with the Wazuh Engine via Unix domain socket at /usr/share/wazuh-indexer/engine/sockets/engine-api.sock. Used for logtest execution, content validation, and configuration reload.

Data Flows

CTI Sync (Snapshot)

Job Scheduler triggers
  → Consumer Service checks .wazuh-cti-consumers (offset = 0)
  → If custom catalog URL is configured: try remote snapshot first
  → If remote init fails: fallback to local packaged snapshot
  → If no custom catalog URL: initialize from local packaged snapshot
  → Extracts and bulk-indexes into wazuh-threatintel-rules, wazuh-threatintel-decoders, etc.
  → Updates .wazuh-cti-consumers with new offset
  → Security Analytics Service creates detectors using dynamic CTI configuration (max 100 rules per detector)

CTI Sync (Incremental)

Job Scheduler triggers
  → Consumer Service checks .wazuh-cti-consumers (local_offset < remote_offset)
  → Update Service fetches change batches from CTI API
  → Applies CREATE/UPDATE/DELETE to content indices
  → Updates .wazuh-cti-consumers offset
  → Security Analytics Service syncs changes

Update Check Heartbeat

Registration (on node start or dynamic enable)
  → TelemetryPingJob document indexed in .wazuh-content-manager-jobs
  → Immediate first ping fired once the document is written

Job Scheduler triggers (every 24h thereafter)
  → TelemetryPingJob checks plugins.content_manager.telemetry.enabled
  → Reads cluster UUID and current Wazuh version
  → TelemetryClient sends GET /ping to CTI Update check API
  → Wazuh Dashboard can surface update availability to users

User-Generated Content (CUD)

REST request (POST/PUT/DELETE)
  → Space Service routes to draft space
  → Writes to wazuh-threatintel-rules / wazuh-threatintel-decoders / wazuh-threatintel-integrations / wazuh-threatintel-kvdbs
  → Returns created/updated/deleted resource

Standard Policy Engine Loading

The local Wazuh Engine must always reflect the latest version of the standard space policy. Whenever the standard space space.hash changes, the full policy — including all referenced integrations, decoders, kvdbs, filters, and rules — is built and sent to the Engine via EngineService.promote().

The space.hash is an aggregate SHA-256 computed from the individual hashes of the policy and every resource it references. Any change to the policy will trigger a reload. These changes include:

  • New or updated integrations, decoders, rules, kvdbs, or filters (via CTI sync)
  • Changes to policy settings (enabled, index_unclassified_events, index_discarded_events)
  • Changes to the enrichment types list
  • Reordering of the filters list

The engine load is best-effort: if the Engine is unreachable, the error is logged but the operation (sync or REST update) still succeeds.

Promotion

GET /promote?space=draft
  → Space Service computes diff (draft vs test, or test vs custom)
  → Returns changes preview (adds, updates, deletes per content type)

POST /promote
  → Capture pre-promotion snapshots of target-space resources
  → Engine validates configuration (draft → test only, and only when the
    changeset includes decoders, kvdbs, or filters — promotions limited to
    integrations, rules, or the policy skip the engine call)
  → Consolidate changes to CM indices (tracked for rollback)
      → Apply adds/updates: policy, integrations, kvdbs, decoders, filters, rules
      → Apply deletes: integrations, kvdbs, decoders, filters, rules
  → Sync integrations and rules to SAP:
      → ADDs use POST (new SAP document)
      → UPDATEs use PUT (existing SAP document)
  → Delete removed integrations/rules from SAP

Rollback on Failure

If any Content Manager index mutation fails during the consolidation phase, the promotion endpoint automatically performs a LIFO (Last-In, First-Out) rollback to restore the system to its pre-promotion state.

Pre-Promotion Snapshots

Before any writes, the system captures:

  • Old versions (captureOldVersions): For each resource being added or updated, the current target-space version is fetched and stored. If the resource does not exist in the target space, null is stored.
  • Delete snapshots (captureDeleteSnapshots): For each resource being deleted, the full document is fetched from the source space and stored.

CM Index Rollback

Each successful index mutation is recorded as a RollbackStep(kind, resourceType). On failure, steps are replayed in strict reverse (LIFO) order:

Forward operationOld versionRollback action
ADD (apply)nullDelete the newly created document
UPDATE (apply)non-nullRestore the previous version
DELETEsnapshotRe-index the snapshotted document

Individual rollback step failures are logged and skipped so remaining steps can proceed.

SAP Reconciliation

After CM rollback completes, a best-effort SAP reconciliation runs in dependency order:

  1. Revert applied rules — ADDs are deleted from SAP; UPDATEs are restored to old version.
  2. Revert applied integrations — Same as above.
  3. Restore deleted integrations — Re-created from pre-deletion snapshots via POST.
  4. Restore deleted rules — Same as above.

SAP reconciliation failures are logged as warnings but do not cause the overall rollback to fail, since SAP sync is considered best-effort.

Consolidation fails at step N
  → LIFO rollback: undo step N-1, N-2, ..., 1
      → APPLY + null old version → delete from target index
      → APPLY + old version → restore old version to target index
      → DELETE → re-index snapshot to target index
  → SAP reconciliation (best-effort):
      → Delete rules that were added to SAP
      → Restore rules that were updated in SAP
      → Restore integrations that were added/updated in SAP
      → Re-create integrations/rules that were deleted from SAP
  → Return 500 with error message

Plan Change Handling (Blue/Green Swap)

When a subscription plan changes (e.g., free → pro, or vice versa), all downloaded content must be replaced with the content matching the new plan. The Content Manager uses a blue/green index swap to perform this replacement without any user-visible downtime.

How it works

  1. Detection. During each sync cycle, the Content Manager compares the plan-provided catalog URL against the one stored locally. If they differ, a plan change is detected.
  2. Shadow download. New content is downloaded into hidden staging indices. These shadow indices are invisible to users, dashboards, and REST queries during the rebuild.
  3. User content preservation. Any user-created content (draft rules, test decoders, custom integrations, etc.) is copied from the live indices into the shadow indices.
  4. Atomic switch. Once the shadow indices are fully ready, all index aliases are swapped in a single atomic operation. Users see either the entire old content or the entire new content — never a mix or an empty state.
  5. Cleanup. The old indices are deleted, freeing the temporary disk space.

Failure behavior

If the new content cannot be downloaded or processed (network error, source unavailable, etc.), the swap is abandoned cleanly: the staging indices are discarded, users continue to see the old content, and the system retries on the next scheduled sync. A failed swap is invisible to the end user.

Index Structure

Each content index (e.g., wazuh-threatintel-rules) is backed by an alias. The public alias name is the stable identifier used by all queries, dashboards, and REST APIs. The actual data lives in a physical index suffixed with -a or -b:

Public alias (stable name)Physical index (actual storage)
wazuh-threatintel-ruleswazuh-threatintel-rules-a or wazuh-threatintel-rules-b

Only one physical index is live at a time. The other is reserved as the staging slot for the next plan-change swap. Administrators and users should always address indices by their alias name — the physical suffix is an internal implementation detail.

Each content index stores documents from all spaces. Documents are differentiated by internal metadata fields that indicate their space membership. The document _id is a UUID assigned at creation time.

Example document structure in wazuh-threatintel-rules:

{
  "_index": "wazuh-threatintel-rules-a",
  "_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "_source": {
    "title": "SSH brute force attempt",
    "integration": "openssh",
    "space.name": "draft",
    ...
  }
}

The .wazuh-cti-consumers index stores one document per consumer type:

{
  "_index": ".wazuh-cti-consumers",
  "_id": "cti:catalog:consumer:ruleset",
  "_source": {
    "name": "public-ruleset-5",
    "context": "beta-2-ruleset-5",
    "type": "cti:catalog:consumer:ruleset",
    "resource": "https://api.pre.cloud.wazuh.com/api/v1/catalog/contexts/beta-2-ruleset-5/consumers/public-ruleset-5",
    "is_public": true,
    "status": "idle",
    "local_offset": 3932,
    "remote_offset": 3932
  }
}

The status field reflects the consumer’s synchronization lifecycle:

ValueMeaning
idleSync is complete; content indices are up-to-date and safe to read.
updatingSync is in progress; content may be partially written or inconsistent.

The status is set to updating at the very start of a sync cycle and only transitions back to idle after all post-sync work finishes — including hash recalculation, Security Analytics Plugin synchronization, and Engine IoC notification. If a sync fails mid-cycle, the status remains updating as an observable failure signal.