Elasticsearch API访问权限控制:禁用外部端点访问
背景与需求
在实际的生产环境中,我们经常遇到这样的需求:
- Elasticsearch需要对外提供基本的API服务
- 内部系统(如Grafana)需要完整的ES访问权限
- 但要禁用某些敏感的API端点(如
/_cat
)防止信息泄露
本文记录了在Docker部署的Elasticsearch中实现这种细粒度权限控制的完整过程,包括遇到的各种坑点和解决方案。
环境信息
- 操作系统: Ubuntu 服务器
- Elasticsearch: 8.17.0 (Docker部署)
- Grafana: 需要访问ES的完整功能
- 目标: 禁用外部对
/_cat
API的访问,保留其他功能
方案对比
方案一:修改网络配置(不推荐)
最初尝试通过修改ES的网络配置来限制访问:
network.host: 127.0.0.1
http.host: 127.0.0.1
踩坑点:这种方案会完全禁用外部访问,包括Grafana也无法访问,不符合需求。
方案二:使用nginx代理
通过nginx反向代理实现精确的端点控制。(太麻烦不想做)
方案三:ES原生安全功能(推荐)
使用Elasticsearch的X-Pack Security功能,通过角色和权限实现细粒度控制。
经过对比,方案三最为优雅,既充分利用了ES的原生安全机制,又便于管理和维护。
实施步骤详解
第一步:确认当前ES部署方式
首先检查ES容器的部署情况:
# 查看ES容器
sudo docker ps | grep elasticsearch# 查看原始启动命令(重要!)
docker inspect es | grep -A 20 "Cmd"
踩坑点1:发现原启动命令中有 -e "xpack.security.enabled=false"
,这会强制禁用安全功能。
第二步:数据安全确认
在重建容器前,确认数据不会丢失:
# 检查数据挂载
ls -la /srv/elasticsearch/data/
du -sh /srv/elasticsearch/data/# 确认数据大小和内容
docker exec es curl -s http://127.0.0.1:9200/_cat/indices
踩坑点2:很多人担心重建容器会丢失数据。实际上,由于使用了数据卷挂载 (-v /srv/elasticsearch/data:/usr/share/elasticsearch/data
),数据存储在主机上,重建容器不会影响数据。
第三步:配置ES安全功能
3.1 修改配置文件
vi /srv/elasticsearch/config/elasticsearch.yml
cluster.name: es-docker-cluster
node.name: single-node
path.data: /usr/share/elasticsearch/data
path.logs: /usr/share/elasticsearch/logs
discovery.type: single-node
bootstrap.memory_lock: true
network.host: 0.0.0.0
http.host: 0.0.0.0# 启用X-Pack安全功能
xpack.security.enabled: true
xpack.security.enrollment.enabled: false
xpack.security.authc.api_key.enabled: true# 禁用SSL(简化配置,生产环境建议启用)
xpack.security.http.ssl.enabled: false
xpack.security.transport.ssl.enabled: false
3.2 重建容器
# 停止并删除容器
docker stop es
docker rm es# 重新运行with安全功能
docker run -itd --restart always --name es \-p 9200:9200 \-p 9300:9300 \-e "ES_JAVA_OPTS=-Xms4g -Xmx4g" \-e "ELASTIC_PASSWORD=YourStrongPassword123" \-v /srv/elasticsearch/data:/usr/share/elasticsearch/data \-v /srv/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro \docker.elastic.co/elasticsearch/elasticsearch:8.17.0
踩坑点3:配置文件挂载使用了:ro
(只读)模式,所以无法在容器内直接修改配置文件。必须在主机上修改。
第四步:创建角色和权限
4.1 为Grafana创建超级用户角色
curl -X POST "http://elastic:YourStrongPassword123@10.6.110.86:9200/_security/role/grafana_superuser" \
-H "Content-Type: application/json" -d'
{"cluster": ["all"],"indices": [{"names": ["*"],"privileges": ["all"]}],"applications": [{"application": "*","privileges": ["*"],"resources": ["*"]}]
}'
4.2 创建受限角色(无cat权限)
curl -X POST "http://elastic:YourStrongPassword123@10.6.110.86:9200/_security/role/external_limited" \
-H "Content-Type: application/json" -d'
{"cluster": [],"indices": [{"names": ["*"],"privileges": ["read", "view_index_metadata"]}]
}'
第五步:API Key认证尝试与踩坑
5.1 创建API Key
curl -X POST "http://elastic:YourStrongPassword123@10.6.110.86:9200/_security/api_key" \
-H "Content-Type: application/json" -d'
{"name": "grafana-full-access","expiration": "365d","role_descriptors": {"grafana_access": {"cluster": ["all"],"indices": [{"names": ["*"],"privileges": ["all"]}]}}
}'
返回结果示例:
{"id":"U6mEm5cB7ZDEudMvimns","name":"grafana-full-access","api_key":"Ik4YfdYTS1yGnidQaKXC9g","encoded":"VTZtRW01Y0I3WkRFdWRNdmltbnM6SWs0WWZkWVRTMXlHbmlkUWFLWEM5Zw=="
}
踩坑点4:很多人错误地使用api_key
字段,实际应该使用encoded
字段。
# 错误用法
curl -H "Authorization: ApiKey Ik4YfdYTS1yGnidQaKXC9g" http://10.6.110.86:9200/# 正确用法
curl -H "Authorization: ApiKey VTZtRW01Y0I3WkRFdWRNdmltbnM6SWs0WWZkWVRTMXlHbmlkUWFLWEM5Zw==" http://10.6.110.86:9200/
5.2 Grafana集成踩坑
踩坑点5:发现Grafana的Basic Auth无法直接使用API Key。
# 这种方式不工作
curl -u api_key:VTZtRW01Y0I3WkRFdWRNdmltbnM6SWs0WWZkWVRTMXlHbmlkUWFLWEM5Zw== http://10.6.110.86:9200/
# 返回401错误
第六步:最终解决方案 - 用户名密码认证
由于Grafana的限制,最终采用传统的用户名密码认证:
6.1 创建Grafana专用用户
curl -X POST "http://elastic:YourStrongPassword123@10.6.110.86:9200/_security/user/grafana_user" \
-H "Content-Type: application/json" -d'
{"password": "GrafanaPassword123","roles": ["grafana_superuser"],"full_name": "Grafana Service User","metadata": {"application": "grafana"}
}'
6.2 验证用户权限
# 测试Grafana用户的完整权限
curl -u grafana_user:GrafanaPassword123 http://10.6.110.86:9200/
curl -u grafana_user:GrafanaPassword123 http://10.6.110.86:9200/_cat/health# 测试受限API Key
curl -H "Authorization: ApiKey LIMITED_API_KEY" http://10.6.110.86:9200/_cat/health
# 应该返回403权限不足
配置管理
密码修改
# 修改用户密码
curl -X POST "http://elastic:YourStrongPassword123@10.6.110.86:9200/_security/user/grafana_user/_password" \
-H "Content-Type: application/json" -d'
{"password": "NewPassword456"
}'
Grafana数据源配置
在Grafana中配置:
- URL:
http://10.6.110.86:9200
- Access: Server (default)
- Auth: ✅ Basic Auth
- User:
grafana_user
- Password:
GrafanaPassword123
最终架构与验证
权限矩阵
用户类型 | 基本API | _cat API | _search API | 集群管理 |
---|---|---|---|---|
elastic (超级管理员) | ✅ | ✅ | ✅ | ✅ |
grafana_user | ✅ | ✅ | ✅ | ✅ |
受限API Key | ✅ | ❌ | ✅ | ❌ |
无认证用户 | ❌ | ❌ | ❌ | ❌ |
完整验证脚本
#!/bin/bashecho "=== 1. 测试Grafana用户(应该全部成功) ==="
curl -u grafana_user:GrafanaPassword123 http://10.6.110.86:9200/
curl -u grafana_user:GrafanaPassword123 http://10.6.110.86:9200/_cat/healthecho "=== 2. 测试受限API Key(cat应该失败) ==="
LIMITED_KEY="VmFtSG01Y0I3WkRFdWRNdkpHbXg6NFYtSEJtdWpUTXkwQ3NDSmRPdmF0Zw=="
curl -H "Authorization: ApiKey $LIMITED_KEY" http://10.6.110.86:9200/
curl -H "Authorization: ApiKey $LIMITED_KEY" http://10.6.110.86:9200/_cat/healthecho "=== 3. 测试无认证访问(应该全部失败) ==="
curl http://10.6.110.86:9200/
curl http://10.6.110.86:9200/_cat/health
总结与最佳实践
主要踩坑点总结
- 容器重建数据安全性:使用数据卷挂载的容器重建不会丢失数据
- 配置文件只读挂载:无法在容器内修改配置,必须在主机上操作
- API Key使用方式:必须使用
encoded
字段,不是api_key
字段 - Grafana认证限制:Basic Auth无法直接使用API Key,需要创建用户
- 权限配置细节:角色权限需要精确配置,避免意外的权限泄露
最佳实践建议
-
分层权限设计:
- 超级管理员:完全权限
- 应用服务:必要权限
- 外部用户:最小权限
-
密码安全管理:
- 使用强密码
- 定期轮换密码
- 考虑使用密钥管理服务
-
监控与审计:
- 启用ES安全日志
- 监控异常访问模式
- 定期审查用户权限
-
生产环境增强:
- 启用TLS加密
- 使用证书认证
- 设置IP白名单
适用场景
这个方案特别适合以下场景:
- 需要对外提供ES服务但要保护敏感信息
- 多系统共享ES集群但需要不同权限
- 需要精确控制API访问权限的企业环境
通过ES原生的安全功能,我们成功实现了细粒度的权限控制,既满足了业务需求,又保证了系统安全。
相关rep:
https://github.com/TIMPICKLE/mitmproxy_log_to_EShttps://github.com/TIMPICKLE/mitmproxy_log_to_ES