현재 도메인 이름 시스템(DNS)을 둘러싼 많은 이야기가 있고, 36년 된 프로토콜에 대한 대대적인 변경이 제안되고 있습니다. ARPANET 에서 유래한 인터넷의 이름 서비스는 처음 나온 이후로 이전 버전과의 호환성이 깨지는 일이 없었습니다. 하지만 DNS 전송 메커니즘을 변경하려는 새로운 제안이 그것을 바꿀지도 모릅니다.
이 글에서는 DNS 보안을 위한 두 가지 새로운 기술인 TLS를 통한 DNS(DoT)와 HTTPS를 통한 DNS(DoH)를 살펴보고, NGINX 오픈 소스와 NGINX Plus를 사용하여 이를 구현하는 방법을 보여드리겠습니다.
[ 편집자 - 이 게시물은 NGINX JavaScript 모듈의 사용 사례를 탐색하는 여러 게시물 중 하나입니다. 전체 목록은 NGINX JavaScript 모듈의 사용 사례를 참조하세요 .
이 게시물의 코드는 NGINX Plus R23<.htmla> 이상 에서 사용되지 않는 지시어를 대체하는 지시어를 사용하도록 업데이트되었습니다. 자세한 내용은 NGINX JavaScript 모듈 의 참조 문서를 참조하세요 . 예제 구성 섹션은 NGINX 구성 및 JavaScript 파일에 대한 올바른 구문을 보여줍니다.]js_import js_include
DNS의 역사
DNS는 ARPA(Advanced Research Project Agency)의 초기 인터넷을 위한 네이밍 서비스를 만드는 두 번째 시도였으며, 첫 번째는 John Postel이 1979년에 IEN-116 으로 발표한 인터넷 이름 서버 프로토콜이었습니다.. DNS는 계층적으로 설계되었으며 호스트 이름을 영역으로 분산시키고 여러 별도 기관에서 관리할 수 있는 구조를 제공합니다. DNS에 대한 첫 번째 RFC는 1983년에 발표되었으며(RFC 882 및 883 ) 수년에 걸쳐 여러 확장이 있었지만 당시 정의된 표준에 따라 작성된 클라이언트는 오늘날에도 여전히 작동합니다.
그렇다면 지금 왜 프로토콜을 변경해야 할까요? 의도한 대로 작동하는 것은 분명하며, DNS 패킷에 버전 번호를 포함하지 않은 작성자의 자신감을 정당화합니다. 그런 주장을 할 수 있는 다른 프로토콜이 많이 생각나지 않습니다. DNS는 대부분 프로토콜이 일반 텍스트이고 종종 7비트 ASCII 였던 더 순진한 시기에 고안되었지만 , 오늘날의 인터넷은 1980년대의 ARPANET보다 훨씬 더 무서운 곳입니다. 오늘날 대부분의 프로토콜은 암호화 및 검증 목적으로 TLS(전송 계층 보안)를 채택했습니다. DNS 비판론자들은 추가적인 보안 보호가 필요할 때가 훨씬 지났다고 주장합니다.
따라서 DNS가 인터넷에 이름 서비스 프로토콜을 제공하려는 두 번째 시도였던 것과 같은 방식으로 TLS(DoT)를 통한 DNS와 HTTPS(DoH)를 통한 DNS가 DNS 프로토콜을 보안하려는 두 번째 시도로 등장하고 있습니다. 첫 번째 시도는 DNSSEC 이라는 확장이었고 , 대부분의 최상위 도메인(TLD)이 DNSSEC을 사용하지만 DNS 패킷에 포함된 데이터를 암호화하도록 의도된 것은 아닙니다. 데이터가 변조되지 않았음을 확인할 뿐입니다. DoT와 DoH는 DNS를 TLS 터널 내부에 래핑하는 프로토콜 확장이며, 채택되면 36년간의 이전 버전과의 호환성이 종료됩니다.
DoT와 DoH에 대한 자세한 정보
DoT는 대체로 합리적인 확장으로 여겨진다고 생각합니다. 이미 인터넷 할당 번호 기관(IANA)에서 자체 포트 번호(TCP/853)를 할당했으며, 단순히 TCP DNS 패킷을 TLS로 암호화된 터널 내부에 래핑합니다. 많은 프로토콜이 이전에 이를 수행했습니다. HTTPS는 TLS 터널 내부의 HTTP이고 SMTPS, IMAPS, LDAPS는 이러한 프로토콜의 보안 버전입니다. DNS는 항상 UDP(또는 특정 경우 TCP)를 전송 프로토콜로 사용했기 때문에 TLS 래퍼를 추가하는 것은 큰 변화가 아닙니다.
반면 DoH는 조금 더 논란이 많습니다. DoH는 DNS 패킷을 가져와 HTTP GET또는 POST요청으로 래핑한 다음 HTTPS 연결을 통해 HTTP/2 이상을 사용하여 전송합니다. 이는 모든 의도와 목적에서 다른 HTTPS 연결과 똑같은 것으로 보이며 기업이나 서비스 제공자가 어떤 요청이 이루어지고 있는지 볼 수 없습니다. Mozilla와 다른 지지자들은 이 관행이 사용자가 방문하는 사이트를 엿보는 눈으로부터 비공개로 유지하여 사용자의 개인 정보를 증가시킨다고 말합니다.
하지만 이는 정확히 사실이 아닙니다. DoH에 대한 비판가들은 이것이 DNS의 Tor 와는 다르다고 지적합니다. 왜냐하면 브라우저가 결국 DoH를 사용하여 조회한 호스트에 연결을 할 때, 요청은 거의 확실히 TLS 서버 이름 표시 (SNI) 확장을 사용할 것이고, 여기에는 호스트 이름이 포함되고 일반 텍스트로 전송되기 때문입니다. 또한 브라우저가 온라인 인증서 상태 프로토콜(OSCP)을 사용하여 서버의 인증서를 검증하려고 시도하는 경우, 그 프로세스도 일반 텍스트로 수행될 가능성이 큽니다. 따라서 DNS 조회를 모니터링할 수 있는 사람은 누구나 연결의 SNI나 OCSP 검증의 인증서 이름을 읽을 수 있습니다.
많은 사람들에게 DoH의 가장 큰 문제는 브라우저 공급업체가 사용자가 만든 DoH 요청이 기본적으로 전송되는 DNS 서버를 선택한다는 것입니다(예를 들어 미국 Firefox 사용자의 경우 DNS 서버는 Cloudflare 에 속함 ). DNS 서버 운영자는 사용자의 IP 주소와 요청을 하는 사이트의 도메인 이름을 볼 수 있습니다. 그다지 중요하지 않은 것처럼 보일 수 있지만 일리노이 대학의 연구자들은 웹 페이지 요소에 대한 요청의 대상 주소만 가지고 도 어떤 웹사이트를 방문했는지 유추 할 수 있다는 것을 발견했습니다 . 이를 페이지 로드 지문이라고 합니다. 그런 다음 이 정보를 사용하여 "광고를 위해 사용자를 프로파일링하고 타겟팅"할 수 있습니다.
NGINX가 어떻게 도움이 될 수 있나요?
DoT와 DoH는 본질적으로 악하지 않으며, 사용자 개인 정보 보호를 강화하는 사용 사례가 있습니다. 그러나 공개적이고 중앙화된 DoH 서비스는 사용자 개인 정보 보호에 좋지 않다는 의견이 점차 커지고 있으며 , 어떤 경우에도 사용하지 않는 것이 좋습니다.
어떤 경우든, 귀하의 사이트와 앱에 대해 귀하는 자체 DNS 존을 관리할 가능성이 높습니다. 일부는 공개, 일부는 비공개, 일부는 분할된 수평선이 있습니다. 어느 시점에서 귀하는 자체 DoT 또는 DoH 서비스를 실행하고 싶다고 결정할 수 있습니다. 여기서 NGINX가 도움이 될 수 있습니다.
DoT가 제공하는 개인 정보 보호 강화는 DNS 보안에 큰 이점을 제공하지만 현재 DNS 서버가 DoT를 지원하지 않는다면 어떨까요? NGINX는 DoT와 표준 DNS 간의 게이트웨이를 제공하여 도움을 줄 수 있습니다.
아니면 DoT 포트가 차단될 수 있는 경우 DoH의 방화벽 파괴 잠재력을 좋아할 수도 있습니다. 다시 한번 NGINX는 DoH-to-DoT/DNS 게이트웨이를 제공하여 도움을 줄 수 있습니다.
간단한 DoT-DNS 게이트웨이 배포
NGINX Stream(TCP/UDP) 모듈은 SSL 종료를 지원하므로 DoT 서비스를 설정하는 것은 실제로 매우 간단합니다. NGINX 구성의 몇 줄만으로 간단한 DoT 게이트웨이를 만들 수 있습니다.
upstreamDNS 서버용 블록과 serverTLS 종료용 블록이 필요합니다 .
| stream { |
| # DNS upstream pool |
| upstream dns { |
| zone dns 64k; |
| server 8.8.8.8:53; |
| } |
|
|
| # DoT server for decryption |
| server { |
| listen 853 ssl; |
| ssl_certificate /etc/nginx/ssl/certs/doh.local.pem; |
| ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem; |
| proxy_pass dns; |
| } |
| } |
view rawdot_to_dns.conf hosted with ❤ by GitHub
물론 다른 방법으로도 들어올 DNS 요청을 업스트림 DoT 서버로 전달할 수 있습니다. 하지만 대부분의 DNS 트래픽이 UDP이고 NGINX는 DoT와 TCP 기반 DNS와 같은 다른 TCP 서비스 사이에서만 변환할 수 있기 때문에 그다지 유용하지 않습니다.
| stream { |
| # DoT upstream pool |
| upstream dot { |
| zone dot 64k; |
| server 8.8.8.8:853; |
| } |
|
|
| # DNS server for upstream encryption |
| server { |
| listen 53; |
| proxy_ssl on; |
| proxy_pass dot; |
| } |
| } |
view rawdns_to_dot.conf hosted with ❤ by GitHub
간단한 DoH-DNS 게이트웨이
DoT 게이트웨이와 비교했을 때, 간단한 DoH 게이트웨이의 구성은 조금 더 복잡합니다. HTTPS 서비스와 Stream 서비스가 모두 필요하고, JavaScript 코드와 NGINX JavaScript 모듈<.htmla> (njs)을 사용하여 두 프로토콜 간에 변환합니다. 가장 간단한 구성은 다음과 같습니다.
| http { |
| # This is our upstream connection to the njs translation process |
| upstream dohloop { |
| zone dohloop 64k; |
| server 127.0.0.1:8053; |
| } |
|
|
| # This virtual server accepts HTTP/2 over HTTPS |
| server { |
| listen 443 ssl http2; |
| ssl_certificate /etc/nginx/ssl/certs/doh.local.pem; |
| ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem; |
|
|
| # Return 404 for non-DoH requests |
| location / { |
| return 404 "404 Not Found\n"; |
| } |
|
|
| # Here we downgrade the HTTP/2 request to HTTP/1.1 and forward it to |
| # the DoH loop service |
| location /dns-query { |
| proxy_http_version 1.1; |
| proxy_set_header Connection ""; |
| proxy_pass http://dohloop; |
| } |
| } |
| } |
|
|
| stream { |
| # Import the JavaScript file that processes the DoH(?) packets |
| js_import /etc/nginx/njs.d/dns/dns.js; |
|
|
| # DNS upstream pool (can also be DoT) |
| upstream dns { |
| zone dns 64k; |
| server 8.8.8.8:53; |
| } |
|
|
| # DNS over HTTPS (gateway) translation process |
| # Upstream can be either DNS (TCP) or DoT |
| server { |
| listen 127.0.0.1:8053; |
| js_filter dns.filter_doh_request; |
| proxy_pass dns; |
| } |
| } |
view rawdoh_simple.conf hosted with ❤ by GitHub
이 구성은 패킷을 DNS 서비스로 보내는 데 필요한 최소한의 처리를 수행합니다. 이 사용 사례는 업스트림 DNS 서버가 다른 필터링, 로깅 또는 보안 기능을 수행한다고 가정합니다.
이 구성에서 사용된 JavaScript 스크립트( nginx_stream.js )에는 다양한 DNS 라이브러리 모듈 파일이 포함되어 있습니다. dns.js 모듈 내부에서 dns_decode_level변수는 DNS 패킷에서 얼마나 많은 처리가 수행되는지 설정합니다. DNS 패킷을 처리하면 성능이 저하됩니다. 위와 같은 구성을 사용하는 dns_decode_level경우 0.
더욱 발전된 DoH 게이트웨이
NGINX에서는 HTTP에 상당히 능숙하기 때문에 NGINX를 간단한 DoH 게이트웨이로만 사용하는 것은 기회 낭비라고 생각합니다.
여기에서 사용된 JavaScript 코드는 DNS 패킷의 전체 또는 부분 디코딩을 수행하도록 설정할 수 있습니다. 이를 통해 Cache-ControlDNS 응답의 최소 TTL에 따라 설정된 Expires 및 헤더를 사용하여 DoH 쿼리에 대한 HTTP 콘텐츠 캐시를 빌드할 수 있습니다.
추가 연결 최적화와 콘텐츠 캐싱 및 로깅 지원이 포함된 보다 완전한 예는 다음과 같습니다.
| http { |
| include /etc/nginx/mime.types; |
| default_type application/octet-stream; |
|
|
| log_format dns '$remote_addr - $remote_user [$time_local] "$request" ' |
| '[ $msec, $request_time, $upstream_response_time $pipe ] ' |
| '$status $body_bytes_sent "-" "-" "$http_x_forwarded_for" ' |
| '$upstream_http_x_dns_question $upstream_http_x_dns_type ' |
| '$upstream_http_x_dns_result ' |
| '$upstream_http_x_dns_ttl $upstream_http_x_dns_answers ' |
| '$upstream_cache_status'; |
|
|
| access_log /var/log/nginx/doh-access.log dns; |
|
|
| upstream dohloop { |
| zone dohloop 64k; |
| server 127.0.0.1:8053; |
| keepalive_timeout 60s; |
| keepalive_requests 100; |
| keepalive 10; |
| } |
|
|
| proxy_cache_path /var/cache/nginx/doh_cache levels=1:2 keys_zone=doh_cache:10m; |
| |
| server { |
| listen 443 ssl http2; |
| ssl_certificate /etc/nginx/ssl/certs/doh.local.pem; |
| ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem; |
| ssl_session_cache shared:ssl_cache:10m; |
| ssl_session_timeout 10m; |
|
|
| proxy_cache_methods GET POST; |
|
|
| location / { |
| return 404 "404 Not Found\n"; |
| } |
|
|
| location /dns-query { |
| proxy_http_version 1.1; |
| proxy_set_header Connection ""; |
| proxy_cache doh_cache; |
| proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body; |
| proxy_pass http://dohloop; |
| } |
| } |
| } |
|
|
| stream { |
| js_import /etc/nginx/njs.d/dns/dns.js; |
|
|
| # DNS upstream pool |
| upstream dns { |
| zone dns 64k; |
| server 8.8.8.8:53; |
| } |
|
|
| # DNS over TLS upstream pool |
| upstream dot { |
| zone dot 64k; |
| server 8.8.8.8:853; |
| } |
|
|
| # DNS over HTTPS (gateway) service |
| # This time we’ve used a DoT upstream |
| server { |
| listen 127.0.0.1:8053; |
| js_filter dns.filter_doh_request; |
| proxy_ssl on; |
| proxy_pass dot; |
| } |
| } |
|
|
view rawdoh_advanced.conf hosted with ❤ by GitHub
NGINX Plus 기능을 사용한 고급 DNS 필터
NGINX Plus 구독이 있는 경우 위의 예를 활성 상태 검사 및 고가용성과 같은 고급 NGINX Plus 기능과 결합하거나 캐시된 DoH 응답을 관리하기 위해 캐시 제거 API를 사용할 수도 있습니다.
NGINX Plus 키-값 저장소를 사용하여 사용자를 악성 도메인으로부터 보호하는 DNS 필터링 시스템을 빌드할 수도 있습니다. 이 시스템은 해당 도메인에 대한 액세스를 효과적으로 차단하는 DNS 응답을 반환합니다. RESTful NGINX Plus API를 사용하여 키-값 저장소의 내용을 동적으로 관리합니다 .
우리는 악성 도메인을 "차단됨"과 "블랙홀"의 두 가지 범주로 정의합니다. DNS 필터링 시스템은 도메인에 대한 DNS 쿼리를 해당 범주에 따라 다르게 처리합니다.
- 차단된 도메인 의 경우 응답을 반환합니다 NXDOMAIN(도메인이 존재하지 않음을 나타냄)
- 블랙홀 도메인 의 경우 레코드 A요청에 대한 응답으로 0.0.0.0의 단일 DNS 레코드를 반환하거나 (IPv6) 레코드 요청에 대한 응답으로 ::의 A단일 레코드를 반환합니다. 다른 레코드 유형의 경우 답변이 0개인 응답을 반환합니다.AAAAAAAA
DNS 필터가 요청을 받으면 먼저 쿼리된 FQDN과 정확히 일치하는 키를 확인합니다. 키를 찾으면 연관된 값에 지정된 대로 요청을 "스크러빙"(차단 또는 블랙홀)합니다. 정확히 일치하는 것이 없으면 차단된 도메인과 블랙홀된 도메인의 두 목록에서 도메인 이름을 찾고 일치하는 것이 있으면 적절한 방식으로 요청을 스크러빙합니다.
쿼리된 도메인이 악성이 아닌 경우 시스템은 정기적인 처리를 위해 해당 도메인을 Google DNS 서버로 전달합니다.
Key-Value 저장소 설정
우리는 NGINX Plus 키-값 저장소의 두 가지 항목으로 악성으로 간주되는 도메인을 정의합니다.
- 각각 값이 blocked또는 인 정규화된 도메인 이름(FQDN)에 대한 개별 항목blackhole
- blocked_domains및 라는 키를 갖는 두 개의 도메인 목록은 blackholed_domains각각 쉼표로 구분된 값(CSV) 형식의 도메인 목록에 매핑됩니다.
키-값 저장소에 대한 다음 구성은 컨텍스트에 포함됩니다 stream. 지시문은 dns_configkeyval_zone 라는 키-값 저장소에 대한 메모리 블록을 할당합니다 . 첫 번째 지시문은 설정된 모든 일치하는 FQDN 키-값 쌍을 로드하는 반면 두 번째와 세 번째 지시문은 두 개의 도메인 목록을 정의합니다.keyval
| # Key-value store for blocking domains (NGINX Plus only) |
| keyval_zone zone=dns_config:64k state=/etc/nginx/zones/dns_config.zone; |
| keyval $dns_qname $scrub_action zone=dns_config; |
| keyval "blocked_domains" $blocked_domains zone=dns_config; |
| keyval "blackhole_domains" $blackhole_domains zone=dns_config; |
view rawdns_filtering.conf hosted with ❤ by GitHub
그런 다음 NGINX Plus API를 사용하여 명시적으로 스크러빙하려는 FQDN 키에 값 blocked또는를 할당하거나 또는 키 와 연관된 CSV 형식 목록을 수정할 수 있습니다 . 언제든지 정확한 FQDN을 수정 또는 제거하거나 두 목록의 도메인 세트를 수정할 수 있으며 DNS 필터는 변경 사항으로 즉시 업데이트됩니다.blackholeblocked_domainsblackhole_domains
요청을 처리할 업스트림 서버 선택
다음 구성은 아래 DNS 쿼리를 수신하는 서버 구성 에서 정의된 블록 의 지시문 $dns_response으로 채워진 변수를 로드합니다 . 호출된 JavaScript 코드가 요청을 스크러빙해야 한다고 판단하면 변수를 또는 적절하게 설정합니다.js_prereadserverblockedblackhole
map우리는 변수의 값을 변수 $dns_response에 할당하기 위해 지시문을 사용하여 아래의 업스트림 서버 정의$upstream_pool 에서 어떤 업스트림 그룹이 요청을 처리할지 제어합니다. 악성이 아닌 도메인에 대한 쿼리는 기본 Google DNS 서버로 전달되고, 차단되거나 블랙홀된 도메인에 대한 쿼리는 해당 범주에 대한 업스트림 서버에서 처리합니다.
| # The DNS response packet; if we're scrubbing the domain, this gets set |
| js_set $dns_response dns.get_response; |
|
|
| # Set upstream to the Google DNS server if $dns_response is empty, otherwise |
| # to 'blocked' or 'blackhole' |
| map $dns_response $upstream_pool { |
| "blocked" blocked; |
| "blackhole" blackhole; |
| default google; |
| } |
view rawdns_filtering.conf hosted with ❤ by GitHub
업스트림 서버 정의
이 구성은 차단된 도메인, 블랙홀 도메인 및 비악성 도메인에 대한 요청을 각각 처리하는 업스트림 서버의 blocked, blackhole, 및 그룹을 정의합니다.google
| # Upstream pool for blocked requests |
| upstream blocked { |
| zone blocked 64k; |
| server 127.0.0.1:9953; |
| } |
|
|
| # Upstream pool for blackholed requests |
| upstream blackhole { |
| zone blackhole 64k; |
| server 127.0.0.1:9853; |
| } |
|
|
| # Upstream pool for standard (Google) DNS |
| upstream google { |
| zone dns 64k; |
| server 8.8.8.8:53; |
| } |
view rawdns_filtering.conf hosted with ❤ by GitHub
DNS 쿼리를 수신하는 서버 구성
stream여기서 우리는 들어오는 DNS 요청을 수신하는 컨텍스트 에서 서버를 정의합니다 . js_preread지시문은 DNS 패킷을 디코딩하고, 패킷의 필드에서 도메인 이름을 검색하고, 키-값 저장소에서 도메인을 찾는 Javascript 코드를 호출합니다 . NAME도메인 이름이 FQDN 키와 일치하거나 또는 목록에 있는 도메인 중 하나의 영역 내에 있으면 스크러빙됩니다. 그렇지 않으면 확인을 위해 Google DNS 서버로 전송됩니다.blocked_domainsblackhole_domains
| # DNS (TCP) server |
| server { |
| listen 53; |
| js_preread dns.preread_dns_request; |
| proxy_pass $upstream_pool; |
| } |
|
|
| # DNS (UDP) server |
| server { |
| listen 53 udp; |
| js_preread dns.preread_dns_request; |
| proxy_responses 1; |
| proxy_pass $upstream_pool; |
| } |
view rawdns_filtering.conf hosted with ❤ by GitHub
이제 거의 다 됐습니다. server블랙홀 또는 차단된 응답으로 쿼리에 응답하는 최종 블록만 추가하면 됩니다.
| # Server for responding to blocked/blackholed responses |
| server { |
| listen 127.0.0.1:9953; |
| listen 127.0.0.1:9853; |
| listen 127.0.0.1:9953 udp; |
| listen 127.0.0.1:9853 udp; |
| js_preread dns.preread_dns_request; |
| return $dns_response; |
| } |
view rawdns_filtering.conf hosted with ❤ by GitHub
이 블록의 서버는 바로 위에 있는 실제 DNS 서버와 거의 동일하며, 주요 차이점은 이러한 서버가 요청을 업스트림 DNS 서버 그룹으로 전달하는 대신 클라이언트에 응답 패킷을 보낸다는 것입니다. JavaScript 코드는 포트 9953 또는 9853에서 실행될 때를 감지하고 패킷을 차단해야 한다는 것을 나타내는 플래그를 설정하는 대신 $dns_response실제 응답 패킷으로 채웁니다. 그게 전부입니다.
물론, 이 필터링을 DoT 및 DoH 서비스에도 적용할 수 있었지만, 우리는 단순함을 유지하기 위해 표준 DNS를 사용했습니다. DNS 필터링을 DoH 게이트웨이와 병합하는 것은 독자의 연습으로 남겨둡니다.
필터 테스트
이전에 NGINX Plus API를 사용하여 두 개의 FQDN에 대한 항목을 추가하고 두 도메인 목록에 일부 도메인을 추가했습니다.
$ curl -s http://localhost:8080/api/5/stream/keyvals/dns_config | jq{
"www.some.bad.host": "blocked",
"www.some.other.host": "blackhole",
"blocked_domains": "bar.com,baz.com",
"blackhole_domains": "foo.com,nginx.com"
}그래서 www.foo.com (블랙홀된 foo.com 도메인 내의 호스트 ) 의 확인을 요청하면 AIP 주소 0.0.0.0의 레코드를 얻습니다 .
$ dig @localhost www.foo.com
; <<>> DiG 9.11.3-1ubuntu1.9-Ubuntu <<>> @localhost www.foo.com
; (1 server found)
;; global options: +mcd
;; Got answer:
;; ->>HEADER,,- opcode: QUERY, status: NOERROR, id: 58558
;; flags: qr aa rd ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
; www.foo.com. IN A
;; ANSWER SECTION:
www.foo.com. 300 IN A 0.0.0.0
;; Query time: 0 msec
;; SERVER: 172.0.0.1#53(126.0.0.1)
;; WHEN: Mon Dec 2 14:31:35 UTC 2019
;; MSG SIZEW rcvd: 45
코드 접근하기
DOH 및 DOT 파일은 내 GitHub 저장소 에서 사용할 수 있습니다 . (https://github.com/tuxinvader/nginx-dns)
- njs.d/dns 폴더 의 JavaScript 코드
- 예제 폴더 의 NGINX 구성
현재 도메인 이름 시스템(DNS)을 둘러싼 많은 이야기가 있고, 36년 된 프로토콜에 대한 대대적인 변경이 제안되고 있습니다. ARPANET 에서 유래한 인터넷의 이름 서비스는 처음 나온 이후로 이전 버전과의 호환성이 깨지는 일이 없었습니다. 하지만 DNS 전송 메커니즘을 변경하려는 새로운 제안이 그것을 바꿀지도 모릅니다.
이 글에서는 DNS 보안을 위한 두 가지 새로운 기술인 TLS를 통한 DNS(DoT)와 HTTPS를 통한 DNS(DoH)를 살펴보고, NGINX 오픈 소스와 NGINX Plus를 사용하여 이를 구현하는 방법을 보여드리겠습니다.
[ 편집자 - 이 게시물은 NGINX JavaScript 모듈의 사용 사례를 탐색하는 여러 게시물 중 하나입니다. 전체 목록은 NGINX JavaScript 모듈의 사용 사례를 참조하세요 .
이 게시물의 코드는 NGINX Plus R23<.htmla> 이상 에서 사용되지 않는 지시어를 대체하는 지시어를 사용하도록 업데이트되었습니다. 자세한 내용은 NGINX JavaScript 모듈 의 참조 문서를 참조하세요 . 예제 구성 섹션은 NGINX 구성 및 JavaScript 파일에 대한 올바른 구문을 보여줍니다.]js_import js_include
DNS의 역사
DNS는 ARPA(Advanced Research Project Agency)의 초기 인터넷을 위한 네이밍 서비스를 만드는 두 번째 시도였으며, 첫 번째는 John Postel이 1979년에 IEN-116 으로 발표한 인터넷 이름 서버 프로토콜이었습니다.. DNS는 계층적으로 설계되었으며 호스트 이름을 영역으로 분산시키고 여러 별도 기관에서 관리할 수 있는 구조를 제공합니다. DNS에 대한 첫 번째 RFC는 1983년에 발표되었으며(RFC 882 및 883 ) 수년에 걸쳐 여러 확장이 있었지만 당시 정의된 표준에 따라 작성된 클라이언트는 오늘날에도 여전히 작동합니다.
그렇다면 지금 왜 프로토콜을 변경해야 할까요? 의도한 대로 작동하는 것은 분명하며, DNS 패킷에 버전 번호를 포함하지 않은 작성자의 자신감을 정당화합니다. 그런 주장을 할 수 있는 다른 프로토콜이 많이 생각나지 않습니다. DNS는 대부분 프로토콜이 일반 텍스트이고 종종 7비트 ASCII 였던 더 순진한 시기에 고안되었지만 , 오늘날의 인터넷은 1980년대의 ARPANET보다 훨씬 더 무서운 곳입니다. 오늘날 대부분의 프로토콜은 암호화 및 검증 목적으로 TLS(전송 계층 보안)를 채택했습니다. DNS 비판론자들은 추가적인 보안 보호가 필요할 때가 훨씬 지났다고 주장합니다.
따라서 DNS가 인터넷에 이름 서비스 프로토콜을 제공하려는 두 번째 시도였던 것과 같은 방식으로 TLS(DoT)를 통한 DNS와 HTTPS(DoH)를 통한 DNS가 DNS 프로토콜을 보안하려는 두 번째 시도로 등장하고 있습니다. 첫 번째 시도는 DNSSEC 이라는 확장이었고 , 대부분의 최상위 도메인(TLD)이 DNSSEC을 사용하지만 DNS 패킷에 포함된 데이터를 암호화하도록 의도된 것은 아닙니다. 데이터가 변조되지 않았음을 확인할 뿐입니다. DoT와 DoH는 DNS를 TLS 터널 내부에 래핑하는 프로토콜 확장이며, 채택되면 36년간의 이전 버전과의 호환성이 종료됩니다.
DoT와 DoH에 대한 자세한 정보
DoT는 대체로 합리적인 확장으로 여겨진다고 생각합니다. 이미 인터넷 할당 번호 기관(IANA)에서 자체 포트 번호(TCP/853)를 할당했으며, 단순히 TCP DNS 패킷을 TLS로 암호화된 터널 내부에 래핑합니다. 많은 프로토콜이 이전에 이를 수행했습니다. HTTPS는 TLS 터널 내부의 HTTP이고 SMTPS, IMAPS, LDAPS는 이러한 프로토콜의 보안 버전입니다. DNS는 항상 UDP(또는 특정 경우 TCP)를 전송 프로토콜로 사용했기 때문에 TLS 래퍼를 추가하는 것은 큰 변화가 아닙니다.
반면 DoH는 조금 더 논란이 많습니다. DoH는 DNS 패킷을 가져와 HTTP GET또는 POST요청으로 래핑한 다음 HTTPS 연결을 통해 HTTP/2 이상을 사용하여 전송합니다. 이는 모든 의도와 목적에서 다른 HTTPS 연결과 똑같은 것으로 보이며 기업이나 서비스 제공자가 어떤 요청이 이루어지고 있는지 볼 수 없습니다. Mozilla와 다른 지지자들은 이 관행이 사용자가 방문하는 사이트를 엿보는 눈으로부터 비공개로 유지하여 사용자의 개인 정보를 증가시킨다고 말합니다.
하지만 이는 정확히 사실이 아닙니다. DoH에 대한 비판가들은 이것이 DNS의 Tor 와는 다르다고 지적합니다. 왜냐하면 브라우저가 결국 DoH를 사용하여 조회한 호스트에 연결을 할 때, 요청은 거의 확실히 TLS 서버 이름 표시 (SNI) 확장을 사용할 것이고, 여기에는 호스트 이름이 포함되고 일반 텍스트로 전송되기 때문입니다. 또한 브라우저가 온라인 인증서 상태 프로토콜(OSCP)을 사용하여 서버의 인증서를 검증하려고 시도하는 경우, 그 프로세스도 일반 텍스트로 수행될 가능성이 큽니다. 따라서 DNS 조회를 모니터링할 수 있는 사람은 누구나 연결의 SNI나 OCSP 검증의 인증서 이름을 읽을 수 있습니다.
많은 사람들에게 DoH의 가장 큰 문제는 브라우저 공급업체가 사용자가 만든 DoH 요청이 기본적으로 전송되는 DNS 서버를 선택한다는 것입니다(예를 들어 미국 Firefox 사용자의 경우 DNS 서버는 Cloudflare 에 속함 ). DNS 서버 운영자는 사용자의 IP 주소와 요청을 하는 사이트의 도메인 이름을 볼 수 있습니다. 그다지 중요하지 않은 것처럼 보일 수 있지만 일리노이 대학의 연구자들은 웹 페이지 요소에 대한 요청의 대상 주소만 가지고 도 어떤 웹사이트를 방문했는지 유추 할 수 있다는 것을 발견했습니다 . 이를 페이지 로드 지문이라고 합니다. 그런 다음 이 정보를 사용하여 "광고를 위해 사용자를 프로파일링하고 타겟팅"할 수 있습니다.
NGINX가 어떻게 도움이 될 수 있나요?
DoT와 DoH는 본질적으로 악하지 않으며, 사용자 개인 정보 보호를 강화하는 사용 사례가 있습니다. 그러나 공개적이고 중앙화된 DoH 서비스는 사용자 개인 정보 보호에 좋지 않다는 의견이 점차 커지고 있으며 , 어떤 경우에도 사용하지 않는 것이 좋습니다.
어떤 경우든, 귀하의 사이트와 앱에 대해 귀하는 자체 DNS 존을 관리할 가능성이 높습니다. 일부는 공개, 일부는 비공개, 일부는 분할된 수평선이 있습니다. 어느 시점에서 귀하는 자체 DoT 또는 DoH 서비스를 실행하고 싶다고 결정할 수 있습니다. 여기서 NGINX가 도움이 될 수 있습니다.
DoT가 제공하는 개인 정보 보호 강화는 DNS 보안에 큰 이점을 제공하지만 현재 DNS 서버가 DoT를 지원하지 않는다면 어떨까요? NGINX는 DoT와 표준 DNS 간의 게이트웨이를 제공하여 도움을 줄 수 있습니다.
아니면 DoT 포트가 차단될 수 있는 경우 DoH의 방화벽 파괴 잠재력을 좋아할 수도 있습니다. 다시 한번 NGINX는 DoH-to-DoT/DNS 게이트웨이를 제공하여 도움을 줄 수 있습니다.
간단한 DoT-DNS 게이트웨이 배포
NGINX Stream(TCP/UDP) 모듈은 SSL 종료를 지원하므로 DoT 서비스를 설정하는 것은 실제로 매우 간단합니다. NGINX 구성의 몇 줄만으로 간단한 DoT 게이트웨이를 만들 수 있습니다.
upstreamDNS 서버용 블록과 serverTLS 종료용 블록이 필요합니다 .
물론 다른 방법으로도 들어올 DNS 요청을 업스트림 DoT 서버로 전달할 수 있습니다. 하지만 대부분의 DNS 트래픽이 UDP이고 NGINX는 DoT와 TCP 기반 DNS와 같은 다른 TCP 서비스 사이에서만 변환할 수 있기 때문에 그다지 유용하지 않습니다.
간단한 DoH-DNS 게이트웨이
DoT 게이트웨이와 비교했을 때, 간단한 DoH 게이트웨이의 구성은 조금 더 복잡합니다. HTTPS 서비스와 Stream 서비스가 모두 필요하고, JavaScript 코드와 NGINX JavaScript 모듈<.htmla> (njs)을 사용하여 두 프로토콜 간에 변환합니다. 가장 간단한 구성은 다음과 같습니다.
이 구성은 패킷을 DNS 서비스로 보내는 데 필요한 최소한의 처리를 수행합니다. 이 사용 사례는 업스트림 DNS 서버가 다른 필터링, 로깅 또는 보안 기능을 수행한다고 가정합니다.
이 구성에서 사용된 JavaScript 스크립트( nginx_stream.js )에는 다양한 DNS 라이브러리 모듈 파일이 포함되어 있습니다. dns.js 모듈 내부에서 dns_decode_level변수는 DNS 패킷에서 얼마나 많은 처리가 수행되는지 설정합니다. DNS 패킷을 처리하면 성능이 저하됩니다. 위와 같은 구성을 사용하는 dns_decode_level경우 0.
더욱 발전된 DoH 게이트웨이
NGINX에서는 HTTP에 상당히 능숙하기 때문에 NGINX를 간단한 DoH 게이트웨이로만 사용하는 것은 기회 낭비라고 생각합니다.
여기에서 사용된 JavaScript 코드는 DNS 패킷의 전체 또는 부분 디코딩을 수행하도록 설정할 수 있습니다. 이를 통해 Cache-ControlDNS 응답의 최소 TTL에 따라 설정된 Expires 및 헤더를 사용하여 DoH 쿼리에 대한 HTTP 콘텐츠 캐시를 빌드할 수 있습니다.
추가 연결 최적화와 콘텐츠 캐싱 및 로깅 지원이 포함된 보다 완전한 예는 다음과 같습니다.
NGINX Plus 기능을 사용한 고급 DNS 필터
NGINX Plus 구독이 있는 경우 위의 예를 활성 상태 검사 및 고가용성과 같은 고급 NGINX Plus 기능과 결합하거나 캐시된 DoH 응답을 관리하기 위해 캐시 제거 API를 사용할 수도 있습니다.
NGINX Plus 키-값 저장소를 사용하여 사용자를 악성 도메인으로부터 보호하는 DNS 필터링 시스템을 빌드할 수도 있습니다. 이 시스템은 해당 도메인에 대한 액세스를 효과적으로 차단하는 DNS 응답을 반환합니다. RESTful NGINX Plus API를 사용하여 키-값 저장소의 내용을 동적으로 관리합니다 .
우리는 악성 도메인을 "차단됨"과 "블랙홀"의 두 가지 범주로 정의합니다. DNS 필터링 시스템은 도메인에 대한 DNS 쿼리를 해당 범주에 따라 다르게 처리합니다.
DNS 필터가 요청을 받으면 먼저 쿼리된 FQDN과 정확히 일치하는 키를 확인합니다. 키를 찾으면 연관된 값에 지정된 대로 요청을 "스크러빙"(차단 또는 블랙홀)합니다. 정확히 일치하는 것이 없으면 차단된 도메인과 블랙홀된 도메인의 두 목록에서 도메인 이름을 찾고 일치하는 것이 있으면 적절한 방식으로 요청을 스크러빙합니다.
쿼리된 도메인이 악성이 아닌 경우 시스템은 정기적인 처리를 위해 해당 도메인을 Google DNS 서버로 전달합니다.
Key-Value 저장소 설정
우리는 NGINX Plus 키-값 저장소의 두 가지 항목으로 악성으로 간주되는 도메인을 정의합니다.
키-값 저장소에 대한 다음 구성은 컨텍스트에 포함됩니다 stream. 지시문은 dns_configkeyval_zone 라는 키-값 저장소에 대한 메모리 블록을 할당합니다 . 첫 번째 지시문은 설정된 모든 일치하는 FQDN 키-값 쌍을 로드하는 반면 두 번째와 세 번째 지시문은 두 개의 도메인 목록을 정의합니다.keyval
그런 다음 NGINX Plus API를 사용하여 명시적으로 스크러빙하려는 FQDN 키에 값 blocked또는를 할당하거나 또는 키 와 연관된 CSV 형식 목록을 수정할 수 있습니다 . 언제든지 정확한 FQDN을 수정 또는 제거하거나 두 목록의 도메인 세트를 수정할 수 있으며 DNS 필터는 변경 사항으로 즉시 업데이트됩니다.blackholeblocked_domainsblackhole_domains
요청을 처리할 업스트림 서버 선택
다음 구성은 아래 DNS 쿼리를 수신하는 서버 구성 에서 정의된 블록 의 지시문 $dns_response으로 채워진 변수를 로드합니다 . 호출된 JavaScript 코드가 요청을 스크러빙해야 한다고 판단하면 변수를 또는 적절하게 설정합니다.js_prereadserverblockedblackhole
map우리는 변수의 값을 변수 $dns_response에 할당하기 위해 지시문을 사용하여 아래의 업스트림 서버 정의$upstream_pool 에서 어떤 업스트림 그룹이 요청을 처리할지 제어합니다. 악성이 아닌 도메인에 대한 쿼리는 기본 Google DNS 서버로 전달되고, 차단되거나 블랙홀된 도메인에 대한 쿼리는 해당 범주에 대한 업스트림 서버에서 처리합니다.
업스트림 서버 정의
이 구성은 차단된 도메인, 블랙홀 도메인 및 비악성 도메인에 대한 요청을 각각 처리하는 업스트림 서버의 blocked, blackhole, 및 그룹을 정의합니다.google
DNS 쿼리를 수신하는 서버 구성
stream여기서 우리는 들어오는 DNS 요청을 수신하는 컨텍스트 에서 서버를 정의합니다 . js_preread지시문은 DNS 패킷을 디코딩하고, 패킷의 필드에서 도메인 이름을 검색하고, 키-값 저장소에서 도메인을 찾는 Javascript 코드를 호출합니다 . NAME도메인 이름이 FQDN 키와 일치하거나 또는 목록에 있는 도메인 중 하나의 영역 내에 있으면 스크러빙됩니다. 그렇지 않으면 확인을 위해 Google DNS 서버로 전송됩니다.blocked_domainsblackhole_domains
이제 거의 다 됐습니다. server블랙홀 또는 차단된 응답으로 쿼리에 응답하는 최종 블록만 추가하면 됩니다.
이 블록의 서버는 바로 위에 있는 실제 DNS 서버와 거의 동일하며, 주요 차이점은 이러한 서버가 요청을 업스트림 DNS 서버 그룹으로 전달하는 대신 클라이언트에 응답 패킷을 보낸다는 것입니다. JavaScript 코드는 포트 9953 또는 9853에서 실행될 때를 감지하고 패킷을 차단해야 한다는 것을 나타내는 플래그를 설정하는 대신 $dns_response실제 응답 패킷으로 채웁니다. 그게 전부입니다.
물론, 이 필터링을 DoT 및 DoH 서비스에도 적용할 수 있었지만, 우리는 단순함을 유지하기 위해 표준 DNS를 사용했습니다. DNS 필터링을 DoH 게이트웨이와 병합하는 것은 독자의 연습으로 남겨둡니다.
필터 테스트
이전에 NGINX Plus API를 사용하여 두 개의 FQDN에 대한 항목을 추가하고 두 도메인 목록에 일부 도메인을 추가했습니다.
$ curl -s http://localhost:8080/api/5/stream/keyvals/dns_config | jq{ "www.some.bad.host": "blocked", "www.some.other.host": "blackhole", "blocked_domains": "bar.com,baz.com", "blackhole_domains": "foo.com,nginx.com" }그래서 www.foo.com (블랙홀된 foo.com 도메인 내의 호스트 ) 의 확인을 요청하면 AIP 주소 0.0.0.0의 레코드를 얻습니다 .
$ dig @localhost www.foo.com ; <<>> DiG 9.11.3-1ubuntu1.9-Ubuntu <<>> @localhost www.foo.com ; (1 server found) ;; global options: +mcd ;; Got answer: ;; ->>HEADER,,- opcode: QUERY, status: NOERROR, id: 58558 ;; flags: qr aa rd ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; WARNING: recursion requested but not available ;; QUESTION SECTION: ; www.foo.com. IN A ;; ANSWER SECTION: www.foo.com. 300 IN A 0.0.0.0 ;; Query time: 0 msec ;; SERVER: 172.0.0.1#53(126.0.0.1) ;; WHEN: Mon Dec 2 14:31:35 UTC 2019 ;; MSG SIZEW rcvd: 45코드 접근하기
DOH 및 DOT 파일은 내 GitHub 저장소 에서 사용할 수 있습니다 . (https://github.com/tuxinvader/nginx-dns)
위 내용과 같이 NGINX Plus 를 활용하여 Demo 가 필요하시면 하단의 전문가에게 상담받기 버튼을 클릭해주세요
전문가에게 상담받기