NGINX JavaScript 모듈을 사용해 데이터 마스킹
이 포스트는 원래 nginScript라고 불렸던 NGINX JavaScript 모듈의 사용 사례를 탐색하는 여러 포스트 중 하나입니다. 전체 목록은 NGINX JavaScript 모듈의 사용 사례를 참조하십시오.

Adam Harvey가 디자인한 안면 인식 소프트웨어를 혼동시키는 패브릭 패턴
[ 2016년 10월 유럽 연합 사법 재판소는 IP 주소가 “개인 정보”이므로 데이터 보호 지침 및 일반 데이터 보호 규정(GDPR)에 해당한다고 판결했습니다. 이는 많은 웹사이트 소유자에게 데이터가 EU를 벗어나는 경우 로그 파일을 보관하고 분석하는 데 어려움을 줍니다. 미국으로 이동하는 데이터의 경우 EU‑US Privacy Shield 가 일부 보호를 제공하지만 보호 수준이 적절하지 않다고 생각하는 개인 정보 보호 그룹 및 정부의 법적 문제에 직면합니다.
그러나 로그 파일에서 개인 데이터를 보호하는 것은 EU만의 문제가 아닙니다. ISO/ICE 27001과 같은 보안 인증을 받은 조직의 경우 로그 파일을 생성된 보안 영역 외부로 이동하면(예: 네트워크 운영에서 마케팅으로) 인증의 범위와 규정 준수가 손상될 수 있습니다.
이 포스트에서는 종종 개인 식별 정보(PII)라고 하는 것을 노출하지 않고 안전하게 내보낼 수 있도록 NGINX Plus 및 NGINX 로그 파일을 삭제하는 몇 가지 간단한 솔루션을 설명합니다.
이 포스트은 NGINX Plus R23 이상에서 js_include 지시어를 대체하는 js_import 지시어를 사용하도록 업데이트되었습니다. 자세한 내용은 NGINX JavaScript 모듈에 대한 참조 문서를 참조하세요. 구성 예 섹션은 NGINX 구성 및 JavaScript 파일에 대한 올바른 구문을 보여줍니다.]
목차
1. 간단한 접근 방식은 작동하지 않습니다.
2. 클라이언트 IP 주소 마스킹
3. NGINX 및 NGINX Plus 구성을 위한 IP 주소 마스킹
4. NGINX JavaScript 코드용 IP 주소 마스킹
5. IP 주소 마스킹 실행
6. 쿼리 문자열에서 개인 데이터 마스킹
7. 쿼리 문자열 마스킹을 위한 NGINX 및 NGINX Plus 구성
8. 쿼리 문자열 마스킹을 위한 NGINX JavaScript 코드
9. 쿼리 문자열 마스킹 실행
10. 결론
– NGINX 및 NGINX Plus용 NGINX JavaScript 활성화
1. 간단한 접근 방식은 작동하지 않습니다.
개인 데이터 보호에 대한 가장 간단한 접근 방식은 로그를 내보내기 전에 로그에서 IP 주소를 제거하는 것입니다. 이는 표준 Linux 명령줄 도구로 쉽게 달성할 수 있지만 로그 분석 시스템은 표준 형식의 로그 파일을 예상하고 IP 주소 필드를 생략한 로그를 가져오지 못할 수 있습니다. 로그를 성공적으로 가져오더라도 분석 시스템이 사이트 전체에서 사용자를 추적하기 위해 IP 주소에 의존하는 경우 로그 처리 값이 크게 감소할 수 있습니다.
실제 IP 주소를 가짜 또는 무작위 값으로 대체하는 또 다른 잠재적인 접근 방식은 로그 파일이 완전해 보이지만 각 로그 항목이 임의로 생성된 서로 다른 IP 주소에서 시작된 것처럼 보이기 때문에 로그 분석의 품질이 저하됩니다.
2. 클라이언트 IP 주소 마스킹
가장 효과적인 솔루션은 데이터 마스킹이라는 기술을 사용하여 실제 IP 주소를 최종 사용자를 식별하지 않지만 특정 사용자에 대한 웹 사이트 활동의 상관 관계를 허용하는 주소로 변환하는 것입니다. 데이터 마스킹 알고리즘은 원래 입력 값으로 다시 변환할 수 없도록 항상 지정된 입력 값에 대해 동일한 의사 난수 값을 생성합니다. IP 주소의 모든 발생은 항상 동일한 의사 난수 값으로 변환됩니다.
NGINX JavaScript 모듈을 사용하여 NGINX 및 NGINX Plus에서 IP 주소 마스킹을 구현할 수 있습니다. 이 모듈은 서버 측 사용 사례 및 요청별 처리를 위해 특별히 설계된 NGINX 및 NGINX Plus용 고유한 JavaScript 구현입니다. 이 경우 각 요청이 기록될 때 클라이언트 IP 주소를 마스킹하기 위해 소량의 JavaScript 코드를 실행합니다.
NGINX 및 NGINX Plus에서 NGINX JavaScript 모듈을 활성화하는 방법은 이 문서의 끝 부분에 나와 있습니다.
3. NGINX 및 NGINX Plus 구성을 위한 IP 주소 마스킹
log_format 지시문은 액세스 로그에 표시되는 정보를 제어합니다. NGINX 및 NGINX Plus는 대부분의 로그 처리 도구에서 처리할 수 있는 로그 파일을 생성하는 결합이라는 기본 로그 형식과 함께 제공됩니다.
이 구성의 경우 $remote_addr 변수를 $remote_addr_masked로 바꾸는 첫 번째 필드를 제외하고는 결합된 형식과 동일한 masked 라는 새 로그 형식을 만듭니다. 새 변수는 클라이언트 IP 주소의 마스킹된 버전을 생성하는 JavaScript 코드를 실행하여 평가됩니다.
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)를 작성하도록 하려면 마스킹된 형식을 지정하는 access_log 지시문으로 server 블록을 구성합니다.
js_import mask_ip_uri.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_masked\n"; # For testing only
}
}
NGINX JavaScript를 사용하여 클라이언트 IP 주소를 마스킹할 것이므로 js_import 지시문을 사용하여 JavaScript 코드의 위치를 지정합니다. js_set 지시문은 $remote_addr_masked
변수를 평가할 때 실행될 JavaScript 함수를 지정합니다.
우리는 두 개의 access_log
지시문을 사용합니다. 첫 번째는 기본 로그 형식을 사용하여 관리자가 운영 목적으로 사용할 수 있는 액세스 로그(Access Log)를 생성합니다. 두 번째는 마스킹된 로그 형식을 지정합니다. 이 구성을 사용하여 각 요청에 대해 두 개의 액세스 로그(Access Log)를 작성합니다. 하나는 sysadmins 및 DevOps용이고 다른 하나는 내보내기용입니다.
마지막으로 location 블록은 return 지시문을 사용하여 데이터 마스킹이 작동하고 있음을 보여주는 매우 간단한 응답을 정의합니다. 프로덕션 환경에서는 요청을 백엔드 서버로 보내는 proxy_pass 지시문이 포함될 가능성이 큽니다.
4. NGINX JavaScript 코드용 IP 주소 마스킹
세 가지 간단한 JavaScript 함수를 사용하여 마스킹된 IP 주소를 구성합니다. 종속 함수가 먼저 나타나야 하므로 순서대로 설명하겠습니다.
데이터 마스킹 솔루션의 본질은 단방향 해싱 알고리즘을 사용하여 클라이언트 IP 주소를 변환하는 것입니다. 이 예에서 우리는 FNV‑1a hash algorithm을 사용하고 있습니다. 이 알고리즘은 작고 빠르며 상당히 좋은 배포 특성을 가지고 있습니다. 다른 장점은 양의 정수 32비트(IPv4 주소와 동일한 크기)를 반환하므로 IP 주소로 표시하기가 쉽다는 것입니다. fnv32a
함수는 FNV‑1a algorithm의 JavaScript 구현입니다.
i2ipv4
함수는 32비트 정수를 quad-dotted 표기법의 IPv4 주소로 변환합니다. fnv32a()에서 해시된 값을 가져오고 액세스 로그(Access Log)에서 “올바르게 보이는” 표현을 제공합니다. IPv6 주소와 IPv4 주소는 모두 IPv4 형식으로 표시됩니다.
마지막으로 위의 NGINX 및 NGINX Plus 구성에서 js_set
지시문이 참조하는 maskRemoteAddress
함수가 있습니다. 여기에는 HTTP 요청을 나타내는 JavaScript 객체인 단일 매개변수 req가 있습니다. 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 }
5. IP 주소 마스킹 실행
위의 구성을 사용하면 서버에 간단한 요청을 하고 응답과 그에 따른 액세스 로그(Access Log) 항목을 확인할 수 있습니다.
$ 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"
6. 쿼리(Query) 문자열에서 개인 데이터 마스킹
로그 파일에는 IP 주소 이외의 개인 데이터(Personal Data)도 포함될 수 있습니다. 이메일 주소, 우편 주소 및 기타 식별자는 쿼리 문자열(Query String) 매개 변수로 전달될 수 있으며 결과적으로 요청 URI의 일부로 기록됩니다. 애플리케이션이 이러한 방식으로 개인 데이터를 전달하는 경우 NGINX JavaScript IP 주소 마스킹 솔루션을 확장하여 쿼리 문자열에서 개인 데이터를 삭제할 수 있습니다.
7. 쿼리 문자열 마스킹을 위한 NGINX 및 NGINX Plus 구성
기본 결합 형식과 마찬가지로 IP 주소 마스킹에 대해 위에 정의된 마스킹된 로그 형식은 요청의 세 가지 구성 요소인 HTTP 메서드, URI(쿼리 문자열 포함) 및 HTTP 버전을 캡처하는 $request 변수를 기록합니다. 쿼리 문자열만 마스킹해야 하므로 코드 효율성을 위해 세 가지 구성 요소 각각에 대해 별도의 변수를 사용하고 요청 URI(두 번째 구성 요소)만 $request_uri_masked
변수로 변환하고 표준 변수($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"';
$request_uri_masked
변수를 평가하는 방법을 정의하려면 server 블록에 다른 js_set
지시문이 필요합니다.
js_import mask_ip_uri.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_masked\n"; # For testing only
}
}
8. 쿼리(Query) 문자열 마스킹을 위한 NGINX JavaScript 코드
위에 표시된 mask_ip_uri.js 파일에 maskRequestURI
JavaScript 함수를 추가합니다. maskRemoteAddress()
와 마찬가지로 maskRequestURI()
는 fnv32a 해싱 함수에 의존하므로 파일에서 그 아래에 나타납니다.
function maskRequestURI(req) {
var query_string = req.variables.query_string;
if (query_string.length) { // Proceed if we have query string
var kvpairs = query_string.split('&'); // Convert to array of key=value
for (var i = 0; i < kvpairs.length; i++) { // Iterate through each KV pair
var kvpair = kvpairs[i].split('='); // Split KV pair into new array
if (kvpair[0] == "zip") { // Mask zip
// Use first 5 digits of masked value
kvpairs[i] = kvpair[0] + "=" + fnv32a(kvpair[1]).toString().substr(0,5);
} else if (kvpair[0] == "email") { // Mask email
// Use hash as prefix for a single domain
kvpairs[i] = kvpair[0] + "=" + fnv32a(kvpair[1]) + "@example.com";
}
}
return req.uri + "?" + kvpairs.join('&'); // Construct masked URI
}
return req.uri; // No query string, return URI
}
export default { maskRemoteAddress maskRequestURI }
maskRequestURI
함수는 쿼리(Query) 문자열의 각 키-값(Key-Value) 쌍을 반복하여 개인 데이터를 포함하는 것으로 알려진 특정 키를 찾습니다. 이러한 각 키에 대해 값이 마스킹된 값으로 변환됩니다.
NGINX 및 NGINX Plus 로그 파일에서 수행할 처리 유형에 따라 마스킹된 쿼리 문자열 값이 실제 데이터와 유사해야 할 수 있습니다. 위의 예에서 우리는 RFC 821을 준수하기 위해 zip을 5자리로 형식화하고 이메일을 형식화했습니다. 다른 키는 키를 구성하기 위해 더 정교한 형식화 또는 전용 기능이 필요할 수 있습니다.
9. 쿼리 문자열 마스킹 실행
이러한 구성을 추가하면 쿼리 문자열 마스킹이 작동하는 것을 볼 수 있습니다.
$ 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"
10. 결론
NGINX JavaScript 모듈이 포함된 NGINX 및 NGINX Plus는 사용자 지정 논리를 요청 처리에 적용하기 위한 간단하고 강력한 솔루션을 제공합니다. 이 포스트에서는 데이터 보호 요구 사항을 위반하지 않고 오프라인 분석을 허용하는 방식으로 로그 파일을 작성할 수 있도록 NGINX JavaScript를 사용하여 개인 데이터를 마스킹하는 방법을 시연했습니다.
NGINX JavaScript를 사용하여 비즈니스 문제를 해결하는 방법에 대해 듣고 싶습니다. 아래 의견 섹션에서 알려주세요.
NGINX Plus를 사용해 보려면 무료 30일 평가판을 시작하거나 당사에 연락하여 사용 사례에 대해 논의하십시오.
NGINX 및 NGINX Plus용 NGINX JavaScript 활성화
- 1. NGINX Plus용 NGINX JavaScript 모듈 로드
- 2. NGINX Open Source용 NGINX JavaScript 모듈 로드
- 3. NGINX Open Source용 동적 모듈로 NGINX JavaScript 컴파일
1. NGINX Plus용 NGINX JavaScript 모듈 로드
NGINX JavaScript는 NGINX Plus 구독자를 위한 무료 동적 모듈로 제공됩니다. 로드 지침은 NGINX Plus 관리자 가이드를 참조하세요.
2. NGINX Open Source용 NGINX JavaScript 모듈 로드
NGINX JavaScript 모듈은 기본적으로 공식 NGINX Docker 이미지에 포함되어 있습니다. 시스템이 NGINX Open Source 용 공식 사전 빌드 패키지를 사용하도록 구성되어 있고 설치된 버전이 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 트래픽 모두에 대해 NGINX JavaScript 모듈을 로드합니다.
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;
3. NGINX를 다시 로드하여 NGINX JavaScript 모듈을 실행 중인 인스턴스에 로드합니다.
3. NGINX Open Source용 동적 모듈로 NGINX JavaScript 컴파일
Source에서 NGINX 모듈을 컴파일하려는 경우:
- 다음 지침에 따라 Open Source 리포지토리에서 HTTP 및 TCP/UDP NGINX JavaScript 모듈 중 하나 또는 모두를 빌드합니다.
- 모듈 바이너리(ngx_http_js_module.so, ngx_stream_js_module.so)를 NGINX root의 모듈 하위 디렉토리(일반적으로 /etc/nginx/modules)에 복사합니다.
- NGINX Open Source용 NGINX JavaScript모듈 로드의 2단계와 3단계를 수행합니다.
사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.
댓글을 달려면 로그인해야 합니다.