背景:
最近出现一起因为误操作导致ES Index数据丢失的事件,引发一些列的反思和复盘。
需求:
拦截掉cerebro界面上,对后端ES可能造成危险的操作,避免再出现类似情况出现。
实现:
这里使用nginx+lua来根据query来拦截。简单起见我们直接安装openresty来实现本次需求。
架构调整:
实现细节:
nginx.conf 配置文件如下:
user nginx;
worker_processes 4;
#error_log logs/error.log;
#error_log logs/error.log notice;
error_log logs/error.log info;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 增加一个log_format,注意这里我开启了request_body的记录功能(生产的nginx的vserver上一般不开的,因为body可能比较大,影响nginx的性能)
log_format cerebro '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$request_body"';
# 2022-05-30 update 增加一个日志格式, 直接将编码转为肉眼识别的,而不是十六进制的编码
log_format log_json escape=json '{"timestamp": "$time_local",'
'"remote_addr": "$remote_addr",'
'"referer": "$http_referer",'
'"request": "$request",'
'"statu": "$status",'
'"byte": "$body_bytes_sent",'
'"agen": "$http_user_agent",'
'"x_forwarde": "$http_x_forwarded_for",'
'"up_addr": "$upstream_addr",'
'"up_host": "$upstream_http_host",'
'"up_resp_time": "$upstream_response_time",'
'"request_body": "$request_body",'
'"request_time": "$request_time"}';
access_log logs/access.log main;
sendfile on;
keepalive_timeout 120;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 32k;
gzip_http_version 1.1;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
gzip_vary on;
gzip_proxied any;
gzip_disable "MSIE [1-6]\.";
proxy_buffer_size 8k;
proxy_buffering on;
client_header_buffer_size 8k;
client_body_buffer_size 8k;
proxy_request_buffering on;
proxy_cache_lock on;
proxy_cache_use_stale updating;
include /usr/local/openresty-1.19.3.2/nginx/conf/conf.d/*.conf;
}
cerebro_proxy.conf 配置文件如下:
server {
listen 9111;
# access_log /var/log/nginx/cerebro_proxy_access.log cerebro ;
access_log /var/log/nginx/cerebro_proxy_access.log log_json ;
error_log /var/log/nginx/cerebro_proxy_err.log;
root /usr/share/nginx/html;
location / {
default_type application/json;
# 说明: 192.168.2.4:9001 是我的cerebro的监听地址
proxy_pass http://192.168.2.4:9001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# overview界面的危险操作全部拦截掉
location /cluster_settings {
return 403;
}
location ~ (/index_settings|create_index|/templates|/cat|/snapshots/aliases/|/aliases.html|/commons/indices|overview/disable_shard_allocation|/analysis|/repositories|/snapshots) {
return 403;
}
location /overview/relocate_shard {
return 403;
}
location /overview/delete_indices {
return 403;
}
location /overview/close_indices {
return 403;
}
location /overview/force_merge {
return 403;
}
location /overview/flush_indices {
return 403;
}
location /overview/refresh_indices {
return 403;
}
location /overview/clear_indices_cache {
return 403;
}
# 对于rest界面的请求进行的拦截
location /rest/request {
default_type application/json;
lua_need_request_body on;
access_by_lua_block {
local data1 = ngx.req.get_body_data()
-- 拦截纯DELETE操作
result1 = string.match(data1, "DELETE")
if result1 == "DELETE" then
ngx.exit(403)
end
-- 拦截_delete_by_query并且是match_all的操作
result2 = string.match(data1, "_delete_by_query")
result3 = string.match(data1, "match_all")
result4 = string.match(data1, "POST")
if result2 == "_delete_by_query" and result3 == "match_all" and result4 == "POST" then
ngx.exit(403)
end
}
proxy_pass http://192.168.2.4:9001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
核心配置就上面的access_by_lua_block段内的内容。
效果演示:
1、原始数据有3条
2、执行一个post类型的删除 index-2的一个条件的数据的操作,可以看到执行是成功的
2、执行一个post类型的删除 index-2的全部数据的操作,可以看到执行失败了(实际上返回是403,cerebro界面上做了优雅处理)
3、执行一个delete index的操作,可以看到执行失败了(实际上返回是403,cerebro界面上做了优雅处理)
4、其他overview界面的一些危险(或者存在风险)的按钮,如果我们点击,也是被拦截的
5、其它可能还有没想到的一些query,欢迎客官留言补充。
openrestry上记录到的访问日志如下:
post请求:
get请求:
TIPS: 如果我们要把这个cerebro开放到更多的研发人员去查看数据使用。建议还需要对接下LDAP或者公司的其它统一登录系统。 这里我没有再继续探索下去。
反思:
1、生产的大部分ES,都在业务层做了冗余,出问题这套当时没有做冗余,本次出问题就是比较头疼。
2、数据备份的重要性,ES的index还需要定期快照备份到NFS或者OSS(如果是阿里云的话,他们可以提供相关插件),并定期做离线恢复演练,并逐步实现 操作文档化 --> 半自动化 --> 自自动
3、运维过程中,难免会存在误操作或者命令写的有问题导致未获得期望结果的情况出现,提前将命令在记事本里写好,check没问题后,再到生产去执行。
4、权限管控,最小化权限原则 (这块对于高权限的运维来说,实际上还是比较难落地操作的)