运营面向公众的网站对于任何企业来说都并非易事。一方面,网站关系到业务运营,需要确保 100% 的正常运行时间和不折不扣的稳定性;另一方面,网站又需要根据企业需求而频繁更新。这种稳定性与多变性之间相悖的要求是运维团队面临的主要挑战之一。但更大的挑战可能是如何抵御攻击者、黑客及“脚本小子”。
主页篡改相当于在您的门店或总部门前进行数字涂鸦。它是一种持续的威胁,一旦发生,就会招来不必要的麻烦。网站运营负责人员或许还会发现,主页篡改可能会让其面对首席执行官的质问,甚至会影响个人职业发展。
安全防护需要采用多层方法。本文介绍了如何将 NGINX Plus 用作深度安全防护技术栈(其中可能还包括防火墙、入侵检测系统及强大的账户管理)的一部分,使用 NGINX Plus 的主动健康检查功能来主动监控网站主页是否有意外更改,并在检测到任何此类更改时自动提供先前版本。
注:NGINX Plus 的主动健康检查功能通常用于测试后端系统的总体可用性和端到端应用健康状况。本文将介绍一个安全用例,它可用于补充现有的健康检查。有关健康检查配置的更多信息,请参阅《NGINX Plus 管理指南》。
使用 ETag
检测更改
为了探讨如何使用 NGINX Plus 检测主页篡改,我们将使用一个简单的测试环境,代表一个面向公众的网站所包含的关键组件。
这一简单的解决方案使用了后端 Web 服务器上主页资源的 HTTP ETag
。ETag
值通常用于检测缓存资源的变更,随资源的改变而改变。我们可以通过查看主页的请求头来获取 ETag
值。
$ curl -i http://10.0.0.1/
HTTP/1.1 200 OK
Date: Fri, 21 Jul 2017 15:05:25 GMT
Content-Type: text/html
Content-Length: 612
ETag: "58ad6e69-264"
然后,我们定义了一项健康检查,只要后端 Web 服务器返回相同的 ETag
,便可通过检测。
match
块定义了第 14 行的 health_check
指令必须满足的通过条件。此处,我们要求 HTTP 请求头 ETag
与从后端 Web 服务器获取的值完全一致。默认情况下,health_check
指令测试根 URI (/),因此我们无需进一步配置即可检查主页。
该配置可有效防止对主页进行任何未经授权的更改。如果 ETag
发生变化,健康检查就会失败,后端 Web 服务器被视为不可用。显然,这种保护级别会产生我们不希望出现的后果:一个离线的网站与被篡改的网站一样糟糕。此外,在 NGINX 配置中更新 ETag
值之前,例行更改和计划更改也会导致中断。
为了解决这些问题,我们在 NGINX 中启用了内容缓存功能,并对其进行了配置。这样,在健康检查失败时,NGINX 会继续提供最新的已知良好版本。该配置的另一个好处是,NGINX 可以直接提供缓存内容,而非不断地从后端 Web 服务器中获取内容,这有助于提高网站的整体性能。
第 10 行的 proxy_cache_path
指令定义了缓存内容的位置。keys_zone
参数为缓存索引定义了一个共享内存区(称为 hc_cache
),并分配了 1 MB 容量。max_size
参数定义了一个上限,当达到这个上限时,NGINX Plus 开始从缓存中删除最近请求次数最少的资源,以便为新的缓存项腾出空间。inactive
参数定义了自上次请求后缓存内容在磁盘上的保存时长,不考虑从后端 Web 服务器接收的 Cache-Control
属性。
在 location 块中,proxy_cache
指令通过指定用于存储缓存键的共享内存区的名称(hc_cache
),启用此位置的缓存。proxy_cache_valid
指令告知 NGINX 资源的缓存过期时间。该值将被从后端 Web 服务器收到的任何 Cache-Control
属性覆盖。
为了满足在后端 Web 服务器被认为不健康时提供主页的最新已知良好版本的要求,我们使用了 proxy_cache_use_stale
指令,允许在后端不可用的情况下提供过期内容。
借助 JavaScript 哈希横向扩展变更检测
[编者按 —— NGINX JavaScript 模块的用例有很多,以下用例只是其中之一。查看完整列表,请参阅《NGINX JavaScript 模块的用例》。]
针对 NGINX JavaScript 的实现自该博文最初发布以来的变化,本节中的代码更新如下:
- 使用 NGINX JavaScript 0.2.4 中引入的 Stream 模块的重构会话对象。
- 在 NGINX Plus R23 及更高版本中,使用
js_import
指令取代js_include
指令。更多信息,请参阅 NGINX JavaScript 模块的参考文档 —“示例配置 ”一节显示了 NGINX 配置和 JavaScript 文件的正确语法。
]
检测 ETag
的变更是一种简单而有效的解决方案,但它无法从反向代理环境扩展到负载均衡环境。在负载均衡环境中,即使内容相同,NGINX Plus 的单个实例也可能从每台后端 Web 服务器收到不同的 ETag
值。
一种更可靠的意外变更检测方法是使用每台后端 Web 服务器提供的实际内容的加密哈希值,而非依赖 ETag
。为此,我们为每台后端 Web 服务器提供一个相邻 Web 服务,后者可针对任何给定的 URL 生成 SHA‑256 哈希值。该相邻 Web 服务(即我们的“哈希服务”)将仅供 NGINX Plus 健康检查使用,以获取每台后端 Web 服务器上当前主页内容的哈希值。
后端 Web 服务器无需运行 NGINX 或 NGINX Plus 来提供哈希服务。为此,我们可将 NGINX 与现有 Web 服务器软件独立安装,只需很少的配置。
哈希服务在 Stream 上下文中作为简单的 TCP 代理实现,并使用 NGINX JavaScript 模块对从本地 Web 服务器的 80 端口收到的 HTTP 响应执行哈希操作。更多信息,请参阅《借助 NGINX JavaScript 模块,充分利用 JavaScript 的强大功能和便利性以快速处理每个请求》。哈希服务的代码如下所示。
完成此配置后,我们便可通过查询哈希服务或将主页输出发送到外部哈希库来获取主页的 SHA-256 哈希值。此处,我们使用 OpenSSL 来检查哈希服务的结果是否正确。
$ curl -i http://127.0.0.1:4256/
HTTP/1.1 204 No Content
Hash: 38ffd4972ae513a0c79a8be4573403edcd709f0f572105362b08ff50cf6de521
$ curl -s http://127.0.0.1/ | openssl dgst -sha256
(stdin)= 38ffd4972ae513a0c79a8be4573403edcd709f0f572105362b08ff50cf6de521
哈希服务会返回一个最小的 HTTP 响应,仅以哈希值作为单个 HTTP 请求头,现在即可修改 NGINX Plus 配置,使用哈希服务进行健康检查。
match
块现在将正常响应定义为包含状态码 204 和哈希值(从哈希服务中获取)请求头的响应。为了让 NGINX Plus 在每台后端 Web 服务器上使用哈希服务, health_check
指令指定了哈希服务所用的端口号,该端口号会覆盖 upstream
组中每个 server
指令指定的端口(第 7-9 行)。
这样,每台后端 Web 服务器的主页内容都完全相同,如果主页内容发生变化,NGINX Plus 就会从上游组中移除一台后端服务器。不过,NGINX Plus 会继续从缓存中提供最新的已知良好内容,因此用户不会受到影响。然后,NGINX Plus 仪表盘会突出显示任何未通过健康检查的后端服务器,帮助您确保高性能、安全、可靠且可扩展的网站体验。