写点什么

nginScript 系列:使用 nginScript 掩蔽用户隐私数据

  • 2017-06-27
  • 本文字数:4275 字

    阅读完需:约 14 分钟

这是 nginScript 系列文章的第四篇,将介绍如何使用 nginScript 掩蔽用户隐私数据。查看第一篇“ nginScript 简介”,第二篇“使用nginScript 将客户端重定向到新服务器”,查看第三篇“通过TCP 负载均衡和Galera 集群来扩展MySQL ”。

2016 年 10 月,欧洲联盟法院规定将IP 地址归为“个人信息”,于是IP 地址进入了数据保护指令一般性数据保护条例的保护范围。对于很多网站来说,这意味着如果数据离开了欧盟管辖的地区,那么对这些数据进行归档和分析就面临着法规上的挑战。对于进入美国的数据,西欧隐私盾协议提供了一定程度的保护,但仍然面临私权组织政府的法律质疑。

不过,保护日志里的隐私数据并不是EU 独有的问题。对于像 IOS/ICE 27001 这样的安全认证组织,将数据移出安全区域也会破坏认证的合规性。

在这篇文章里,我们介绍一些简单的方案用于处理 NGINX Plus 和 NGINX 的日志文件,这样它们就可以安全地导出到外部,不会暴露所谓的个人识别信息(Personal Identification Information)。

无效的方式

保护数据最简单的办法是在导出数据之前把 IP 地址信息剔除掉。通过标准的 Linux 命令行工具就能做到这点,不过日志分析系统希望日志文件具有标准的格式,缺少 IP 字段会导致无法导入日志。即使日志成功导入,如果分析系统依赖 IP 地址来追踪用户行为,那么分析结果的准确性就会大打折扣。

另外一种可能的办法是,使用随机的假 IP 地址替换原来的值,这样就可以保持完整的日志文件,但是这样仍然会影响日志分析结果的质量,因为 IP 地址是假的。

掩蔽客户端 IP 地址

最好的解决方案是使用一种叫作数据掩蔽的技术对真实的 IP 地址进行转换,既不会暴露用户身份信息,又能够用于分析用户网站活动行为。数据掩蔽算法为给定值生成不可逆的伪随机值,同一个 IP 地址总是能被转换成同样的伪随机值。

你可以在 NGINX 和 NGINX Plus 中使用 nginScript 模块来实现 IP 地址掩蔽。nginScript 是为 NGINX 和 NGINX Plus 专门实现的 JavaScript,专门为服务器端的使用场景而设计。在记录请求消息时,我们通过执行一小段 JavaScript 代码来掩蔽客户端的 IP 地址。

为掩蔽 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 以这种格式生成访问日志,我们在 server 配置块里使用 access_log 指令指定了masked格式。

复制代码
js_include mask_ip_uri.js;
js_set $remote_addr_masked 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_masked\n"; #只用于测试
}
}

因为我们打算使用 nginScript 掩蔽客户端 IP 地址,所以我们使用 js_include 指令指定 JavaScript 代码的位置。js_set 指令指定了一个 JavaScript 函数,这个函数会在计算 $remote_addr_masked 变量时被调用。

我们使用了两个 access_log 指令。第一个使用了默认的日志格式,它生成的访问日志是给管理员使用的,用于日常的运维。第二个指定了masked格式。于是,我们为每个请求生成两个访问日志——一个用于系统管理和运维,一个用于导出。

最后,location 配置块使用 return 指令定义了一个非常简单的返回结果,表示数据掩蔽功能是有效的。在生产环境,这里一般会包含一个 proxy_pass 指令,将请求重定向到后端的服务器上。

用于 IP 地址掩蔽的 nginScript 代码

我们使用了三个简单的 JavaScript 函数来生成掩蔽过的 IP 地址。被依赖的函数要出现在前面,所以我们按照它们的先后顺序逐个说明。

数据掩蔽的本质在于使用一种单向的散列算法来转换客户端 IP 地址。在我们的例子里,我们使用 FNV-1a 散列算法,这种算法紧凑、快速,而且具有很好的分布特征。它返回32 位的整数(与IPv4 地址一样的大小),非常适合用于表示IP 地址。fnv32a 函数实现了FNV-1a 算法。

i2ipv4 函数将 32 位整数转换成 IPv4 格式。它接收由 fnv32a() 生成的散列值,并将其转换成适当的格式。IPv4 地址和 IPv6 地址都用 IP4v 的格式来表示。

最后,我们使用 js_set 指令指定了 maskRemoteAddress() 函数,它接受一个单独的参数 req,req 表示 HTTP 请求对象。req 的 remoteAddress 属性包含了客户端 IP 地址。

复制代码
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));
}

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 地址之外的敏感数据。比如邮件地址、邮政编码,或者其他通过查询字符串传递过来的信息。如果你的应用是通过这种方式传递数据的,那么你可以通过扩展之前的 IP 地址掩蔽方案来处理查询字符串里的敏感数据。

为掩蔽查询字符串配置 NGINX 和 NGINX Plus

与默认的combined格式类似,之前定义的masked日志格式记录了 $request 变量,通过这个变量可以捕捉到三个 request 组件:HTTP 方法、URI(包含了查询字符串)和 HTTP 版本。我们只需要掩蔽查询字符串,但是为了代码的可读性,我们分别使用了三个独立的变量来表示这三个组件:$request_uri_masked 变量表示请求 URI,$request_method 表示 HTTP 方法,$server_protocol 表示 HTTP 版本。

复制代码
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_url_masked 变量。

复制代码
js_include mask_ip_uri.js;
js_set $request_uri_masked 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_masked\n"; # 只用于测试
}
}

掩蔽查询字符串的 nginScript 代码

我们在 logmask.js 文件里增加了 maskRequestURI() 函数。与 maskRemoteAddress() 函数类似,maskRequestURI() 也需要调用 fnv32a() 散列函数,所以它出现在 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") { // 掩蔽邮政编码
// 使用前 5 个数字作为掩蔽值
kvpairs[i] = kvpair[0] + "=" + fnv32a(kvpair[1]).toString().substr(5);
} else if (kvpair[0] == "email") { // Mask email
// 使用散列值作为前缀
kvpairs[i] = kvpair[0] + "=" + fnv32a(kvpair[1]) + "@example.com";
}
}
return req.uri + "?" + kvpairs.join('&'); // 构建掩蔽过的 URI
}
return req.uri; // 没有查询字符串,返回 URI
}

maskRequestURI() 函数遍历查询字符串里的每一个键值对,找出包含敏感数据的键,并掩蔽这些键所对应的值。

根据使用日志的不同情况,掩蔽过的值需要能够反映真实的数据。在上面的例子里,我们将邮政编码掩蔽成 5 个数字,邮件地址被掩蔽成符合 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 和 NGINX Plus 为自定义处理请求提供了一种简单而强大的方案。在这篇文章里,我们演示了如何使用 nginScript 来掩蔽敏感数据,在不违反数据安全合规的情况下,让日志文件可以被导出并用于离线分析。

查看英文原文: Data Masking for User Privacy with nginScript

2017-06-27 17:321502

评论

发布
暂无评论
发现更多内容

TDSQL | 云原生时代的数据库技术革命

腾讯云数据库

tdsql 国产数据库

详细解读MySQL高性能优化

秋水

MySQL性能优化 内容合集 签约计划第二季

使用Git将本地项目添加至Git仓库

Bug终结者

Java 实用工具 gitee git version

Linux之pwd命令

入门小站

Linux

运营商行业软件开发| 内容合集

鲸品堂

通信 运营商 技术专题合集

架构实战营-毕业总结

李焕之

Android C++系列:Linux Socket编程(一)预备知识

轻口味

android 28天写作 12月日更

讨论:低代码集成OA/ERP/MES系统,或将是企业应用生态的重要一环?

优秀

低代码

Linux系统学习攻略《Linux一学就会》:Linux系统启动原理及故障排除

侠盗安全

Linux linux运维 运维工程师 云计算架构师 linux电子书

大数据开发之Spark和Flink的对比(转载)

@零度

大数据 flink spark

java开发之Java ORM 框架推荐

@零度

Java Java ORM ​ObjectiveSQL

MySQL性能优化:EXPLAIN 执行计划与join

秋水

MySQL性能优化 执行计划 内容合集 签约计划第二季

架构训练营毕业设计-电商秒杀系统

李焕之

权威认可!腾讯云TDSQL通过信通院数据库服务商运维运营服务能力评估

腾讯云数据库

tdsql 国产数据库

前端开发MySQL 数据库之数据引擎

@零度

MySQL 前端

云原生应用管理,像管理手机APP一样管理企业应用

北京好雨科技有限公司

PaaS #Kubernetes# 应用管理

大数据基石之Hadoop的读写流程与2.X架构

编程江湖

大数据 hadoop

Redis(二):内存模型及回收算法

IT巅峰技术

Java redis 架构师 分布式缓存 redis分布式

TDSQL | ‘‘微盟式’’SaaS,让商业变得更智慧

腾讯云数据库

tdsql 国产数据库

区块链电子合同应用平台开发,区块链电子合同解决方案

电微13828808271

基于SSM+Shiro+Bootstrap实现用户权限管理系统

Bug终结者

Java bootstrap ssm shiro

在线JSON转GraphQL工具

入门小站

工具

【SpringCloud技术专题】「Gateway网关系列」(2)微服务网关服务的Gateway功能配置指南分析

洛神灬殇

api 网关 SpringCloud Gateway netty 12月日更 服务网关

我还是很怀念谷歌

张老蔫

28天写作

什么是 Java Marker Interface(标记接口)

汪子熙

Java jdk volatile 28天写作 12月日更

再获信通院权威认证,优等生华为云GaussDB数据库凭什么?

华为云开发者联盟

数据库 opengauss 华为云 GaussDB 信通院

智慧园区综合管理平台解决方案,智慧平安社区搭建

电微13828808271

TDSQL在核心交易系统领域的联合解决方案

腾讯云数据库

tdsql 国产数据库

京东白条数据架构进化之路:要在数据的不确定性中探索架构的稳定性

SphereEx

数据库 开源 架构 ShardingSphere 京东白条

如何利用测试自动化,实现 DevOps?

SoFlu软件机器人

Linux之mkdir命令

入门小站

Linux

nginScript系列:使用nginScript掩蔽用户隐私数据_大数据_Nginx_InfoQ精选文章