Skip to content

Elastic SIEM

A supported mapping from the API onto Elastic Common Schema, plus the Elasticsearch and Kibana objects that go with it. Everything on this page is downloadable and versioned — you are meant to install it, not to copy it out of a code block.

Why we publish this rather than leave it to you

A mapping is not a convenience. Two of the decisions below are ones that fail quietly when guessed wrong: an index whose free-form fields were dynamically mapped silently rejects a fraction of your assets, and a detection rule pointed at the wrong timestamp matches nothing at all. Both look like a working integration. Using the published artifact also means your dashboards and rules are portable — ours, and anyone else's.

The artifact

File What it is
index-template.json Index template for securesight-*: field types, the free-form bags, the threat.age_days runtime field
filebeat-securesight.yml A stock Filebeat httpjson input that polls the delta stream and emits ECS. Configuration only — no code, no custom connector
kibana-objects.ndjson Data views, saved searches and the SecureSight — overview dashboard
detection-rules.ndjson Five example detection rules

The template carries its own version in _meta.version. Check it before assuming a field exists.

Install

export EXTAPI_CLIENT_ID=...  EXTAPI_CLIENT_SECRET=...
export EXTAPI_BASE_URL=https://api.secutec.com
export EXTAPI_TOKEN_URL=https://auth.my.secutec.com/realms/secutec/protocol/openid-connect/token
export ES_HOST=http://elasticsearch:9200

curl -X PUT "$ES_HOST/_index_template/securesight" \
  -H 'Content-Type: application/json' --data-binary @index-template.json

filebeat -e -c filebeat-securesight.yml

curl -X POST "$KIBANA/api/saved_objects/_import?overwrite=true" \
  -H 'kbn-xsrf: true' --form file=@kibana-objects.ndjson
curl -X POST "$KIBANA/api/detection_engine/rules/_import?overwrite=true" \
  -H 'kbn-xsrf: true' --form file=@detection-rules.ndjson

Apply the index template before the first ingest. Field types cannot be changed in place, so a wrongly typed index has to be reindexed.

The mapping

SecureSight ECS
threat event.kind: alert, event.module, event.severity (numeric), rule.name
cvss_score / cvss_vector vulnerability.score.base / .version, event.category: [vulnerability]
updated_at @timestamp
stream_position event.ingested
asset ip host.ip, related.ip
asset person user.email, user.name, related.user
asset subdomain / domain host.name, url.domain, related.hosts
asset service / web_entity host.name + destination.port
asset software / operating_system package.name
removal (tombstone) event.action: asset-removed, ss.deleted: true

Everything the API returns is also kept verbatim under ss.*, so no field is lost to the mapping. related.* is populated as well as the typed field, because that is the family Elastic's own pivots and prebuilt rules search across.

service and web_entity names are <host>:<port>, split so the host is pivotable and the port typed. IPv6 sockets look like a:b:c:…:443, so the port is whatever follows the last colon, and only when it parses as a number.

Two clocks — read this before writing a rule

This is the decision that fails quietly.

ECS field From Answers
@timestamp updated_at When did the resource change?
event.ingested stream_position When did it reach you?

@timestamp is resource time, which is what Discover and every dashboard here should read: the stream is a state feed, so two polls of an unchanged resource must not produce two points in time.

Detection rules need the other one. A rule windows on the timestamp it is given. Pointed at @timestamp, a rule with a 30-minute lookback matches nothing on a first backfill — every document is historical by that clock, however recently it arrived — and a critical finding untouched since July stays invisible however open it still is. Every shipped rule therefore sets timestamp_override: event.ingested.

event.ingested comes from the API's stream_position, not from the agent's read time. That matters on a replay, a backfill, or a second agent: read time differs per run, stream_position is the same value for the same delivery, so alerting stays reproducible.

Two mapping decisions worth knowing

The document id is the resource id. fingerprint over ss.id becomes @metadata._id, so the delta stream's at-least-once redelivery upserts instead of duplicating — the "de-duplicate on id" rule from Pagination & deltas, enforced by the index rather than left to the reader. It is also why a tombstone replaces the asset it refers to rather than appending a second copy.

Regular indices, not a data stream: a data stream is append-only and would accumulate one copy per poll.

Free-form fields are mapped flattened. ss.identifying_properties and ss.module_data are module-specific and differently shaped per module. Left to Elasticsearch's dynamic mapping, the first document wins the type for the whole index — one asset with a numeric identifying_properties.version maps the field as long, and every later asset carrying "v4" is rejected with a 400. Filebeat logs Cannot index event (status=400): dropping event! and puts the reason in a separate event log, so the ingest looks clean while a fraction of your inventory is missing. In our own validation run that was 556 of 5083 assets. flattened also makes module_data aggregatable, which a dynamic text mapping does not.

The detection rules

Rule Severity
High or critical finding still unsolved high → critical via severity_mapping
Critical finding open longer than 30 days critical
Leaked credentials for a person high
Newly discovered internet-facing asset medium
Asset removed from the inventory (tombstone) low

They are examples, not a policy — tune the thresholds to your environment. Each carries an investigation guide in its note field, and the two that map cleanly onto ATT&CK carry the mapping.

Rules answer "what changed"; dashboards answer "where are we". An ageing critical is a standing state, and no rule can report it — which is why the ageing criticals number is a dashboard panel rather than an alert.

Two things to expect:

  • Date math does not work inside a KQL comparison. ss.first_seen_at >= "now-7d" in a rule query silently matches nothing rather than erroring. Use a real range filter, as the shipped new-public-asset rule does.
  • A first backfill can drop a few alerts to timestamp ties. The detection engine pages by the timestamp field, and a backfill delivers hundreds of documents sharing one stream_position. Steady state has no such ties. Either way the index, not the alert, is your delivery guarantee.

What this does not cover

There is no Elastic Agent integration package yet — this is a Filebeat input and a set of saved objects. If you run Fleet and would prefer a package, tell us; the mapping here is what it would contain.