Secure Elasticsearch with cnspec
Scan Elasticsearch clusters, roles, and API keys against security and compliance best practices with cnspec.
Scan your Elasticsearch clusters to find security risks before they become incidents. cnspec evaluates whether the security features are enabled, anonymous access, HTTP and transport TLS, audit logging, native-realm users, roles and the index patterns they reach, role mappings, and API key expiration.
cnspec inventories the cluster through read-only requests to the REST API. It never reads your documents.
This provider is for Elasticsearch. OpenSearch serves a different security API and is covered by the separate opensearch provider; pointing this one at an OpenSearch node is detected and refused.
If you're new to cnspec, start with the Quickstart. For an overview of every database cnspec can scan, see the database scanning overview.
Prerequisites
To scan Elasticsearch with cnspec, you must have:
- cnspec installed on your workstation
- Network access to the REST API (port 9200 by default)
- A user with the
monitorcluster privilege andread_security
Authenticate
cnspec authenticates with basic auth or an API key:
cnspec shell elasticsearch es.contoso.com --user elastic --ask-passFor a cluster served with a private CA, add the CA flag:
cnspec shell elasticsearch es.contoso.com --scheme https --user auditor --ask-pass --tls-ca ca.pemPrefer a least-privileged account for auditing. monitor plus read_security reads everything below. With only monitor, the cluster and security-posture summary still resolve while the user, role, role-mapping, and API-key collections come back empty rather than failing the scan, so confirm they're populated before trusting a passing check that reads them.
Connection options
| Option | Description |
|---|---|
--host | Cluster hostname or IP address (also accepted as the positional argument) |
--port | REST API port (default 9200) |
--scheme | http or https (default https; Elasticsearch 8+ enables HTTPS) |
--user, -u | User for basic authentication |
--password, -p | Password, or --ask-pass to be prompted |
--api-key | API key to authenticate with instead of basic auth (base64 of id:api_key) |
--tls-ca | Path to a CA certificate to verify an https cluster with a private CA |
--tls-insecure | Skip TLS certificate verification (testing only) |
Verify with a quick Elasticsearch check
cnspec shell elasticsearch es.contoso.com --user elastic --ask-passcnspec> elasticsearch.cluster { name version distribution healthStatus nodeCount }
elasticsearch.cluster: {
name: "docker-cluster"
version: "8.15.3"
distribution: "elasticsearch"
healthStatus: "green"
nodeCount: 1
}If cnspec connects and shows the version, you're ready to scan.
Scan Elasticsearch
cnspec scan elasticsearch es.contoso.com --user auditor --ask-passWhen a scan completes, cnspec prints a summary of all the checks it ran, grouped by policy, along with a risk score from 0 (no risk) to 100 (highest risk). Failed checks include remediation guidance to help you fix issues. To learn more about reading scan results, read Understand cnspec Results.
Mondoo doesn't yet ship an out-of-the-box Elasticsearch policy, so use the checks below as a starting point and create your own policies to meet your specific requirements.
Explore and test checks interactively
cnspec shell elasticsearch es.contoso.com --user auditor --ask-passRead the security posture
cnspec> elasticsearch.cluster.security { available enabled anonymousAccessEnabled httpTlsEnabled transportTlsEnabled auditLoggingEnabled }
elasticsearch.cluster.security: {
available: true
enabled: true
anonymousAccessEnabled: false
httpTlsEnabled: false
transportTlsEnabled: false
auditLoggingEnabled: false
}List native-realm users
cnspec> elasticsearch.cluster.users { name enabled isReserved roles }List roles and their cluster privileges
cnspec> elasticsearch.cluster.roles { name isReserved clusterPrivileges }Inspect a role's index privileges
cnspec> elasticsearch.cluster.roles.where(name == "reader").first.indexPrivileges { names privileges hasFieldSecurity hasDocumentSecurity }List API keys
cnspec> elasticsearch.cluster.apiKeys { name username creation expiration neverExpires invalidated }List role mappings
cnspec> elasticsearch.cluster.roleMappings { name enabled roles }Example security checks
Ensure the security features are enabled
Without them the cluster performs no authentication at all:
cnspec> elasticsearch.cluster.security.enabled == true
[ok] value: trueEnsure anonymous access is disabled
cnspec> elasticsearch.cluster.security.anonymousAccessEnabled == false
[ok] value: trueEnsure the REST layer uses TLS
cnspec> elasticsearch.cluster.security.httpTlsEnabled == true
[ok] value: trueEnsure node-to-node traffic uses TLS
Transport TLS is what stops an unauthorized node from joining the cluster and reading everything it replicates:
cnspec> elasticsearch.cluster.security.transportTlsEnabled == true
[ok] value: trueEnsure audit logging is enabled
cnspec> elasticsearch.cluster.security.auditLoggingEnabled == true
[ok] value: trueEnsure no custom role grants full cluster access
cnspec> elasticsearch.cluster.roles.where(isReserved == false).none(clusterPrivileges.contains("all"))
[ok] value: trueEnsure no custom role grants "all" on every index
cnspec> elasticsearch.cluster.roles.where(isReserved == false).all(indexPrivileges.none(names.contains("*") && privileges.contains("all")))
[ok] value: trueEnsure no role reaches restricted internal indices
Restricted indices hold the security configuration itself, including users and roles:
cnspec> elasticsearch.cluster.roles.where(isReserved == false).all(indexPrivileges.none(allowRestrictedIndices == true))
[ok] value: trueEnsure no API key lives forever
cnspec> elasticsearch.cluster.apiKeys.where(invalidated == false).none(neverExpires == true)
[ok] value: trueEnsure no live API key has already expired
An expired key that was never invalidated is a leftover worth cleaning up:
cnspec> elasticsearch.cluster.apiKeys.where(invalidated == false && neverExpires == false).none(expiration < time.now)
[ok] value: trueEnsure reserved users are disabled where unused
The built-in accounts ship with well-known names:
cnspec> elasticsearch.cluster.users.where(isReserved) { name enabled roles }Review who holds the superuser role
cnspec> elasticsearch.cluster.users.where(roles.contains("superuser")) { name enabled }
cnspec> elasticsearch.cluster.roleMappings.where(enabled && roles.contains("superuser")) { name roles }Review roles that can impersonate other users
runAs lets a role issue requests as someone else, which detaches the audit trail from the real caller:
cnspec> elasticsearch.cluster.roles.where(runAs.length > 0) { name runAs }Learn more
- Elasticsearch Resource Pack Reference: every Elasticsearch resource and field cnspec can query
- Secure OpenSearch with cnspec: for OpenSearch clusters
- Write Effective MQL: guide to authoring checks and queries