Secure Link 모듈 사용하여 NGINX URL 보안 구현

NGINX Open Source 소프트웨어와 NGINX Plus는 웹 서버, 리버스 프록시 및 콘텐츠 캐시로서 매우 안전하고 안정적입니다. 승인되지 않은 클라이언트의 액세스에 대한 추가 보호를 위해 Secure Link 모듈 의 지시문을 사용하여 클라이언트가 요청하는 자산의 URL에 특정 Hash 문자열을 포함하도록 요구할 수 있습니다.

이 포스트에서는 Secure Link 모듈 에 구현된 두 가지 방법을 구성하는 방법에 대해 설명합니다. 샘플 구성 스니펫은 HTML 및 미디어 재생 목록 파일을 보호하지만 모든 유형의 URL에 적용할 수 있습니다. 이 방법은 NGINX 와 NGINX Plus 모두에 적용되지만 간결함을 위해 이 포스트에서는 NGINX Plus만 언급하겠습니다.

목차

1. Secure Link 모듈 개요
2. 기본 보안 URL 사용
2-1. 기본 보안 URL에 대한 클라이언트 Hash 생성
2-2. 기본 보안 URL에 대한 서버 응답
3. 만료되는 보안 URL 사용
3-1. 클라이언트에서 해시 및 만료 시간 생성
3-2. 프로그래밍 방식으로 해시 생성
3-3. 만료 시간이 있는 보안 URL에 대한 서버 응답
4. Secure Link 모듈 요약

Secure Link 모듈 은 HTTP 요청의 URL에 있는 인코딩된 문자열과 해당 요청에 대해 계산한 문자열을 비교하여 요청된 리소스의 유효성을 검증합니다. 링크가 제한된 수명을 가지고 있고 시간이 만료된 경우, 해당 링크는 오래된 것으로 간주합니다. 이러한 검사의 상태는 $secure_link 변수에 캡처되어 처리 흐름을 제어하는 데 사용됩니다.

언급한 것처럼, 이 모듈은 두 가지 방법을 제공합니다. 주어진 http, server 또는 location 맥락에서는 그들 중 하나만 구성할 수 있습니다.

  • 첫 번째이자 더 간단한 방법은 secure_link_secret 디렉티브를 사용하여 활성화할 수 있습니다. 인코딩된 문자열은 URL의 마지막 부분과 NGINX Plus 구성에서 정의된 비밀어(secret word)를 연결한 두 텍스트 문자열에 대한 MD5 해시입니다. (첫 번째 텍스트 문자열에 대한 자세한 내용은 기본 보안 URL 사용을 참조하십시오.)
    보호된 리소스에 액세스하려면 클라이언트는 슬래시가 없는 임의의 문자열인 URL 접두사 바로 뒤에 해시를 포함해야 합니다. 이 샘플 URL에서 접두사는 비디오이고 보호된 리소스는 bunny.m3u8 파일입니다.

    /videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8

    이 방법의 한 가지 사용 사례는 사용자가 공유를 위해 이미지나 문서를 서버에 업로드했지만 공식 링크가 게시될 때까지 파일 이름을 하는 사람이 액세스하는 것을 방지하려는 경우입니다.
  • 보다 유연한 두 번째 방법은 secure_link 및 secure_link_md5 지시문에 의해 활성화됩니다. 여기서 인코딩된 문자열은 NGINX Plus 구성 파일에 정의된 변수의 MD5 해시입니다. 가장 일반적으로 $remote_addr 변수는 특정 클라이언트 IP 주소에 대한 액세스를 제한하기 위해 포함되지만, 사용자 에이전트 헤더를 캡처하여 특정 브라우저에 대한 액세스를 제한하는 $http_user_agent와 같은 다른 값을 사용할 수 있습니다.

    선택적으로 해시가 올바른 경우에도 URL이 더 이상 작동하지 않는 만료 날짜를 지정할 수 있습니다.

    클라이언트는 요청 URL에 md5 인수를 추가하여 해시를 지정해야 합니다. 해시된 문자열에 만료 날짜가 포함된 경우 클라이언트는 보호된 pricelist.html을 요청하기 위한 이 샘플 URL에서와 같이 만료 인수를 추가하여 날짜를 지정해야 합니다.

    /files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

Secure Link 모듈 은 nginx.org의 미리 빌드된 NGINX Open Source 바이너리, 운영체제 공급업체에서 제공하는 NGINX 패키지 및 NGINX Plus에 포함되어 있습니다. 소스에서 NGINX를 빌드할 때 기본적으로 포함되지 않습니다. configure 명령에 -with-http_secure_link_module 인수를 포함하여 활성화합니다.

URL을 보호하는 보다 기본적인 방법은 secure_link_secret 지시문을 사용하는 것입니다. 다음 샘플 스니펫에서는 /bunny.m3u8 이라는 HLS(HTTP Live Streaming) 미디어 재생 목록 파일을 보호합니다. /opt/secure/hls 디렉토리에 저장되지만 비디오 접두사로 시작하는 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

해시된 문자열은 슬래시가 없는 임의의 문자열인 접두사 바로 뒤에 옵니다. (여기서는 비디오)

해시는 두 요소를 연결하는 텍스트 문자열에서 계산됩니다.

  • 해시 뒤에 오는 URL 부분(여기서는 hls/bunny.m3u8)입니다.
  • secure_link_secret 지시문에 대한 매개변수(여기서는 enigma)입니다.

클라이언트의 요청 URL에 올바른 해시가 없으면 NGINX Plus는 $secure_link 변수를 빈 문자열로 설정합니다. if 테스트가 실패하고 NGINX Plus는 HTTP 응답에 403 Forbidden 상태 코드를 반환합니다.

그렇지 않으면(해시가 정확함을 의미) rewrite 지시문이 URL을 다시 씁니다. 이 예에서는 /secure/hls/bunny.m3u8($secure_linl 변수는 해시 뒤에 오는 URL 부분을 캡처함)입니다. /secure로 시작하는 URL은 두 번째 location 블록에서 처리됩니다. 해당 블록의 root 지시문은 /opt를 요청된 파일의 root 디렉토리로 설정하고 internal 지시문은 블록이 내부적으로 생성된 요청에만 사용되도록 지정합니다.

2-1. 기본 보안 URL에 대한 클라이언트 해시 생성

클라이언트가 URL에 포함해야 하는 16진수 형식의 MD5 해시를 얻으려면 -hex 옵션과 함께 openssl md5 명령을 실행합니다.

# echo -n 'hls/bunny.m3u8enigma' | openssl md5 -hex
(stdin)= 80e2dfecb5f54513ad4e2e6217d36fd4

프로그래밍 방식으로 해시 생성에 대한 설명은 프로그래밍 방식으로 해시 생성을 참조하십시오.

2-2. 기본 보안 URL에 대한 서버 응답

다음 샘플 curl 명령은 서버가 다른 보안 URL에 응답하는 방식을 보여줍니다.

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 | head -n 1
HTTP/1.1 200 OK

MD5 해시가 잘못된 경우 응답은 403 Forbidden 입니다.

# curl -I http://secure-link-demo/videos/2c5e80de986b6fc80dd33e16cf824123/hls/bunny.m3u8 | head -n 1
HTTP/1.1 403 Forbidden

bunny.m3u8의 해시가 다른 파일에 사용되는 경우에도 403 Forbidden을 응답합니다.

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hs/oven.m3u8 | head -n 1
HTTP/1.1 403 Forbidden

3. 만료되는 보안 URL 사용

URL 보안을 위한 보다 유연한 방법은 secure_link 및 secure_link_md5 지시문을 사용합니다. 이 예에서는 이를 사용하여 IP 주소가 192.168.33.14인 클라이언트에서만 2023년 12월 31일까지만 /var/www/files/pricelist.html 파일에 액세스할 수 있도록 허용합니다.

가상 서버 포트 80에서 수신 대기하고 location /files 블록 아래의 모든 보안 HTTP 요청을 처리합니다. 여기서 root 지시문은 /var/www를 요청된 파일의 root 디렉토리로 설정합니다.

secure_link 지시문은 요청 URL에서 인수를 캡처하는 두 개의 변수를 정의합니다. $arg_md5는 md5 인수 값으로 설정되고 $arg_expires는 expires 인수 값으로 설정됩니다.

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에 액세스하려면 192.168.33.14인 클라이언트가 2023년 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 상태코드를 반환합니다.

3-1. 클라이언트에서 해시 및 만료 시간 생성

이제 클라이언트가 md5를 계산하고 URL에 포함할 인수를 만료하는 방법을 살펴보겠습니다.

첫 번째 단계는 해당 값이 $secure_link_expires 변수의 형태로 해시된 표현식에 포함되어 있기 때문에 만료 날짜에 해당하는 Unix 시간을 결정하는 것입니다. Unix 시간 (Epoch(1970-01-01 00:00:00 UTC) 이후의 초 수)을 얻으려면 -d 옵션 및 +%s 형식 지정자와 함께 date 명령을 사용합니다.

이 예에서는 만료 시간을 2023년 12월 31일 일요일 23:59:00 UTC로 설정하므로 명령은 다음과 같습니다.

# date -d "2023-12-31 23:59" +%s
1483228740

클라이언트는 이 값을 요청 URL의 expires=1753228740 인수로 포함합니다.

이제 secure_link_md5 지시문($secure_link_expires$uri$remote_addr enigma)에 의해 정의된 문자열을 세 가지 명령을 통해 실행합니다.

  • -binary 옵션이 있는 openssl md5 명령은 바이너리 형식으로 MD5 해시를 생성합니다.
  • openssl base64 명령은 해시된 값에 Base64 인코딩을 적용합니다.
  • 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 인수로 포함합니다.

3-2. 프로그래밍 방식으로 해시 생성

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/2023 23:59:00').getTime()), '/files/pricelist.html', “192.168.33.14”, "enigma");

3-3. 만료 시간이 있는 보안 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 1
HTTP/1.1 403 Forbidden

md5 인수의 해시 값이 잘못된 경우 응답은 403 Forbidden입니다.

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=qeUNjiY2FTIVMaXUsxG-7w&expires=1483228740' | head -n 1
HTTP/1.1 403 Forbidden

URL이 만료된 경우(expires 인수로 표시된 날짜가 과거인 경우) 응답은 410 Gone입니다.

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=Z2rNva2InyVcRTlhqAkT4Q&expires=1467417540' | head -n 1
HTTP/1.1 410 Gone

3-4. 예시 – 만료 날짜기 있는 세그먼트 파일 보안

다음은 미디어 자산의 재생 목록과 세그먼트 파일을 모두 보호하는 데 사용되는 만료 날짜가 있는 보안 URL의 또 다른 예시입니다.

이전 예시와 한 가지 차이점은 여기서 map 구성 블록을 추가하여 .m3u8 파일과 HLS 세그먼트(.ts 파일)에서 확장자를 제거하며, 파일 이름을 secure_link_md5 지시문에 전달되는 $file_name 변수에 저장합니다. 이는 개별 .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일 평가판을 시작하거나 NGINXSTORE에 연락하여 사용 사례에 대해 논의하십시오.

사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.