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 realrangefilter, 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.