fail2ban 监视 docker 内的 wordpress

更新记录:
2020.07.27 适配 wp-fail2ban 4.3.0 版本
2023.11.20 适配 wp-fail2ban 5.2.1 版本


环境说明

  1. 宿主机运行 fail2ban
  2. docker 内运行 wordpress fpm 和 nginx,都是官方镜像
  3. wordpress 开启 wp-fail2ban 插件

遇到的问题

  1. wp-fail2ban 插件只会输出日志到 syslog,无法修改输出方式
  2. 由于懒得手写 Dockerfile,直接使用了官方镜像,但官方镜像不包含 syslog 服务,于是就无法获取到日志

可能的解决方案

  1. 在 docker 容器内跑一个 syslog 服务
  2. 修改 wp-fail2ban 源码,把日志输出到其他位置

纠结之后,选择了后者,因为实在懒得写 Dockerfile,写完还要调试,各种麻烦。修改 wp-fail2ban 源码的话,把日志直接输出给 docker 引擎,fail2ban 监视 docker 日志文件就可以了。

修改 wp-fail2ban

还好代码没有混淆,结构清晰,非常容易改。打开 wp-fail2ban/lib/syslog.php 文件,找到 write() 函数,修改为以下内容:

public static function write(int $level, string $msg, string $remote_addr = null): bool
{
    $rv = false;

    if (null === ($rv = apply_filters(__METHOD__, null, $level, $msg, $remote_addr))) {
        $msg .= ' from ';
        $msg .= (is_null($remote_addr))
                    ? remote_addr()
                    : $remote_addr;
    // 写出日志
    error_log($msg);
        if (false === ($rv = \syslog($level, $msg))) {
            error_log("WPf2b: Cannot write to syslog: '{$msg}'", 0); // @codeCoverageIgnore
        } elseif (defined('WP_FAIL2BAN_TRACE')) {
            error_log("WPf2b: Wrote to syslog: '{$msg}'", 0); // @codeCoverageIgnore
        }
    }
// 省略之后内容 ...

由于 docker 官方的 wordpress fpm 镜像默认开启了错误输出,所以这是可行的,添加后将能够在 docker logs 命令中查看。

编写 fail2ban 规则

分为两部分,filter 和 jail,先说 filter。

进入 /etc/fail2ban/filter.d/ 目录,创建文件 wordpress.conf,写入以下内容。

[Definition]

failregex = ^{.*PHP message: Empty username from <HOST>\\n.*$
            ^{.*PHP message: Authentication failure for .* from <HOST>\\n.*$
            ^{.*PHP message: REST authentication failure for .* from <HOST>\\n.*$
            ^{.*PHP message: XML-RPC authentication failure for .* from <HOST>\\n.*$
            ^{.*PHP message: Authentication attempt for unknown user .* from <HOST>\\n.*$
            ^{.*PHP message: Blocked username authentication attempt for .* from <HOST>\\n.*$
            ^{.*PHP message: Pingback requested from <HOST>\\n.*$
            ^{.*PHP message: Comment attempt on .* post \d+ from <HOST>\\n.*$
            ^{.*PHP message: Untrusted X-Forwarded-For header from <HOST>\\n.*$
            ^{.*PHP message: REST authentication attempt for unknown user .* from <HOST>\\n.*$
            ^{.*PHP message: XML-RPC authentication attempt for unknown user .* from <HOST>\\n.*$
            ^{.*PHP message: Immediately block connections from <HOST>\\n.*$
            ^{.*PHP message: Blocked access from country '..' from <HOST>\\n.*$
            ^{.*PHP message: XML-RPC request blocked from <HOST>\\n.*$
            ^{.*PHP message: .*; Bogus Pingback from <HOST>\\n.*$
            ^{.*PHP message: Akismet discarded spam comment from <HOST>\\n.*$
            ^{.*PHP message: Spam comment \d+ from <HOST>\\n.*$
            ^{.*PHP message: Blocked authentication attempt for .* from <HOST>\\n.*$
            ^{.*PHP message: XML-RPC multicall authentication failure from <HOST>\\n.*$
            ^{.*PHP message: Pingback error .* generated from <HOST>\\n.*$
            ^{.*PHP message: Blocked user enumeration attempt from <HOST>\\n.*$

ignoreregex =

为啥要这样写呢,参考 wp-fail2ban/filters.d/ 目录下的 3 个 conf 文件,我也不需要太强的过滤,忽略掉 extra 规则,只包含 soft 和 hard 就好。

wp-fail2ban 提供的默认 filter 是适用于 syslog 日志文件的,不能直接用于 docker 的日志文件,所以需要修改成我上面提供的这样的格式。

有了 filter,再写 jail。

进入 /etc/fail2ban/jail.d/ 目录,创建文件 wordpress.local

[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/lib/docker/containers/*/*-json.log
logtimezone = UTC
bantime = 86400
findtime  = 3600
maxretry = 3

其中日志路径包含了所有容器的日志,因为目录名和日志文件名是以容器 ID 命名的,ID 并不固定,所以无法固定写死一个文件,索性就监视所有日志。还有一种方式是修改容器的 logging driver,使用 journald 方式输出,这样可以兼顾 docker 工具和 fail2ban;如果不需要在 docker 管理工具中查看日志,甚至可以直接指定一个单独的日志文件名。

特别注意 timezone,docker 镜像默认都是使用 UTC 时区的,这不受宿主机影响,所以 docker 输出的日志中的时间也是 UTC,而 fail2ban 可能存在 bug,并不能正确处理 ISO 8601 时间末尾的“Z”字符(UTC),把这样的时间当成了宿主机默认时区处理,这就导致 fail2ban 的 findtime 出现问题。

其他注意事项

由于我在 wordpress 的前面还跑了一个 nginx 反向代理,以便于在一个统一的位置给所有服务添加 https 支持,所以需要对这层反向代理做额外处理。

打开 wordpress 根目录下的 wp-config.php 文件,加入以下内容。

// 加入这些才能正确支持 https,前提是反代 nginx 需要正确添加 HTTP_X_FORWARDED_PROTO 头
if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
        $_SERVER['HTTPS']='on';

// 从 HTTP_X_FORWARDED_FOR 头中提取远程地址,避免某些兼容性差的插件运行异常,比如 login-security-solution 插件
$_SERVER['REMOTE_ADDR'] = preg_replace('/^([^,]+).*$/', '\1', $_SERVER['HTTP_X_FORWARDED_FOR']);

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

扫码去手机上看