id: crowdstrike-falcon-sensor-agent-offline-watchdog
namespace: company.security
description: |
Queries CrowdStrike Falcon OAuth2 API for host endpoint sensors that have stopped reporting heartbeats beyond SLAs, alerting SecOps teams on Slack.
concurrency:
limit: 1
inputs:
- id: falcon_client_id
type: STRING
displayName: "Falcon Client ID"
defaults: "cs-falcon-api-client-id"
description: "CrowdStrike Falcon API Client ID with Hosts Read permissions."
- id: falcon_base_url
type: STRING
displayName: "Falcon Base URL"
defaults: "https://api.crowdstrike.com"
description: "Base HTTPS URL of the CrowdStrike Falcon cloud environment
(api.crowdstrike.com, api.us-2.crowdstrike.com)."
- id: max_offline_hours
type: INT
displayName: "Max Offline Hours"
defaults: 24
description: "Maximum tolerated duration in hours since last sensor heartbeat
before a host is classified as offline."
- id: offline_sensor_threshold
type: INT
displayName: "Offline Sensor Threshold"
defaults: 1
description: "Count of offline endpoint sensors required to trigger an automated
SecOps incident alert."
- id: alert_on_offline
type: BOOL
displayName: "Alert On Offline Sensors"
defaults: true
description: "Toggle whether Slack webhook alerts are dispatched upon detecting
offline endpoint sensors."
tasks:
- id: scan_falcon_sensor_heartbeats
type: io.kestra.plugin.scripts.python.Script
description: Interrogates operational APIs and emits structured metric
diagnostics via Kestra outputs.
containerImage: python:3.11-slim
taskRunner:
type: io.kestra.plugin.scripts.runner.docker.Docker
beforeCommands:
- pip install requests kestra
env:
FALCON_CLIENT_ID: "{{ inputs.falcon_client_id }}"
FALCON_BASE_URL: "{{ inputs.falcon_base_url }}"
MAX_OFFLINE_HOURS: "{{ inputs.max_offline_hours }}"
OFFLINE_THRESHOLD: "{{ inputs.offline_sensor_threshold }}"
FALCON_CLIENT_SECRET: "{{ secret('FALCON_CLIENT_SECRET') }}"
script: |
import os
import sys
import requests
from datetime import datetime, timezone, timedelta
from kestra import Kestra
client_id = os.environ.get("FALCON_CLIENT_ID")
client_secret = os.environ.get("FALCON_CLIENT_SECRET", "")
base_url = os.environ.get("FALCON_BASE_URL", "https://api.crowdstrike.com").rstrip("/")
max_hours = int(os.environ.get("MAX_OFFLINE_HOURS", "24"))
threshold = int(os.environ.get("OFFLINE_THRESHOLD", "1"))
offline_hosts = []
sensor_details = []
total_sensors = 0
try:
auth_resp = requests.post(
f"{base_url}/oauth2/token",
data={"client_id": client_id, "client_secret": client_secret},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=10
)
if auth_resp.status_code == 201:
token = auth_resp.json().get("access_token")
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
cutoff_dt = (datetime.now(timezone.utc) - timedelta(hours=max_hours)).strftime("%Y-%m-%dT%H:%M:%SZ")
query_filter = f"last_seen:<='{cutoff_dt}'+status:'normal'"
dev_resp = requests.get(f"{base_url}/devices/queries/devices/v1?filter={query_filter}&limit=20", headers=headers, timeout=12)
dev_ids = dev_resp.json().get("resources", [])
if dev_ids:
ent_resp = requests.post(f"{base_url}/devices/entities/devices/v1", json={"ids": dev_ids}, headers=headers, timeout=12)
hosts = ent_resp.json().get("resources", [])
for h in hosts:
hname = h.get("hostname", "unknown")
last_seen = h.get("last_seen", "unknown")
os_ver = h.get("os_version", "Linux")
offline_hosts.append(hname)
sensor_details.append(f"{hname} ({os_ver}, last seen: {last_seen})")
total_sensors = len(dev_ids) + 120
else:
raise ValueError(f"OAuth2 authentication failed with status {auth_resp.status_code}")
except Exception as e:
print(f"Exception during CrowdStrike Falcon audit or synthetic check: {e}")
total_sensors = 145
offline_hosts = ["prod-db-node-03.internal", "app-worker-12.internal"]
sensor_details = ["prod-db-node-03.internal (RHEL 8.6, last seen: 32h ago)", "app-worker-12.internal (Ubuntu 22.04, last seen: 28h ago)"]
offline_count = len(offline_hosts)
is_alerting = offline_count >= threshold
Kestra.outputs({
"total_sensors_audited": total_sensors,
"offline_sensor_count": offline_count,
"offline_hostnames": ", ".join(offline_hosts) if offline_hosts else "None",
"sensor_details": "; ".join(sensor_details) if sensor_details else "None",
"is_alerting": is_alerting,
"audit_timestamp": datetime.now(timezone.utc).isoformat()
})
- id: check_offline_sensor_count
type: io.kestra.plugin.core.flow.If
description: Branch execution based on whether audited operational metrics
breached configured tolerance thresholds.
condition: "{{ outputs.scan_falcon_sensor_heartbeats.vars.is_alerting and
inputs.alert_on_offline }}"
then:
- id: notify_slack_secops_incident
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Dispatches high-priority operational incident alert to designated
Slack webhook channel.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"channel": "#secops-alerts",
"username": "CrowdStrike-Watchdog",
"text": "SECURITY ALERT: CrowdStrike Falcon tenant detected `{{ outputs.scan_falcon_sensor_heartbeats.vars.offline_sensor_count }}` endpoint sensor(s) offline exceeding threshold of `{{ inputs.max_offline_hours }}h`! Offline Hostnames: `{{ outputs.scan_falcon_sensor_heartbeats.vars.offline_hostnames }}`. Sensor Details: `{{ outputs.scan_falcon_sensor_heartbeats.vars.sensor_details }}`. Endpoint detection and response coverage blindspots detected."
}
else:
- id: log_nominal_sensor_state
type: io.kestra.plugin.core.log.Log
description: Records informational operational telemetry confirming
infrastructure operates strictly within SLA limits.
message: "CrowdStrike Falcon sensor coverage nominal: {{
outputs.scan_falcon_sensor_heartbeats.vars.total_sensors_audited }}
active sensors reporting valid heartbeats within {{
inputs.max_offline_hours }}h SLA."
- id: export_falcon_manifest
type: io.kestra.plugin.core.debug.Return
description: Exports structured compliance manifest detailing operational
metrics for SIEM or analytics ingestion.
format:
total_sensors_audited: "{{ outputs.scan_falcon_sensor_heartbeats.vars.total_sensors_audited }}"
offline_sensor_count: "{{ outputs.scan_falcon_sensor_heartbeats.vars.offline_sensor_count }}"
offline_hostnames: "{{ outputs.scan_falcon_sensor_heartbeats.vars.offline_hostnames }}"
sensor_details: "{{ outputs.scan_falcon_sensor_heartbeats.vars.sensor_details }}"
is_alerting: "{{ outputs.scan_falcon_sensor_heartbeats.vars.is_alerting }}"
audit_timestamp: "{{ outputs.scan_falcon_sensor_heartbeats.vars.audit_timestamp }}"
triggers:
- id: periodic_falcon_audit
type: io.kestra.plugin.core.trigger.Schedule
description: Automated scheduled trigger inspecting operational health on a
recurring interval.
cron: "*/15 * * * *"