把 Elasticsearch 直接暴露在公网且没有开启任何鉴权,相当于把数据库的 root 密码写在门牌上。很多团队在搭建 ELK 栈时,为了方便调试,先关掉安全功能,结果上线后忘了开启,导致数据泄露的案例比比皆是。安全配置不是可选项,而是 Elasticsearch 集群上线前的最后一道必须完成的工序。

X-Pack 安全功能的正确开启方式

从 Elasticsearch 6.8 和 7.1 版本开始,基础安全功能已经纳入免费授权范畴。你不再需要购买商业许可证就能使用传输层加密、用户认证和基于角色的访问控制。开启安全功能的第一步,是在 elasticsearch.yml 配置文件中添加一行关键配置:

xpack.security.enabled: true

仅仅加上这行还不够。如果你同时启用了传输层 SSL,还需要配置证书。单节点环境下,最简单的做法是让 Elasticsearch 自动生成自签名证书,在配置文件中加入:

xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12

证书文件可以通过 elasticsearch-certutil 工具生成。执行 bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass "" 就能得到一个无密码保护的 PKCS12 证书文件。生产环境强烈建议使用正规 CA 签发的证书,并且设置强密码保护密钥库。

内置用户密码的设置与重置陷阱

安全功能开启后,Elasticsearch 默认启用了一组内置用户,包括 elastic、kibana、logstash_system 等。这些用户的密码需要通过命令行工具手动设置。很多运维人员在这里犯过一个错误:只设置了 elastic 用户的密码,却忘了设置 kibana 和 logstash_system 用户的密码,导致 Kibana 无法连接 Elasticsearch,Logstash 管道也无法正常工作。

正确的做法是依次执行以下命令,为所有需要使用的内置用户设置密码:

bin/elasticsearch-setup-passwords interactive

这个交互式命令会依次提示你输入 elastic、kibana、logstash_system、beats_system、apm_system 和 remote_monitoring_user 的密码。如果你希望自动化部署,可以使用 auto 参数让系统自动生成随机密码:

bin/elasticsearch-setup-passwords auto

自动生成的密码会打印在控制台上,务必将它们安全地保存到密码管理器中。一旦丢失,只能通过重置密码流程恢复,这个过程需要先暂停集群的安全功能,操作风险较大。

基于角色的访问控制核心设计

Elasticsearch 的权限模型建立在“用户-角色-权限”三层结构之上。角色定义了可以对哪些索引执行哪些操作,用户则被分配一个或多个角色。这套机制的精妙之处在于,你可以精确控制到单个索引、单个字段甚至单个文档的访问权限。

创建一个角色最直接的方式是使用 Kibana 的“堆栈管理”界面,但通过 API 创建更加灵活可控。以下是一个典型的只读角色定义:

POST /_security/role/logs_reader
{
  "cluster": ["monitor"],
  "indices": [
    {
      "names": ["logs-*"],
      "privileges": ["read", "view_index_metadata"],
      "allow_restricted_indices": false
    }
  ]
}

这个角色拥有集群级别的监控权限,以及对所有以 logs- 开头的索引的读取和元数据查看权限。注意 privileges 数组中并没有 write 或 delete,这意味着持有该角色的用户无法修改或删除任何数据。集群权限和索引权限是分开定义的,这种设计让你能够实现最小权限原则。

索引级别的精细化权限控制

索引权限的粒度远不止读写两种。Elasticsearch 提供了二十多种索引级权限,包括 create_index、delete_index、manage、manage_ilm 等。一个常见的需求是,让应用只能向特定索引写入数据,但不能修改已有文档。这可以通过组合 create 和 write 权限来实现,但需要注意,write 权限本身包含了更新和删除文档的能力。如果你只想允许新增文档,应该使用 create_doc 权限:

POST /_security/role/app_ingest_only
{
  "indices": [
    {
      "names": ["app-logs-*"],
      "privileges": ["create_doc"],
      "field_security": {
        "grant": ["timestamp", "message", "level", "service"]
      }
    }
  ]
}

这个角色还引入了字段级别的安全控制。field_security 中的 grant 列表指定了用户只能看到这四个字段,索引中的其他字段对用户完全透明。反过来,你也可以用 except 来排除敏感字段,比如排除包含个人身份信息的字段。

文档级别安全性的实际应用

比字段级别更细的是文档级别安全控制。通过 Elasticsearch 的查询模板,你可以限制用户只能搜索到符合特定条件的文档。这在多租户场景下特别有用,比如每个部门只能看到自己的数据。实现方式是在角色定义中加入 query 字段:

POST /_security/role/department_engineering
{
  "indices": [
    {
      "names": ["corporate-*"],
      "privileges": ["read"],
      "query": {
        "template": {
          "source": "{\"term\": {\"department.keyword\": \"engineering\"}}"
        }
      }
    }
  ]
}

这个角色在每次查询时都会自动注入过滤条件,确保用户只能检索到 department 字段值为 engineering 的文档。即使用户在查询体中明确写入了 match_all,Elasticsearch 也会在底层叠加这个限制条件。需要注意的是,文档级别安全会影响搜索性能,因为每个查询都要附加额外的过滤逻辑。

API 密钥与服务账户管理

对于程序化访问场景,直接使用用户密码并不是最佳实践。Elasticsearch 提供了 API 密钥机制,你可以为特定角色生成有时间限制的密钥,程序使用密钥而非密码进行认证。创建 API 密钥的请求如下:

POST /_security/api_key
{
  "name": "my-application-key",
  "role_descriptors": {
    "app-role": {
      "cluster": ["monitor"],
      "indices": [
        {
          "names": ["app-data-*"],
          "privileges": ["read", "write"]
        }
      ]
    }
  },
  "expiration": "30d"
}

返回结果中的 id 和 api_key 需要妥善保管,api_key 值只在创建时显示一次。程序在请求头中使用 Base64 编码的 "id:api_key" 作为 Authorization 头的值。API 密钥的优势在于可以随时吊销,不影响其他密钥和用户,而且可以设置过期时间,降低密钥泄露的风险。

传输层与 HTTP 层加密的双重保障

安全配置不能只停留在认证层面,数据传输过程中的加密同样关键。Elasticsearch 的通信分为两层:节点间通信的传输层和客户端访问的 HTTP 层。传输层加密是必须开启的,它防止集群内部通信被窃听。HTTP 层加密则是保护客户端与集群之间的数据安全。

启用 HTTP 层 SSL 需要在 elasticsearch.yml 中添加:

xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: elastic-certificates.p12
xpack.security.http.ssl.truststore.path: elastic-certificates.p12

配置完成后,所有对 Elasticsearch 的访问都必须使用 HTTPS 协议。这意味着 Logstash、Filebeat、Kibana 等组件的连接配置也需要同步更新,将 http:// 改为 https://,并配置相应的证书或信任策略。

审计日志的开启与关键事件追踪

安全配置的最后一道防线是审计日志。它记录谁在什么时间执行了什么操作,结果如何。在 elasticsearch.yml 中启用审计功能:

xpack.security.audit.enabled: true

默认情况下,审计日志输出到 Elasticsearch 的日志文件中。对于生产环境,建议将审计事件输出到独立的索引中,方便集中管理和分析。可以配置审计输出目标:

xpack.security.audit.outputs: [ index, logfile ]

审计日志会记录认证失败、权限拒绝、索引创建删除等关键事件。定期审查审计日志,能够及时发现异常的访问模式,比如某用户短时间内大量尝试访问无权限的索引,这往往是攻击的前兆。

常见安全配置误区与排查思路

配置完安全功能后,连接失败是最常遇到的问题。排查时先确认 elasticsearch.yml 中 xpack.security.enabled 是否设置为 true,然后检查所有客户端是否已更新为使用认证信息。Kibana 连接失败的典型错误是“Authentication required”,这意味着 kibana.yml 中的 elasticsearch.username 和 elasticsearch.password 配置缺失或错误。

另一个容易被忽视的细节是,当启用 HTTPS 后,Kibana 默认仍尝试通过 HTTP 连接 Elasticsearch。需要在 kibana.yml 中明确指定 elasticsearch.hosts 使用 https:// 前缀,并配置 elasticsearch.ssl.verificationMode 为 certificate 或 full。如果使用的是自签名证书,还需要将 CA 证书导入到 Kibana 的信任库中。

索引权限配置中有一个常见的过度授权问题:使用 names 通配符时过于宽泛。比如将索引模式设置为 * 或 _all,这会让角色获得对所有索引的操作权限,违背了最小权限原则。建议始终使用具体的前缀模式,如 app-logs-*,并定期审查角色定义中的索引匹配范围。

最后提醒一点,Elasticsearch 的节点间通信密钥和 HTTP 层密钥可以共用同一个证书,但从安全最佳实践角度,建议分开管理。节点间通信使用内部 CA 签发的证书,HTTP 层使用公共 CA 签发的证书,这样外部客户端访问时不会遇到证书信任问题。