Elastic least-privilege credentials
deadair needs read access to three things:
- Elastic Security detection-rule metadata through Kibana.
- Data-view metadata through Kibana when a rule uses a data view.
- Elasticsearch index, alias, and data-stream resolution, plus source inventory, freshness, and optional field metadata.
It does not need document writes, rule writes, index management, cluster admin, or Kibana write privileges.
Live integration tests cover Elastic 8.19.19 and 9.4.4. Each version is scanned with only this role.
The tests exercise native input resolution and require representative write attempts to return
403.
Create the role
// POST _security/role/deadair_monitor
{
"cluster": ["monitor"],
"indices": [
{
"names": ["*"],
"privileges": ["monitor", "view_index_metadata", "read"]
}
],
"applications": [
{
"application": "kibana-.kibana",
"privileges": ["feature_siem.read", "feature_siemV2.read", "feature_indexPatterns.read"],
"resources": ["space:default"]
}
]
}
Privilege notes:
| Privilege | Why deadair needs it |
|---|---|
cluster: monitor |
read data-stream and index stats |
indices: monitor |
read index and data-stream metadata |
view_index_metadata |
resolve index, alias, and data-stream selectors; read targeted field_caps, plus full mappings when --schema is used |
read |
run size-0 freshness aggregations and read a bounded sample of paired timestamps for lag checks |
feature_siem.read, feature_siemV2.read |
read detection rules through Kibana |
feature_indexPatterns.read |
read Data View Management objects referenced by detection rules |
Narrow indices.names from "*" when telemetry follows known patterns such as logs-*,
winlogbeat-*, or audit-*. deadair can report only on sources visible to its role.
That visibility affects verdicts. If an enabled rule expects winlogbeat-* but the role can read
only logs-*, deadair sees no matching source even if Winlogbeat indices exist. deadair check
confirms that required API calls are allowed; it cannot prove that a scoped role includes every
source your rules use. After tightening the role, verify at least one known-good rule and source in
the first JSON report before triaging no-match findings.
For a non-default Kibana space, change the resource and run scans with --kibana-space:
"resources": ["space:soc"]
deadair scan --kibana-space soc
Create an API key
// POST _security/api_key
{
"name": "deadair",
"role_descriptors": {
"deadair_monitor": {
/* role body from above */
}
}
}
Use the response’s encoded value.
For an interactive scan:
export DEADAIR_ES_URL=https://es.example.internal:9200
export DEADAIR_KIBANA_URL=https://kibana.example.internal:5601
export DEADAIR_API_KEY=<encoded>
deadair check
deadair scan
For a long-running service, keep the API key in a file with 0600 permissions:
install -m 0600 /dev/null /etc/deadair/api-key
printf '%s' '<encoded>' > /etc/deadair/api-key
deadair serve \
--es-url https://es.example.internal:9200 \
--kibana-url https://kibana.example.internal:5601 \
--api-key-file /etc/deadair/api-key
Calls deadair makes
GET /for backend version discoveryGET /api/detection_engine/rules/_findGET /api/data_views/data_view/<id>for data-view-backed rulesGET /_resolve/index/<expression>?ignore_unavailable=trueGET /_data_stream/_statsGET /_cat/indicesPOST /<index>/_searchwith asize: 0aggregation for freshnessPOST /<index>/_searchfor up to 500 recent events, limited to pairedevent.ingestedand@timestampfields, when an enabled rule can be affected by lagPOST /<index>/_field_capsfor targeted declared-field checks; when--schemais enabled, deadair also reads a fullfields=*snapshot withGET
If an audit shows deadair requesting a write API or broader privileges than this document, treat that as a bug.