Databases

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:

Authenticate

cnspec authenticates with basic auth or an API key:

cnspec shell elasticsearch es.contoso.com --user elastic --ask-pass

For 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.pem

Prefer 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

OptionDescription
--hostCluster hostname or IP address (also accepted as the positional argument)
--portREST API port (default 9200)
--schemehttp or https (default https; Elasticsearch 8+ enables HTTPS)
--user, -uUser for basic authentication
--password, -pPassword, or --ask-pass to be prompted
--api-keyAPI key to authenticate with instead of basic auth (base64 of id:api_key)
--tls-caPath to a CA certificate to verify an https cluster with a private CA
--tls-insecureSkip TLS certificate verification (testing only)

Verify with a quick Elasticsearch check

cnspec shell elasticsearch es.contoso.com --user elastic --ask-pass
cnspec> 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-pass

When 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-pass

Read 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: true

Ensure anonymous access is disabled

cnspec> elasticsearch.cluster.security.anonymousAccessEnabled == false
[ok] value: true

Ensure the REST layer uses TLS

cnspec> elasticsearch.cluster.security.httpTlsEnabled == true
[ok] value: true

Ensure 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: true

Ensure audit logging is enabled

cnspec> elasticsearch.cluster.security.auditLoggingEnabled == true
[ok] value: true

Ensure no custom role grants full cluster access

cnspec> elasticsearch.cluster.roles.where(isReserved == false).none(clusterPrivileges.contains("all"))
[ok] value: true

Ensure 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: true

Ensure 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: true

Ensure no API key lives forever

cnspec> elasticsearch.cluster.apiKeys.where(invalidated == false).none(neverExpires == true)
[ok] value: true

Ensure 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: true

Ensure 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

On this page