폐쇄망의 이름과 시간
한 줄 요약
폐쇄망에도 이름과 시간은 있어야 한다. 사내 DNS 는 상류 없이 사내 영역을 권한으로 답해야 모르는 이름에서 시간 초과를 기다리지 않고, 사내 시간 서버는 상류가 없어도 기준이 되겠다고 선언해야 서버들의 시계가 서로 흩어지지 않는다.
왜 이게 필요했나
사내 미러를 pypi.airgap.internal 로 부르려면 그 이름을 푸는 DNS 가 있어야 한다. /etc/hosts 를 서버마다 고치는 것은 서버가 열 대만 넘어도 틀어진다. 그런데 사내 DNS 를 평소처럼 세우면 모르는 이름을 상류로 넘기고, 폐쇄망의 상류는 답하지 않으니 클라이언트는 질의마다 시간 초과를 기다린다. 시간은 더 조용히 망가진다. 인터넷 시간 서버에 닿지 않는 서버는 제 시계대로 흘러가고, 한 달이면 서버마다 수십 초에서 분 단위로 벌어진다. 그러면 인증서가 "아직 유효하지 않다" 며 거절되고, 여러 서버의 로그를 시각으로 맞춰 볼 수 없게 된다.
상류가 없는 폐쇄망에서 dnsmasq 에 no-resolv 만 두면 모르는 이름에 거절 응답이 나가고, local 로 영역을 선언하면 권한 있는 없음 응답이 나간다. 본문이 이 실습 VM 에서 재 본 결과를 나란히 놓는다.
- no-resolv 만 둔다: REFUSED이름표에 없는 사내 이름도 바깥 이름도 status REFUSED 였다. 넘길 상류가 없으니 여기서는 답하지 않겠다는 뜻이다. 본문은 REFUSED 를 받은 클라이언트가 다음 네임서버를 찾는다고 한다.
- local=/airgap.internal/ 을 더한다: NXDOMAIN같은 사내 이름이 권한 있는 없음 응답으로 바뀐다. NXDOMAIN 을 받은 클라이언트는 거기서 멈춘다. 사내 영역만큼은 권한 있는 답을 내야 하는 이유이다.
여기서 구분할 것 RFC 1035 가 뒷받침하는 것은 응답 코드 3 이 권한 있는 네임서버가 그 이름이 없다고 알리는 것이고 5 는 정책상의 거절이라는 정의이다. 클라이언트가 REFUSED 에서 다음 네임서버로 넘어가고 NXDOMAIN 에서 멈춘다는 동작과 dnsmasq 옵션의 의미는 본문의 서술이다.
잠깐, 예측해 보세요 클라이언트의 resolv.conf 에 네임서버가 둘이고 첫째가 이 dnsmasq 이다. 이름표에 없는 사내 이름을 물었다. 첫째가 REFUSED 를 주면 일어날 일과 NXDOMAIN 을 주면 일어날 일은 각각 무엇일까?
설명 확인 · 채점 없는 자가 점검
REFUSED 를 받으면 클라이언트가 다음 네임서버를 찾아 질의를 이어 간다. NXDOMAIN 을 받으면 거기서 멈춘다. 본문은 그래서 사내 영역만큼은 상류 없이도 권한 있는 답을 내야 한다고 한다.
어떻게 동작하나
dnsmasq 로 사내 영역. dnsmasq 매뉴얼에 따르면 listen-address 와 bind-interfaces 는 지정한 주소에만 소켓을 연다(우분투의 systemd-resolved 스텁이 127.0.0.53·127.0.0.54 의 53 을 쓰므로 이것이 없으면 부딪힌다). no-resolv 는 resolv.conf 에서 상류를 읽지 않게 하고, local=/airgap.internal/ 은 그 영역의 질의를 바깥으로 넘기지 않고 /etc/hosts·DHCP 로만 답하게 한다(--local 은 --server 의 다른 이름이다). domain= 은 짧은 이름에 붙일 영역을, addn-hosts= 는 /etc/hosts 말고 더 읽을 이름표 파일을 정한다. address=/도메인/주소 는 하위 이름까지 한 주소로 답하는데, 사내 영역 전체를 한 주소로 묶는 것이 아니라면 이름표 파일이 관리하기 쉽다.
재시작 없는 반영. dnsmasq 는 SIGHUP 을 받으면 캐시를 비우고 /etc/hosts 와 addn-hosts 파일을 다시 읽는다. 설정 파일 자체는 다시 읽지 않는다. 그래서 자주 바뀌는 이름표는 설정과 떼어 두는 편이 운영에 편하다.
클라이언트 쪽. ip netns exec 는 /etc/netns/<이름>/resolv.conf 가 있으면 그 네임스페이스 안에서 /etc/resolv.conf 자리에 끼워 준다. 실제 서버라면 /etc/resolv.conf(또는 systemd-resolved 의 DNS 설정)에 사내 DNS 와 search 도메인을 적는 것에 해당한다.
chrony 로 사내 시간. chrony 문서는 기본적으로 아무 클라이언트도 받지 않고 순수한 NTP 클라이언트로만 동작한다고 적는다. allow <대역> 이 있어야 시간을 내준다. local stratum N(1~15, 기본 10)은 상류 동기화가 없어도 자기 시계를 그 층위의 기준으로 내주게 한다. 여러 대가 서로를 기준으로 삼게 하려면 orphan 선택지를 쓴다. 클라이언트에서 시계를 건드리지 않고 확인만 하려면 chronyd -Q 가 차이만 찍고 끝나고(-q 는 한 번 맞추고 끝난다), 서버에서는 chronyc tracking·chronyc clients 로 상태와 받은 질의를 본다.
기준 시계 하나의 한계. local stratum 은 "이 시계가 맞다" 는 선언일 뿐 맞게 만들지는 않는다. 폐쇄망 전체가 한 시계를 따르게 되니 서로 어긋나지는 않지만, 그 시계가 틀리면 모두 함께 틀린다. 현장에서는 GPS·표준시 수신기 같은 하드웨어 기준을 두거나, 반입 절차 때마다 사람이 확인한 시각으로 맞추는 절차를 둔다.
현장에서 만나는 모습
이 실습 VM 에서 재 보았다. no-resolv 만 두고 local= 을 빼면 이름표에 없는 nope.airgap.internal 도, 바깥 이름 ubuntu.com 도 status: REFUSED 였다. 넘길 상류가 없으니 "여기서는 답하지 않겠다" 는 뜻이다. local=/airgap.internal/ 을 넣자 같은 사내 이름이 NXDOMAIN(권한 있는 '없음')으로 바뀌었다. REFUSED 를 받은 클라이언트는 다음 네임서버를 찾고, NXDOMAIN 을 받은 클라이언트는 거기서 멈춘다 — 사내 영역만큼은 권한 있는 답을 내야 하는 이유다. chrony 는 local stratum 8 을 넣고 다시 띄우자 chronyc tracking 이 Reference ID : 7F7F0101 ()·Stratum : 8 을 보였고, systemctl reload dnsmasq 는 유닛에 적힌 대로 kill -HUP $MAINPID 였다.
이 실습의 VM 은 처음부터 바깥의 NTP(UDP 123)에 닿지 않는다 — 공인 80/443 과 DNS 만 열려 있다. 그래서 설치 직후 chrony 는 상류 서버를 하나도 동기화하지 못한 상태로 떠 있다. 폐쇄망 서버에서 흔히 보는 바로 그 모습이다. 여기에 바깥 송신까지 막으면 이름 해석도 끊긴다.
인터넷 시간 서버에 닿지 않는 폐쇄망에서 시계가 어떻게 흩어지고 무엇이 망가지는지를 본문 순서대로 옮겼다.
- 서버마다 제 시계대로 흐른다시계를 맞춰 줄 곳이 없다. 본문은 한 달이면 서버마다 수십 초에서 분 단위로 벌어진다고 하는데, 설명용 예시이다.
- TLS 가 인증서 유효 기간을 지금 시각과 비교한다시계가 틀린 서버는 멀쩡한 인증서를 아직 유효하지 않다거나 만료됐다고 거절한다.
- 로그 시각이 어긋나고 해법은 사내 시간 서버이다여러 서버의 로그를 시각으로 맞춰 볼 수 없게 된다. 사내 chrony 가 allow 로 대역에 시간을 내주고 local stratum 으로 상류 없이도 기준이 되겠다고 선언한다.
여기서 구분할 것 RFC 5280 이 뒷받침하는 것은 인증서의 유효 기간이 notBefore 부터 notAfter 까지의 구간이라는 정의이다. local stratum 은 이 시계가 맞다는 선언일 뿐 맞게 만들지는 않아서, 그 시계가 틀리면 폐쇄망 전체가 함께 틀린다. 시계가 벌어지는 속도는 본문의 설명용 예시이다.
잠깐, 예측해 보세요 서버가 사내 미러의 인증서를 아직 유효하지 않다며 거절했다. 인증서를 새로 발급받아야 할까? 먼저 무엇을 확인해야 할까?
설명 확인 · 채점 없는 자가 점검
먼저 서버의 시계를 확인한다. TLS 는 인증서의 유효 기간을 지금 시각과 비교하므로 시계가 틀린 서버는 멀쩡한 인증서도 아직 유효하지 않다거나 만료됐다고 거절한다. 사내 시간 서버로 시계를 맞춘 뒤에 다시 시험한다.
다음 실습에서 할 것
VM 의 바깥 송신을 막아 진짜 폐쇄망을 만들고, dnsmasq 를 상류 없는 airgap.internal 권한 서버로 세운다. client 네임스페이스가 그 DNS 와 검색 도메인을 쓰게 하고, 재시작 없이 이름을 더한다. chrony 를 사내 시간 서버로 바꿔 client 에서 질의하고, 인계용 점검 스크립트로 끝낸다.
참고 문서: dnsmasq(8) · resolved.conf(5) · ip-netns(8) · chrony.conf(5) · chronyd(8) · chronyc(1)