通过 NGINX JavaScript 模块脱敏数据,有效保护用户隐私
原文作者:Liam Crilly - F5 产品管理高级总监
原文链接:通过 NGINX JavaScript 模块脱敏数据,有效保护用户隐私
转载来源:NGINX 中文官网
NGINX 唯一中文官方社区 ,尽在 nginx.org.cn
迷惑面部识别软件的织物图案
由 Adam Harvey 设计
2016 年 10 月,欧盟法院裁定 IP 地址为“个人信息”,因此适用《数据保护指令》和《通用数据保护条例》(GDPR)。对于许多网站所有者而言,这意味着如果数据离开了欧盟,那么日志文件的归档和分析就会面临问题。进入美国的数据虽有欧盟-美国隐私护盾提供一定的保护,但仍面临着隐私组织和政府的法律质疑,他们认为目前的安全防护还不够。
然而,保护日志文件中的个人数据不仅仅是欧盟的问题。对于获得 ISO/ICE 27001 等安全认证的企业而言,将日志文件移出其原生安全区,例如从网络运维部门移至营销部门,就可能会影响其认证范围和合规性。
本文介绍了一些简单的解决方案,可用于清理 NGINX Plus 和 NGINX 日志文件,这样就能将其安全导出,而不会泄露“个人身份信息 (PII)”。
[编者按——本文是探讨 NGINX JavaScript 模块(最初称为 nginScript)用例的系列文章之一。
本文已更新,在 NGINX Plus R23 及更高版本中,使用 js_import 指令取代 js_include 指令。
简单方法行不通
保护个人数据的最简单方法是在导出日志之前将 IP 地址信息移除。使用标准 Linux 命令行工具很容易做到这一点,但日志分析系统可能需要日志文件采用标准格式,且无法导入缺少 IP 地址字段的日志。即使成功导入了日志,如果分析系统依靠 IP 地址来跟踪用户活动,日志处理的价值也会大大降低。
另一种可能的方法是用虚假值或随机值替代真实 IP 地址,这样得到的日志文件虽然看起来完整,但日志分析的质量却大打折扣,因为每个日志条目都显示为来自随机生成的不同 IP 地址。
脱敏客户端 IP 地址
最有效的解决方案是使用一项称为“数据脱敏”的技术,将真实 IP 地址转换为另一种 IP 地址,后者无法用于识别最终用户,但仍支持对特定用户的网站活动进行关联。数据脱敏算法始终为给定的输入值生成相同的伪随机值,同时可确保这些伪随机值无法被转换回原始输入值。每次出现的 IP 地址都会被转换为相同的伪随机值。
您可以使用 NGINX JavaScript 模块在 NGINX 和 NGINX Plus 中实现 IP 地址脱敏。该模块是一个面向 NGINX 和 NGINX Plus 的 JavaScript 实现,专为服务器端用例和按请求处理而设计。在本例中,我们会执行少量 JavaScript 代码,从而在记录每个请求时脱敏客户端 IP 地址。
文末提供了在 NGINX 和 NGINX Plus 上启用 NGINX JavaScript 模块的操作说明。
支持 IP 地址脱敏的 NGINX 和 NGINX Plus 配置
log_format 指令控制访问日志中显示的信息。NGINX 和 NGINX Plus 的默认日志格式为 combined,用它生成的日志文件可使用大多数日志处理工具进行处理。
对于此配置,我们创建了一种新的日志格式 — masked,它与 combined 格式差别不大,除了第一个字段 — 我们使用 $remote_addr_masked 替换了 $remote_addr 变量。这个新变量通过执行 JavaScript 代码进行计算,从而生成客户端 IP 地址的脱敏值。
log_format masked '$remote_addr_masked - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"';
为了让 NGINX 和 NGINX Plus 以这种格式编写访问日志,我们使用 access_log 指令配置 server 块,以指定 masked 格式。
js_import logmask.js; js_set $remote_addr_masked mask_ip_uri.maskRemoteAddress; server { listen 80; access_log /var/log/nginx/access.log; access_log /var/log/nginx/access_masked.log masked; location / { return 200 "$remote_addr -> $remote_addr_maskedn"; # 仅用于测试 } }
因为我们打算使用 NGINX JavaScript 脱敏客户端 IP 地址,所以我们使用 js_import 指令来指定 JavaScript 代码的位置。该 js_set 指令指定了在计算 $remote_addr_masked 变量时要执行的 JavaScript 函数。
请注意,我们使用了两个 access_log 指令。第一个指令使用默认日志格式生成访问日志,可供管理员用于运维。第二个指令指定了 masked 日志格式。通过这种配置,我们为每个请求编写两个访问日志 — 一个供系统管理员和 DevOps 使用,另一个用于导出。
最后,location 块使用 return 指令定义了一个非常简单的响应,以确认数据脱敏正常运行。在生产环境中,这很可能会包含一个 proxy_pass 指令,用于将请求定向到后端服务器。
用于 IP 地址脱敏的 NGINX JavaScript 代码
我们使用三个简单的 JavaScript 函数构建脱敏的 IP 地址。因为依赖函数必须先出现,所以我们将按照它们出现的先后顺序进行说明。
数据脱敏解决方案的本质是使用单向哈希算法来转换客户端 IP 地址。在本例中,我们使用的是 FNV-1a 哈希算法。该算法不仅紧凑、快速,并具有良好的分布特性,而且还会返回 32 位正整数(与 IPv4 地址相同的大小),因此非常适合用于表示 IP 地址。fnv32a 函数是 FNV-1a 算法的 JavaScript 实现。
i2ipv4 函数将 32 位整数转换为点分四进制记法的 Ipv4 地址。它从 fnv32a() 中获取哈希值,并提供在访问日志中“看起来正确”的表示形式。IPv6 地址和 IPv4 地址都用 IPv4 格式表示。
最后,我们使用 maskRemoteAddress 函数。该函数由上述 NGINX 和 NGINX Plus 配置中的 js_set 指令引用。它带有一个参数 req,即表示 HTTP 请求的 JavaScript 对象。remoteAddress 属性包含客户端 IP 地址的值(相当于 $remote_addr 变量)。
function fnv32a(str) { var hval = 2166136261; for (var i = 0; i < str.length; ++i ) { hval ^= str.charCodeAt(i); hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); } return hval >>> 0; } function i2ipv4(i) { var octet1 = (i >> 24) & 255; var octet2 = (i >> 16) & 255; var octet3 = (i >> 8) & 255; var octet4 = i & 255; return octet1 + "." + octet2 + "." + octet3 + "." + octet4; } function maskRemoteAddress(req) { return i2ipv4(fnv32a(req.remoteAddress)); } export default { maskRemoteAddress maskRequestURI }
IP 地址脱敏示例
上述配置完成后,我们就可以向服务器发出一个简单的请求,并检查响应和生成的访问日志条目。
$ curl http://localhost/ 127.0.0.1 -> 8.163.209.30 $ sudo tail --lines=1 /var/log/nginx/access*.log ==> /var/log/nginx/access.log <== 127.0.0.1 - - [16/Mar/2017:19:08:19 +0000] "GET / HTTP/1.1" 200 26 "-" "curl/7.47.0" ==> /var/log/nginx/access_masked.log <== 8.163.209.30 - - [16/Mar/2017:19:08:19 +0000] "GET / HTTP/1.1" 200 26 "-" "curl/7.47.0"
脱敏查询字符串中的个人数据
日志文件也可能包含除 IP 地址以外的个人数据。电子邮件地址、邮政地址及其他标识符均可作为查询字符串参数传递,因此被记录为请求 URI 的一部分。如果您的应用以这种方式传递个人数据,则可扩展 NGINX JavaScript IP 地址脱敏解决方案,以清理查询字符串中的个人数据。
支持查询字符串脱敏的 NGINX 和 NGINX Plus 配置
与默认 combined 格式一样,上面为 IP 地址脱敏定义的 masked 日志格式会记录 $request 变量。该变量捕获请求的三个组成部分:HTTP 方法、URI(包括查询字符串)及 HTTP 版本。我们只需要脱敏查询字符串,因此为了提高代码效率,我们分别对这三个组成部分使用单独的变量 — 仅使用 $request_uri_masked 变量转换请求 URI(第二个组成部分),并对第一个和第三个组成部分使用标准变量($request_method 和 $server_protocol)。
log_format masked '$remote_addr_masked - $remote_user [$time_local] ' '"$request_method $request_uri_masked $server_protocol" ' '$status $body_bytes_sent "$http_referer" "$http_user_agent"';
server 块需要使用另一个 js_set 指令来定义如何计算 $request_uri_masked 变量。
js_import logmask.js; js_set $request_uri_masked mask_ip_uri.maskRequestURI; server { listen 80; access_log /var/log/nginx/access.log; access_log /var/log/nginx/access_masked.log masked; location / { return 200 "$remote_addr -> $remote_addr_maskedn $request -> $request_uri_masked"; # 仅用于测试 } }
用于查询字符串脱敏的 NGINX JavaScript 代码
我们将 maskRequestURI JavaScript 函数添加到上述 mask_ip_uri.js 文件中。与 maskRemoteAddress() 一样,maskRequestURI() 依赖于 fnv32a 哈希函数,因此在文件中它位于该函数的下方。
function maskRequestURI(req) { var query_string = req.variables.query_string; if (query_string.length) { // 如有查询字符串,则继续执行 var kvpairs = query_string.split('&'); // 转换为键值对数组 for (var i = 0; i < kvpairs.length; i++) { // 遍历每个键值对 var kvpair = kvpairs[i].split('='); // 将键值对拆分为新数组 if (kvpair[0] == "zip") { // 脱敏 zip // 使用脱敏值的前 5 位数字 kvpairs[i] = kvpair[0] + "=" + fnv32a(kvpair[1]).toString().substr(5); } else if (kvpair[0] == "email") { // 脱敏电子邮件 // 使用哈希值作为单个域的前缀 kvpairs[i] = kvpair[0] + "=" + fnv32a(kvpair[1]) + "@example.com"; } } return req.uri + "?" + kvpairs.join('&'); // 构建脱敏的 URI } return req.uri; // 没有查询字符串,返回 URI } export default { maskRemoteAddress maskRequestURI }
maskRequestURI 函数会遍历查询字符串中的每个键值对,查找已知包含个人数据的特定键。这些键的值都会被转换为脱敏值。
根据要对 NGINX 和 NGINX Plus 日志文件执行的处理类型,脱敏后的查询字符串值可能需要与真实数据格式相同。在上面的示例中,我们将 zip 格式设置为五位数,并将 email 格式设置为符合 RFC 821。其他键可能需要更复杂的格式设置或专用函数来构建。
查询字符串脱敏实例
经过这些额外的配置,查询字符串脱敏实例如下所示。
$ curl "http://localhost/index.php?foo=bar&zip=90210&email=liam@nginx.com" 127.0.0.1 -> 8.163.209.30 $ sudo tail --lines=1 /var/log/nginx/access*.log ==> /var/log/nginx/access.log <== 127.0.0.1 - - [16/Mar/2017:20:05:55 +0000] "GET /index.php?foo=bar&zip=90210&email=liam@nginx.com HTTP/1.1" 200 26 "-" "curl/7.47.0" ==> /var/log/nginx/access_masked.log <== 8.163.209.30 - - [16/Mar/2017:20:05:55 +0000] "GET /index.php?foo=bar&zip=38643&email=2852675791@example.com HTTP/1.1" 200 26 "-" "curl/7.47.0"
总结
带有 NGINX JavaScript 模块的 NGINX 和 NGINX Plus 提供了简单而强大的解决方案来将自定义逻辑应用于请求处理。在本文中,我们演示了如何使用 NGINX JavaScript 脱敏个人数据,这样得到的日志文件便可在不违反数据保护要求的情况下进行离线分析。
我们希望了解您正如何使用 NGINX JavaScript 来解决业务问题;欢迎大家在下方评论区或者 nginx.org.cn 进行讨论分享。
为 NGINX 和 NGINX Plus 启用 NGINX JavaScript
-
为 NGINX Plus 加载 NGINX JavaScript 模块
-
为 NGINX 开源版加载 NGINX JavaScript 模块
-
将 NGINX JavaScript 编译为 NGINX 开源版的动态模块
为 NGINX Plus 加载 NGINX JavaScript 模块
NGINX JavaScript 可作为免费动态模块供 NGINX Plus 用户使用。
为 NGINX 开源版加载 NGINX JavaScript 模块
NGINX JavaScript 模块默认包含在 NGINX Docker 官方镜像中。如果您的系统配置为使用 面向 NGINX 开源版的官方预构建包,并且您安装的版本是 1.9.11 或更高版本,则可以按预构建包的形式安装 NGINX JavaScript。
1.安装预构建包。
-
Ubuntu 和 Debian 系统:
$ sudo apt-get install nginx-module-njs
-
RedHat、CentOS 和 Oracle Linux 系统:
$ sudo yum install nginx-module-njs
2.在 nginx.conf 配置文件的顶层(“main”)上下文(而非 http 或 stream 上下文)中添加一个 load_module 指令,以启用该模块。本例面向 HTTP 和 TCP/UDP 流量加载 JavaScript 模块。
load_module modules/ngx_http_js_module.so; load_module modules/ngx_stream_js_module.so;
3.重新加载 NGINX,以便将 NGINX JavaScript 模块加载到运行实例中。
$ sudo nginx -s reload
将 NGINX JavaScript 编译为 NGINX 开源版的动态模块
如果您更喜欢从源码编译 NGINX 模块:
1. 按照说明从开源仓库构建 HTTP 和/或 TCP/UDP NGINX JavaScript 模块。
2. 将模块二进制文件(ngx_http_js_module.so、ngx_stream_js_module.so)复制到 NGINX 根目录的 modules 子目录(通常为 /etc/nginx/modules)。
3. 执行“为 NGINX 开源版加载 NGINX JavaScript 模块”的第 2 步和第 3 步。
NGINX 唯一中文官方社区 ,尽在 nginx.org.cn

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
MySQL 如何赶上 PostgreSQL 的势头?
> 原文地址 我与 MySQL 社区的前辈交谈时,经常遇到这个问题:「为什么 MySQL 这么棒,而且(至少根据 DB-Engines 的计算)仍然比 PostgreSQL 更流行;但它的地位在下降,PostgreSQL 却势不可挡地越来越受欢迎?」MySQL 生态能做些什么来挽回局势吗?我们来探讨一下! 图片 来看看为什么 PostgreSQL 发展如此强劲,而 MySQL 却在走下坡路。我认为这归结于所有权和治理、许可证、社区、架构和开源产品的发展势头。 所有权和治理 MySQL 从未像 PostgreSQL 那样受到「社区驱动」。然而,当 MySQL 由瑞典小公司 MySQL AB 和 BDFL(仁慈的终身独裁者 -- Benevolent Dictator for Life,一个非正式称号,常用于幽默地称呼开源开发社区项目的发起人)Michael「Monty」Widenious 掌管时,它获得了很多社区的信任。更重要的是,它没有被大公司视为特别的威胁。 现在,情况不同了 -- 甲骨文拥有 MySQL,业内许多大公司,尤其云计算厂商,都将其视为对手。当然没有理由为对手贡献代...
- 下一篇
阿里云 AI 搜索方案解读:大模型驱动下的智能搜索,助力企业数字化转型
在过去的一年里面,随着大模型的技术突飞猛进,大模型的能力日益增强。这些都驱动着我们的搜索技术快速的演进到了下一代,也就是 AI 搜索的技术。大模型的快速发展不仅重塑了搜索技术的基础,也为各行各业的数字化转型提供了强有力的支持。 一、AI 搜索技术的特点 AI 搜索技术具有以下几个显著的特点: 重构:一个是 AI 搜索技术方面的重构。AI 搜索技术目前正在基于大模型进行全面重构,通过大模型,搜索的全链路能力得到了重组,包括文本解析、切片和向量化等能力的重新定义。另一个是信息获取的方式与产品的形态的重构。传统搜索依赖关键词匹配,而现在更多采用自然语言的问答式交互,这带来了新的业务场景,如虚拟数字人、企业知识库问答和电商平台的智能客服。 AI 基建:AI 搜索技术已成为 AI native 应用的重要组成部分,包括搜索向量检索、语义搜索和检索生成技术,构成了众多 AI 应用的基础设施。这不仅提高了数据处理的效率,还提升了用户与系统的交互体验,帮助企业实现更高效的信息管理和服务。 效果提升:目前,效果的关注度在学术界和工业界都达到了空前的高度。大模型的加持使得搜索效果相比传统搜索有了质的飞跃...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- MySQL8.0.19开启GTID主从同步CentOS8