5.2 把"系统调用过滤器有没有装成功"变成 bootstrap check:过滤器开着但没装上,节点拒绝启动。同时
bootstrap.seccomp改成bootstrap.system_call_filter(旧名在 5.x 仍可用,6.0 删除)。官方 bootstrap checks 文档对这项检查的通过条件写得很直接:要么修好让过滤器能装上,要么自担风险把过滤器关掉——一步,不是两步。本文按 v5.6.16 源码把检查的实现条件、各系统上被拦的系统调用、装不上的常见原因列出来。
说明
来源和边界先交代:本文的依据出自 v5.6.16 标签下的官方文件(migrate_5_2.asciidoc、SystemCallFilter.java、bootstrap-checks.asciidoc),文中引用的原文均来自这些文件。示例取自官方示例;哪些是文档写明、哪些是我的推断,正文里逐处标注。
实测环境:本地另起一个 Elasticsearch 5.6.16 单节点实例(官方 tar 包,端口 9200,独立 path.data),按默认配置启动,未改动 bootstrap 参数。文末实测小节里的命令与响应均取自这些实例。
图 1:官方文件 migrate_5_2.asciidoc(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 migrate_5_2.asciidoc
从"警告一下"到"拒绝启动"
5.2 迁移文档(migrate_5_2.asciidoc 第 9–16 行):
Elasticsearch has attempted to install a system call filter since version 2.1.0. On some systems, installing this system call filter could fail. Previous versions of Elasticsearch would log a warning, but would otherwise continue executing potentially leaving the end-user unaware of this situation. Starting in Elasticsearch 5.2.0, there is now a bootstrap check for success of installing the system call filter. If you encounter an issue starting Elasticsearch due to this bootstrap check, you need to either fix your configuration so that the system call filter can be installed, or at your own risk disable the system call filter check.
过滤器装不上意味着这层防护缺失,而旧版本只写一行 warning,用户往往不知道。5.2 把它加进 bootstrap check 的理由就是这个:安全相关的降级必须显式暴露。
图 2:官方文件 SystemCallFilter.java(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 SystemCallFilter.java
被拦的是什么
SystemCallFilter 的类注释(SystemCallFilter.java 第 46–75 行)写明了它做什么:装一个过滤器来阻止进程执行(Installs a system call filter to block process execution)。按系统分,做法不同:
| 系统 | 做法 |
|---|---|
| Linux | BPF 过滤器,拦 execve、fork、vfork、execveat,返回 EACCES |
| Solaris 10+ | 用 priv_set(3C) 去掉 PRIV_PROC_FORK、PRIV_PROC_EXEC 权限 |
| BSD 系 | 用 setrlimit(RLIMIT_NPROC) 限制进程创建 |
| Mac OS X Leopard+ | 装自定义 sandbox(7)(Seatbelt)profile |
| Windows | 也支持(注释里列在支持列表中) |
Linux 上的条件(第 50–54 行):支持 amd64 和 i386,要求内核 3.5 以上,且内核编译时开了 CONFIG_SECCOMP 和 CONFIG_SECCOMP_FILTER。过滤器装的时候优先用 seccomp(2)(3.17+),因为它能应用到进程里已有的线程;否则退回 prctl(2)(3.5+),只对 ES 自己的应用线程生效。
所以"过滤器"这个名字比 "seccomp" 准确:Linux 上用 seccomp,别的系统上是别的机制,目的一致。
防护的作用是:ES 进程被攻破后(5.x 时代的脚本漏洞是常见攻击面),攻击者也难以在这个进程里起新进程。
图 3:官方文件 bootstrap-checks.asciidoc(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 bootstrap-checks.asciidoc
只改一处就够
官方 bootstrap checks 文档(bootstrap-checks.asciidoc 第 188–199 行):
The system call filter check ensures that if system call filters are enabled, then they were successfully installed. To pass the system call filter check you must either fix any configuration errors on your system that prevented system call filters from installing (check your logs), or at your own risk disable system call filters by setting
bootstrap.system_call_filtertofalse.
源码里的条件(BootstrapChecks.java 第 528–531 行):
public boolean check() {
return areSystemCallFiltersEnabled && !isSystemCallFilterInstalled();
}
也就是说:只有"过滤器开着"且"没装上"才算失败。把过滤器关掉(bootstrap.system_call_filter: false),areSystemCallFiltersEnabled 变成 false,检查直接通过。不需要另外再禁用这项检查——它是随着过滤器开关自动生效的。
失败时的消息(第 539–542 行):
system call filters failed to install; check the logs and fix your configuration or disable system call filters at your own risk
这条消息把两种处理方式都列出来了。
设置改名
These are enabled by default and could be disabled via
bootstrap.seccomp. The naming of this setting is poor since seccomp is specific to Linux but Elasticsearch attempts to install a system call filter on various operating systems. Starting in Elasticsearch 5.2.0, this setting has been renamed tobootstrap.system_call_filter. The previous setting is still support but will be removed in Elasticsearch 6.0.0.
(migrate_5_2.asciidoc 第 23–28 行)
迁移:bootstrap.seccomp: false 改成 bootstrap.system_call_filter: false。旧名 5.x 还能用,6.0 删除,所以 5.x 期间不用急着改,但要在 6.0 之前改完。
装不上的常见原因
- 容器的 seccomp profile 不允许:Docker 默认的 profile 会挡掉 ES 装过滤器所需的系统调用。这是容器化环境里最常见的一条;
- 内核太旧或未开相关编译选项:Linux 上要求 3.5+ 且开了
CONFIG_SECCOMP/CONFIG_SECCOMP_FILTER(见上表); - JNA 不可用:ES 通过 JNA 装过滤器,JNA 不可用时
Natives会打一条 warning 然后放弃(Natives.java 第 107–112 行),这种情况过滤器没装上,5.2 起同样会触发启动检查; - 虚拟化 / 沙箱环境拦截相关系统调用。
按官方的说法,判断依据是启动日志("check your logs")。日志里会有过滤器安装失败的具体原因,比照上面的分类逐项排除。
静默降级比不可用更危险
这条改动的逻辑很直接:过滤器装不上时,旧版本只打一条警告就继续跑,用户可能完全不知道自己的 ES 少了一层安全保护。5.2 之后装不上就不启动。
这个取舍是对的,理由是"以为安全、实际不安全"比"起不来"糟糕得多:前者你不会去管,直到出事;后者你会立刻处理。
顺着这个思路看 5.x 的整体倾向,是把"本来就该是硬约束"的检查放进 bootstrap check(内存锁定、文件描述符、系统调用过滤器都是)。当时升级 5.x 最常见的痛苦就是节点起不来,原因往往就是这些检查,但方向是对的。
补一层:这类"静默降级"不只在安全领域出现。凡是"能跑但缺了点什么"的情况,都值得问一句——缺的这点要不要显式暴露出来。显式失败的代价是运维工作量,收益是状态可预期。
容器里的处理方式,按优先级
- 首选:调整容器的 seccomp profile。自己写一份允许相关系统调用的 profile 挂进去,改动最小、不放额外权限;
- 次选:
--security-opt seccomp=unfiltered。省事,但等于这个容器完全不设防,只在可信环境用; - 不推荐:放大 capability(比如为这个目的加
--cap-add)。这比上面那条更危险; - 兜底:关掉过滤器(
bootstrap.system_call_filter: false)。最后才考虑,并且要记录原因。
前两种之外的选项都应该附一份书面说明(为什么必须这么配、风险是什么)。这类配置一旦写进部署模板,后面没人会再问为什么。
关掉过滤器的节点要额外做网络隔离:少了一层保护,就要在别的层面补上。
怎么确认过滤器装上了
别靠"节点起来了"判断——5.2 之后装不上就起不来,所以这一条反而简单。真正要防的是"有人为了先跑起来把过滤器关了,后面没人发现"。这类配置不会报错,只在被攻击时才体现出差别。
做法:
- 启动时看日志里有没有过滤器安装成功的记录;不同 OS 的措辞可能不同(前面说过,各系统实现不同),判断标准是"有没有明确的安装成功记录",不是某个固定字符串;
- 把这一项做成部署后的自检项:新环境部署完跑一遍确认;
- 定期核对配置:确认
bootstrap.system_call_filter没有被设成false,或者明确知道哪些集群设了、原因是什么。
# 启动日志里找过滤器相关记录
journalctl -u elasticsearch --since "10 min ago" | grep -i "system call filter\|seccomp"
# 确认配置里有没有关掉
grep -rn "system_call_filter\|bootstrap.seccomp" /etc/elasticsearch/
和其它 bootstrap check 的关系
5.x 的 bootstrap check 是一组启动前置条件(堆大小、文件描述符、mmap 数量、内存锁定、onError/onOutOfMemoryError 与 fork 的冲突等)。系统调用过滤器是其中一个安全向的成员。
同一页里还有两项和过滤器相关的检查值得留意(bootstrap-checks.asciidoc 第 201–215 行):JVM 的 OnError 和 OnOutOfMemoryError 允许在致命错误或 OOM 时执行任意命令,而过滤器会阻止 fork,两者冲突时节点起不来。所以如果配了这两个选项,要么去掉它们,要么关掉过滤器——这是一组需要一起看的配置。
排查启动失败时,日志会明确指出是哪一项没过。看到 system call filter 相关,按上面的顺序排查:先看容器/内核是否允许,再决定要不要关。
升级 5.2 的检查清单
- 配置里
bootstrap.seccomp改成bootstrap.system_call_filter; - 先在灰度节点启动一遍:这项检查在老内核、受限容器上最容易触发,直接全量升级可能一批节点起不来;
- 启动了看日志确认过滤器装上(不是只看"起来了");
- 容器环境优先修 seccomp profile,不要一上来就
false; - 确认 JVM 参数里没有
OnError/OnOutOfMemoryError(它们和过滤器冲突); - 记录哪些集群因为环境限制关掉了过滤器,把这类节点视为需要额外网络隔离的对象。
在 5.6.16 上跑一遍
过滤器开关的默认值和改动方式,读一次集群设置就能看清。
图 4:5.6.16 单节点实例的实测输出。默认 bootstrap.system_call_filter=true、bootstrap.seccomp=true;两项动态改都报 not dynamically updateable;对照的普通动态设置返回 acknowledged:true。
curl -s 'http://localhost:9200/_cluster/settings?include_defaults=true' \
| jq -c '{system_call_filter:.defaults.bootstrap.system_call_filter, seccomp:.defaults.bootstrap.seccomp}'
# {"system_call_filter":"true","seccomp":"true"}
curl -s -X PUT 'http://localhost:9200/_cluster/settings' -H 'Content-Type: application/json' \
-d '{"persistent":{"bootstrap.system_call_filter":false}}' | jq -r '.error.root_cause[0].reason'
# persistent setting [bootstrap.system_call_filter], not dynamically updateable
curl -s -X PUT 'http://localhost:9200/_cluster/settings' -H 'Content-Type: application/json' \
-d '{"persistent":{"bootstrap.seccomp":false}}' | jq -r '.error.root_cause[0].reason'
# persistent setting [bootstrap.seccomp], not dynamically updateable
curl -s -X PUT 'http://localhost:9200/_cluster/settings' -H 'Content-Type: application/json' \
-d '{"transient":{"cluster.routing.allocation.enable":"all"}}' | jq -c '.acknowledged'
# true
图 4 坐实了两件事:5.6.16 默认开着两项过滤器;关掉它们必须在节点启动前改配置,运行中改不了。这也解释了正文说的「自担风险关掉」为什么是配置层面的动作,集群设置 API 动不了它。
边界:
- 图 4 出自 5.6.16 单节点实例,过滤器正常安装,未模拟安装失败,「装不上就拒绝启动」这条 bootstrap check 未触发验证;
- 启动日志里没有 seccomp 相关输出,安装过程无法从日志确认;
- 各系统被拦的系统调用清单仍是源码依据,未逐个验证。
写在最后
5.2 把系统调用过滤器从"装不上就警告"变成 bootstrap check,同时把设置名从 Linux 专有的 seccomp 改成通用的 system_call_filter。
通过条件只有一步:要么修好环境让过滤器装上,要么自担风险关掉过滤器(system_call_filter: false)。关掉之后检查自动通过,不需要再单独禁用这项检查。
容器化环境里最常见的原因是 seccomp profile 不允许,优先修 profile 而不是关检查。关掉的节点记下来,按"少了一层保护"来对待。
参考
本文对照 Elasticsearch
v5.6.16标签的官方文档核对,引文逐字取自原文。「在 5.6.16 上跑一遍」里已实际读到默认bootstrap.system_call_filter=true、bootstrap.seccomp=true,两项动态改都被not dynamically updateable拒绝,对照的普通动态设置返回acknowledged:true。**"装不上就拒绝启动"**未触发验证(本机过滤器正常安装);各系统被拦的系统调用清单仍是源码依据,未逐个验证。
