Elasticsearch 脚本安全使用指南

x33g5p2x  于2021-11-19 转载在 ElasticSearch  
字(7.2k)|赞(0)|评价(0)|浏览(600)

1、关于 Elasticsearch 脚本实战问题

最近星球群里讨论了脚本的使用。

当看到复杂脚本的时候,我的第一反应是:

  • 类似复杂查询要搞这么复杂吗?
  • 能否前置让 ingest 预处理多花时间,哪怕加个字段?
  • Elasticsearch 更擅长的是检索,能否让他专注干更擅长的事?
  • 预处理或者写入前的 ETL 能否解决类似问题?

那么问题来了。

  • Elasticsearch 脚本有没有替代方案?
  • 如何在 Elasticsearch 端限制脚本的使用?
  • 我们可以控制 Elasticsearch 脚本的使用吗?

带着这些疑问,本文展开讲解。

2、Elasticsearch 脚本演变史

版本使用脚本类型
< Elasticsearch 1.4MVEL 脚本
< Elasticsearch 5.0Groovy 脚本
>= Elasticsearch 5.0painless 脚本

Groovy 的出现是解决MVEL的安全隐患问题;但Groovy仍存在内存泄露+安全漏洞问题。

painless 脚本的官宣时间:2016年9月21日。

正如其名字:无痛。painless 的出现是为了用户更方便、高效的使用脚本。

https://www.elastic.co/cn/blog/painless-a-new-scripting-language

Painless 是一种简单,安全的脚本语言,专为与 Elasticsearch 一起使用而设计。它是 Elasticsearch 的默认脚本语言,可以安全地用于内联(inline)和存储(stored)脚本。

关于 inline 和 stored 的区别,后面会讲解。

Painless特点:

  • 性能牛逼:Painless脚本运行速度比备选方案(包括Groovy)快几倍。
  • 安全性强:使用白名单来限制函数与字段的访问,避免了可能的安全隐患。
  • 可选输入:变量和参数可以使用显式类型或动态 def 类型。
  • 上手容易:扩展了java 的基本语法,并兼容 groove 风格的脚本语言特性。
  • 特定优化:是 ES 官方专为 Elasticsearch 脚本编写而设计。

3、Elasticsearch 使用脚本可能带来的问题?

3.1 语法相对晦涩,实现起来不是特别便捷

从 Elastic 中文社区、各个微信群、QQ群的技术交流可见一斑,几乎隔几天就会有“脚本语法如何使用?”的问题抛出来。

3.2 可能带来性能问题

Elasticsearch 脚本会给集群带来沉重的负担,编写脚本往往仅考虑功能实现层面,而极大可能会忽略或者忘记考虑到它可能需要的资源。

4、Elasticsearch 脚本替换方案

直接上替换方案——空间换时间,在写入前将相关数据尽可能使用 Ingest 管道完成“ETL”抽取、转换、加载“清洗”工作。

这时候读者不免有读者会问:“上来就提方案,你的方案依据是什么?空间换时间仅是你的一家之言,你有什么资格提方案?”

其实这是企业内部讨论方案经常被问到的问题,实际说辞可能会比这要委婉一些。

首先:实践和咨询经验的总结。

其次,官方文档有详细阐述,可以参考如下,为了更精准说明,我保留了英文原文。

  • If possible, avoid using script-based sorting, scripts in aggregations, and the script_score query.
  • Scripts are incredibly useful, but can’t use Elasticsearch’s index structures or related optimizations. This relationship can sometimes result in slower search speeds.
  • If you often use scripts to transform indexed data, you can make search faster by transforming data during ingest instead.

英文释义解读如下:

  • 第一:如果可能,避免使用脚本进行排序、聚合、script_score 类型检索操作。
  • 第二:脚本非常有用,但不能使用 Elasticsearch 的索引结构或相关优化。这有时会导致搜索速度变慢。
  • 第三:如果你经常使用脚本来转换索引数据,则可以通过在 Ingest 数据预处理阶段转换数据来加快搜索速度。

三条解释,清晰明了。

https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/scripts-and-search-speed.html

这里要分为几种情况:

第一:java 或者 python 客户端直连 Elasticsearch。

借助 Ingest pipeline 可以实现写入数据的预处理。

第二:Beats 直连 Elasticsearch。

借助 Ingest  pipeline 可以实现写入数据的预处理。

Beats 端输出到 Elasticsearch 配置 pipeline 参考:

  1. output.elasticsearch:
  2. hosts: ["localhost:9200"]
  3. pipeline: my-pipeline

第三:logstash 直连 Elasticsearch。

借助 Ingest  pipeline 或者 logstash filter 可以实现数据预处理。

那,到底什么是空间换时间呢?

举个栗子进一步解释一把:

以最易理解的学生成绩表为例,要统计每个学生的成绩总和,并要求按照成绩之和排序。

  1. DELETE my_test_scores
  2. # 创建索引并指定 Mapping
  3. PUT my_test_scores
  4. {
  5.   "mappings": {
  6.     "properties": {
  7.       "name": {
  8.         "type": "keyword"
  9.       },
  10.       "english_score": {
  11.         "type": "integer"
  12.       },
  13.       "math_score": {
  14.         "type": "integer"
  15.       }
  16.     }
  17.   }
  18. }
  19. # 写入数据
  20. POST my_test_scores/_bulk
  21. {"index":{"_id":1}}
  22. {"name":"xiaoming","english_score":95,"math_score":85}
  23. {"index":{"_id":2}}
  24. {"name":"xiaowang","english_score":75,"math_score":95}
  25. {"index":{"_id":3}}
  26. {"name":"xiaozhang","english_score":94,"math_score":81}
  27. # 按照两门课程成绩之和召回排序结果
  28. GET /my_test_scores/_search
  29. {
  30.   "sort": [
  31.     {
  32.       "_script": {
  33.         "type": "number",
  34.         "script": {
  35.           "source": "doc['math_score'].value + doc['english_score'].value"
  36.         },
  37.         "order": "desc"
  38.       }
  39.     }
  40.   ]
  41. }
  42. # 不用脚本如何搞?
  43. # 更新 Mapping
  44. PUT /my_test_scores/_mapping
  45. {
  46.   "properties": {
  47.     "total_score": {
  48.       "type": "long"
  49.     }
  50.   }
  51. }
  52. # 写入之前管道预处理:两门课程之和字段生成
  53. PUT _ingest/pipeline/sum_score_pipeline
  54. {
  55.   "description": "Calculates the total test score",
  56.   "processors": [
  57.     {
  58.       "script": {
  59.         "source": "ctx.total_score = (ctx.math_score + ctx.english_score)"
  60.       }
  61.     }
  62.   ]
  63. }
  64. # 数据迁移
  65. POST _reindex
  66. {
  67.   "source": {
  68.     "index": "my_test_scores"
  69.   },
  70.   "dest": {
  71.     "index": "my_test_scores_ext",
  72.     "pipeline": "sum_score_pipeline"
  73.   }
  74. }
  75. GET my_test_scores_ext/_search
  76. # 重新获取排序结果数据
  77. GET /my_test_scores_ext/_search
  78. {
  79.   "sort": [
  80.     {
  81.       "total_score": {
  82.         "order": "desc"
  83.       }
  84.     }
  85.   ]
  86. }

如上的示例前半部分使用脚本处理实现。

后半部分使用预处理管道实现,sum_score_pipeline 预处理管道实现了成绩字段的求和操作。

本质上是:新增了total_score 字段,且写入前加了管道的处理。写入会慢一些,但检索的时候就不涉及任何脚本处理,所以是增加空间换来了检索时间的缩短,提升了检索效率

5、 Elasticsearch 脚本如何安全受控使用?

既然前面提到了脚本的使用弊端和“空间换时间”前置预处理的替换解决方案。

那么问题来了,作为集群管理人员或者研发团队的Boss,能否做到整个集群禁用脚本呢?

这个脑洞可以!实际也是可以实现的,操作层面实现参考如下内容。

5.1 大前提——Elasticsearch 安全原则

安全无小事,“小心驶得万年船”。

  • 1、在启用安全的前提下运行 Elasticsearch。

使用 XPack 的免费的精简安全(配置登录账号和密码)、基础安全(SSL层面)功能。

  • 2、不要使用 root 账号登录 Elasticsearch。

即便可以实现,但安全风险把控角度,强烈不建议。

  • 3、不要暴露集群的公网 IP。

尽可能保持 Elasticsearch 的隔离,最好是在防火墙和 VPN 之后使用 Elasticsearch。

关于“裸奔”的危害,看这里:你的Elasticsearch在裸奔吗?

  • 4、实施基于角色的方案控制策略。

Kibana 就可能快捷上手配置,包含但不限于:Space(空间)、Role(角色)、Privelege(访问权限)。

字段级别的权限是收费功能,其他都是免费的。

5.2 限制允许运行的脚本类型

这个也是我的知识盲点,我也是近期才关注到的。

Elasticsearch 支持两种脚本类型:内联(inline)和存储(stored)。

默认情况下,Elasticsearch 配置为运行这两种类型的脚本。

相信读者读到这里会和我一样一脸懵逼,啥叫 inline?啥叫 stored?inline 和 stored 有什么区别?

5.2.1 stored 类型脚本

所谓:stored 存储类型的脚本。就是先定义好脚本,“存储起来”,后面可以用,当然也可以不用。

用的时候指定 id 来取就可以。

如下示例延续上面的例子。

定义脚本,实现成绩求和。

  1. POST _scripts/sum_score_script
  2. {
  3.   "script": {
  4.     "lang": "painless",
  5.     "source": "ctx._source.total_score=ctx._source.math_score + ctx._source.english_score"
  6.   }
  7. }

批量全量更新,更新时使用刚才定义的脚本 id(这里本质就是 stored 类型脚本)。

  1. POST my_test_scores/_update_by_query
  2. {
  3.   "script": {
  4.     "id": "sum_score_script"
  5.   },
  6.   "query": {
  7.     "match_all": {}
  8.   }
  9. }

检索,验证脚本是否生效。

  1. GET my_test_scores/_search
5.2.2 inline 类型脚本

批量全量更新,更新时使用刚才定义的脚本 id。

  1. POST my_test_scores/_update_by_query
  2. {
  3.   "script": {
  4.     "lang": "painless",
  5.     "source": "ctx._source.total_score=ctx._source.math_score + ctx._source.english_score"
  6.   },
  7.   "query": {
  8.     "match_all": {}
  9.   }
  10. }

检索,验证脚本是否生效。

  1. GET my_test_scores/_search

对比 stored 类型,inline 脚本就是使用的时候直接指定脚本,不存在提前创建脚本的说法。

知道了两者的区别,如何做限制呢?

5.2.3 脚本分级限制

如下使用的配置:script.allowed_types 是 集群配置层面 elasticsearch.yml 的配置,不支持动态更新配置。

  • 第一:默认配置,没有任何限制。

等价于:

  1. script.allowed_types: both

如果你想保留现状,脚本不做任何使用限制,那就无需更改任何配置。

  • 第二:部分限制。

要限制运行的脚本类型,请将 script.allowed_types 设置为内联(inline)或存储(stored)。

  1. script.allowed_types: inline

如果仅设置支持:inline,再创建 stored 类型脚本就会报错。

如果仅设置支持:stored,集群启动就会报错。

如果您使用 Kibana,请将 script.allowed_types 设置为 both 或 inline。

因为:某些 Kibana 功能依赖于内联脚本,如果 Elasticsearch 不允许内联脚本,则无法按预期运行。

  • 第三:完全禁止。

要阻止任何脚本运行,请将 script.allowed_types 设置为 none。

  1. script.allowed_types: none

5.3 控制可以运行脚本的可用范围

范围有哪些?举例:

  • scoring:计算评分。
  • update :更新操作。
  • Ingest processor:管道预处理。
  • reindex:索引迁移。
  • sort:排序。
  • metric aggregation map:指标聚合。等等......

默认情况下,使用范围是没有任何限制的。

如果全部范围都不允许使用,可以配置如下:

  1. script.allowed_contexts: none

如果仅允许部分使用,比如只允许评分、更新使用脚本,可以配置如下:

  1. script.allowed_contexts: score, update

如上几条,就实现了脚本的受控的使用,实战环节结合当前业务需求和未来扩展业务需求,谨慎选型。

6、小结

对于脚本来说,要辩证的看待。存在即为合理,有应用场景就会有新的 feature。

如果没有准实时的要求的业务场景,多半都会接受延时写入,但对检索响应慢会“深恶痛绝”。

遇到类似问题的时候,多在建模、设计阶段花时间。建议不要把问题都抛到检索的时候实现,一方面:脚本实现起来的确有性能问题;另一方面:脚本处理的方式已然不是 Elasticsearch 最擅长的事。

可以选择 logstash filter 环节做数据的预处理 或者借助 pipeline 实现写入数据的预处理。

空间换时间,推荐使用 Ingest 管道预处理的方式在写入前尽可能的对字段实时预处理。

选型阶段多考虑选型 Elasticsearch 终极目的和初衷,使用过程中发挥 Elasticsearch 优势,且要确保优势最大化。

PS:细心的 Elastic 爱好者会发现 Elasticsearch 的官方文档在往条理更加清晰、模块更加分明的方向努力,这样我们的学习有了更新的、更大的动力!

参考

https://opster.com/guides/elasticsearch/best-practices/elasticsearch-all-script-types-allowed/

https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-security.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/es-security-principles.html

相关文章