Varnish 限制访问频率

Varnish 4 仍然没有内置的访问频率限制功能,要达到这一目的需要安装 vsthrottle VMOD。

这个 VMOD 可以从这里获取:https://github.com/varnish/libvmod-vsthrottle

可以使用源码目录中的 autogen.sh 利用 autotools 先生成 configure 脚本,然后再按照“./configure; make; make install”步骤安装好这个 VMOD(如果是使用官方 rpm 安装 Varnish 的话,还需要 varnish-libs-devel 这个包才能编译)。

安装好之后就可以在 vcl 配置里面使用它了。

vcl 4.0;

import vsthrottle;

sub vcl_recv {
        set client.identity = client.ip;
        if (vsthrottle.is_denied(client.identity, 15, 10s)) {
                # Client has exceeded 15 reqs per 10s
                return (synth(429, "Too Many Requests"));
        }
}

vsthrottle 的使用比较简单,只有一个“is_denied()”函数。函数原型为:

is_denied(STRING key, INT limit, DURATION period)

第一个 key 参数和 nginx 里面的 limit_req_zone 指令里面的 key 差不多,就是作为限制频率的依据。上面的配置里面我们按照客户端地址进行了访问限制,10s 内如果超过 15 次访问则给它返回 429 状态码——Too Many Requests。但是可以看到并没有直接将 client.ip 传递给 vsthrottle.is_denied,这是因为 key 参数必须是字符串类型,而 client.ip 则是个 IP 地址类型,只能将它先用 client.identity 保存起来后才能进行传递。key 的定义很灵活,我们可以把 req.http.User-Agent 等作为key,也可以利用类型转换,或者是 regsub 函数修改字符来作为 key。

Strict-Transport-Security HTTP 头造成的重定向

Strict-Transport-Security(HSTS) HTTP 头可以告诉浏览器使用强制的更高安全控制,比如当访问到的 SSL/TLS 证书异常时,浏览器会拒绝让用户添加例外(如下图,没有 HSTS 头的时候,Firefox 会有个让用户添加例外的选项,用户可以忽略这个证书异常,继续访问网站。如果是下图的情况,用户登录了这个伪装为 onedrive 的网站,恐怕后果就很严重了),或者是让浏览器以后都默认使用 TLS 协议访问这个网站,而不用再通过 80 端口去重定向(这个时候也是很容易被攻击的)。

但有时候 HSTS 头的不当使用也会造成问题,比如一些站点的莫名无法访问——明明在地址栏输入的是 http:// 但是却老是会自动跳转到 https://,这个时候我们往往会以为在服务器端有什么重定向,但是经过检查却没有!
经过检查,Chrome 或者 Firefox 都有此问题,但是 IE 却没有,所以可以怀疑是浏览器这头做的跳转。
其实就是 HSTS 的 includeSubDomains 这段区域造成的,这段参数使得浏览器在访问每个子站点都会走 TLS 协议

解决办法:服务器上面去掉这个参数,并且在浏览器中也需要清除这个记录,Chrome 可以访问 chrome://net-internal/#hsts 这个地址,查看和删除已经存在的站点,Firefox 则只能选择忘记整个站点的记录来清除。IE 目前还不支持 HSTS(Windows 10 会支持,但还在开发当中),所以没有此问题。

Varnish: 让客户端强制刷新更新服务器缓存

从 RFC2616 的要求来看,缓存服务器是需要对客户端发过来的”Cache-Control”头作出回应的

Sometimes a user agent might want or need to insist that a cache revalidate its cache entry with the origin server (and not just with the next cache along the path to the origin server), or to reload its cache entry from the origin server. End-to-end revalidation might be necessary if either the cache or the origin server has overestimated the expiration time of the cached response. End-to-end reload may be necessary if the cache entry has become corrupted for some reason.

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4
其中说到了一种叫做”End-to-end reload”的:

End-to-end reload
The request includes a “no-cache” cache-control directive or, for compatibility with HTTP/1.0 clients, “Pragma: no-cache”. Field names MUST NOT be included with the no-cache directive in a request. The server MUST NOT use a cached copy when responding to such a request.

也就是说,当客户端(通常来说是浏览器)发过来”Cache-Control: no-cache”头(通常当我们按 Ctrl+f5 或者 Shift+鼠标点击刷新按钮的时候就会发出这个 HTTP 头)的时候,缓存服务器绝不能响应之前被缓存的拷贝,而是要重新去从源服务器获取一份发送给客户端。
但是在现实中往往不是这样:当我们在浏览器进行强制刷新的时候,只是将浏览器本地的缓存给清除掉了,缓存服务器的可能并没有更新。造成这个现象的原因主要有两个:

  1. 缓存服务器的性能可能满足不了这样的要求。
    在一个繁忙的缓存服务器,尤其是以磁盘作为主要缓存存储设备的服务器上面,限于磁盘IO,如果每次都必须将缓存对象彻底清除(实际上应该是可以讨巧的不去碰旧的缓存对象,然后去后端请求一个新的,然后替代旧的),即使再好的算法恐怕也无法应对大量的刷新缓存需求。
  2. 忽略客户端发过来的”Cache-Control”头,可以达到更高的缓存命中率。
    之所以要做缓存服务器,就是要让缓存率越高越好,开放让用户端去刷新缓存会降低缓存命中率。

但是现在也许该用新一点的思维来思考这个问题了:如果我们的服务器可以承受这样的刷新量,我们也许就可以让用户拥有刷新缓存服务器的能力。毕竟这是个信息迅速过期的时代,”Cache-Control: max-age=31536000″ 的作用也许已并不重要了。
我们可以用 Varnish 缓存服务器来做到这点:
前提要求是最好用纯内存作为缓存。添加如下的配置

vcl 4.0;

acl purge {
    "127.0.0.1";
}

sub vcl_recv {
    if (req.http.Cache-Control ~ "no-cache" && client.ip ~ purge) {
        # Force a cache miss
        set req.hash_always_miss = true;
    }
}

req.hash_always_miss 让 Varnish 去后端取新的内容,在 hash 里面替代旧的缓存对象,但是旧的并不会被立即清除,要等待它的 ttl 到了或者其它方法才会清除掉。这个做法的缺点是会在内存里面留下大量的旧的无用的缓存对象拷贝。
另一个方法是使用 ban 规则去清除缓存:

vcl 4.0;

acl purge {
    "127.0.0.1";
}

sub vcl_backend_response {
    # 后端响应添加到缓存时,把 Host 和 Url 添加到缓存里面,以方便后续可以根据 obj. 对象进行缓存清除。
    set beresp.http.X-Cache-Host = bereq.http.Host;
    set beresp.http.X-Cache-Url = bereq.url;
}

sub vcl_recv {
    # purge 地址内的客户端发过来"Cache-Control: no-cache"头,则添加一条 ban 规则,清除缓存
    if (req.http.Cache-Control ~ "no-cache" && client.ip ~ purge) {
        ban ("obj.http.X-Cache-Host == " + req.http.Host + " && obj.http.X-Cache-Url == " + req.url);
    }
}

sub vcl_backend_response {
        # 设置 Varnish 最长缓存保留时间为一天
        if (beresp.ttl > 1d) {
                set beresp.ttl = 1d;
        }
}

sub vcl_deliver {
    # 在发送给客户端之前,我们可以将只用于清除缓存的头去掉,没必要发给客户。
    unset resp.http.X-Cache-Host;
    unset resp.http.X-Cache-Url;
}

可以根据需求修改要不要对可以进行刷缓存的客户端 IP 进行限制。
最后,为什么要设置 Varnish 的最长保留缓存时间为一天?这是因为如果刷新请求量一旦比较大的话,会累积大量的 ban 规则(ban 规则只有当缓存里面最老的缓存对象都比他要新的时候才会被删除,否则就会一直累积着。因此,即使只有一个缓存对象保存时间为一年的话,都会导致 Varnish 累积一年的 ban 规则),这仍然还是会对性能产生很大的影响,所以为了不至于累积过多的 ban 规则,拖累 ban lurker 线程和整个 Varnish 的性能,直接设置最多保存一天就够了。(实际上,需要被缓存一天以上的东西本来就不多,可以通过 varnishtop -i TTL 查看到。)
更新:后面使用 ban 规则的方法似乎设置过最长 ttl 为一天后,还是会出现大量 ban 规则累积的情况,暂时没时间研究了。。。 🙁

貌似被 GFW 认证了

最近这段时间在自己 VPS 上面搭建的 PPTP VPN 连接每过几分钟都会中断,试验了下,发现是 1723 端口每隔一段时间都会被屏蔽。
这应该就是 GFW 在屏蔽 VPN 吧,也许我该换个 IP 地址了(我不想换做用别的类型 VPN,因为 PPTP 的客户端是最容易的,基本上任何 OS 上面都是内置支持的)。
最后我想说:真鸡巴恶心,肏你妈屄的,早点去死吧!

也谈 12306 泄密

12306 官方把这次密码泄露事件推给了第三方刷票工具,但也有安全专家把原因归结为“撞库”。
但我认为在这次事件中,12306 是负有责任的:
1、已订票单过于容易被取消。事实上就是登录进去账号之后点几下就能把订单给取消了,完全没有进行身份验证——比如短信形式的二次验证。
2、www.12306.cn 是一个反人类的网站,变相在鼓励用户使用第三方工具。我之前已经说过这个垃圾网站有多恶心。不管这次密码泄露是第三方工具还是“撞库”,这都应该给 12306 和用户敲响警钟,12306 应该认真考虑做个可用的网站出来,而用户也该意识到使用第三方工具的风险。
3、12306 没有任何措施保护已经失窃的账号。如果我没有理解错的话,12306 不是一个普通的商业网站,是依靠税收建立的,意味着其实每个公民都有“投资”,但是结果是 12306 这帮人对“投资人”都没有一丁点责任心,至少你应当可以把那些账号锁定,要求用户以其它更安全的方式验证身份后解锁。(当然我不是在说商业网站在这种情况下就应该撒手不管)。结果呢?就是有用户被他人给退票了。
总而言之,12306 就是个只会推卸责任,没有一点安全意识的人在管理。你要是以前不懂,但俗话说得好“吃一堑,长一智”,以后也该聪明点了,但是现在我看不出它有任何改进。

另一个越来越不喜欢 Debian 的理由

Debian 现在就完全没有个稍微能快那么一丁点的 repo 了,http.debian.net 更是扯淡,直接给我返回的是奇慢无比的俄国站点。。。
都不知道该怎么说了,反正这点上面 yum 真的是比 apt 要好很多。

CentOS 7 修改 MariaDB 的最大打开文件数

记一下这个坑

最近将一个 php 环境迁移到了 CentOS 7,一开始一切都好,但是网站正式上线后一段时间却老是出现 php-fpm 进程繁忙的情况了。

刚开始以为是 php-fpm 哪里配错了,后来才发现是 MariaDB 的问题,日志里面一直在报达到了最大打开文件数。看了下,发现最大可以打开文件数是 1024,确实小了。

limits.conf 里面是早就把限制调高了的,所以在 /etc/my.cnf 里面加了一行

open-file-limits = 10240

结果重启后却还是 1024。

最终是在 /usr/lib/systemd/system/mariadb.service 里面找到了线索

# It's not recommended to modify this file in-place, because it will be
# overwritten during package upgrades.  If you want to customize, the
# best way is to create a file "/etc/systemd/system/mariadb.service",
# containing
#       .include /lib/systemd/system/mariadb.service
#       ...make your changes here...
# or create a file "/etc/systemd/system/mariadb.service.d/foo.conf",
# which doesn't need to include ".include" call and which will be parsed
# after the file mariadb.service itself is parsed.
#
# For more info about custom unit files, see systemd.unit(5) or
# http://fedoraproject.org/wiki/Systemd#How_do_I_customize_a_unit_file.2F_add_a_custom_unit_file.3F
# For example, if you want to increase mariadb's open-files-limit to 10000,
# you need to increase systemd's LimitNOFILE setting, so create a file named
# "/etc/systemd/system/mariadb.service.d/limits.conf" containing:
#       [Service]
#       LimitNOFILE=10000

需要由 systemd 来提高最大可以打开文件数。

于是按照提示创建了 /etc/systemd/system/mariadb.d/limits.conf 加入了

[Service]
LimitNOFILE=65535

结果发现还是老样子

最终,再把 /etc/my.cnf 里面的 open-file-limits = 10240 删除掉才行。。。

Linux 网络命名空间 (netns),如何进入隐藏的 netns

Linux 的网络命名空间 (netns) 是非常有用和好玩的功能,可以允许进程进入一个隔离的网络空间,在这里面可以安全地进行网络测试、iptables 策略测试等。

通常情况下,一般我们用 ip netns add 添加新的 netns,然后我们可以用 ip netns list 查看所有的 netns。但有的时候进程的 netns 却并没有显式导出,ip netns list 无法列出它,比如 docker 的 container 的网络空间。

也许我们还是需要进入到那个进程的 netns,怎么办呢?答案是做个符号链接就可以了。

ln -sf /proc/<pid>/ns/net /var/run/netns/$ns

简要说明一下这个命令:每个进程的网络命名空间都是通过 proc 文件系统导出来了的,位于 /proc/<pid>/ns/net (这个文件不可读,它只是相当于一个访问点);而 ip netns list 命令是从 /var/run/netns 这个路径读取 netns 列表的,因此直接将进程的命名空间链接到 /var/run/netns 目录下就可以了。

这之后,可以按照以往的 ip netns $ns exec /bin/bash 进去这个命名空间玩耍了。

Fuck Google Fonts

不是因为 Google 在国内被屏蔽而导致诸多网站加载 Web Fonts 或者 JQuery 缓慢。

让自己的网站去调用第三方网站的东西,本来就是个傻逼主意。说实话吧,自己的网站上放个压缩的 JQuery 或者字体能有个多大的事?!别说用第三方的能省流量、加载速度,事实是这些都是极其小的影响。既然都喜欢让 Google 通过 js 去知道你的网站访问情况,干脆你不就把网站全交给 Google 算了!

近期小结

  1. 使用 ipset 替换了许多 iptables 规则的匹配。
  2. 再熟悉了一下 iproute 这套工具里面的东东。诸如 ip netns 等东西都非常有用
  3. 彻底只允许 cloudflare 访问我的那个 ip 地址了,隐藏掉其它的访问。