在上一篇文章里面,我们已经为Kibana服务器添加了带有表单验证的安全措施,作为下一环节,我们需要把用户验证的安全信息传递给ES服务器,以为后续基于ES上开发 RBAC(基于角色的访问控制)和IP ACL安全过滤系统打下基础。
对于ELK整体安全解决方案个人总结下来有以下几类:
- X-PACK(SHIELD)
官方出品,可以保护Kibana,ES,以及Logstash的验证。控制颗粒可以达到Node,Index,设置Field。使用Elastic Cloud包含在订阅内的价格是$45一个月。 - NGINX反向代理
使用Nginx给Kibana,ES做HTTP的反向代理服务,并且完全依赖于Nginx的Basic验证机制以及LUA脚本来实现角色控制访问,控制颗粒度没有x-pack那么细致,在实际运维中,所有配置由系统管理员控制,无法下放到ELK管理员手中。 - SEARCHGUARD
第三方ES插件以替代X-pack完整功能,并且在免费版本中即提供Basic 身份验证,基于角色的Index,alias的访问控制,并且可以和Kibana以及Logstash集成,但是只会在收费版本中提供LDAP验证的支持
个人觉得一个完整的ELK的安全套件需要拥有以下这些特性:
- 完整端到端的安全控制,包括ELK的各个环节。
- 用户验证,支持LDAP的验证,提供表单验证体验。
- 角色授权,可以支持到Indices,Alias,Field以及各种ES管理接口的控制。
- 提供Kibana到ES的代理客户端的登录安全穿透,不使用特权账号访问后台数据。
- 配置管理由ELK管理员管理,而不假手于其他人。
- 提供审计功能。
言归正传,我们来看下Kibana到底是如何来和ES打交道的
const elasticsearch = require('elasticsearch');
...........
options = _.defaults(options || {}, {
url: config.get('elasticsearch.url'),
username: config.get('elasticsearch.username'),
password: config.get('elasticsearch.password'),
verifySsl: config.get('elasticsearch.ssl.verify'),
clientCrt: config.get('elasticsearch.ssl.cert'),
clientKey: config.get('elasticsearch.ssl.key'),
ca: config.get('elasticsearch.ssl.ca'),
apiVersion: config.get('elasticsearch.apiVersion'),
pingTimeout: config.get('elasticsearch.pingTimeout'),
requestTimeout: config.get('elasticsearch.requestTimeout'),
keepAlive: true,
auth: true
});
...........
let authorization;
if (options.auth && options.username && options.password) {
uri.auth = util.format('%s:%s', options.username, options.password);
}
....................
return new elasticsearch.Client({
host,
ssl,
plugins: options.plugins,
apiVersion: options.apiVersion,
keepAlive: options.keepAlive,
pingTimeout: options.pingTimeout,
requestTimeout: options.requestTimeout,
log: function () {
this.error = function (err) {
server.log(['error', 'elasticsearch'], err);
};
this.warning = function (message) {
server.log(['warning', 'elasticsearch'], message);
};
this.info = _.noop;
this.debug = _.noop;
this.trace = _.noop;
this.close = _.noop;
}
});
可以看出,在Kibana服务器启动的时候,系统会全局初始化elasticsearch.Client的对象,如果在kibana的配置文件中,配置了elasticsearch.username 和 elasticsearch.password,系统会以标准的Http 验证头的格式访问ES URL,如:http://username:password@example.com/ ,这里能够为我们提供全局性验证信息。
再次细化,Kibana并不是完全依赖于Node的ES客户端完成数据的交互,它还使用了直接HTTP请求的,由客户端直接穿透到ES服务器端进行搜索操作。
下图是在Kibana上进行数据浏览的时候,客户端发出的一个请求,我们发现客户端组发出了一个 /_msearch ES endpoint请求
回到系统初始化阶段,我们发现Kiaban为 /_msearch端点设置了代理类
init(server, options) {
const kibanaIndex = server.config().get('kibana.index');
// Expose the client to the server
exposeClient(server);
createProxy(server, 'GET', '/{paths*}');
createProxy(server, 'POST', '/_mget');
createProxy(server, 'POST', '/{index}/_search');
createProxy(server, 'POST', '/{index}/_field_stats');
createProxy(server, 'POST', '/_msearch');
createProxy(server, 'POST', '/_search/scroll');
........
const options = {
method: method,
path: createProxy.createPath(route),
config: {
timeout: {
socket: server.config().get('elasticsearch.requestTimeout')
}
},
handler: {
proxy: {
mapUri: mapUri(server),
agent: createAgent(server),
xforward: true,
timeout: server.config().get('elasticsearch.requestTimeout'),
onResponse: function (err, responseFromUpstream, request, reply) {
reply(err, responseFromUpstream);
}
}
},
};
我们只需要把之前表单验证后的Basic Authentication 携带给ES就可以完美实现注入验证信息:
let customHeaders = {} ;
//console.log(customHeaders);
if(request.auth.isAuthenticated){
console.log('credentials:'+ request.auth.credentials);
customHeaders = setHeaders(headers, assign({},config.get('elasticsearch.customHeaders'), {'authorization': request.auth.credentials}));
}
else
{
customHeaders = setHeaders(headers, config.get('elasticsearch.customHeaders'));
}
我们分别使用两个账号登录到Kibana系统,进行ES的查询交互
最后使用Wire shark来抓包看看是否如同我们所需要的结果:
可以看出ES完美接收到客户端传递给它的Basic身份信息,接下来的事情就交给Elasticsearch吧!~