Skip to content

Add Huntress.io integration#3019

Open
kyletaylored wants to merge 43 commits into
DataDog:masterfrom
kyletaylored:huntress
Open

Add Huntress.io integration#3019
kyletaylored wants to merge 43 commits into
DataDog:masterfrom
kyletaylored:huntress

Conversation

@kyletaylored

Copy link
Copy Markdown

What does this PR do?

Adds a new community integration for Huntress, a managed security platform offering EDR and Managed SIEM. The integration polls the Huntress SIEM API using ES|QL queries and forwards security events to Datadog as logs.

Each collection run:

  • Loads a checkpoint (timestamp of last successful collection) from persistent cache and queries only the elapsed window, avoiding duplicates across Agent restarts
  • Paginates through all result pages up to a configurable cap (max_pages_per_run)
  • Optionally enriches each log with Huntress organization metadata (org name, key, account ID) via a TTL-cached lookup, useful for MSPs managing multiple client accounts
  • Forwards logs to Datadog preserving all Elastic Common Schema (ECS) field names as top-level attributes
  • Advances the checkpoint only after all pages are successfully sent

The integration also tracks Huntress API rate limit consumption via response headers (x-huntress-api-call-limit, x-huntress-api-call-remaining) and emits them as gauges so teams can monitor headroom and alert before hitting the 60 req/min cap.

Assets included:

  • Prebuilt dashboard: Huntress SIEM Overview (log volume, pages fetched, run duration, rate limit gauges)
  • 3 monitor templates: collection run failed (service check), error rate spike, no logs collected in 2h
  • metadata.csv with 6 metrics
  • assets/service_checks.json and assets/configuration/spec.yaml
  • Mockoon-based Docker test harness with the Huntress-provided mock dataset

Motivation

MSP partners and enterprise security teams using Huntress Managed SIEM want to correlate endpoint threat detections alongside infrastructure, application, and cloud telemetry already in Datadog. This integration enables that without requiring any custom scripting or log shipper configuration on the customer side.

Review checklist

  • PR has a meaningful title
  • Feature has tests — 31 unit tests covering all PRD scenarios (auth failures, retries, pagination, checkpoint persistence, org enrichment, batching, rate limit parsing, multi-instance isolation) plus 3 Docker integration tests against a live Mockoon mock server (ddev test huntress / ddev test huntress -- -m integration)
  • Git history is clean
  • Docs — README covers installation, full configuration reference, collected data, and a troubleshooting table; no separate documentation repo issue needed as the tile content is self-contained in README.md
  • Log pipeline — logs are forwarded with ddsource: huntress, which will trigger automatic pipeline processing once a Huntress log pipeline is configured in the Datadog backend. No custom pipeline is bundled with this PR; ECS field names are preserved as-is on the log payload.

Additional Notes

  • Multi-account support: each entry under instances: runs fully independently with its own checkpoint, org cache, and metrics — MSPs can configure one instance per Huntress account.
  • ES|QL query validation: the check rejects any esql_query that does not begin with FROM logs at startup to catch
    misconfiguration early.
  • Org enrichment caching: the org metadata cache is stored in persistent cache keyed by a hash of the instance config, with a configurable TTL (default 1 hour). Setting org_cache_ttl_seconds: 0 forces a refresh on every run, which the integration test uses to validate the full fetch path against the mock.
  • source_type_id: 10350 in manifest.json is a placeholder — please assign a real ID before merging.
  • The Huntress-provided Mockoon dataset (huntress_mockoon.json) contained a missing comma in the v1/account route template, which was fixed to a static response for the test harness. The fix is scoped to our copy of the file.

@datadog-prod-us1-5

This comment has been minimized.

Comment thread huntress/checks.d/huntress.py Fixed
Comment thread huntress/datadog_checks/huntress/huntress.py Fixed
Comment thread huntress/datadog_checks/huntress/huntress.py Fixed
Comment thread huntress/checks.d/huntress.py Fixed
Comment thread huntress/datadog_checks/huntress/huntress.py Fixed
@kyletaylored kyletaylored marked this pull request as ready for review June 1, 2026 15:53
@kyletaylored kyletaylored requested review from a team as code owners June 1, 2026 15:53
@kyletaylored

Copy link
Copy Markdown
Author

Left some feedback! Give that a look through and let me know if you have any Qs. Tag me/re-request a review when this is ready for another look!

Thanks @domalessi ! I've added the suggestions and fixed the rest of the documentation changes. The only suggestion I had to roll back was related to the en dash because the README validation rejects non-ascii characters and wouldn't pass, but it looks like everything else is clearing now.

@kyletaylored kyletaylored requested a review from domalessi June 3, 2026 05:38

@domalessi domalessi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes! Left some more suggestions/comments. I think we'll be in good shape after that 💪

Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
kyletaylored and others added 6 commits June 3, 2026 14:34
Co-authored-by: domalessi <111786334+domalessi@users.noreply.github.com>
- Configuration step 1: "Create" → "Copy the [example configuration file][4]…and edit it", steps collapsed from 3 → 2, "fully-annotated" hyphen removed
- Multiple Huntress accounts: Moved before ### Validation, demoted to ####, conditional lead-in sentence added
- Config reference table: No\* → Conditional for both conditional fields, column widths realigned, footnote \* replaced with **Note:** paragraph
- Rate limits: Renamed from "Rate limit considerations"
- Link references: Added [3] for service_checks.json (was broken before) and [4] for the example config file on GitHub
…mmary and KEEP guidance

Bug fixes:
- Truncate nanosecond-precision @timestamp values to microseconds before parsing; Python's fromisoformat() only handles up to 6 fractional digits, causing silent date_ms=None on all Huntress log events
- Match organization_id flat field (actual API response shape) in addition to organization.id when resolving org enrichment tags
- Cap 429 retry loop at one retry with error_type:rate_limited counting; the previous implementation retried indefinitely, blocking the check thread

Observability:
- Emit a self.log.info() summary after each successful run reporting logs collected and pages fetched per query, and agent count/pages when metrics.agents is enabled
- Surface the same summary as the service check message so it appears in `datadog-agent status` output
- Warn on the first page when log entries contain only uuid and organization_id, indicating a missing KEEP clause in the ES|QL query

Docs and config:
- Document the Huntress API KEEP requirement in spec.yaml, conf.yaml.example, and README; despite what the Huntress API docs state, queries without an explicit KEEP return only uuid and organization_id, not all fields
- Update all example queries in README and conf.yaml.example to include a representative KEEP clause

@domalessi domalessi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes! A few remaining items — see inline feedback.

Structural note:
### Configuration reference and ### Rate limits currently appear after ### Validation. Both contain guidance users need before validating the setup. I suggest reordering as 4th-level subsections of the Configuration section:

### Configuration
#### Multiple Huntress accounts
#### Rate limits 
#### Configuration reference
### Validation

Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/assets/monitors/huntress_siem_errors_high.json Outdated
Comment thread huntress/assets/monitors/huntress_siem_no_logs_collected.json Outdated
Comment thread huntress/README.md Outdated
Comment thread huntress/README.md Outdated

@domalessi domalessi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, @kyletaylored ! A few minor fixes left but should be good to go after that (from an editorial perspective, at least).

Comment thread huntress/README.md

[1]: https://github.com/DataDog/integrations-extras/blob/master/huntress/metadata.csv
[2]: https://docs.datadoghq.com/help/
[3]: https://github.com/DataDog/integrations-extras/blob/master/huntress/service_checks.json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the correct path?

Suggested change
[3]: https://github.com/DataDog/integrations-extras/blob/master/huntress/service_checks.json
[3]: https://github.com/DataDog/integrations-extras/blob/master/huntress/assets/service_checks.json

Comment thread huntress/manifest.json
"configuration": "README.md#Setup",
"support": "README.md#Support",
"changelog": "CHANGELOG.md",
"description": "Collect and forward Huntress Managed SIEM logs to Datadog for security monitoring.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"description": "Collect and forward Huntress Managed SIEM logs to Datadog for security monitoring.",
"description": "Collect and forward Huntress SIEM logs to Datadog for security monitoring.",

Comment thread huntress/README.md
Comment on lines +166 to +171
- Run `sudo datadog-agent check huntress` and inspect the output - the summary line reports how many logs were collected and the exact time range queried
- Verify the API key pair is valid by checking the Huntress Partner Portal
- Confirm the Managed SIEM feature is enabled on the account
- Confirm `min_collection_interval: 900` is set in your instance config - without it, the Agent uses its default 15-second interval, and each run queries only a 15-second window with no new events
- Check that each `log_queries[].esql_query` begins with `FROM logs`
- The Huntress SIEM API requires an explicit `KEEP` clause to return log fields. Without one, responses contain only `uuid` and `organization_id`. Add `| KEEP @timestamp, message, host.hostname, event.category, event.code, ...` to your query. The Agent logs will show a warning if this is detected.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Run `sudo datadog-agent check huntress` and inspect the output - the summary line reports how many logs were collected and the exact time range queried
- Verify the API key pair is valid by checking the Huntress Partner Portal
- Confirm the Managed SIEM feature is enabled on the account
- Confirm `min_collection_interval: 900` is set in your instance config - without it, the Agent uses its default 15-second interval, and each run queries only a 15-second window with no new events
- Check that each `log_queries[].esql_query` begins with `FROM logs`
- The Huntress SIEM API requires an explicit `KEEP` clause to return log fields. Without one, responses contain only `uuid` and `organization_id`. Add `| KEEP @timestamp, message, host.hostname, event.category, event.code, ...` to your query. The Agent logs will show a warning if this is detected.
- Run `sudo datadog-agent check huntress` and inspect the output. The summary line reports how many logs were collected and the exact time range queried.
- Verify the API key pair is valid by checking the Huntress Partner Portal.
- Confirm the Managed SIEM feature is enabled on the account.
- Confirm `min_collection_interval: 900` is set in your instance config. Otherwise, the Agent uses its default 15-second interval, and each run queries only a 15-second window with no new events.
- Check that each `log_queries[].esql_query` begins with `FROM logs`.
- The Huntress SIEM API requires an explicit `KEEP` clause to return log fields. Without one, responses contain only `uuid` and `organization_id`. Add `| KEEP @timestamp, message, host.hostname, event.category, event.code, ...` to your query. The Agent logs show a warning if this is detected.

Comment thread huntress/README.md

## Overview

[Huntress][5] is a managed security platform providing endpoint detection and response (EDR), antivirus, security awareness training, and a Managed SIEM product that continuously collects and analyzes endpoint telemetry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[Huntress][5] is a managed security platform providing endpoint detection and response (EDR), antivirus, security awareness training, and a Managed SIEM product that continuously collects and analyzes endpoint telemetry.
[Huntress][5] is a managed security platform that provides endpoint detection and response (EDR), antivirus, security awareness training, and a Managed SIEM product that continuously collects and analyzes endpoint telemetry.

Comment thread huntress/README.md

#### Multiple Huntress accounts

If you manage multiple Huntress accounts, add an additional block under `instances:` for each one. Each block runs independently with its own checkpoint, org metadata cache, and metrics:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you manage multiple Huntress accounts, add an additional block under `instances:` for each one. Each block runs independently with its own checkpoint, org metadata cache, and metrics:
If you manage multiple Huntress accounts, add an additional block under `instances:` for each account. Each block runs independently with its own checkpoint, org metadata cache, and metrics:

Comment thread huntress/README.md

#### Rate limits

The Huntress API allows 60 requests per minute per API key pair. A typical run with 3 SIEM queries and agent metrics uses roughly 5 to 25 requests, well within this budget. For large accounts with high log volume or thousands of agents, consider splitting concerns across two instances using separate API key pairs:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The Huntress API allows 60 requests per minute per API key pair. A typical run with 3 SIEM queries and agent metrics uses roughly 5 to 25 requests, well within this budget. For large accounts with high log volume or thousands of agents, consider splitting concerns across two instances using separate API key pairs:
The Huntress API allows 60 requests per minute per API key pair. A typical run with 3 SIEM queries and agent metrics uses roughly 5 to 25 requests, well within the limit. For large accounts with high log volume or thousands of agents, consider distributing the load across two instances using separate API key pairs:

Comment thread huntress/README.md
| -------------------------- | ----------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `huntress_api_key` | Yes | - | Huntress public API key |
| `huntress_secret_key` | Yes | - | Huntress secret API key |
| `log_queries` | Conditional | - | List of query objects; each has `name` (required), `esql_query` (required, must begin with `FROM logs`), and `tags` (optional). `name` is used as the `query_name` tag on metrics. **Always include a `KEEP` clause.** Despite what the Huntress API docs state, queries without `KEEP` return only `uuid` and `organization_id`, not all fields. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `log_queries` | Conditional | - | List of query objects; each has `name` (required), `esql_query` (required, must begin with `FROM logs`), and `tags` (optional). `name` is used as the `query_name` tag on metrics. **Always include a `KEEP` clause.** Despite what the Huntress API docs state, queries without `KEEP` return only `uuid` and `organization_id`, not all fields. |
| `log_queries` | Conditional | - | List of query objects; each has `name` (required), `esql_query` (required, must begin with `FROM logs`), and `tags` (optional). `name` is used as the `query_name` tag on metrics. Always include a `KEEP` clause. Queries without `KEEP` return only `uuid` and `organization_id`. |

Comment thread huntress/README.md

**`LogsSent: 0` in `datadog-agent status` even though logs are being collected**

This is expected. The integration sends logs directly to the Datadog Logs Intake API rather than through the Agent's internal log pipeline, so the Logs Agent counters (`LogsProcessed`, `LogsSent`) will always read 0. Use the Agent check output or Datadog Log Explorer filtered by `source:huntress` to confirm logs are arriving.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is expected. The integration sends logs directly to the Datadog Logs Intake API rather than through the Agent's internal log pipeline, so the Logs Agent counters (`LogsProcessed`, `LogsSent`) will always read 0. Use the Agent check output or Datadog Log Explorer filtered by `source:huntress` to confirm logs are arriving.
This is expected. The integration sends logs directly to the Datadog Logs Intake API rather than through the Agent's internal log pipeline, so the Logs Agent counters (`LogsProcessed`, `LogsSent`) always read 0. Use the Agent check output or Datadog Log Explorer filtered by `source:huntress` to confirm logs are arriving.

Comment thread huntress/README.md
| `error_type` | Cause | Resolution |
| ------------------ | ---------------------------------- | ------------------------------------------------------------- |
| `auth_failure` | Invalid or rotated API credentials | Update `huntress_api_key` or `huntress_secret_key` |
| `timeout` | ES\|QL query too broad | Add a `KEEP` or `WHERE` clause to the query |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem to be rendering properly in the preview. Let's try this.

Suggested change
| `timeout` | ES\|QL query too broad | Add a `KEEP` or `WHERE` clause to the query |
| `timeout` | ES&#124;QL query too broad | Add a `KEEP` or `WHERE` clause to the query |

Comment thread huntress/README.md
| ------------------ | ---------------------------------- | ------------------------------------------------------------- |
| `auth_failure` | Invalid or rotated API credentials | Update `huntress_api_key` or `huntress_secret_key` |
| `timeout` | ES\|QL query too broad | Add a `KEEP` or `WHERE` clause to the query |
| `invalid_query` | Malformed ES\|QL | Fix the `esql_query` value in the failing `log_queries` entry |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `invalid_query` | Malformed ES\|QL | Fix the `esql_query` value in the failing `log_queries` entry |
| `invalid_query` | Malformed ES&#124;QL | Fix the `esql_query` value in the failing `log_queries` entry |

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants