概述
衔接前文
前文《集群分布式原理与高可用》拆解了 ES 集群的协调机制与故障转移。然而,一个开放且高可用的集群也意味着更多的安全挑战:节点间通信可能被窃听,未经认证的用户可能直接读写数据,不同租户的数据可能互相可见。ES 8.x 默认启用了安全功能,但如何配置 TLS、设计 RBAC 权限、实现字段级和文档级的精细化安全控制?本文将从 ES 的安全体系出发,逐层拆解传输加密、身份认证、授权模型与多租户隔离。
总结性引言
Elasticsearch 早期因安全机制的缺失而饱受诟病,但从 6.8/7.1 版本开始将安全功能免费开放,8.x 更是将安全默认启用。如今 ES 提供了完整的安全栈:通过 TLS 加密节点间与客户端通信,通过 RBAC 实现细粒度权限控制,通过字段级与文档级安全实现“同一索引不同用户看到不同数据”。本文将系统拆解这些安全机制的底层实现与生产配置,帮助读者构建安全合规的 ES 搜索服务。
核心要点
- 传输层加密:节点间与 HTTP 层 TLS 的证书配置与热更新。
- 用户认证:内置域、文件域、LDAP 集成、服务账号、API Key。
- RBAC 授权:角色→权限→资源模型,集群权限与索引权限的粒度控制。
- 字段级与文档级安全:
field_level_security与document_level_security的实现原理。 - 多租户架构:索引级别隔离与 Kibana Spaces 配合。
- 审计日志:认证事件与请求详情的追踪。
文章组织架构
flowchart TD
1[1. 传输层加密 TLS] --> 2[2. 用户认证体系]
2 --> 3[3. RBAC 授权模型]
3 --> 4[4. 字段级与文档级安全]
4 --> 5[5. 多租户架构设计]
5 --> 6[6. 审计日志]
6 --> 7[7. 面试高频专题]
架构图说明
- 总览说明:全文 7 个模块从传输加密出发,逐步深入认证、授权、细粒度安全控制和多租户,最后以审计日志和面试题收尾。
- 逐模块说明:模块 1 建立通信安全基础;模块 2-3 构建身份认证与权限管理核心;模块 4 实现数据级安全过滤;模块 5 设计多租户隔离方案;模块 6 追踪安全事件;模块 7 面试巩固。
- 关键结论:ES 的安全体系通过 TLS 加密保障通信、RBAC 控制操作权限、字段/文档级安全限制数据可见性,三层递进构建了从网络到数据的纵深防御。理解这些机制是部署生产级 ES 集群的安全基础。
1. 传输层加密(TLS)
ES 节点间通信(transport 层,默认 9300 端口)与客户端 REST 通信(HTTP 层,默认 9200 端口)分别承载集群内部协调消息和外部索引/搜索请求。在开放网络环境中,若两者均为明文传输,攻击者可轻易嗅探数据、伪造节点或篡改内容。因此,ES 8.x 默认要求传输层加密,并强烈建议对 HTTP 层也启用 TLS。
1.1 节点间 TLS 与 HTTP 层 TLS 的分离设计
ES 将传输层加密配置拆分为两个独立域:
xpack.security.transport.ssl— 控制节点间 transport 通信的 SSL/TLS。xpack.security.http.ssl— 控制 REST API 通信的 SSL/TLS。
这种分离的核心设计意图是:集群内部通信通常比客户端访问拥有更高的信任度,可能使用自签 CA 颁发的证书和更严格的验证模式;而对外暴露的 HTTP 层可使用公共 CA 签发的证书,以满足浏览器和第三方客户端的验证需求。同时,隔离配置允许在不停机的情况下仅重载某一层的证书。
从网络栈上看,transport 层基于自定义的二进制协议,使用 Netty 构建,TLS 集成在 Netty 的 SslHandler 中;HTTP 层同样基于 Netty,但使用的是 REST 风格的请求-响应模型。两者共享底层 SSL 引擎配置,但连接管理独立,因此可以拥有不同的信任库和密钥库。
1.2 证书生成与部署
ES 提供 elasticsearch-certutil 工具生成 PKI 体系所需的证书和密钥。
生成 CA 证书
# 生成集群自签 CA 证书(PEM 格式)
./bin/elasticsearch-certutil ca --pem --out ca.zip
解压后获得 ca.crt 和 ca.key,ca.crt 必须分发至所有节点的证书信任库。CA 证书是信任锚,任何由此 CA 签发的证书在 certificate 模式下均可被信任,在 full 模式下还需通过主机名验证。
生成节点证书
# 为节点生成由上述 CA 签发的证书(含 SAN)
./bin/elasticsearch-certutil cert --ca-cert ca.crt --ca-key ca.key --name node1 --dns node1.example.com --ip 10.0.0.1 --pem --out node1.zip
该命令生成 node1.crt 和 node1.key,并将 CA 证书链嵌入。SAN(Subject Alternative Name) 中包含主机名和 IP,是 full 验证模式的基础。在 X.509 证书扩展中,SAN 字段允许指定多个 DNS 名称和 IP 地址,验证时客户端会尝试将连接目标地址与 SAN 列表匹配。若使用 --dns 和 --ip 参数分别添加了 node1.example.com 和 10.0.0.1,那么任何以这两个地址发起的连接都不会触发主机名验证失败。
部署至节点
将 .crt 和 .key 放入 config/certs 目录,然后在 elasticsearch.yml 中引用。密钥文件的访问权限必须设为 600,防止其他用户读取私钥。
1.3 证书验证模式
ES 支持两种验证模式,配置项为 xpack.security.transport.ssl.verification_mode 和 xpack.security.http.ssl.verification_mode:
certificate:仅验证证书是否由受信任的 CA 签发、是否在有效期内。不检查主机名或 IP。该模式存在中间人攻击风险:攻击者若持有同一 CA 签发的任何有效证书,即可伪装成合法节点。在早期版本中,该模式是默认值,因为配置简单;但从 8.x 开始 transport 默认提升为full。full(生产推荐):在certificate的基础上,校验证书中的 SAN 或 CN 字段是否与连接的实际地址(主机名/IP)匹配。这有效防止了证书被盗用后的伪装攻击。full模式下,如果证书中未包含匹配的 SAN/IP,连接将被拒绝,日志中会记录SSLPeerUnverifiedException或类似错误。
ES 8.x 默认将 transport 的验证模式设为 full,HTTP 层默认为 full(若启用 TLS)。当使用 elasticsearch-certutil 生成证书时,工具会自动将实例名、DNS 和 IP 写入 SAN。
验证流程底层由 Java 的 TrustManager 和 HostnameVerifier 协作完成:TrustManager 检查证书链信任关系,HostnameVerifier 则根据验证模式进行主机名匹配。ES 自定义了 SSLConfiguration 和 SSLService 来管理这些组件的生命周期。
1.4 证书热更新:_ssl/reload API
在不重启节点的情况下更新即将过期的证书是生产运维的刚性需求。ES 提供 _ssl/reload API:
POST _ssl/reload
该 API 指示节点重新加载 SSL 上下文:从配置的文件路径重新读取密钥库或 PEM 文件,创建新的 SSLEngine,并将新连接迁移至新上下文。已建立的连接不会中断,因为 TLS 连接在握手完成后不再依赖配置更新。此机制依赖于 SSLContext 的热刷新能力,要求新证书的 KeyManager 和 TrustManager 能够无缝替换。
底层实现上,SSLService 维护一个 SSLContextHolder,reload 操作会触发重新加载文件、构建新的 SSLContext,然后原子地替换引用。已建立的 SSLEngine 仍然保持原有会话,直到连接关闭;新建连接则会使用新的上下文进行握手。因此,旧证书可以在短时间内与新的同时有效,形成平滑过渡。
ES 安全体系分层架构
flowchart LR
subgraph 安全体系
A[TLS 通信加密层] --> B[身份认证层]
B --> C[授权与 RBAC 层]
C --> D[数据过滤层]
end
A1[节点间 TLS] --> A
A2[HTTP 层 TLS] --> A
B1[内置域] --> B
B2[文件域] --> B
B3[LDAP/AD] --> B
B4[服务账号/API Key] --> B
C1[角色定义] --> C
C2[权限解析] --> C
C3[资源匹配] --> C
D1[字段级安全] --> D
D2[文档级安全] --> D
图表说明
- 图表描述:安全体系被抽象为四个纵向分层,每层解决一类安全问题。
- 关键组件:TLS 层包含节点间和 HTTP 两套加密;认证层囊括五种认证方式;授权层通过 RBAC 将角色、权限、资源绑定;数据过滤层在查询执行时动态附加字段和文档级约束。
- 设计意图:分层架构遵循纵深防御原则,即使攻击者绕过某一层,下层仍可阻拦或限制其行为。
- 安全价值:该模型确保通信保密性、身份可信性、操作最小权限以及数据可见性控制,形成端到端安全闭环。
TLS 证书链验证序列
sequenceDiagram
participant C as 客户端/节点
participant S as ES 节点
C->>S: ClientHello (支持的密码套件)
S-->>C: ServerHello + 证书链 (节点证书 + 中间CA + 根CA)
C->>C: 用信任库中的CA证书验证证书链签名
alt 验证模式 full
C->>C: 提取证书SAN/DNS/IP,与连接目标地址比对
end
alt 验证成功
C->>S: 密钥交换,建立加密通道
else 验证失败
C-->>S: 连接拒绝
end
图表说明
- 图表描述:该序列图展示 TLS 握手时证书验证的核心步骤。
- 关键步骤:Server 发送证书链;客户端验证 CA 签名;在
full模式下额外比对主机名;验证通过后建立加密信道。 - 设计意图:
full模式将通信层安全从“信任同一 CA 的所有证书”收紧为“只信任特定主机的证书”,消除证书重放风险。 - 安全价值:即使内部 CA 管理的证书被盗,攻击者也无法伪装成其他节点,极大强化了集群内部通信的身份保障。
1.5 配置示例与解读
elasticsearch.yml 片段
# ---------- 传输层 TLS ----------
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: full
xpack.security.transport.ssl.key: certs/node1.key
xpack.security.transport.ssl.certificate: certs/node1.crt
xpack.security.transport.ssl.certificate_authorities: [ "certs/ca.crt" ]
# ---------- HTTP 层 TLS ----------
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.verification_mode: full
xpack.security.http.ssl.key: certs/node1.key
xpack.security.http.ssl.certificate: certs/node1.crt
xpack.security.http.ssl.certificate_authorities: [ "certs/ca.crt" ]
设计解读
enabled: true:强制加密传输,拒绝任何明文连接。节点启动时,如果 enabled=true 但找不到有效证书,进程会直接退出,避免无意间暴露未加密服务。verification_mode: full:双重验证——确保证书由信任的 CA 签发且主机身份匹配,是防止节点冒充的关键。- 证书复用:同一套节点证书可同时用于 transport 和 HTTP 层,降低管理复杂度。若需要对外提供浏览器友好的证书,HTTP 层可单独更换为由公共 CA 签发的证书。
certificate_authorities:指定信任锚,可包含多个 CA 文件,用于证书轮换期间的过渡。当需要迁移到新的 CA 时,可同时包含新旧 CA 证书,待所有节点证书更新后再移除旧 CA。
2. 用户认证体系
ES 采用认证域(Realm) 机制管理用户身份。认证域可以组成链,用户登录时依次尝试,首个成功的域即为用户颁发身份凭证。ES 内置几种认证域,并支持与企业目录(LDAP/AD)及单点登录系统集成。
认证域的链式处理由 AuthenticationService 实现:当一个请求携带认证头(如 Basic Auth、Bearer Token)到达时,ES 会提取用户名和凭证,按域顺序逐个尝试 authenticate()。如果某个域返回成功结果,则终止链并构建 User 对象;如果所有域均失败,返回认证失败。
2.1 内置域(Native Realm)
Native 域将用户凭证存储在专门的安全索引 .security-7(版本号随发行变化)中。密码经过 bcrypt 哈希处理,具备抗暴力破解的 salt 和 cost factor。该域支持通过 API 动态管理用户,是生产环境中的推荐选择。
安全索引 .security-7 本身受保护,只有具备 manage_security 或相应权限的用户才能读写。其映射中密码字段存储 bcrypt 哈希值,以及角色列表、元数据等。bcrypt 的 cost factor 默认为 10,意味着需要进行 2^10 轮哈希运算,显著增加暴力破解的时间成本。用户登录时,输入的密码会使用存储的 salt 重新计算哈希并比较,整个过程在服务端完成。
用户管理
- 命令行工具
elasticsearch-users:用于文件域的用户管理,但也可通过-r native操作内置域(依赖配置文件users和users_roles的同步,实际场景中多使用 API)。 - _security/user API:管理用户的推荐方式。
POST /_security/user/john_doe
{
"password" : "s3cr3t",
"roles" : [ "editor", "viewer" ],
"full_name" : "John Doe",
"email" : "john.doe@example.com"
}
该请求创建用户 john_doe,密码将通过 bcrypt 哈希后存储。roles 字段绑定已定义的角色。密码策略可通过 xpack.security.authc.password_hashing.algorithm 配置,默认 bcrypt,也支持 PBKDF2 等。
密码哈希细节:bcrypt 加盐,cost 可配置(xpack.security.authc.password_hashing.algorithm 默认 bcrypt,cost 10)。ES 不会存储明文密码,认证时重新哈希并比对。
2.2 文件域(File Realm)
文件域从两个静态文件读取用户和角色映射:
config/users:每行格式用户名:bcrypt哈希密码。config/users_roles:每行格式角色名:用户名。
文件域的优势是无需依赖安全索引,适合引导集群或无法访问网络时的紧急访问。其缺陷也很明显:修改后需通过 _security/reload_file_based_realms API 或重启节点生效,不支持动态增删;且所有节点必须保持文件同步。
文件域由 FileRealm 类实现,它在初始化时将文件内容加载到内存中的哈希表,认证时直接在内存中查找并比对密码。重新加载 API 会触发文件重新读取和内存结构刷新。
2.3 LDAP / Active Directory 域集成
企业环境通常拥有统一的身份源(如 Active Directory)。ES 的 LDAP 域允许用户使用域账号密码登录,并可按组成员关系动态映射角色。
配置方式
在 elasticsearch.yml 中定义 LDAP 域:
xpack.security.authc.realms.ldap.ldap1:
order: 2
url: "ldaps://ldap.example.com:636"
bind_dn: "cn=admin,dc=example,dc=com"
bind_password: "admin_password"
user_search:
base_dn: "dc=example,dc=com"
filter: "(uid={0})"
group_search:
base_dn: "dc=example,dc=com"
filter: "(member={0})"
user_search 模式下,ES 用登录名替换 {0} 查询用户条目;user_dn_templates 模式可直接拼接 DN,适合结构简单的目录。认证成功后,group_search 可获取用户所属组,进而映射到 ES 内置角色或自定义角色(通过 role mapping API)。LDAP 连接池可通过 user_search.pool.enabled 开启,提高并发认证性能。连接池会复用 LDAP 连接,避免每次认证都重新建立 TCP 连接和 TLS 握手。
安全考量:LDAP 通信必须使用 LDAPS 或 STARTTLS 防止凭据泄露;绑定账号权限应最小化;配置中密码可通过 elasticsearch-keystore 安全存储,例如:
./bin/elasticsearch-keystore add xpack.security.authc.realms.ldap.ldap1.bind_password
2.4 服务账号(Service Account)
ES 8.x 引入服务账号,用于内部组件(如 Beats、Logstash、Fleet Server)与 ES 交互。服务账号无需密码,而是携带由 ES 动态分发的 Bearer Token。每个内置组件都有预定义的服务账号(如 elastic/fleet-server),权限已被精确限定。
服务账号的 Token 基于 ES 内部的 JWT 机制生成,包含账号标识、过期时间和签名。Token 校验由 TokenService 负责,它不依赖任何外部身份提供者,Token 本身即包含足够信息进行验证。相比用户名密码,服务账号 Token 可以被独立吊销,并且可以设置较短的过期时间,减少泄露影响。
创建 Token
POST _security/service/elastic/fleet-server/credential/token
响应中的 access_token 可配置给客户端。服务账号的 Token 支持过期和撤销,消除了传统静态密码泄露后的长期风险。
Token 的刷新:可以为同一服务账号生成多个 Token,每个 Token 拥有独立的 ID 和过期时间。服务端可随时撤销任意 Token,而不会影响其他 Token。
2.5 API Key 管理
API Key 是面向第三方应用程序的轻量级凭证。创建时可基于当前用户角色细化权限并设置过期时间,实现“最小权限原则”。API Key 的实质是一对 Base64 编码的凭证字符串:id:api_key,客户端将其放入 Authorization: ApiKey ... 头部。
生成 API Key
POST /_security/api_key
{
"name": "my-application",
"role_descriptors": {
"my-role": {
"cluster": ["monitor"],
"index": [
{
"names": ["app-logs-*"],
"privileges": ["read", "write"]
}
]
}
},
"expiration": "1d"
}
返回 id 和 api_key,客户端将两者用冒号拼接并进行 Base64 编码后放入 Authorization: ApiKey ... 头。API Key 不关联用户密码,可独立吊销,不会暴露用户主体凭据。
API Key 的权限模型:如果创建时指定了 role_descriptors,则 Key 的权限被限定在这些角色描述符的并集中,与创建用户的权限取交集。换句话说,Key 的最终权限是 用户权限 ∩ Key 角色描述符权限。这保证了应用程序永远不会获得比创建用户更高的权限。如果 role_descriptors 未指定或为空,则 Key 继承创建用户的当前所有角色,但权限随用户角色变化而动态变化(不推荐,因为可能意外扩大权限)。
吊销:可以通过 API Key 的 ID 或名称批量吊销,立即生效,所有使用该 Key 的连接在下一个请求时将收到 401。
2.6 _authenticate API
该 API 返回当前认证成功的用户信息,包括用户名、角色和认证域,常用于调试和验证。
GET /_authenticate
{
"username": "john_doe",
"roles": ["editor", "viewer"],
"full_name": "John Doe",
"email": "john.doe@example.com",
"metadata": {},
"enabled": true,
"authentication_realm": { "name": "native", "type": "native" },
"lookup_realm": { "name": "native", "type": "native" }
}
authentication_realm 表示实际完成认证的域,lookup_realm 表示从中获取用户角色的域(可配置为不同域)。此 API 在排查权限问题时非常有用:能确认用户身份和拥有的角色列表。
3. RBAC 授权模型
3.1 角色→权限→资源三要素
ES 的 RBAC 围绕三个抽象构建:
- 资源(Resource):集群(cluster)、索引(index)、索引别名、数据流等。索引资源支持通配模式(如
log-*)。 - 权限(Privilege):对资源的操作许可,分为集群权限和索引权限。每个权限实际上是一组细粒度操作的集合。
- 角色(Role):权限的命名集合。用户通过绑定一个或多个角色获取最终的权限并集。
授权流程:请求进入 → 解析用户关联的所有角色 → 合并权限(取并集) → 检查请求操作是否被权限覆盖。如果请求涉及多个索引,会对每个索引独立评估。ES 的授权检查发生在请求处理的早期:对于 REST 请求,SecurityActionFilter 会拦截并调用 AuthorizationService 进行权限评估;对于 transport 请求,AuthorizationService 在 transport 拦截器中执行。
权限合并时,索引权限按索引模式分组,针对每个实际请求的索引,ES 会找到所有匹配的角色索引权限块,将这些块中的 privileges 取并集,字段级安全和文档级安全则取交集(以保证最严格的安全约束)。这种机制保证了即使某个角色允许所有字段,另一个角色限制字段时,用户最终看到的是限制后的字段集。
3.2 权限类型与粒度
集群权限 控制集群级别的管理操作,每个权限是一个命名的操作组,例如:
all:所有集群操作,需极度谨慎,包括manage,monitor,transport_client等一切权限。manage:管理集群设置、索引模板、ILM 策略、节点特性等。monitor:查看集群健康、状态、节点信息等,是只读权限。transport_client:允许 transport 客户端连接(已废弃,保留兼容)。
索引权限 作用于具体索引模式,每个权限映射到一组索引级别的操作码:
read:允许读取文档(get、search、mget、terms_enum 等)。write:允许索引、更新、删除文档(index、update、delete、bulk)。create_index:允许通过索引请求自动创建索引。注意,如果索引已存在,需要write权限才能写入文档。delete:删除索引。manage:索引级别管理,如刷新、合并、修改设置和映射,开放/关闭索引等。manage_ilm:管理索引生命周期策略。create_doc:仅允许索引新文档(使用 op_type=create 的 index 操作),不能更新/删除已有文档。适合只追加数据的场景。read_cross_cluster:跨集群搜索所需的读取权限。
权限粒度控制的核心在于操作码(action)的聚合。ES 预定义了约 250+ 个操作码,如 indices:data/read/search, indices:admin/create 等。每个高级权限(如 read)对应一组操作码。自定义角色时,可以直接指定操作码以实现比预定义权限更精细的控制,例如只允许 indices:data/read/search 而不允许 get,但生产环境极少需要这种粒度,因为预定义权限已足够覆盖大部分需求。
3.3 内置角色
ES 提供数十个内置角色,便于快速授权:
superuser:完全控制集群,可绕过所有安全检查。kibana_admin:管理 Kibana 配置和全局资源,但不能管理 ES 数据。kibana_user:基础 Kibana 用户,可访问已授权 Space。logstash_admin:管理 Logstash 的配置文件(通过 ES 输出配置)。ingest_admin:管理摄取管道和处理器。viewer:只读访问集群状态和索引,包括monitor集群权限和索引的read,view_index_metadata。editor:读写访问索引,无管理权限,基于viewer增加了write,create_index等。
使用内置角色时,应注意它们是为通用场景设计的,权限可能超出实际需求。例如 editor 角色允许在任何匹配的索引模式上创建索引,这在严格的多租户环境中可能过于宽松,需要替换为自定义角色。
3.4 自定义角色示例
POST /_security/role/custom_log_reader
{
"cluster": ["monitor"],
"indices": [
{
"names": ["logs-*", "metrics-*"],
"privileges": ["read", "view_index_metadata"],
"field_security": {
"grant": ["message", "timestamp", "level"],
"except": ["trace_id"]
},
"query": {
"bool": {
"must_not": { "term": { "classification": "sensitive" } }
}
}
}
],
"applications": [
{
"application": "kibana-.kibana",
"privileges": ["feature_discover.read"],
"resources": ["space:finance"]
}
]
}
该角色:
- 集群权限仅监控。
- 索引权限作用于
logs-*和metrics-*,允许读取和查看元数据。 - 同时配置字段级安全(只看到 message、timestamp、level,但排除 trace_id)和文档级安全(屏蔽 classification 为 sensitive 的文档)。
- 授予 Kibana 的 Discover 功能在
financeSpace 中的访问权限。
这样的角色定义遵循了“最小权限”原则:用户仅获取完成工作所必需的信息,所有敏感数据默认隐藏。
RBAC 角色→权限→资源映射
graph TD
R[角色 custom_log_reader] --> C[集群权限: monitor]
R --> I[索引权限]
I --> IN[索引模式: logs-*, metrics-*]
IN --> PR[read, view_index_metadata]
IN --> FS[字段级安全: grant message,timestamp,level]
IN --> DS[文档级安全: query must_not classification=sensitive]
R --> A[应用权限]
A --> K[Kibana: discover.read on space:finance]
图表说明
- 图表描述:该示意图以角色为中心,展示其包含的集群权限、索引权限(含字段和文档级约束)以及应用权限。
- 关键映射:一个角色可持有多个索引权限块,每个块绑定不同的索引模式和安全过滤条件。
- 设计意图:通过结构化定义,将复杂的授权策略压缩进单一角色,实现声明式管理。
- 安全价值:清晰的角色定义使审计和合规审查变得直接,降低“权限蔓延”风险。
3.5 权限解析机制
当请求到达,ES 进行以下步骤:
- 收集用户所有角色(来自认证域映射和直接绑定)。
- 合并索引权限:对于请求涉及的索引,收集所有角色中匹配该索引模式的权限块,权限取并集,字段级安全和文档级安全过滤器取交集(更严格)。合并时,字段白名单和黑名单需要协同处理:最终允许的字段 = 所有角色的 grant 取交集后再减去所有 except 的并集。
- 检查操作是否被权限覆盖,若未通过则拒绝。
文档级安全的 query 合并为 must 组合,即每个角色的 DLS query 都作为 must 子句加入最终查询,再与用户查询进行 AND。如果一个用户绑定多个角色,每个角色都有 DLS 条件,那么最终用户能看到的文档必须满足所有角色的 DLS 条件(因为取交集),这也是更严格的策略。
ES 通过 IndicesPermission 和 ClusterPermission 类实现这些逻辑,它们内部维护了由 Automaton(自动机)构建的通配符匹配树,能够高效地判断索引名是否匹配某个索引模式,并提取对应的权限块。
4. 字段级与文档级安全
4.1 字段级安全(Field Level Security)
字段级安全允许按角色限制用户能看到的文档字段。在角色定义的 field_security 中可设置:
grant:显式允许的字段白名单(可取*表示全部)。except:排除的字段黑名单(必须在grant范围内,或单独使用时仅排除)。
实现原理
当用户执行搜索时,协调节点在向数据节点分发 shard 请求之前,会将允许的字段集合注入到请求中。具体地,协调节点的 TransportSearchAction 会解析角色中所有匹配的索引权限块的字段安全设置,合并后生成一个字段集合,然后将该集合设置到 SearchRequest 的 source().fetchSource() 或 docValueFields() 等参数上。数据节点在提取 _source 或从字段存储中加载数据时,只返回被允许的字段。对于 fields 选项指定的字段,若不在白名单内,则将其忽略或返回空。这一过滤对用户完全透明:用户的查询语句无需修改,ES 在响应序列化阶段自动裁剪字段。
在 Lucene 层面,如果查询请求了 stored_fields 或 docvalue_fields,这些会被限制;_source 的返回也会被过滤。对于聚合、排序等依赖于特定字段的操作,如果该字段被字段级安全屏蔽,操作会失败并返回错误(例如“field [salary] is not allowed”),避免数据间接泄露。
配置示例
"field_security": {
"grant": ["title", "content", "author"],
"except": ["author.email"]
}
该配置允许查看 title、content、author 对象(但排除 author.email 字段)。注意:若 author 是对象类型,except 必须精确到子字段。若只写 except: ["email"] 但没有任何字段名叫 email(它实际是 author.email),则不会生效。
局限性
字段级安全无法隐藏字段的存在性(如映射信息可能暴露字段名)。因此,敏感字段名本身不应与敏感数据关联,最佳实践是使用无关性的字段名。
4.2 文档级安全(Document Level Security)
文档级安全通过向用户的每次搜索自动注入一个查询条件来限制可见文档。角色定义中的 query 字段即为“预设过滤器”。
执行机制
- 用户发起搜索请求(含自身查询条件)。
- 协调节点识别用户角色中的
query,将其与用户查询通过 Bool 查询的must子句组合,形成最终查询。 - 最终查询被分发到各 shard 执行,确保用户永远不会看到不符合安全过滤的文档。
- 如果用户未指定查询(即 match_all),最终查询等同于安全过滤器自身。
整个附加过程对用户无感知:响应中不会返回安全过滤器的细节,也无法通过 explain 或 profile API 观察到原过滤器条件(ES 会隐藏安全相关子句)。这从根本上杜绝了通过分析查询语句推测其他数据存在的可能性。
文档级安全过滤器的执行顺序在查询解析阶段,由 DocumentLevelSecurityQueryBuilder 负责。它会将用户查询包装进一个 BooleanQuery,将 DLS 查询作为 FILTER 子句(而非 MUST)加入,不影响评分,但必须满足。如果用户角色存在多个 DLS 查询(来自不同索引权限块或不同角色),这些查询会全部作为 FILTER 子句出现,形成逻辑 AND。
文档级安全过滤序列图
sequenceDiagram
participant U as 用户
participant C as 协调节点
participant D as 数据节点
U->>C: 搜索请求 { "query": { "match": { "title": "ES" } } }
C->>C: 提取用户角色 DLS query: { "term": { "department": "sales" } }
C->>C: 组合查询: bool must [用户查询, DLS query]
C->>D: 分发组合查询到相关 shard
D-->>C: 返回文档结果 (仅 department=sales 且 title 含 ES)
C-->>U: 序列化响应 (不暴露 DLS 条件)
图表说明
- 图表描述:该图展示了用户搜索请求经过 DLS 注入的全过程。
- 关键步骤:协调节点获取用户角色的 DLS 查询,与用户查询做逻辑 AND,分发至数据节点。
- 设计意图:在尽可能早的阶段(协调节点)注入安全约束,避免数据节点暴露无关数据,最小化传输和内存开销。
- 安全价值:用户无法绕过文档级访问控制,因为过滤发生在服务端,且无法通过返回内容反推安全条件。
4.3 安全过滤器的透明性与保护
字段级和文档级安全共同实现了“同一索引内数据视图隔离”。应用程序和用户无需学习额外的 API,只需绑定相应角色即可获得定制化视图。从安全角度看,这两种过滤机制构建了深度防御的数据层:即使攻击者通过 SQL 注入或直接 API 调用获得了某些查询能力,也无法获取未授权的字段和文档。
ES 设计者有意将安全过滤器与用户查询分离,并阻止其在 explain/profile 中暴露,是因为攻击者可能通过构造特殊查询并分析评分或性能来推断 DLS 条件的存在和内容,这种“侧信道”攻击被 ES 的安全隐藏机制所缓解。
5. 多租户架构设计
在 SaaS 平台或大型企业内部,多个业务单元(租户)共享同一个 ES 集群时,必须实现严格的数据隔离和性能保护。
5.1 索引级别隔离
最直接的多租户策略是为每个租户分配独立的索引命名空间,例如:
- 租户 A:
tenant_a_logs,tenant_a_analytics - 租户 B:
tenant_b_logs,tenant_b_analytics
然后通过 RBAC 角色限制租户只能访问其自身索引模式:
POST /_security/role/tenant_a_role
{
"indices": [
{
"names": ["tenant_a_*"],
"privileges": ["read", "write", "view_index_metadata"]
}
]
}
用户绑定该角色后,任何访问 tenant_b_* 的请求都将被授权层拒绝。配合 Kibana Spaces,租户的索引模式、仪表板和可视化对象也被限定在自己的 Space 内,形成完整的隔离环境。
索引命名规范:建议使用前缀 tenant-<ID>-,其中 ID 可以是租户的唯一标识。这允许在生命周期管理中灵活地按租户进行索引操作,比如定期删除过期数据。
优点:实现简单,隔离明确,迁移和备份可独立进行。 缺点:索引数量膨胀,集群状态压力增大,需注意分片数量控制。当租户数量达到数千时,每个租户可能拥有多个索引,主节点内存会急剧消耗。此时应考虑使用数据流(Data Streams)配合按时间滚动的索引,减少活动索引数量,或采用“hot-warm-cold”分层结合 ILM 自动管理。
5.2 Kibana Spaces 配合
Kibana Spaces 将仪表板、可视化、索引模式等资源分组。将 Space 与 ES 角色关联后,用户登录 Kibana 只能看到授权 Space。其本质是 Kibana 在内部安全 API 基础上实施的另一层过滤:Space 的资源 ID 与角色中的 applications 权限交叉验证。
Kibana 保存对象时,会在内部索引 .kibana 中为每个对象打上 namespace 属性,默认对应 Space 的 ID。当用户请求仪表板列表时,Kibana 调用 ES 搜索,并附加一个 term 查询过滤 namespace,只返回用户有权访问的 Space 中的对象。这个过程对用户透明,且与 ES 的文档级安全概念一脉相承。
例如,创建 Space finance 后,授权角色:
"applications": [
{
"application": "kibana-.kibana",
"privileges": ["feature_discover.all", "feature_dashboard.all"],
"resources": ["space:finance"]
}
]
这样用户能在 finance Space 中使用 Discover 和 Dashboard 功能,完全无法感知其他 Space 的存在。若要允许访问所有 Space,可将 resources 设为 ["*"]。
5.3 物理资源隔离
为避免“吵闹邻居”效应,可将不同租户的索引分布到不同物理节点。利用 index.routing.allocation.require 设置:
PUT tenant_a_logs/_settings
{
"index.routing.allocation.require.tenant": "a"
}
节点通过 elasticsearch.yml 标记:
node.attr.tenant: a
结合 shard allocation awareness,可按机架或专用节点进一步隔离。这种物理隔离确保了 CPU、内存和磁盘 IO 等资源不会被跨租户争抢,满足高 SLA 租户的需求。
还可结合 index.routing.allocation.total_shards_per_node 限制某租户在单个节点上的分片数,防止单一租户索引过度占据节点资源。
5.4 多租户下的安全考量
- 认证与授权:认证域可以与租户绑定(如不同 LDAP 组),角色中索引模式隔离。
- 审计日志:开启审计日志记录哪个租户的用户执行了哪些操作,便于合规和计费。审计日志中的
user字段可追溯至具体租户用户,结合索引模式可分析租户活动。 - 性能保护:通过索引级吞吐量限制(
indices.requests.cache.size等)防止单一租户打垮集群。 - 数据加密:如果存储高度敏感的租户数据,可考虑应用层加密或利用 ES 的加密节点存储(静态加密)能力。
6. 审计日志
审计日志是安全可观测性的核心组件,为事后追溯、入侵检测和合规审计提供证据。
6.1 审计日志配置
在 elasticsearch.yml 中开启审计:
xpack.security.audit.enabled: true
xpack.security.audit.outputs: [ logfile, index ]
xpack.security.audit.logfile.events.include: [ access_denied, authentication_failed, connection_denied ]
xpack.security.audit.index.events.include: [ access_granted, authentication_success ]
- 输出类型:
logfile写入本地文件,index写入 ES 索引.audit-logs-*(可进一步搜索和告警)。index输出方式会产生自引用风险(审计日志本身也可能触发审计事件),ES 通过内部过滤器避免无限循环。 - 事件过滤:可以按事件类型、用户、索引模式等精细控制审计范围,降低吞吐压力。
events.include指定要记录的事件,events.exclude排除特定事件,events.ignore_users忽略特定用户(如内部健康检查用户)产生的事件。 - 本地日志轮转与完整性:日志文件配置滚动策略,文件权限严格限制,防止篡改。可配置日志文件大小、保留数量等。
6.2 审计事件类型
主要事件包括:
authentication_success/authentication_failed:认证结果。包含尝试的认证域、来源 IP。access_granted/access_denied:授权结果。包含请求的详细内容(URI、索引、操作类型)和用户信息。connection_granted/connection_denied:传输层连接建立结果。tampered_request:请求被篡改检测(如 header 不一致)。run_as_granted/run_as_denied:run_as操作,特权用户模拟其他用户时的审计。
每条事件记录包含时间戳、用户名、来源 IP、请求类型、索引、操作是否成功等详细信息。对于 access_denied 事件,还包含缺失的具体权限,方便诊断。
6.3 审计日志的价值
- 安全合规:满足 PCI-DSS、HIPAA、GDPR 等对访问日志的要求。
- 入侵检测:通过对
authentication_failed频率聚合,可检测暴力破解;通过对access_denied的异常模式监控,可发现权限提升尝试。 - 故障排查:当用户报告权限问题时,
access_denied事件直接指出缺失的权限和请求细节,加速排障(详见 ES 系列第 13 篇)。 - 计费:在多租户 SaaS 中,可以统计每个租户的 API 调用次数和数据传输量,用于计量计费。
为了便于分析,通常将审计日志索引到一个独立的监控 ES 集群,避免影响生产集群性能,并利用 Kibana 构建审计仪表板,实时展示认证失败趋势、Top 活跃用户等。
7. 面试高频专题
(本模块严格独立于正文,供面试场景参考,内容深度扩展)
Q1: ES 8.x 默认启用了哪些安全功能?与 7.x 有何变化?
一句话回答:ES 8.x 默认启用 TLS 加密传输、自动生成 elastic 超级用户密码,并强制要求认证;而 7.x 安全功能虽免费但需手动开启。
详细解释:在 ES 7.x 中,安全功能已免费,但首次启动时仍以明文通信运行,管理员需手动配置 TLS 和设置密码。8.x 则直接将 xpack.security.enabled 隐式设为 true,transport 层自动启用 TLS(自签证书),elastic 用户的密码在首次启动时随机生成并输出到控制台及安全索引,用户首次登录必须更改密码。此变更大幅降低了因疏忽导致集群无安全防护的风险。此外,8.x 默认还启用了 API Key 和服务账号的支持,以及 Kibana 的加密通信建议。在节点启动日志中,会明确打印出 elastic 用户的初始密码,并提示修改。
多角度追问
- 若不想使用自签证书,如何在 8.x 禁用自动 TLS?答:必须在
elasticsearch.yml中显式禁用xpack.security.transport.ssl.enabled: false,但强烈不推荐,因为这将导致节点间明文通信。禁用后还需手动配置其他安全措施。 elastic用户的初始密码存储在哪里?答:8.x 输出到控制台日志,并存入安全索引.security(需要现有凭据才能访问)。如果日志丢失,只能通过elasticsearch-users工具重置密码(需要文件域或使用工具绕过)。- 自动配置的 TLS 证书有效期多久?如何更换?答:默认 3 年(PEM 证书的
notAfter),可通过elasticsearch-certutil重新生成并使用_ssl/reload热更换。生产环境应提前计划证书轮换。 - 8.x 中 Kibana 如何知道需要与安全的 ES 通信?答:Kibana 会自动检测 ES 的安全状态,并提示配置
elasticsearch.username和elasticsearch.password,否则无法连接。
加分回答:8.x 还引入了“安全自动配置”阶段(bootstrap),在集群首次启动时如果没有发现安全配置,则自动生成证书和密码,这一过程称为“Security auto-configuration”。该机制确保即使新手也能获得一个基本安全的集群。
Q2: 节点间 TLS 和 HTTP 层 TLS 有什么区别?certificate 和 full 验证模式如何选择?
一句话回答:节点间 TLS 加密集群内部通信,HTTP 层 TLS 加密客户端 REST 流量;certificate 只验证证书链,full 还验证主机名,生产环境必须选 full。
详细解释:Transport 层承载分片分配、主节点选举等关键内部指令,若被窃听或篡改将危及整个集群。HTTP 层则是用户和应用的入口。certificate 模式信任任何由同一 CA 签发的证书,存在证书被盗用后伪装节点的风险。full 模式通过校验证书 SAN 中的 IP/DNS 确保连接目标与证书主体一致,杜绝中间人攻击。在 full 模式下,如果节点配置了多个 IP 或 DNS,证书必须包含所有这些标识,否则内部通信就会失败。
多角度追问
- 为什么 transport 和 HTTP 可以共用同一张证书?答:只要证书 SAN 包含节点的主机名和 IP,即可同时满足内部和外部验证需求,但安全上不强制分离。然而,如果 HTTP 层需要公共 CA 签发的浏览器信任证书,则必须分开。
- 证书过期但暂时无法更换,如何维持集群运行?答:可临时切换
verification_mode为certificate,并尽快更新证书后恢复。但切换期间集群易受中间人攻击。 - 如何监控证书过期?答:通过
_ssl/certificatesAPI 查看有效期,结合外部监控告警。API 会返回每个证书的expiry字段。 - 如果 transport 使用
full模式但节点 IP 改变了怎么办?答:需要重新生成包含新 IP 的证书,并执行_ssl/reload,或先使用certificate模式过渡。
加分回答:使用多 CA 链可实现零停机证书轮换,同时配置两个 CA 证书(新旧),逐步迁移节点证书。certificate_authorities可以配置为数组,包含多个 CA 文件,这样由新旧 CA 签发的证书在过渡期内均可被信任。
Q3: ES 支持哪些用户认证方式?LDAP 域集成如何配置?
一句话回答:支持内置域(native)、文件域(file)、LDAP/AD 域、SAML/OIDC(单点登录)、PKI 客户端证书认证、服务账号以及 API Key;LDAP 集成需配置 URL、绑定凭证、用户搜索基础和组搜索。
详细解释:Native 和 file 适用于中小规模或静态用户。LDAP 域通过 user_search 或 user_dn_templates 模式查找用户,并可自动映射域组到 ES 角色。SAML/OIDC 用于 Kibana 单点登录。PKI 允许用户使用客户端证书认证。服务账号和 API Key 用于程序化访问。LDAP 域认证流程:ES 先使用 bind_dn 绑定 LDAP,再执行 user_search 找到用户 DN,然后用用户 DN 和输入的密码重新绑定进行密码验证。组搜索则使用用户 DN 查找其所属组。
多角度追问
- LDAP 的 bind_dn 密码如何安全存储?答:应通过
elasticsearch-keystore添加xpack.security.authc.realms.ldap.ldap1.bind_password,配置文件只引用密钥名。 - 如何让 LDAP 用户拥有特定索引权限?答:创建角色映射(Role Mapping),将 LDAP 组映射到 ES 内置或自定义角色。映射规则支持
any,all,except字段。 - 多个认证域的优先级如何确定?答:
order属性数值越小优先级越高,一旦某域认证成功,后续域不再尝试。通常将本地域(file/native)的 order 设置得很高(如 0),作为紧急入口,LDAP 域 order 稍大。 - LDAP 认证时如何保护传输中的凭据?答:URL 必须使用
ldaps://,或者启用 STARTTLS(配置start_tls.enabled: true)。 - 如何处理 LDAP 服务器短暂不可用?答:域可以配置缓存
cache.ttl来缓存认证成功的结果,在 LDAP 不可用时使用缓存过的凭据继续服务,但这会有安全风险(凭据更改不及时反映)。
加分回答:LDAP 域支持连接池化,配置user_search.pool.enabled可提高性能,但需监控 LDAP 服务器负载。组映射可以配合metadata字段添加自定义属性。
Q4: RBAC 中的角色、权限、资源分别是什么?集群权限和索引权限有什么区别?
一句话回答:角色是权限的集合,权限定义对资源(集群或索引)的操作许可;集群权限控制节点和集群级别设置,索引权限限制对具体索引模式的读写和管理。
详细解释:cluster 权限如 manage 可以修改集群设置、重路由分片等危险操作;monitor 只能获取状态。索引权限作用于 names 定义的索引模式,read 包含 get/search/mget,write 包含 index/update/delete。合理的角色设计应遵循最小权限原则。ES 的权限检查是分层的:对于任何请求,首先检查是否具备所需的集群权限(如果操作涉及集群级资源),然后针对涉及的每个索引,检查是否具备所需的索引权限。只有两层都通过,请求才被允许。
多角度追问
- 如何授权用户只能写入但不能读取索引?答:授予
write权限而不授予read,但注意写入操作通常需要read以检查版本等,此时可用create_doc代替。create_doc只允许index操作且op_type=create,不允许更新或删除。 - 索引权限中的
manage与集群manage有何不同?答:前者只能操作特定索引的设置、映射、别名等,无法修改集群级配置。例如,索引manage可以PUT /index/_settings,集群manage可以PUT /_cluster/settings。 - 角色合并时权限冲突如何处理?答:权限取并集,字段/文档级过滤器取交集(更严格)。比如角色A允许字段 a,b,c,角色B允许字段 b,c,d,最终用户只能看到 b,c。
- 能否限制用户只能创建符合特定模式的索引?答:可以通过
create_index权限配合索引模式实现,但索引名必须匹配。如果用户试图创建other-index,但角色只允许logs-*,则会被拒绝。
加分回答:使用_has_privilegesAPI 可验证当前用户对特定索引和操作的权限,适合在应用中实现权限预检。API 示例:
POST /_security/user/_has_privileges
{
"index": [ { "names": ["logs-*"], "privileges": ["read"] } ]
}
响应中会明确告知每个权限是否拥有。
Q5: 字段级安全和文档级安全分别如何实现?它们的过滤发生在哪个阶段?
一句话回答:字段级安全在角色中定义 field_security 白名单/黑名单,执行搜索时在数据节点裁剪字段;文档级安全通过角色中的 query 注入查询条件,在协调节点与用户查询合并后再分发到 shard。
详细解释:字段级安全通过修改搜索请求中的字段列表实现,协调节点会计算出允许的字段集合并注入到发给数据节点的请求中。数据节点在返回结果时仅包含允许的字段。文档级安全则是在请求处理管线早期,由 DocumentLevelSecurityQueryBuilder 包装原查询,注入附加查询。两者均对用户透明,且可以叠加使用。字段级安全对 _source 过滤、fields 选项、docvalue_fields 均生效,甚至会阻止通过脚本来访问受限字段(因为脚本在执行时会进行字段权限检查)。
多角度追问
- 如果用户请求指定了被禁止的字段会发生什么?答:该字段将被忽略,返回结果中不包含,可能产生空值或缺失。若是通过
fields选项指定了受限字段,请求会失败并返回错误,防止信息通过错误消息泄露。 - 文档级安全是否会暴露在
explain结果中?答:不会,ES 会过滤掉安全查询子句,返回的评分和解释看似与用户查询一致。这是为了防止用户反推出 DLS 条件。 - 字段级安全能否与
_source禁用配合使用?答:可以,但此时字段级安全只能过滤stored_fields或从 docvalues 加载的字段。如果_source禁用且没有stored_fields,则字段级安全无法发挥作用,因为没有字段数据可返回。 - 文档级安全对聚合有影响吗?答:是的,聚合查询也会自动附加 DLS 过滤,因此用户只能对可见文档进行聚合。
- 如何测试字段级或文档级安全是否正确生效?答:可以创建具有受限角色的测试用户,然后使用
_authenticate确认角色,再用该用户执行搜索,验证返回的字段和文档范围。
加分回答:在大规模多租户场景下,使用索引隔离比文档级安全性能更好,因为后者会增加查询开销并影响缓存命中率。每一个搜索请求都需要额外解析和附加查询,增加了协调节点 CPU 使用率。
Q6: 如何设计一个多租户的 ES 架构,保证不同租户的数据完全隔离?
一句话回答:采用索引命名空间隔离,结合 RBAC 限制用户只能访问自身租户索引,并利用 Kibana Spaces 隔离可视化,必要时使用节点属性实现物理资源隔离。
详细解释:每个租户分配独立索引前缀(如 tenantA_*),角色中索引权限绑定该前缀。认证时根据用户属性(LDAP 组或自定义字段)赋予对应角色。Kibana Space 为每个租户创建独立工作空间,确保仪表板和可视化互不可见。为保障性能,可打标节点并通过 index.routing.allocation.require.tenant 将不同租户数据分配至专属节点。对于非常大的租户数量,应考虑使用数据流按时间分割索引,并启用 ILM 自动管理索引生命周期,避免索引数量爆炸。
多角度追问
- 租户数量急剧增加导致索引膨胀怎么办?答:采用数据流(Data Stream)按时间切分索引,结合 ILM 定期滚动和删除;或使用别名分组。每个租户的数据流只维护一个写入索引(
.ds-...),历史索引自动滚动并由 ILM 删除。 - 如何防止租户之间因高负载互相影响?答:设置索引写入速率限制、请求队列大小,配合节点物理隔离。还可以使用任务管理 API 监控和取消某个租户的超长时间查询。
- 审计日志如何区分租户?答:日志中记录用户名,通过命名约定即可关联租户。如果在用户属性中存储
tenant_id,可在审计事件中提取。 - 如果租户要求自身内部进一步划分权限怎么办?答:可以在租户角色基础上再叠加细粒度角色,例如“租户A管理员”、“租户A只读用户”,并利用字段/文档级安全进一步细分。
- 如何实现跨租户数据共享?答:额外创建共享索引,并明确授权两个租户的指定用户,严格审计。共享索引应使用独立的命名空间,如
shared_*。
加分回答:可开发自动化供给系统,租户创建时调用 ES API 创建角色、Space 和索引模板,实现自服务。结合 Kubernetes 部署时,还可为高价值租户动态调配专用节点池。
Q7: 审计日志记录了哪些信息?如何配置输出到 ES 索引便于搜索?
一句话回答:审计日志记录认证结果、授权决策、连接事件及请求细节;配置 outputs: [logfile, index] 即可将审计事件写入 .audit-logs-* 索引。
详细解释:每条事件包含 user, origin_address, request, indices, action, success 等字段。输出到索引后,可利用 ES 自身搜索和可视化能力分析异常,如暴力破解检测。索引每日滚动,默认保留策略可调整。审计日志索引配置:xpack.security.audit.index.bulk_size 控制批量写入大小,xpack.security.audit.index.flush_interval 控制刷新间隔。
多角度追问
- 审计日志索引会影响集群性能吗?答:有一定影响,建议使用独立监控集群或配置批量写入和独立节点。如果开启
index输出,审计事件会以 bulk 方式写入,但仍会增加节点 IO 和 CPU 消耗。 - 如何过滤掉特定用户的大量审计事件?答:
xpack.security.audit.logfile.events.ignore_users配置可忽略指定用户。常见的如忽略内部健康检查专用账号。 - 审计日志丢失如何处理?答:配置本地文件兜底,并监控文件大小。
logfile输出作为持久备份,索引输出提供可搜索性。 - 如何确保审计日志的完整性?答:文件输出应限制权限,索引输出应配置只允许安全审计员访问
.audit-logs-*索引。 - 审计日志可用于触发实时告警吗?答:可以。通过 Watcher 监控审计索引,当出现大量
authentication_failed或access_denied时发送警报。
加分回答:开启xpack.security.audit.index.flush_interval为 5s,可降低写入延迟,但对性能要求更高。还可以配置xpack.security.audit.logfile.events.emit_request_body来记录请求体(注意可能包含敏感数据)。
Q8: API Key 与用户密码认证有什么不同?分别适用什么场景?
一句话回答:API Key 是基于 Base64 编码凭证的无状态认证,权限可细化并设置过期;密码认证依赖用户主体,通常权限较宽;API Key 适用于第三方应用和微服务,密码适用于人工和交互式登录。
详细解释:API Key 创建时可限制特权,不会泄露用户密码,吊销简便。API Key 的权限是创建时确定的,除非设定为继承模式(不推荐),否则不会随用户角色变化而改变,这提供了稳定的权限快照。而密码认证的用户权限可能会因管理员调整角色而实时变化。
多角度追问
- API Key 创建者的权限变更后,Key 的权限会自动同步吗?答:不会,除非在创建时指定
role_descriptors为空(继承当前用户权限)则权限随用户动态变化,但安全上不推荐,因为可能意外获得过多权限。 - 如何撤销所有过期的 API Key?答:使用
_security/api_key的查询接口配合脚本批量删除。例如GET _security/api_key?filter_expired=true可列出所有过期 Key,然后批量吊销。 - API Key 可以用于 Kibana 登录吗?答:不可以,Kibana 登录仅支持用户名/密码或单点登录。但可以用 API Key 授权 Beats、Logstash 等组件向 ES 发送数据。
- API Key 的权限可以比创建用户更大吗?答:不可以,Key 的有效权限是用户权限与 Key 自身角色描述符权限的交集,所以不可能超出用户权限。
- 如何安全地分发 API Key?答:最好通过安全传输(如加密邮件、密钥管理服务)分发,并设置较短的过期时间,让应用定期轮换。
加分回答:API Key 支持跨集群搜索和复制,可以在远程集群中配置信任关系后使用 Key 认证。在 CCR(跨集群复制)中,从集群可以使用 API Key 认证到主集群,安全地进行索引复制。
Q9: 服务账号(Service Account)的作用和使用方式。
一句话回答:服务账号是 ES 内部组件(如 Fleet Server、Beats)使用的无密码凭证,通过动态分发的 Token 认证,权限被预设且不可更改。
详细解释:以 elastic/fleet-server 为例,ES 内置了该账号及其所需的最小权限(管理 .fleet-* 索引等)。管理员通过 API 生成 Token,配置在 Fleet Server 中。Token 有过期时间且可吊销。服务账号的 Token 基于 ES 内部的 JWT 实现,签名密钥由 ES 保管,Token 中包含服务账号名、过期时间、Token ID。校验时 ES 只需验证签名和有效期,无需查询存储。与普通用户账号不同,服务账号不能通过 _security/user 管理,其角色不可定制,确保了组件的安全边界。
多角度追问
- 如何查看已有的服务账号列表?答:
GET _security/service,返回所有服务账号及其关联的组件。 - 可以创建自定义服务账号吗?答:不能,服务账号由 Elastic 预定义,用于特定内部组件。自定义服务可以使用 API Key 替代。
- Token 泄露后如何撤销?答:调用
DELETE _security/service/elastic/fleet-server/credential/token/<token_id>,或通过名称一次性撤销所有 Token:DELETE _security/service/elastic/fleet-server/credential。 - 服务账号 Token 的最长有效期是多久?答:默认无限,但可以通过请求参数
expiration设置。生产建议设置合理期限,如 90 天。
加分回答:服务账号的设计遵循“零信任”原则,组件凭据短周期、可轮换,大幅降低内部组件被攻破后的持久化风险。并且每个组件实例可以使用独立的 Token,便于审计和隔离。
Q10: 如何在不重启节点的情况下更新 TLS 证书?
一句话回答:使用 POST _ssl/reload API 动态重新加载密钥库或 PEM 文件,新连接立即使用新证书,已有连接不受影响。
详细解释:该 API 指示 ES 重新初始化 SSLContext,读取磁盘上的最新证书和私钥。由于 TLS 握手在连接建立时进行,旧连接继续有效,直至自然断开。需要注意,新证书必须与旧证书信任链兼容,否则 reload 会失败并记录日志。在生产中,先将新证书复制到所有节点并校验权限,再调用 API。_ssl/reload 是同步操作,会阻塞当前线程直到加载完成,通常很快,但如果密钥库很大可能会有短暂延迟。
多角度追问
- 重载证书时是否需要所有节点同时操作?答:不需要,可逐个节点执行,但旧证书有效期应覆盖切换窗口。逐个节点重载可以避免所有节点同时重建 SSLContext 引发性能波动。
- 重载失败如何回滚?答:保留旧证书文件,修复新证书后再次 reload;若集群仍可用,可逐步排查。如果失败,原 SSLContext 依然有效,不会中断服务。
- 能否自动化证书轮换?答:可结合 cron 和 certbot 自动更新文件,然后调用 reload API。还可以将
_ssl/reload集成到 CI/CD 流程中。 - 重载后如何验证新证书已生效?答:通过
_ssl/certificatesAPI 查看证书链的生效时间,或使用 openssl s_client 连接节点验证返回的证书。
加分回答:_ssl/reload仅适用于 PEM 或 PKCS12 文件类型,若使用 JKS 需先转换。另外,如果使用加密的私钥,secure_password变化时也需要一起更新密钥库密码,并通过elasticsearch-keystore更新。
Q11: 内置角色 kibana_admin 和 superuser 的区别是什么?
一句话回答:kibana_admin 只能管理 Kibana 自身的配置和资源,不能执行 ES 索引/集群操作;superuser 拥有 ES 集群的完全控制权限,可绕过所有安全限制。
详细解释:kibana_admin 可以创建/删除 Spaces、管理 Saved Objects、修改 Kibana 高级设置,但对 ES 索引无任何直接权限。superuser 则类似 Linux 的 root,能查看所有数据、修改配置、关闭集群等,应严格限制分配给人员。日常管理应使用更精细的角色如 kibana_admin、ingest_admin 等。kibana_admin 角色本身不包含任何索引权限,即使 Kibana 需要读写 ES 索引,也需要额外赋予索引角色。
多角度追问
- 若用户需在 Kibana 中查看日志,至少需要什么角色?答:
kibana_user+ 索引读取权限(如viewer或自定义角色)。kibana_user允许登录 Kibana,索引权限才赋予数据访问。 - 如何审计
superuser的操作?答:开启审计日志,并导出至独立安全监控索引。superuser的操作依然会记录,但由于其拥有全部权限,无法通过授权阻止。 - 能限制
superuser的访问吗?答:无法在权限层限制,只能在网络层使用 IP 过滤或 VPN。也可以使用xpack.security.authc.realms中的files或native域并限制超级用户的数量。 kibana_admin可以修改 Kibana 的索引模式吗?答:可以,它有权管理 Kibana 中的 Saved Objects 和配置,包括索引模式。
加分回答:ES 提供run_as功能允许特权用户以其他用户身份执行操作,可用于支持团队临时排查,但需谨慎审计。例如,superuser可以通过es-security-runas-user: victim头模拟victim用户进行调试,所有操作会记录在审计日志中并标记run_as。
Q12 (系统设计题): 设计一个面向企业级 SaaS 平台的 ES 安全方案,要求支持多租户数据隔离、租户内基于角色的权限控制、敏感字段脱敏,并给出完整的角色定义示例和安全配置策略。
一句话回答:方案核心为“租户索引命名空间 + 自定义角色 + 字段/文档级安全 + Kibana Spaces”,配合 TLS、LDAP、审计日志形成纵深防御。
详细解释
总体架构
- 每个租户分配专属索引前缀:
tenant-{租户ID}-*,确保数据物理分离。 - 认证层对接企业 LDAP,根据组映射分配租户角色。
- 角色分为三层:租户管理员(可管理该租户索引、创建模板等)、租户普通用户(只读或读写自身业务索引)、租户只读审计员(只能查看特定字段)。
- 字段脱敏通过字段级安全实现,例如隐藏
salary字段或限制email为仅管理员可见;文档级安全进一步按部门或项目过滤。 - Kibana Space 与租户绑定,租户只能访问其 Space。
- 物理节点通过
tenant属性隔离高优先级租户。
角色定义示例
- 租户 A 管理员角色:
{
"cluster": ["monitor", "manage_ilm"],
"indices": [
{
"names": ["tenant-a-*"],
"privileges": ["all"],
"field_security": { "grant": ["*"] }
}
],
"applications": [{ "application": "kibana-.kibana", "privileges": ["all"], "resources": ["space:tenant-a"] }]
}
- 租户 A 普通成员角色(无权查看
salary):
{
"indices": [
{
"names": ["tenant-a-*"],
"privileges": ["read", "view_index_metadata"],
"field_security": { "except": ["salary"] },
"query": { "term": { "department": "user_department" } } // 由脚本动态填充用户属性
}
]
}
安全配置策略
- Transport TLS 强制 full 验证,HTTP TLS 使用公共 CA 证书。
- 启用审计日志写入索引,保留 180 天,监控
access_denied事件。 - 定期轮换 API Key,避免硬编码密码。
- 使用索引生命周期策略自动清理过期租户数据。
多角度追问
- 如何处理租户的自定义字段可能导致的对象映射爆炸?答:配置
index.mapping.total_fields.limit和动态模板限制。可以为每个租户的索引设置默认映射和字段限制。 - 租户要求导出数据如何安全实现?答:生成临时只读 API Key,限制仅访问该租户索引,导入/导出工具使用该 Key。API Key 设置较短过期时间,例如 1 小时。
- 如何实现跨租户数据共享?答:额外创建共享索引角色并明确授权两个租户的指定用户,严格审计。共享索引使用独立命名空间,如
shared-*,并限制访问用户列表。 - 如果租户数量爆炸(>10000),索引命名空间和角色管理如何扩展?答:可以使用索引别名作为抽象层,租户看到的是别名而非真实索引,后台按物理索引管理。角色可以使用通配符匹配
tenant-*-logs等模式,减少角色数量。同时,自动化供给系统动态创建命名空间和角色。 - 如何避免租户A的管理员意外删除租户B的索引?答:授权模型确保角色只能匹配自己的命名空间,即使管理员也无法越界。通过索引权限的
names严格限定前缀。
加分回答:将租户管理抽象为 RESTful API,开发自服务门户调用 ES 安全 API 创建角色/用户/Space,实现零人工干预。结合 OPA(Open Policy Agent)等外部策略引擎可实现更灵活的跨索引、跨租户访问策略。
Q13 (额外系统设计): 如果 SaaS 平台需要实现“同一租户内,部门经理只能看到本部门员工数据,HR 可以看到全公司但看不到薪资字段”,请设计角色和字段/文档级安全策略。
详细解释:
- 为每个部门创建一个角色,例如
dept_sales_read,文档级安全:{ "term": { "department": "sales" } },字段级安全:grant: ["*"], except: ["salary"]。 - 为 HR 创建一个角色,无文档级安全(查看所有部门),字段级安全
except: ["salary"](如果他们也不该看薪资)或者放开部分。 - 用户通过 LDAP 组成员关系获得对应部门角色,HR 用户获得 HR 角色。角色合并后,部门用户的 DLS 会限定部门,而 HR 没有 DLS,因此可见所有部门文档;字段限制两者都有,确保敏感字段隐藏。
追问:若部门经理还需要编辑本部门文档,如何处理?答:在部门角色中增加write权限,并配合 DLS,编辑请求也会自动附加 DLS 过滤,确保不能修改其他部门文档。
Q14 (理论题): 字段级安全与文档级安全在实现上的性能影响和局限性有哪些?
详细解释:
- 字段级安全会增加协调节点处理开销(需计算允许字段集合并注入请求),数据节点在返回结果时需要过滤
_source和字段值,有一定 CPU 消耗,但通常可忽略。主要局限是无法隐藏字段存在性,且可能影响某些依赖特定字段的功能(如高亮、脚本)。 - 文档级安全在每个搜索请求上附加额外查询,增加了查询解析和执行的 CPU 成本,尤其是 DLS 条件复杂时。它还会降低查询缓存命中率,因为不同用户的 DLS 条件不同导致缓存键不同,缓存失效。局限在于 DLS 查询必须高效,否则可能拖慢搜索;且不适合极高频访问的数据流。
追问:如何优化 DLS 的性能?答:确保 DLS 条件命中索引的有序字段,尽量使用term或range查询,避免脚本或正则;必要时可考虑在写入时对文档预标记租户 ID 并作为 routing 依据。
ES 安全速查表
| 类别 | 关键项 | 说明 |
|---|---|---|
| 配置项 | xpack.security.enabled | 全局安全开关(8.x 默认 true) |
xpack.security.transport.ssl.* | 节点间 TLS 配置 | |
xpack.security.http.ssl.* | HTTP 层 TLS 配置 | |
xpack.security.authc.realms.* | 认证域定义(native, file, ldap 等) | |
xpack.security.audit.enabled | 审计日志开启 | |
| 认证方式 | Native realm | 基于安全索引的动态用户管理 |
| File realm | 静态文件用户,引导用 | |
| LDAP / AD | 企业目录集成 | |
| SAML / OIDC | 单点登录(Kibana) | |
| PKI | 客户端证书认证 | |
| Service Account | 内置组件无密码认证 | |
| API Key | 程序化访问凭证 | |
| 权限类型 | 集群权限 | all, manage, monitor, transport_client |
| 索引权限 | all, read, write, create_index, delete, manage, view_index_metadata, create_doc, manage_ilm | |
| 应用权限 | Kibana 特性访问控制 | |
| 审计事件 | authentication_success | 认证成功 |
authentication_failed | 认证失败 | |
access_granted / access_denied | 授权结果 | |
connection_granted / connection_denied | 传输层连接 | |
tampered_request | 请求篡改 | |
| 常用 API | _security/user | 用户 CRUD |
_security/role | 角色 CRUD | |
_security/api_key | API Key 管理 | |
_security/service | 服务账号信息 | |
_security/role_mapping | 角色映射(LDAP 组) | |
_authenticate | 当前用户信息 | |
_ssl/reload | 热加载 SSL 证书 | |
_ssl/certificates | 查看节点证书信息 | |
_has_privileges | 检查用户权限 | |
_audit/settings | 审计配置查看 |
延伸阅读
- Elasticsearch 官方文档 Security 章节:www.elastic.co/guide/en/el…
- 《Elasticsearch: The Definitive Guide》安全部分(虽基于较旧版本,但概念相通)