閉域網の名前と時刻
한국어 원문으로 표시합니다.
한 줄 요약
폐쇄망에도 이름과 시간은 있어야 한다. 사내 DNS 는 상류 없이 사내 영역을 권한으로 답해야 모르는 이름에서 시간 초과를 기다리지 않고, 사내 시간 서버는 상류가 없어도 기준이 되겠다고 선언해야 서버들의 시계가 서로 흩어지지 않는다.
왜 이게 필요했나
사내 미러를 pypi.airgap.internal 로 부르려면 그 이름을 푸는 DNS 가 있어야 한다. /etc/hosts 를 서버마다 고치는 것은 서버가 열 대만 넘어도 틀어진다. 그런데 사내 DNS 를 평소처럼 세우면 모르는 이름을 상류로 넘기고, 폐쇄망의 상류는 답하지 않으니 클라이언트는 질의마다 시간 초과를 기다린다. 시간은 더 조용히 망가진다. 인터넷 시간 서버에 닿지 않는 서버는 제 시계대로 흘러가고, 한 달이면 서버마다 수십 초에서 분 단위로 벌어진다. 그러면 인증서가 "아직 유효하지 않다" 며 거절되고, 여러 서버의 로그를 시각으로 맞춰 볼 수 없게 된다.
어떻게 동작하나
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 는 상류 서버를 하나도 동기화하지 못한 상태로 떠 있다. 폐쇄망 서버에서 흔히 보는 바로 그 모습이다. 여기에 바깥 송신까지 막으면 이름 해석도 끊긴다.
다음 실습에서 할 것
VM 의 바깥 송신을 막아 진짜 폐쇄망을 만들고, dnsmasq 를 상류 없는 airgap.internal 권한 서버로 세운다. client 네임스페이스가 그 DNS 와 검색 도메인을 쓰게 하고, 재시작 없이 이름을 더한다. chrony 를 사내 시간 서버로 바꿔 client 에서 질의하고, 인계용 점검 스크립트로 끝낸다.
참고 문서: dnsmasq(8) · resolved.conf(5) · ip-netns(8) · chrony.conf(5) · chronyd(8) · chronyc(1)