- 网络安全
- 运维
【免费下载链接】fail2ban
Daemon to ban hosts that cause multiple authentication errors
本篇技术指南以 Fail2Ban 开发者文档中的fail2ban.server.strptime模块为主题,深入剖析其基于 Pythontime.strptime改造的日期/时间解析实现:%指令扩展、时区偏移换算、缺省字段补全策略,以及它与DateDetector、DatePatternRegex配合完成日志时间提取的完整链路。读完本文,你将掌握 Fail2Ban 自定义datepattern的全部核心指令及其底层正则与换算规则,能够自行编写兼容官方语义的日志时间匹配模式。
一、模块定位:Fail2Ban 时间解析的基石
Fail2Ban 的核心工作流是"从日志行中识别失败认证时间 → 在时间窗口内统计失败次数 → 触发封禁"。其中时间识别由 fail2ban/server/datedetector.py 与 fail2ban/server/datetemplate.py 承担,而这两者的底层换算引擎正是本文主题模块 fail2ban/server/strptime.py。
该模块是对 Python 内置_strptime的深度定制:
- 复用标准指令:直接引入
LocaleTime、TimeRE、_calc_julian_from_U_or_W等内置实现(strptime.py 第 24 行); - 扩展与覆盖:为
TimeRE增加/重写了大量%指令的正则,使其更贴合真实日志的宽松格式; - 提供换算函数:
reGroupDictStrptime()把正则命名的分组字段转换为 Unix 时间戳,并处理"缺少年份/月份/日期"时的回退逻辑。
从源码结构看,fail2ban/server/datetemplate.py 中的DatePatternRegex模板把用户配置的datepattern用本模块生成的timeRE字典替换成最终正则,再用reGroupDictStrptime()完成时间计算,因此该模块决定了所有日期模式的能力边界。
二、扩展指令体系:内置 TimeRE 的覆盖与新增
模块启动时创建模块级TimeRE实例(strptime.py 第 33 行),随后系统性覆盖默认正则,使每个字段允许更宽容的输入(如不补零)。
2.1 基础字段覆盖
| 指令 | 含义 | 正则 | 说明 |
|---|---|---|---|
%k | 24 小时制小时(可空格) | ?(?P<H>[0-2]?\d) | 对应%H,但允许空格代替前导零 |
%l | 12 小时制小时(可空格) | ?(?P<I>1?\d) | 对应%I,同样允许空格 |
%d | 日 | [1-2]\d\|[0 ]?[1-9]\|3[0-1] | 允许1、01、1 |
%m | 月 | 0?[1-9]\|1[0-2] | 允许1或01 |
%Y | 四位年份 | \d{4} | — |
%H | 24 小时 | [0-1]?\d\|2[0-3] | 允许0、00、23 |
%M | 分钟 | [0-5]?\d | 允许0–59不补零 |
%S | 秒 | [0-5]?\d\|6[0-1] | 额外容忍闰秒60/61 |
以上定义见 strptime.py 第 39-61 行。
2.2 精确指令(Ex前缀)
由于默认指令"过于宽松"可能导致误匹配,模块提供了要求恰好两位数字的精确变体(strptime.py 第 63-71 行):
| 指令 | 正则 |
|---|---|
%Exd | [1-2]\d\|0[1-9]\|3[0-1] |
%Exm | 0[1-9]\|1[0-2] |
%ExH | [0-1]\d\|2[0-3] |
%Exk | ?[0-1]?\d\|2[0-3] |
%Exl | ?1[0-2]\|\d |
%ExM | [0-5]\d |
%ExS | [0-5]\d\|6[0-1] |
2.3 动态年份:%ExY与%Exy
默认%Y(四位年份)会匹配任意\d{4},误报率较高。模块通过_updateTimeRE()(strptime.py 第 73-116 行)在运行期动态构造"上一年所在世纪 + 未来 3 年"的年份正则%ExY,即只接受当前世纪内、从上一年到其后 3 年的年份(覆盖 Fail2Ban 长时运行的场景),其余四位年份一律拒绝;同时提供宽松的%Exy(两位年份\d{2})。
由代码可推断:
%ExY的"当前时间"取自MyTime.now()与MyTime.alternateNow(测试用备用时钟),保证测试用例中也能得到一致结果。
三、时区处理:从缩写到偏移量的完整换算
日志中的时区信息千差万别,本模块建立了"缩写表 + 固定偏移"的处理体系。
3.1 时区相关指令
| 指令 | 正则 | 语义 |
|---|---|---|
%Z | Z\|[A-Z]{3,5} | 仅匹配字面Z或 3–5 位大写缩写(避免误吞error等单词) |
%z | Z\|UTC\|GMT\|[+-]\d\d(?::?\d\d)? | 匹配Z、UTC、GMT或数值偏移(+0100、-05:30) |
%ExZ | A-Z? | 宽松版时区缩写(1 或 3–5 字符,不匹配 2 字符,防误判) |
%Exz | (?:缩写)?[+-]\d\d(?::?\d\d)?\|缩写 | 缩写 + 偏移量组合或纯缩写 |
定义见 strptime.py 第 30-53 行。模块注释明确指出%z目前只匹配Z|UTC|GMT和偏移量,以免在类似11-Aug-2013 03:36:11.372 error ...的行中把尾部普通单词误判为时区。
3.2 缩写偏移表 TZ_STR
模块内置了覆盖全球各时区的缩写→偏移表TZ_STR(strptime.py 第 342-382 行),由_init_TZ_ABBR()解析为TZ_ABBR_OFFS字典(单位为分钟)。表中既有整小时偏移(如UTC/GMT/WET为 0,CET为 +60,EET为 +120),也包含半小时、45 分钟等非整点偏移(如NPT为 +345,ACST为 +570,NST为 -210)。
重要限制:这些缩写按"固定偏移"处理,不会自动进行夏令时切换(注释明确说明使用CET不会自动切到CEST)。
3.3 偏移换算:validateTimeZone 与 zone2offset
def validateTimeZone(tz): # 校验并归一化时区字符串 m = FIXED_OFFSET_TZ_RE.match(tz) if m is None: raise ValueError("Unknown or unsupported time zone: %r" % tz) return zone2offset(m.groups(), 0) def zone2offset(tz, dt): # tz: 缩写+偏移量 或 int;返回分钟偏移 ...见 strptime.py 第 141-187 行。zone2offset支持三种输入:int直接返回;字符串走validateTimeZone;(缩写, 偏移量)元组则把缩写基线与数值偏移叠加。偏移量支持hh、hhmm、hh:mm三种写法,并正确处理负号。测试用例(fail2ban/tests/datedetectortestcase.py)验证了default_tz设置、非法时区抛ValueError,以及%Exz搭配偏移量解析的行为。
四、核心换算函数:reGroupDictStrptime
reGroupDictStrptime(found_dict, msec=False, default_tz=None)(strptime.py 第 189-339 行)是本模块的"时间计算器",输入正则命名分组字典,输出 Unix 时间戳(float)。它由DatePatternRegex.getDate()调用(见 datetemplate.py 第 352-356 行)。
4.1 字段映射规则
| 分组名 | 处理逻辑 |
|---|---|
y | 两位年份:≤2000 时自动 +2000(Fail2Ban 假定日志年份属于当前世纪) |
Y | 直接作为四位年份 |
m/b/B | 数字月 / 缩写月名 / 全称月名(通过locale_time索引) |
d | 日 |
H | 小时 |
I | 12 小时制小时,结合p(AM/PM)换算:无p或 AM 时 12→0,PM 时加 12 |
M/S | 分钟 / 秒 |
A/a/w | 星期全称 / 缩写 / 数字(数字按周日=0,内部转为周一=0) |
j | 年内第几天(Julian day) |
U/W | 周数(U周日起始、W周一起始),配合星期反推 Julian 日 |
z/Z | 时区:Z/UTC/GMT→偏移 0;其余经zone2offset换算 |
4.2 缺省字段回退(关键实战行为)
- 缺年份:假定为当前年(
MyTime.now()),并标记assume_year; - 缺月/日:若可用周数与星期推算 Julian 日则推算;否则假定为今天(
assume_today); - 向后回退:若按"今天"假设算出的时间晚于当前时间,说明日志可能是昨天跨午夜前写的,自动减 1 天;若按"今年"假设的时间超出当前时间 24 小时以上,则年份回退 1 年(同时重置月/日为原值);
- 时区修正:若行内无时区信息但配置了
default_tz,则按默认时区偏移换算;有偏移时用calendar.timegm,否则用time.mktime(受本地时区影响)。
五、与其他模块的协同:一次完整的日志时间提取
5.1 模板注册与匹配
datedetector.py 的DateDetector维护一组DateTemplate:
appendTemplate()(datedetector.py 第 258-296 行)注册模板,同名重复注册抛ValueError;matchTime()(datedetector.py 第 327-465 行)遍历模板找首个匹配,记录命中次数hits与匹配位置distance,并按"权重 = hits × weight / distance"对模板做气泡排序,让最常用的模板逐步上浮到列表前部,加速后续匹配;getTime()(datedetector.py 第 475-509 行)调用选中模板的getDate()完成换算,ValueError被静默吞掉并返回None。
5.2 三种内置模板类型
| 类型 | 匹配对象 | 备注 |
|---|---|---|
DatePatternRegex | 由%指令组成的日期模式 | 最终调用reGroupDictStrptime |
DateEpoch | Unix 时间戳(\d{10,11},可带毫秒/微秒) | 支持EPOCH/LEPOCH(毫秒级) |
DateTai64n | @[0-9a-f]{24}TAI64N 格式 | 十六进制秒数直接转时间戳 |
5.3 默认模板集
DateDetectorCache.DEFAULT_TEMPLATES(datedetector.py 第 126-177 行)内置 17 个覆盖常见日志的默认模式,例如:
# ISO 8601 / 简单日期,可选亚秒与时区 %ExY(?P<_sep>[-/.])%m(?P=_sep)%d(?:T| ?)%H:%M:%S(?:[.,]%f)?(?:\s*%z)? # asctime 风格:Sun Jan 23 21:59:59.011 2005 (?:%a )?%b %d %k:%M:%S(?:\.%f)?(?: %ExY)? # Apache 风格:[31/Oct/2006:09:22:55 -0000] %d(?P<_sep>[-/])%b(?P=_sep)%ExY[ :]?%H:%M:%S(?:\.%f)?(?: %z)? # Epoch / TAI64N / 仅时间等 EPOCH / TAI64N / {^LN-BEG}%H:%M:%S / %Exy%Exm%Exd ?%H:%M:%S ...默认模板在首次使用时按需编译并缓存(DD_patternCache,最多 1000 条、缓存 1 小时),并自动为未锚定的模板生成"行首优先"变体(权重 100.0),兼顾安全与性能。
六、在配置中的落地:datepattern 实战
Fail2Ban 的 jail 或 filter 配置通过datepattern参数指定日志日期格式,最终经 fail2ban/server/filter.py 传入DateDetector(filter.py中self.dateDetector.getTime(...)调用链,且通过logtimezone/default_tz指定默认时区)。实践要点:
- 格式即正则模板:
datepattern = %ExY-%Exm-%Exd %H:%M:%S中所有%指令都会被本模块替换为对应正则,其余字符按正则语义处理; - 精确优先:优先使用
Ex系列精确指令降低误匹配,例如%ExY限定当前世纪年份; - 锚定加速:用
{^LN-BEG}前缀要求日期必须出现在行首,能显著提升匹配效率(对应LINE_BEGIN标志); - 取消边界:
**前缀/后缀可关闭自动添加的词边界,{UNB}可完全取消边界包装; - 关闭默认模板:
datepattern = {NONE}表示不添加任何默认模板,只用自定义模式; - 子串替换:
{DATE}占位符配合prematchregex可将自定义前缀与默认日期模式组合; - 时区兜底:当日志不含时区信息时,设置
logtimezone(如UTC、CET+0100),模块会在换算时按该时区修正时间戳。
七、总结
fail2ban.server.strptime虽不足 400 行,却是 Fail2Ban 日志时间识别的"规则引擎":它定义指令语义(宽松/精确/动态年份)、内置全球时区缩写表与偏移换算、实现带缺省字段回退的时间计算,并通过DatePatternRegex桥接到DateDetector的模板匹配与自适应排序体系。理解这一层实现,是编写高精度datepattern、排查"日志时间解析失败/误匹配"问题的前提。相关测试可参考 fail2ban/tests/datedetectortestcase.py 与 fail2ban/tests/fail2banregextestcase.py,其中覆盖了%ExY年份边界、%z/%Exz时区、{^LN-BEG}锚定与缺省字段回退等关键行为。
- 网络安全
- 运维
【免费下载链接】fail2ban
Daemon to ban hosts that cause multiple authentication errors
相关推荐
Findroid未来路线图:Android TV、WebSocket与Chromecast支持计划
Findroid未来路线图:Android TV、WebSocket与Chromecast支持计划 Findroid是一款第三方原生Jellyfin Andro
音视频移动开发Cocos Creator 引擎模块体系解析:Public Modules、'cc' 合成模块与模块扩展完整指南
Cocos Creator 引擎模块体系解析:Public Modules、'cc' 合成模块与模块扩展完整指南 本指南以 docs/contribution/
游戏开发图形学3D渲染Python email.utils 模块完全指南:地址解析、Message-ID 与日期时间格式处理
Python email.utils 模块完全指南:地址解析、Message ID 与日期时间格式处理 email.utils 是 CPython 标准库 em
编程语言语言运行时解释器标准库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考