오픈 소스 NGINX 소프트웨어와 NGINX Plus는 모두 웹 서버, 리버스 프록시 및 콘텐츠 캐시로서 매우 안전하고 안정적입니다. 권한이 없는 클라이언트의 액세스를 추가로 차단하려면 Secure Link 모듈의 지시어를 사용하여 클라이언트가 요청하는 자산의 URL에 특정 해시 문자열을 포함하도록 요구할 수 있습니다.
이 블로그 게시물에서는 Secure Link 모듈에 구현된 두 가지 방식을 구성하는 방법을 설명합니다. 예제 구성 스니펫은 HTML 및 미디어 재생 목록 파일을 보호하지만, 모든 유형의 HTTP URL에 적용할 수 있습니다. 이 방식들은 NGINX와 NGINX Plus 모두에 적용되지만, 간결한 설명을 위해 이 글의 나머지 부분에서는 NGINX Plus로만 지칭하겠습니다.
Secure Link 모듈의 메서드 개요
The Secure Link 모듈은 HTTP 요청 URL의 인코딩된 문자열을 해당 요청에 대해 계산한 문자열과 비교하여 요청된 리소스의 유효성을 검증합니다. 링크에 유효 기간이 설정되어 있고 그 기간이 만료된 경우 해당 링크는 만료된 것으로 간주됩니다. 이러한 검사 결과는 $secure_link 변수에 저장되며 처리 흐름을 제어하는 데 사용됩니다.
앞서 언급했듯이 이 모듈은 두 가지 방식을 제공합니다. 지정된 http, server, 또는 location 컨텍스트에서는 둘 중 하나만 구성할 수 있습니다.
- 첫 번째이자 더 간단한 모드는
secure_link_secret지시문으로 활성화됩니다. 인코딩된 문자열은 URL의 마지막 부분과 NGINX Plus 구성에 정의된 비밀 단어라는 두 텍스트 문자열을 연결하여 계산한 MD5 해시입니다. (첫 번째 텍스트 문자열에 대한 자세한 내용은 기본 보안 URL 사용을 참조하세요.) 보호된 리소스에 액세스하려면 클라이언트가 슬래시가 없는 임의의 문자열인 URL 접두사 바로 뒤에 해시를 포함해야 합니다. 다음 예제 URL에서 접두사는 videos이고 보호된 리소스는 bunny.m3u8 파일입니다: /videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 이 방법은 사용자가 공유할 이미지나 문서를 서버에 업로드하면서도 공식 링크가 게시되기 전까지는 파일 이름을 아는 사람이 해당 파일에 액세스하지 못하도록 하려는 경우에 사용할 수 있습니다. - 두 번째 방법은 더 유연하며
secure_link및secure_link_md5지시문을 통해 활성화됩니다. 여기서 인코딩된 문자열은 NGINX Plus 구성 파일에 정의된 변수의 MD5 해시입니다. 일반적으로 특정 클라이언트 IP 주소로 액세스를 제한하기 위해$remote_addr변수를 포함하지만, 다른 값을 사용할 수도 있습니다. 예를 들어$http_user_agent는User-Agent헤더를 캡처하여 특정 브라우저로 액세스를 제한합니다. 선택적으로 만료 날짜를 지정할 수 있으며, 이 날짜가 지나면 해시가 올바르더라도 URL이 더 이상 작동하지 않습니다. 클라이언트는 해시를 지정하기 위해 요청 URL에 md5 인수를 추가해야 합니다. 해시되는 문자열에 만료 날짜가 포함된 경우 클라이언트는 날짜를 지정하기 위해 expires 인수도 추가해야 합니다. 다음은 보호된 파일 pricelist.html을 요청하는 URL의 예입니다: /files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740
Secure Link 모듈은 사전 빌드된 오픈 소스 nginx.org의 NGINX 바이너리, 운영 체제 공급업체에서 제공하는 NGINX 패키지 및 NGINX Plus에 포함되어 있습니다. 소스에서 NGINX를 빌드할 때는 기본적으로 포함되지 않으므로, --with-http_secure_link_module 인수를 configure 명령에 추가하여 활성화하십시오.
기본 보안 URL 사용
URL을 보호하는 더 기본적인 방법은 secure_link_secret 지시어를 사용하는 것입니다. 다음 예제 코드 조각에서는 /bunny.m3u8이라는 HTTP 라이브 스트리밍(HLS) 미디어 재생 목록 파일을 보호합니다. 이 파일은 /opt/secure/hls 디렉터리에 저장되지만, 클라이언트에는 videos 접두사로 시작하는 URL을 통해 제공됩니다.
server {
listen 80;
server_name secure-link-demo;
location /videos {
secure_link_secret enigma;
if ($secure_link = "") { return 403; }
rewrite ^ /secure/$secure_link;
}
location /secure {
internal;
root /opt;
}
}이 구성에서 파일 /opt/secure/hls/bunny.m3u8에 액세스하려면 클라이언트가 다음 URL을 제시해야 합니다:
/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8
해시 문자열은 슬래시가 없는 임의의 문자열인 접두사 바로 뒤에 옵니다(여기서는 videos입니다).
해시는 두 요소를 연결한 텍스트 문자열을 기반으로 계산됩니다:
- URL에서 해시 뒤에 오는 부분으로, 여기서는 hls/bunny.m3u8입니다.
- The parameter to the
secure_link_secret지시문의 매개변수로, 여기서는enigma입니다.
클라이언트의 요청 URL에 올바른 해시가 없으면 NGINX Plus는 $secure_link 변수를 빈 문자열로 설정합니다. if 테스트가 실패하고 NGINX Plus는 HTTP 응답으로 403 Forbidden 상태 코드를 반환합니다.
그렇지 않은 경우(즉, 해시가 올바른 경우)에는 rewrite 지시문이 URL을 다시 작성합니다. 이 예에서는 /secure/hls/bunny.m3u8로 다시 작성됩니다($secure_link 변수는 URL에서 해시 뒤에 오는 부분을 캡처합니다). /secure로 시작하는 URL은 두 번째 location 블록에서 처리됩니다. 해당 블록의 root 지시문은 요청된 파일의 루트 디렉터리를 /opt로 설정하고, internal 지시문은 이 블록이 내부적으로 생성된 요청에만 사용되도록 지정합니다.
기본 보안 URL을 위한 클라이언트 측 해시 생성
클라이언트가 URL에 포함해야 하는 16진수 형식의 MD5 해시를 얻으려면 openssl md5 명령을 -hex 옵션과 함께 실행합니다:
# echo -n 'hls/bunny.m3u8enigma' | openssl md5 -hex(stdin)= 80e2dfecb5f54513ad4e2e6217d36fd4프로그래밍 방식으로 해시를 생성하는 방법에 대한 설명은 프로그래밍 방식으로 해시 생성을 참조하세요.
기본 보안 URL에 대한 서버 응답
다음 예제 curl 명령은 서버가 다양한 보안 URL에 어떻게 응답하는지 보여 줍니다.
URL에 올바른 MD5 해시가 포함되어 있으면 응답은 200 OK입니다:
# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 | head -n 1HTTP/1.1 200 OKMD5 해시가 올바르지 않으면 응답은 403 Forbidden입니다:
# curl -I http://secure-link-demo/videos/2c5e80de986b6fc80dd33e16cf824123/hls/bunny.m3u8 | head -n 1HTTP/1.1 403 ForbiddenIf the hash for bunny.m3u8의 해시를 다른 파일에 사용하면 응답 역시 403 Forbidden입니다:
# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hs/oven.m3u8 | head -n 1HTTP/1.1 403 Forbidden만료되는 보안 URL 사용
URL을 보호하는 보다 유연한 방법은 secure_link 및 secure_link_md5 지시어를 사용하는 것입니다. 이 예에서는 이러한 지시어를 사용하여 IP 주소가 192.168.33.14인 클라이언트만 /var/www/files/pricelist.html 파일에 액세스할 수 있도록 하며, 액세스는 2016년 12월 31일까지만 허용합니다.
가상 서버는 포트 80에서 수신 대기하며, 보안이 적용된 모든 HTTP 요청을 location /files 블록에서 처리합니다. 이 블록에서 root 지시어는 요청된 파일의 루트 디렉터리를 /var/www로 설정합니다.
The secure_link 지시어는 요청 URL의 인수를 가져오는 두 변수를 정의합니다. $arg_md5에는 md5 인수의 값이 설정되고, $arg_expires에는 expires 인수의 값이 설정됩니다.
The secure_link_md5 지시문은 요청의 MD5 값을 생성하기 위해 해시할 표현식을 정의합니다. URL을 처리하는 동안 이 해시는 $arg_md5 값과 비교됩니다. 여기의 예제 표현식에는 요청에 전달된 만료 시간($secure_link_expires 변수에 저장됨), URL($uri), 클라이언트 IP 주소($remote_addr), 그리고 enigma라는 단어가 포함됩니다.
server {
listen 80;
server_name secure-link-demo;
location /files {
root /var/www;
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr enigma";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
}
}이 구성에서 /var/www/files/pricelist.html에 액세스하려면 IP 주소가 192.168.33.14인 클라이언트가 2016년 12월 31일 토요일 23:59:00(UTC) 이전에 다음 요청 URL을 전송해야 합니다:
/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740
클라이언트가 보낸 URL의 해시($arg_md5 변수에 캡처됨)가 secure_link_md5 지시문에서 계산된 해시와 일치하지 않으면 NGINX Plus는 $secure_link 변수를 빈 문자열로 설정합니다. 그러면 if 테스트가 실패하고 NGINX Plus는 HTTP 응답으로 403 Forbidden 상태 코드를 반환합니다.
해시가 일치하지만 링크가 만료된 경우 NGINX Plus는 $secure_link 변수를 0으로 설정합니다. 이 경우에도 if 테스트가 실패하지만, 이번에는 NGINX Plus가 HTTP 응답으로 410 Gone 상태 코드를 반환합니다.
클라이언트에서 해시 및 만료 시간 생성
이제 클라이언트가 URL에 포함할 md5 및 expires 인수를 계산하는 방법을 살펴보겠습니다.
첫 번째 단계는 만료 날짜에 해당하는 Unix 시간을 확인하는 것입니다. 이 값이 해시 표현식에 $secure_link_expires 변수 형태로 포함되기 때문입니다. Epoch(1970-01-01 00:00:00 UTC) 이후 경과한 시간(초)인 Unix 시간을 구하려면 date 명령에 -d 옵션과 +%s 형식 지정자를 사용합니다.
이 예에서는 만료 시간을 2016년 12월 31일 토요일 23:59:00 UTC로 설정하므로 명령은 다음과 같습니다:
# date -d "2016-12-31 23:59" +%s1483228740클라이언트는 이 값을 요청 URL의 expires=1483228740 인수로 포함합니다.
이제 secure_link_md5 지시어에 정의된 문자열인 $secure_link_expires$uri$remote_addr enigma를 다음 세 가지 명령으로 처리합니다:
- The
opensslmd5명령어에-binary옵션을 사용하면 MD5 해시가 바이너리 형식으로 생성됩니다. - The
opensslbase64명령은 해시 값에 Base64 인코딩을 적용합니다. - The
tr명령은 인코딩된 값에서 더하기 기호(+)를 하이픈(-)으로, 슬래시(/)를 밑줄(_)로 바꾸고 등호(=)를 삭제합니다.
이 예제에서 전체 명령어는 다음과 같습니다:
# echo -n '1483228740/files/pricelist.html192.168.33.14 enigma' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =AUEnXC7T-Tfv9WLsWbf-mw클라이언트는 요청 URL에 이 값을 md5=AUEnXC7T-Tfv9WLsWbf-mw 인수로 포함합니다.
프로그래밍 방식으로 해시 생성하기
NGINX Plus 웹 서버가 애플리케이션 서버의 동적 콘텐츠를 제공하는 경우, NGINX Plus와 애플리케이션 서버 모두 동일한 보안 URL을 사용해야 합니다. URL의 md5 인수에 사용할 해시는 프로그래밍 방식으로 생성할 수 있습니다. 다음 Node.js 함수는 위의 NGINX Plus 구성 코드 조각에 정의된 것과 일치하는 해시를 생성합니다. 이 함수는 만료 시간, URL, 클라이언트 IP 주소 및 비밀 단어를 인수로 받아 Base64로 인코딩된 바이너리 형식의 MD5 해시를 반환합니다.
var crypto = require("crypto");
function generateSecurePathHash(expires, url, client_ip, secret) {
if (!expires || !url || !client_ip || !secret) {
return undefined;
}
var input = expires + url + client_ip + " " + secret;
var binaryHash = crypto.createHash("md5").update(input).digest();
var base64Value = new Buffer(binaryHash).toString('base64');
return base64Value.replace(/=/g, '').replace(/+/g, '-').replace(///g, '_');
}현재 예제의 해시를 계산하려면 다음 인수를 전달합니다:
generateSecurePathHash(new Date('12/31/2016 23:59:00').getTime()), '/files/pricelist.html', “192.168.33.14”, "enigma");만료 시간이 설정된 보안 URL에 대한 서버 응답
다음 샘플 curl 명령은 서버가 보안 URL에 어떻게 응답하는지 보여 줍니다.
IP 주소가 192.168.33.14인 클라이언트가 올바른 MD5 해시와 만료 시간을 포함하면 응답은 200 OK입니다:
# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1
HTTP/1.1 200 OK다른 IP 주소를 사용하는 클라이언트가 동일한 URL을 전송하면 응답은 403 Forbidden입니다:
# curl -I --interface "192.168.33.33" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1HTTP/1.1 403 ForbiddenIf the hash value of the md5 인수의 해시 값이 올바르지 않으면 응답은 403 Forbidden입니다:
# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=qeUNjiY2FTIVMaXUsxG-7w&expires=1483228740' | head -n 1HTTP/1.1 403 ForbiddenURL이 만료된 경우(expires 인수가 나타내는 날짜가 과거인 경우) 응답은 410 Gone입니다:
# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=Z2rNva2InyVcRTlhqAkT4Q&expires=1467417540' | head -n 1HTTP/1.1 410 Gone예시 – 만료 날짜를 사용하여 세그먼트 파일 보호
다음은 만료 날짜가 설정된 보안 URL을 사용하여 미디어 애셋의 재생 목록과 세그먼트 파일을 모두 보호하는 또 다른 예입니다.
앞의 예와 다른 점은 여기에서 map 구성 블록을 추가하여 재생 목록(.m3u8 파일)과 HLS 세그먼트(.ts 파일)의 확장자를 제거한다는 것입니다. 파일 이름은 $file_name 변수에 저장되어 secure_link_md5 지시문에 전달됩니다. 이를 통해 개별 .ts 세그먼트와 재생 목록에 대한 요청을 모두 보호할 수 있습니다.
첫 번째 예제와 또 다른 차이점은 특정 웹 브라우저를 사용하는 클라이언트로 액세스를 제한하기 위해 $http_user_agent 변수(User-Agent 헤더를 캡처함)를 secure_link_md5 지시문에 포함한다는 것입니다. 예를 들어 URL이 Safari에서는 작동하지만 Chrome이나 Firefox에서는 작동하지 않도록 할 수 있습니다.
map $uri $file_name {
default none;
"~*/s/(?<name>.*).m3u8" $name;
"~*/s/(?<name>.*).ts" $name;
}
server {
listen 80;
server_name secure-link-demo;
location /s {
root /opt;
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$file_name$http_user_agent enigma";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
}
}요약
NGINX의 Secure Link 모듈을 사용하면 URL의 특정 부분에 대한 해시와 같은 인코딩된 데이터를 추가하여 무단 파일 접근을 방지할 수 있습니다. 만료 시간을 추가하면 링크의 유효 기간도 제한할 수 있어 보안이 한층 강화됩니다.
NGINX Plus를 사용해 보려면 지금 30일 무료 평가판을 시작하거나 사용 사례에 대해 논의하려면 문의해 주세요.
작성자 소개

관련 블로그 게시물
시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.
F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.
Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.
진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.
NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.
Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.