Fail2Ban 的 systemd journal 后端过滤器:FilterSystemd 模块源码级解析
2026/9/20 14:43:40 网站建设 项目流程

Fail2Ban 的 systemd journal 后端过滤器:FilterSystemd 模块源码级解析

【免费下载链接】fail2banDaemon to ban hosts that cause multiple authentication errors项目地址: https://gitcode.com/gh_mirrors/fa/fail2ban

本指南以仓库 doc/fail2ban.server.filtersystemd.rst 所定义的fail2ban.server.filtersystemd模块为主题,深入解析 Fail2Ban 如何借助 systemd journal 直接读取日志并检测登录失败。你将掌握FilterSystemd类的核心成员与工作流程(journal 匹配、游标续读、轮转重开、持久化断点)、journalmatch的 journalctl 语法用法,以及backend = systemd在 config/jail.conf 中的配置前提与限制。

一、模块定位:从日志文件到 systemd journal

Fail2Ban 的过滤器后端负责"读取日志 → 正则匹配 → 把失败交给 FailManager 计次"。传统polling/pyinotify后端监视的是普通文本文件(logpath),而 systemd 后端直接订阅 journald 的日志流,不再需要指定logpath

在 fail2ban/server/jail.py 中,后端被注册为_BACKENDS = ['pyinotify', 'polling', 'systemd']auto会按该顺序尝试初始化;当显式或自动选中systemd时,jail.py 调用_initSystemd实例化FilterSystemd。模块继承自 fail2ban/server/filter.py 中的JournalFilter基类(基类只是定义了addJournalMatch/delJournalMatch/getJournalMatch的空实现与clearAllParams的清理逻辑),真正的实现全部落在filtersystemd.py中。

二、构造与初始化参数

FilterSystemd.__init__(filtersystemd.py)先通过_getJournalArgs(kwargs)从配置参数中提取 systemd 相关的关键字参数,再创建journal.Reader连接:

self.__jrnlargs = FilterSystemd._getJournalArgs(kwargs) JournalFilter.__init__(self, jail, **kwargs) self.__journal = journal.Reader(**self.__jrnlargs)

_getJournalArgs(filtersystemd.py)支持的 jail 配置参数如下表:

配置参数含义底层处理
journalpath指定 journal 目录路径传入journal.Reader(path=...)
journalfiles指定要监视的 journal 文件(支持通配符)经过glob展开、去重后传入files=
journalflags传给journal.Reader的 flags 整数直接int()转换后传入flags=
rotated是否监视已轮转的日志文件布尔值;为假时改用"排除轮转文件"的策略
namespace命名空间参数(systemd 239+)传入journal.Reader(namespace=...)

需要注意的默认行为:当未显式给出journalflags时,默认 flags 取决于rotated——若rotated为真,默认journal.SYSTEM_ONLY;也可用环境变量F2B_SYSTEMD_DEFAULT_FLAGS覆盖。同时,为避免打开大量用户会话导致 "Too many open files"(对应 gh-2392),当rotated为假且未指定files/namespace时,会调用_globJournalFiles只选取system.journaluser-<uid>.journal,显式剔除*@*.journal这类轮转文件,并把flagspath置空(因为files不能与二者同时指定)。

_globJournalFiles(filtersystemd.py)还依赖_getSystemdPath调用systemd-path system-state-logs/system-runtime-logs获取持久日志与运行时日志目录(结果带缓存),失败时回退到/var/log/journal/run/log/journal;非 root 运行时还会过滤掉无读权限的文件。

三、journal 匹配:journalmatch 的增删改查

匹配的核心是journalmatch配置项,它使用 journalctl 兼容语法。addJournalMatch(filtersystemd.py)把形如_SYSTEMD_UNIT=sshd.service + _COMM=sshd的列表按+拆分成多组,再由_addJournalMatches依次调用journal.add_match(...)journal.add_disjunction(),即同一组内是 AND、组与组之间是 OR

def _addJournalMatches(self, matches): if self.__matches: self.__journal.add_disjunction() # Add OR for match in matches: for match_element in match: self.__journal.add_match(match_element) self.__journal.add_disjunction()

resetJournalMatches(filtersystemd.py)在清理失败或重开 journal 时先flush_matches()再重建全部匹配;delJournalMatch(filtersystemd.py)支持按索引删除或一次性清空。若启动时未配置任何journalmatchrun()会打出 notice 日志,提示将针对全部 journal 条目做正则匹配,出于性能考虑不建议这样做(filtersystemd.py)。

从调用链看,这些方法通过 fail2ban/server/server.py 与 fail2ban/server/transmitter.py 暴露为 fail2ban-client 命令set <jail> addjournalmatchdeljournalmatchget <jail> journalmatch,并有 fail2ban/tests/servertestcase.py 与 fail2ban/tests/clientbeautifiertestcase.py 的测试用例覆盖。

四、时间戳、格式化与断点续读

getJrnEntTime(filtersystemd.py)优先取_SOURCE_REALTIME_TIMESTAMP,缺失时回退到__REALTIME_TIMESTAMP,返回(ISO 字符串, POSIX 时间戳)

formatJournalEntry(filtersystemd.py)把 journal 条目拼装成 syslog 风格行:_HOSTNAMESYSLOG_IDENTIFIER(缺省回退_COMM)、SYSLOG_PID(缺省回退_PID,统一格式化为[pid]),kernel 消息追加单调时间戳,MESSAGE中的换行被转义为\n,最后把时间前缀进日志行,保证与普通文件过滤器共用同一套正则处理管线。

断点续读依赖 fail2ban/server/database.py 的getJournalPos/updateJournal:在run()主循环中,若 jail 关联了数据库,startTimemax(数据库记录位置, now - findtime),再seekToTime(startTime)回溯处理;处理过程中每 100 个 tick 或达到更新间隔时调用_updateDBPending落库,afterStop时也会确保待写位置全部更新(filtersystemd.py)。这正是"重启不重扫、不漏报"的实现基础。

五、主循环:等待、轮转感知与重开

run()(filtersystemd.py)是过滤器线程的主干,关键流程为:

  1. 定位起点seek_tail()取最后一条记录,结合数据库位置与findtime决定回溯起点;空 journal 时直接进入"运行中"模式并seekToTime(now)
  2. 等待新条目:运行模式下用Utils.wait_for包住journal.wait(...),以非轮询方式等待APPEND/INVALIDATE信号;空闲时仍按sleeptime节流,避免高负载。
  3. 处理轮转(INVALIDATE):收到INVALIDATE先短暂等待轮转结束,必要时前后移动游标避免进入死区(gh-3396),并通过_journalAlive检测连接是否失效(如OSError: [Errno 99] Cannot assign requested address),失效则调用_reopenJournal重建journal.Reader
  4. 逐条处理get_next()取条目 →formatJournalEntry格式化 →processLineAndAdd(line, tm)交给基类正则匹配与 FailManager;累计 100 条后继续处理而不进入等待。
  5. 运行模式切换:处理到"当前时刻"或越过起点游标后切换为inOperationMode,此后新到条目直接实时处理。

_reopenJournal(filtersystemd.py)对应 gh-3929 场景(轮转后 journal 描述符失效),先尝试原地重新__init__底层 Reader,失败则整体重建,随后恢复 jail 配置的 journal 匹配并短暂抑制重复的 Invalidate 提示。

六、配置实战:在 jail 中启用 systemd 后端

修改config/jail.confjail.d/*.local,把目标 jail 的backend设为systemd,并通过journalmatch缩小监视范围,例如 sshd:

[sshd] enabled = true backend = systemd journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd maxretry = 5 findtime = 10m bantime = 10m

journalmatch的优先级从高到低为:jail 内显式配置 → 过滤器配置文件的journalmatch段(如 fail2ban/tests/files/filter.d/testcase01.conf 中的_COMM=sshd + _SYSTEMD_UNIT=sshd.service _UID=0)→ 缺省(匹配全部 journal)。+连接多个匹配组时组间为 OR,同一组内多个键值对为 AND。

根据 config/jail.conf 的说明,使用systemd后端时不能再指定logpath;若默认后端为 systemd、但某 jail 的日志只存在于自己的日志文件中,应给该 jail 单独指定其他后端(如polling)并留空journalmatch。启用前需确认系统已安装systemd的 Python 绑定(模块顶部from systemd import journal依赖它),例如 Debian/Ubuntu 的python3-systemd包;Fail2Ban 源码中FilterSystemd及相关方法标注了# pragma: systemd no cover,意味着在未安装绑定的环境下该后端不可用。

运行验证可执行:

fail2ban-client status sshd # 查看 Filter 行与 Journal matches fail2ban-client set sshd addjournalmatch "_COMM=sshd" fail2ban-client get sshd journalmatch fail2ban-client set sshd deljournalmatch

七、小结

FilterSystemd把 Fail2Ban 的"正则判定失败"能力无缝嫁接到 systemd journal 上:journalmatch提供与 journalctl 一致的筛选语法,formatJournalEntry保证条目与文件日志走同一条处理管线,数据库断点实现重启续读,INVALIDATE感知与_reopenJournal应对日志轮转和描述符失效。整套机制在 fail2ban/server/filtersystemd.py 一个文件中即可完整阅读,是理解 Fail2Ban 过滤器抽象(fail2ban/server/filter.py)与后端扩展机制的最佳范例。

【免费下载链接】fail2banDaemon to ban hosts that cause multiple authentication errors项目地址: https://gitcode.com/gh_mirrors/fa/fail2ban

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询