Fail2Ban 开发者指南:从代码测试、编码规范到服务端架构设计
发布时间:2026/9/20 11:29:55 作者:尧图编辑部 阅读量:1,286

Fail2Ban 开发者指南从代码测试、编码规范到服务端架构设计【免费下载链接】fail2banDaemon to ban hosts that cause multiple authentication errors项目地址: https://gitcode.com/gh_mirrors/fa/fail2ban本篇以仓库根目录 DEVELOP由 doc/develop.rst 引入为骨架编写面向希望为 Fail2Ban 贡献代码、或想深入理解其服务端内部设计的开发者。文章覆盖从 Git 工作流、Pull Request 提交规范、测试与覆盖率工具链到 server/ 目录下 Jail、Filter、FailManager、Actions 等核心组件的类层次与数据流并结合fail2ban/server/源码逐一印证文档中的架构描述帮助读者快速上手开发与调试。一、开发环境与协作流程Fail2Ban 使用 Git 分布式版本控制进行开发每个开发者都拥有仓库的完整副本可以自由地添加分支、切换分支、提交本地修改随后请求维护者合并merge自己的改动。代码托管在 GitHub 的 fail2ban/fail2ban 仓库GitHub 提供开源项目免费托管、基于 Web 的 Git 仓库浏览与 Issue 跟踪。如果你熟悉 Python并且想提交一个 bug 修复或新特性推荐的方式是使用 GitHub 的 Pull RequestPR功能。重要前提本文基于当前仓库源码描述开发流程。实际向官方项目提交代码时需要以官方仓库的实时分支状态和贡献要求为准本文给出的所有命令、路径与代码引用均以本仓库当前内容为基准。1.1 Pull Request 提交要求文档 DEVELOP 明确要求提交 Pull Request 时应当清晰描述你要解决的问题Clearly describe the problem youre solving避免引入回归不给系统管理员升级带来困难Dont introduce regressions如果添加的是主要特性major feature请在 master 上 rebase 你的改动并压缩为单个 commit包含测试用例详见下文代码测试包含样例日志如果相关尤其对于 filter 开发更新 ChangeLog的相应章节如果 THANKS 中还没有你请加入自己的名字。如果正在开发新的 filter日志过滤规则请查阅 FILTERS 文件其中有专门的文档说明。二、代码测试测试用例、覆盖率与手动执行2.1 运行现有测试现有测试通过bin/fail2ban-testcases脚本运行该脚本位于仓库 bin/fail2ban-testcases另有聚合脚本 fail2ban-testcases-all 与 fail2ban-testcases-all-python3。它带有--log-level等常用选项bin/fail2ban-testcases --help--help会列出全部可用选项。文档特别提醒测试用例应当覆盖所有常规情况、所有异常情况以及所有边界内/边界外条件并且应当覆盖所有分支。测试源码位于 fail2ban/tests/按模块拆分例如fail2ban/tests/failmanagertestcase.py ——FailManager的单元测试含AddFailure、FailmanagerComplex等测试类fail2ban/tests/filtertestcase.py —— Filter 相关测试fail2ban/tests/actiontestcase.py 与 fail2ban/tests/actionstestcase.py —— Action 相关测试fail2ban/tests/servertestcase.py、fail2ban/tests/databasetestcase.py、fail2ban/tests/datedetectortestcase.py 等。2.2 覆盖率coverage工具链安装python-coverage包后可可视化测试覆盖率注意在 Debian 系系统中脚本名为python-coverage。运行coverage run bin/fail2ban-testcases coverage report可选地生成 HTML 报告coverage html然后浏览器打开htmlcov/index.html即可查看测试用例对代码库的覆盖程度。覆盖率百分之百是好事但文档同时提醒全覆盖并不意味着完备应尽量让测试覆盖尽可能多的独立代码路径。覆盖率工具还能帮助识别缺失的分支——关于分支覆盖率可参考 coverage.py 官方的分支文档。2.3 手动执行在开发环境中运行 fail2ban在开发环境不安装到系统中手动运行文档给出了标准命令./fail2ban-client -c config/ -s /tmp/f2b.sock -i start参数含义-c config/指定配置文件目录为仓库内的 config/-s /tmp/f2b.sock指定控制 socket 路径-i以交互模式运行start启动服务端。启动后可以依次输入下列命令做快速验证这也是文档推荐的 smoke test 流程status add test pyinotify status test set test addaction iptables set test actionban iptables echo ip cidr /tmp/ban set test actionunban iptables echo ip cidr /tmp/unban get test actionban iptables get test actionunban iptables set test banip 192.168.2.2 status test这段流程演示了 Fail2Ban 交互式控制台的核心操作模式status查看全局/监狱状态add jail backend动态添加一个名为test、后端为pyinotify的监狱set test addaction iptables为test监狱挂载iptables动作set test actionban iptables .../set test actionunban iptables ...运行时改写动作的actionban/actionunban命令模板这里将封禁/解封事件回显到/tmp/ban、/tmp/unban便于观察get test actionban iptables读取当前生效的actionban命令set test banip 192.168.2.2手动封禁一个 IP等价于执行一次动作再次status test确认封禁已生效。这些命令对应服务端 transmitter.py 中set/get/add/status等命令处理器并由 server.py 的setBanIP、addAction等方法落地。命令行工具的完整说明见 man/fail2ban-client.1例如set JAIL addaction ACT...、set JAIL banip IP...。2.4 使用 Vagrant 进行隔离测试仓库根目录提供了 Vagrantfile可以在虚拟机中做攻防测试共建立两台 VMsecure用于测试 fail2ban 代码attacker用于对 secure VM 发起攻击。两台 VM 共享192.168.200/24网段。如果你所在的网络恰好使用该网段请检查 Vagrantfile 并修改 IP 以避免冲突。三、编码规范Coding Standards3.1 风格与测试要求项目要求Style目前请使用**制表符tab**缩进可读文本尽量保持80 列以内Tests为新增代码补充有意义的测试Coverage随代码增加测试覆盖率只许上升pyflakes在包括基于 Python 的动作在内的所有 Python 代码上运行Documentation改动后保持本文档、man 手册页同步更新新特性要有足够的使用文档Bugs移除 bug且不要引入新的 bug3.2 分支覆盖的例外说明对于为兼容旧版 Python 而保留的分支允许在代码中使用# pragma: no cover注释在 fail2ban/server/action.py 等抽象方法上也能看到这类用法。但对其他任何pragma: no cover或pragma: no branch的使用必须写明理由——我还没写测试不是充分的理由。3.3 pyflakes 静态检查pyflakes 用于发现未使用的 import、未使用/未定义/被重定义的变量。文档建议对以下路径运行pyflakes bin/ config/ fail2ban/其中config/包含基于 Python 的 action如 config/action.d/smtp.py同样需要检查。3.4 Git 提交信息标签规范提交信息中请使用以下标签前缀BF:—— bug 修复Bug FixDOC:—— 文档修复ENH:—— 功能增强EnhancementTST:—— 仅涉及测试的提交不触碰主代码库多个标签可用连接例如BFTST:。仓库的 ChangeLog 中即大量使用这类前缀。另外可用closes #333、resolves #333、fixes #333等文本让提交自动关闭对应 Issue333 为示例 Issue 号。如果合并产生了冲突需要在合并提交信息的Conflicts:段落中说明对相应文件做了哪些修改。3.5 添加新 Action 的约定如果新增了action.d/*.conf文件还必须在 config/jail.conf 中添加一个示例enabled false、针对 ssh 且maxretry5的配置块config/jail.conf内已有大量此类enabled false的示例模板可直接参照编写。这样系统管理员既能开箱参考又不会因示例默认启用而误封。四、服务端设计核心组件与类层次DEVELOP 文档指出Fail2Ban 最初基于 Python 2.3 开发作者回忆至今仍力求兼容 Python 2.4这种兼容性承诺使得部分代码显得老派文档中标记为 RF-Note即重构时值得关注的点。0.7 版本经历了重大重构形成了client/server 分离、每监狱一线程a-thread-per-jail的架构。下面用文档给出的类层次图作为导航符号约定-继承、委托/聚合、*存储多个实例JailThread - Filter - FileFilter - {FilterPoll, FilterPyinotify, ...} | * FileContainer FailManager DateDetector Jail构造时传入用于把 ticket 从 FailManager 送入 Jail 的队列 Server Jails * Jail Filter (in __filter) * tickets (in __queue) Actions (in __action) * Action BanManager从当前源码看这一结构依然成立fail2ban/server/jailthread.py 定义了抽象线程基类JailThread维护active/idle状态与run/stop/onStop生命周期fail2ban/server/filter.py 中依次定义了Filter(JailThread)、FileFilter(Filter)、FileContainer具体后端 filterpoll.pyFilterPoll(FileFilter)、filterpyinotify.pyFilterPyinotify(FileFilter)、filtersystemd.pyFilterSystemd(JournalFilter)都沿此继承体系实现。4.1 FailManager失败票证的集中管理源码 fail2ban/server/failmanager.py 与文档描述完全对应FailManager以ticket票证为单位记录失败。所有操作都通过self.__lock Lock()加锁完成文档强调All operations are done via acquiring a lock内部用__failList字典按失败标识fid通常为 IP存储FailTicket。关键属性与方法setMaxRetry/getMaxRetry最大重试次数默认3setMaxTime/getMaxTime失败统计窗口默认600秒addFailure(ticket, count1, observedFalse)累加失败次数、扩展匹配日志受maxMatches截断并累加__failTotalcleanup(time)删除超出maxTime的过期 tickettoBan(fidNone)遍历失败列表把达到maxRetry的 ticket 移出列表并返回若没有达到阈值的 ticket 则抛出FailManagerEmpty。FailManagerEmpty(Exception)文档说明它由FailManager.toBan在遍历完 ticket 列表后抛出并附 RF-Note这个设计未来可以考虑改成生成器。4.2 Filter 与 FileFilter行处理与文件监控文档对 fail2ban/server/filter.py 的描述Filter(JailThread)包装非线程化的FailManager并大量代理其方法提供处理新日志行的全部主要逻辑——哪些 IP 需要忽略等。内部关键成员.failManagerFailManager实例.dateDetectorDateDetector实例.__failRegex/.__ignoreRegex失败与忽略规则的正则表达式列表.__findTime数值型时间窗口在processLineAndAdd中用于跳过过期行。FileFilter(Filter)文件感知的 Filter.__logPath被跟踪的文件列表通过addLogPath逐个添加存储为FileContainer对象.getFailures返回True表示成功打开并读取了行直到读空返回False表示打开失败或没有匹配该文件名的容器。FileContainer文件的适配器专门处理日志轮转log rotation。提供.open、.close、.readlineRF-Note 指出文件句柄缺失时readline返回也许应返回None更合理以及位置指针.__pos。在 filter.py 中可以看到实际的票证流转实现performBanwhile True: try: ticket self.failManager.toBan(ip) except FailManagerEmpty: break self.jail.putFailTicket(ticket) if ip: break这正是 DEVELOP 文档所描述的把 ban tickets 从 failManager 送入对应 jail 队列的通道toBan()取出达到阈值的票证jail.putFailTicket(ticket)将其投递到 Jail 的队列中等待 Actions 线程消费。4.3 具体后端过滤器filter*.py文档说明filter*.py是针对特定后端的FileFilter实现。派生类应提供run()的实现通常还需覆写addLogPath、delLogPath方法。所有后端的run()最终都以一种或另一种方式提供如下循环try: while True: ticket self.failManager.toBan() self.jail.putFailTicket(ticket) except FailManagerEmpty: self.failManager.cleanup(MyTime.time())即持续从 failManager 取达到封禁阈值的票证送入 jail当列表为空抛出FailManagerEmpty时清理过期的失败记录。filterpoll.py 的FilterPoll通过os.stat()比较mtime/ino/size判断日志是否被修改isModified主循环run()每轮调用getModified收集被修改的文件并逐个getFailures(filename)对文件缺失可能由轮转引起会记录__file404Cnt计数超过 50 次错误则将该文件移出监控delLogPath。filterpyinotify.py 的FilterPyinotify基于 inotify 事件回调callback、_process_file、_addFileWatcher等实时感知文件变化。filtersystemd.py 的FilterSystemd则对接 systemd journaladdJournalMatch、seekToTime、formatJournalEntry等。4.4 ipdns.pyDNS 与 IP 处理工具ipdns.py 提供两类工具文档所述DNSUtilsDNS 处理的工具类包含dnsToIp、ipToName、textToIp(text, useDns)、getSelfIPs、getIPsFromFile等IPAddrIP 地址处理的对象类支持 IPv4/IPv6、CIDRplen/isInNet/contains、PTR 反查getPTR、地址族判断isIPv4/isIPv6等。4.5 Action 与 Actions封禁命令的执行文档对 action.py 的描述Takes care about executing start/check/ban/unban/stop commands负责执行 start/check/ban/unban/stop 命令。源码层面抽象基类定义在 fail2ban/server/action.pystart/stop/ban/reban/unban均为抽象方法fail2ban/server/actions.py 中的Actions是监狱级动作容器其run()是动作线程主循环启动所有 action → 等待 jail 队列中的封禁票证hasFailTickets→__checkBan()处理封禁、__checkUnBan()处理到期解封 → 线程停止时__flushBan(stopTrue)并stopActions()同一文件中的ActionInfo定义了动作命令可用的插值标签字典AI_DICT如ip、family、bantime、failures、matches、ipmatches、jail.name、jail.banned等——这些就是 config/action.d/ 下各动作配置如iptables.conf、nftables.conf、mail.conf中...占位符的取值来源BanManagerfail2ban/server/banmanager.py负责维护当前封禁列表addBanTicket、unBanList、getBanList等。4.6 其他辅助组件server.py核心服务器管理 Jails 集合addJail/delJail/startJail/stopJail并暴露大量set/get接口addFailRegex、setMaxRetry、setBanTime、setBanIP等由transmitter转发控制命令jails.pyJails容器add(name, backend, dbNone)创建 Jailjail.pyJail聚合 Filter__filter、失败票证队列__queue、Actions__action并负责_setBackend选择polling/pyinotify/systemd后端failmanager.py 的FailTicket/BanTicket定义在 ticket.pygetIP、getAttempt、getMatches、incrBanCount、isTimedOut等datedetector.py 与 datetemplate.py日志行日期识别database.pySQLite 持久化addBan/getBans/getCurrentBans/purge用于重启后恢复封禁状态。五、开发时的自检清单综合 DEVELOP 文档与仓库实践提交代码前建议按以下清单自检问题描述PR 是否清晰说明了解决的问题回归风险改动是否会给系统管理员升级造成困难配置默认值是否保持向后兼容测试是否补充了覆盖常规、异常与边界条件的测试用例运行coverage run bin/fail2ban-testcases coverage report确认覆盖率没有下降# pragma: no cover是否都有合理理由静态检查pyflakes bin/ config/ fail2ban/是否无新增告警未使用 import、未定义变量等文档DEVELOP、FILTERS 与 man/ 手册是否同步更新新特性是否有使用文档提交规范提交信息是否使用BF:/DOC:/ENH:/TST:标签是否需要fixes #N关联 Issue是否需要更新 ChangeLog 与 THANKS新增动作若新增action.d/*.conf是否在 config/jail.conf 提供了enabled false、maxretry5的 ssh 示例手动验证是否用./fail2ban-client -c config/ -s /tmp/f2b.sock -i start走通添加监狱 → 挂载动作 → 手动 banip → 查看状态的完整链路按照上述流程即可在保证质量的前提下为 Fail2Ban 提交修复与新特性而理解第四节的类层次与票证流转机制则是深入调试 Filter 与 Action 行为的关键基础。【免费下载链接】fail2banDaemon to ban hosts that cause multiple authentication errors项目地址: https://gitcode.com/gh_mirrors/fa/fail2ban创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考