들어가며
KISA 보도자료를 보고 처음 든 생각은 "내 DNS는 괜찮나"였다. 2026년 10월 12일 오전 1시에 루트 존 암호키가 바뀌고, 준비 안 된 DNS 서버는 48시간 안에 모든 도메인 조회에 실패한다는 내용이었다.
외부 존은 Cloudflare에 올려두고 내부 뷰는 split-horizon DNS를 직접 만들어 쓰고 있으니 남 얘기가 아니었다. 그래서 루트 존에 dig를 직접 때려봤다.
두 가지를 알게 됐다. 하나는 기사에서 말하는 "암호키 교체"가 생각했던 것과 다르다는 것이다. 새 키는 이미 1년 8개월 전부터 루트 존에 들어와 있었다. 10월에 바뀌는 건 키가 아니라 서명자다.
다른 하나는 내 구성이 점검 대상이 아니라는 것이었다. 그런데 안심할 일이 아니었다. 위험이 사라진 게 아니라 내가 안 보던 곳으로 옮겨가 있었다.
루트 KSK가 뭐고 왜 바꾸나
루트 KSK는 DNSSEC 신뢰 사슬의 맨 꼭대기에 있는 유일한 앵커다. 모든 검증이 여기서 출발한다.
DNSSEC은 DNS 응답에 전자서명을 붙여 위변조를 막는 확장이다. .kr은 상위 존인 루트가 서명해주고, hyochan.site는 .site가 서명해준다. 이렇게 위로 타고 올라가면 결국 루트에 닿는데, 루트 위에는 아무도 없다. 그래서 resolver는 루트의 공개키를 자기 디스크에 미리 박아두고 그걸 신뢰의 시작점으로 삼는다. 이게 trust anchor다.
루트 존에는 두 종류의 키가 있다.
| 키 | 역할 | 교체 주기 |
|---|---|---|
| ZSK(Zone Signing Key) | 실제 존 데이터에 서명 | 분기마다 교체 |
| KSK(Key Signing Key) | DNSKEY RRSet에 서명. trust anchor의 실체 | 수년에 한 번 |
ZSK는 자주 바꿔도 문제없다. KSK가 보증해주기 때문이다. 반면 KSK를 바꾸는 건 전 세계 resolver가 디스크에 저장해둔 값을 동시에 갈아치우는 일이다. 그래서 어렵고, 2010년 이후 이번이 겨우 두 번째다.
지금 루트 존에 실제로 들어 있는 것
루트 존에는 이미 KSK가 두 개 다 들어 있다. 바뀌는 건 둘 중 누가 서명하느냐다.
직접 확인해보면 이렇다.
dig +dnssec +noall +answer . DNSKEY @a.root-servers.net
. 172800 IN DNSKEY 256 3 8 AwEAAeCYD6Z7WWKVLeuWgowKP... ; ZSK, key tag 57780
. 172800 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI9... ; KSK, key tag 20326
. 172800 IN DNSKEY 257 3 8 AwEAAa96jeuknZlaeSrvyAJj6... ; KSK, key tag 38696
. 172800 IN RRSIG DNSKEY 8 0 172800 20260920000000 20260830000000 20326 . PvfCYsLIvqwE...
257은 KSK(SEP 플래그), 256은 ZSK다. KSK가 두 개 보인다. 20326이 기존 KSK-2017이고, 38696이 신규 KSK-2024다. 둘 다 2025년 1월부터 나란히 게시되어 있다.
핵심은 마지막 줄이다. RRSIG 레코드 끝에 붙은 20326이 서명자의 key tag다. 지금은 KSK-2017이 서명하고 있다. 10월 11일 16
38696으로 바뀐다. 그게 이번 롤오버의 전부다.
graph TD
TA["resolver 디스크의 trust anchor<br/>20326 + 38696"] -->|검증| KSK["루트 DNSKEY RRSet<br/>ZSK 57780 / KSK 20326 / KSK 38696"]
KSK -->|"RRSIG signer = 20326<br/>→ 10월 12일부터 38696"| ZSK["ZSK 57780"]
ZSK -->|서명| DS["TLD DS 레코드<br/>kr / com / site ..."]
DS --> TLD["하위 존 검증"]
trust anchor에 38696이 없는 resolver는 저 첫 화살표에서 막힌다. DNSKEY RRSet의 서명을 검증할 수 없으니, 그 아래 모든 것이 무너진다.
RFC 5011, 30일을 기다리는 이유
RFC 5011은 resolver가 새 trust anchor를 자동으로 받아들이게 하는 표준이다. 핵심 장치는 30일 대기다.
새 키가 루트 존에 나타났다고 resolver가 바로 신뢰하면, 공격자가 가짜 키를 밀어넣었을 때 방어할 방법이 없다. 그래서 RFC 5011은 add hold-down time을 둔다. 새 키를 30일(또는 최초 DNSKEY RRSet의 원래 TTL 중 더 큰 값) 이상 계속 관측해야 신뢰 목록에 추가한다. 이 기간 동안 최소 두 번 이상 검증에 성공한 DNSKEY RRSet에서 그 키를 봐야 한다.
전체 일정은 이렇게 흘러왔다.
timeline
2024-04-26 : KSK-2024 생성 (ICANN 키 세리머니)
2024-07-18 : IANA trust anchor 파일 게시
2025-01-11 : 루트 존 DNSKEY RRSet에 pre-publish
2025-02-10 : add hold-down 30일 경과, 자동 채택 시작
2026-10-11 : 16:00 UTC 서명 전환 (KST 10-12 01:00)
2027-01-11 : KSK-2017 REVOKE 및 폐기 예정
2025년 1월에 키를 넣고 2026년 10월에야 쓰는 이유가 여기 있다. 30일이면 규격상 충분하지만, 오래 꺼져 있던 장비나 방치된 어플라이언스까지 따라오게 하려면 1년 반이 필요했다.
왜 하필 "48시간"인가
기사마다 나오는 "48시간 내 장애"라는 숫자는 루트 DNSKEY RRSet의 TTL에서 나온다.
위 dig 출력에서 TTL 필드를 보면 172800이다. 초 단위니까 정확히 48시간이다. 이 값이 그대로 장애 지연 시간이 된다.
10월 12일 01시에 서명자가 바뀌어도 resolver는 즉시 죽지 않는다. 캐시에 남아 있는 기존 DNSKEY RRSet과 그 서명으로 한동안 정상 동작한다. 그러다 TTL이 만료되어 루트에 DNSKEY를 다시 물어보는 순간, 38696이 서명한 RRSIG를 받게 되고, trust anchor에 38696이 없으면 검증에 실패한다. 결과는 SERVFAIL이다.
캐시 만료 시점이 resolver마다 제각각이라 장애도 한꺼번에 터지지 않는다. 여기저기서 산발적으로, 이틀에 걸쳐 번진다.
즉시 전면 장애보다 이쪽이 훨씬 골치 아프다.
배포도 안 했고 설정도 안 만졌는데 어제부터 일부 사이트만 안 된다면, 원인을 DNSSEC trust anchor에서 찾기까지 한참 걸린다.
내 resolver 점검하기
점검은 두 단계다. 검증을 하고 있는지 먼저 보고, 한다면 38696이 있는지 확인한다.
1단계 — DNSSEC 검증을 하고 있나
dig @<resolver-ip> dnssec-failed.org A
dnssec-failed.org은 일부러 서명을 깨놓은 테스트 도메인이다.
status: SERVFAIL→ 검증이 동작 중이다. 점검 대상이다.status: NOERROR→ 검증을 안 한다. 이번 롤오버 영향은 없다.
검증을 안 하면 애초에 영향이 없다는 게 이 사건의 아이러니다. 보안을 켜둔 쪽만 위험하다.
2단계 — trust anchor에 38696이 있나
trust anchor 파일에서 key tag를 찾는다. 경로는 배포판과 소프트웨어에 따라 다르다.
# Unbound
grep -c 38696 /var/lib/unbound/root.key
# BIND 9
grep -c 38696 /var/cache/bind/managed-keys.bind
named-checkconf -px 2>/dev/null | grep -A2 trust-anchors
# Unbound
grep -c 38696 /var/lib/unbound/root.key
# BIND 9
grep -c 38696 /var/named/dynamic/managed-keys.bind
# Homebrew Unbound (Apple Silicon)
grep -c 38696 /opt/homebrew/etc/unbound/root.key
# Intel Mac
grep -c 38696 /usr/local/etc/unbound/root.key
grep -c 38696 /var/lib/unbound/root.key
grep -c 38696 /var/cache/bind/managed-keys.bind
경로를 모르겠으면 파일명으로 훑어도 된다.
sudo find / -name 'root.key' -o -name 'root.keys' -o -name '*managed-keys*' 2>/dev/null \
| xargs grep -l 38696 2>/dev/null
출력이 0이거나 아무것도 안 나오면 문제다.
소프트웨어별 주의사항
| 소프트웨어 | trust anchor 파일 | RFC 5011 자동 갱신 |
|---|---|---|
| BIND 9 | bind.keys, managed-keys.bind | 지원 |
| Unbound | root.key | 지원 |
| Knot Resolver | root.keys | 지원 |
| PowerDNS Recursor | root.key | 미지원. 5.2.0 이상 필요 |
PowerDNS Recursor가 이번 건의 최대 뇌관이다. 공식 문서에 이렇게 명시되어 있다.
it has no support for RFC 5011 key rollover and does not persist a changed root trust anchor to disk.
즉 새 키를 스스로 학습하지도, 학습한 걸 디스크에 남기지도 않는다. 내장 trust anchor에 의존하므로 바이너리를 올리는 것 외에 방법이 없다. 5.2.0-beta1 릴리스 노트에 "2026년 10월부터 루트 존 서명에 쓰일 새 키의 trust anchor를 추가했다"고 적혀 있으니, 5.2.0 이상인지 버전부터 확인해야 한다. KISA가 국내 운영기관에 이 소프트웨어만 콕 집어 경고한 이유다.
자동 갱신을 지원하는 BIND나 Unbound도 안심할 수는 없다. 컨테이너를 매번 새 이미지로 띄우면서 trust anchor 파일을 볼륨에 유지하지 않는 구성이면, 이미지에 박힌 오래된 키로 매번 리셋된다. 30일 hold-down을 채울 기회 자체가 없다.
포워더를 쓰면 위험이 상류로 옮겨간다
포워더는 스스로 검증하지 않는다. 그래서 점검 대상이 내 서버가 아니라 그 뒤에 있는 상류 resolver가 된다.
내 구성이 딱 이 경우다. 외부 존은 Cloudflare가 맡고, 내부 뷰는 직접 만든 split-horizon 컨트롤 플레인(parallax)이 답한다. 자기가 들고 있는 존에는 authoritative로 응답하고, 나머지는 허용된 클라이언트에 한해 상류로 릴레이하는 구조다.
flowchart LR
C(["LAN 클라이언트"]) --> P["split-horizon 리스너<br/>authoritative + forwarder"]
P -->|"보유 존<br/>서명 없음"| Z["10.0.0.11 즉시 응답"]
P -->|"그 외 전부<br/>바이트 그대로 릴레이"| U["상류 resolver<br/>← DNSSEC 검증은 여기서"]
U --> R(["루트 / TLD / 권위 서버"])
style U stroke-width:3px
핵심은 릴레이가 불투명하다는 점이다. 질의 바이트를 그대로 상류에 보내고 돌아온 응답 바이트를 그대로 클라이언트에 돌려준다. 코드에도 그렇게 적어뒀다.
A relayed answer is the upstream's bytes, cookie and all.
trust anchor를 들고 있지 않고 RRSIG를 검증하지도 않는다. 실제로 DNS 경로에서 verify가 나오는 곳은 TSIG(RFC 8945)와 질의 상관관계 확인뿐이다. 결론은 두 가지다.
- 이 서버의 trust anchor에서 38696을 찾을 일은 없다. 애초에 trust anchor가 없다.
- 대신
forwardTo에 적힌 상류가 검증 resolver라면, 그쪽이 죽었을 때 내 서버가 죽은 것처럼 보인다.
두 번째가 함정이다. 상류가 KSK-2024 없이 SERVFAIL을 뱉기 시작하면 포워더는 그 SERVFAIL을 성실하게 릴레이한다. LAN에서 보면 "내부 DNS가 고장났다"로 보이는데, 내 코드에는 아무 잘못이 없다.
상류 장애를 구분하는 법
상류가 "안 닿는 것"과 "닿는데 검증에 실패하는 것"은 완전히 다른 신호로 나타난다. 이걸 섞으면 10월 12일에 엉뚱한 데를 판다.
| 구분 | 상류 도달 불가 | 상류 검증 실패(KSK 미반영) |
|---|---|---|
| 포워딩 실패 카운터 | 증가 | 증가하지 않음 |
| 포워딩 지연 지표 | outcome="failed" | outcome="answered" |
| 응답 코드 | SERVFAIL(내가 만든 것) | SERVFAIL(상류가 준 것) |
검증 실패는 전송 계층에서 보면 완벽한 성공이다. 상류는 평소와 같은 지연 안에 형식상 멀쩡한 DNS 응답을 돌려줬다. 그 안에 담긴 rcode가 SERVFAIL일 뿐이다. 내 경우 지표로는 이렇게 갈린다.
# 상류가 안 닿을 때
parallax_dns_forward_failures_total{reason="resolve|budget"} ← 증가
# 상류가 검증에 실패할 때
parallax_dns_forward_seconds{outcome="answered"} ← 정상
parallax_dns_answers_total{rcode="SERVFAIL"} ← 급증
포워딩 지연 지표는 멀쩡한데 SERVFAIL만 치솟는 조합, 이게 상류 DNSSEC 장애의 지문이다.
확진용 한 줄
소프트웨어와 무관하게 통하는 방법은 CD(Checking Disabled) 비트다. 같은 질의를 +cd를 붙여 한 번 더 던진다.
dnssec-failed.org으로 지금 재현해보면 이렇다. 일부러 서명을 깨놓은 도메인이라, 10월 12일에 trust anchor가 어긋난 상류가 보여줄 모습과 정확히 같은 응답이 나온다.
$ dig @8.8.8.8 dnssec-failed.org A
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 10020
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
$ dig @8.8.8.8 dnssec-failed.org A +cd
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45742
;; flags: qr rd ra cd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
dnssec-failed.org. 243 IN A 96.99.227.255
+cd는 상류에게 "검증을 건너뛰고 데이터만 달라"고 지시한다. 검증을 껐더니 답이 나왔다는 건 네트워크도 존도 멀쩡하다는 뜻이다. 원인은 DNSSEC 검증 실패로 확정된다. 그때 상류의 trust anchor에서 38696을 찾으면 된다.
그래서 내 결론
내 forwardTo는 1.1.1.1과 8.8.8.8이다. 둘 다 검증을 하고 있고, 둘 다 내가 손댈 수 없는 남의 인프라다. 확인해보니 이렇다.
| 상류 | AD 플래그 | 깨진 서명 | +cd 재질의 |
|---|---|---|---|
| 1.1.1.1 (Cloudflare) | ad 있음 | SERVFAIL | NOERROR |
| 8.8.8.8 (Google) | ad 있음 | SERVFAIL | NOERROR |
정상 동작이다. 대형 공용 DNS는 롤오버 준비를 가장 먼저 끝내는 축이라 여기서 할 일은 없다.
Cloudflare에 올려둔 외부 존도 무관하다. 권위 서버는 자기 존에 서명할 뿐이고 루트 KSK는 그보다 위에 있는 별개 계층이다. 레지스트라에 걸어둔 DS 레코드도 내 존과 TLD 사이의 연결이라 루트 키 교체와 상관없다.
결국 내가 10월 12일에 해야 할 일은 없었다. 그런데 그 결론에 도달하는 경로가 "우리 회사는 Cloudflare 쓰니까 괜찮겠지"와는 전혀 다르다. 점검 대상은 도메인을 어디에 올렸느냐가 아니라, 질의가 최종적으로 어떤 검증 resolver를 거치느냐다. 저 상류가 사내에 있는 Unbound였다면 결론은 정반대였을 것이다.
95%인가 20%인가, 측정이 엇갈린다
준비 상태를 재는 방법이 두 가지인데 결과가 심하게 다르다. 이게 이번 롤오버에서 가장 불편한 대목이다.
| 측정 방식 | RFC | 결과 |
|---|---|---|
| TA Signalling | RFC 8145 | 2025년 3월 91.3% → 이후 사실상 100%에 근접 |
| TA Sentinel | RFC 8509 | 2026년 4월 기준, 검증 resolver 뒤 사용자의 20% 미만 |
RFC 8145는 resolver가 루트 서버에 "나는 이런 키들을 신뢰한다"고 자발적으로 신고하는 방식이다. Verisign의 관측이 여기 기반이고, KISA가 인용한 "세계 95%" 역시 같은 계열의 수치다. 문제는 신고하지 않는 resolver는 애초에 통계에 안 잡힌다는 점이다.
RFC 8509는 반대로 사용자 쪽에서 특수 도메인을 조회해 실제 검증 결과를 역추적한다. Geoff Huston이 이 방식으로 재보니 20%도 안 나왔다. 그는 원인을 SERVFAIL의 성질에서 찾는다. resolver는 SERVFAIL을 받으면 포기하지 않고 다른 경로로 "더 나은" 응답을 찾아 나선다. 그래서 실패가 실패로 안 보이고, 측정이 왜곡된다.
두 수치 중 무엇이 맞는지는 10월까지도 확정되지 않을 가능성이 높다. 낙관론의 근거가 생각보다 얇다는 것만은 분명하다. "세계 95%가 준비됐다"는 문장에 내 서버를 얹지 않는 게 맞다.
2018년에는 무슨 일이 있었나
첫 번째 롤오버는 예정보다 1년 늦게, 그러나 큰 사고 없이 끝났다. 진짜 문제는 석 달 뒤에 터졌다.
원래 2017년 10월 11일 예정이었다. 막판 데이터에서 ISP와 네트워크 사업자의 resolver 상당수가 준비되지 않은 것으로 나타나 연기됐다. 1년을 더 기다려 2018년 10월 11일에 KSK-2010에서 KSK-2017로 교체했고, 대형 장애는 없었다. ICANN은 "압도적 성공"으로 평가했다.
그런데 2019년 1월, 낡은 KSK-2010을 REVOKE하자 예상 못 한 일이 벌어졌다.
- 2018년 10월 직후: 루트 DNSKEY 쿼리가 일 1,500만 건에서 7,500만 건으로 5배 증가
- 2019년 1월 REVOKE 시점: 일 11.5억 건. 롤오버 이전 대비 75배
- 이 시점 루트 서버 전체 트랜잭션의 약 **7%**가 DNSKEY 쿼리
- 존에서 revoked 키를 제거하자 즉시 정상화 (2019년 3월경)
원인은 존에 남아 있는 revoked 키가 일부 검증 resolver의 이상 동작을 유발한 것이었다. 롤오버 자체보다 폐기 단계가 위험했던 셈이다.
이번에도 KSK-2017 REVOKE는 2027년 1월 11일 예정이다. 10월 12일을 무사히 넘겼다고 끝이 아니다.
이건 알고리즘 롤오버가 아니다
이번 교체는 키 값만 바꾸는 것이지 암호 알고리즘 변경이 아니다. 국내 기사들이 자주 뭉뚱그리는 지점이라 짚고 간다.
KSK-2017과 KSK-2024 모두 알고리즘 8번(RSASHA256), RSA-2048이다. 위 dig 출력에서 DNSKEY 257 3 8의 마지막 8이 알고리즘 번호다. 두 키가 동일하다.
알고리즘 전환은 별개 안건으로 진행 중이다.
- ICANN이 2026년 2월 3일 public comment를 열고 5월 4일 요약 보고서를 발행했다
- 내용은 루트 KSK를 RSASHA256(8번)에서 ECDSA P-256 with SHA-256(13번)으로 전환하는 것이다
- 2027년 새 ECDSA KSK 생성, 2029년 RSA KSK 폐기 일정이다
- 근거는 2025년 말 발행된 RFC 9904다. 알고리즘 요구사항의 정본을 RFC 8624에서 IANA 레지스트리로 옮겼고, 그 레지스트리에서 13번은 서명·검증 모두
MUST다 - SSAC도 SAC133에서 전환을 지지했다
동기는 응답 크기다. ECDSA 서명은 RSA-2048보다 훨씬 작다. Huston이 지적하듯 DNS 응답은 UDP 프래그먼트 임계인 1,210바이트를 넘기지 않는 게 안전한데, RSA 키를 두 개씩 물고 있는 지금의 DNSKEY 응답은 여유가 별로 없다.
정리
| 항목 | 내용 |
|---|---|
| 전환 시각 | 2026-10-11 16 UTC = 2026-10-12 01 KST |
| 기존 키 | KSK-2017, key tag 20326, alg 8 |
| 신규 키 | KSK-2024, key tag 38696, alg 8 |
| 실제 변경점 | 루트 DNSKEY RRSIG의 signer key tag가 20326에서 38696으로 |
| 장애 발생 시점 | 전환 후 최대 48시간(루트 DNSKEY TTL 172800초) |
| 점검 명령 | grep 38696 <trust-anchor-file> |
| 최우선 대상 | PowerDNS Recursor 5.2.0 미만 |
| 포워더 운영 시 | 내 서버가 아니라 상류 resolver를 점검. 공용 DNS면 할 일 없음 |
| 확진 방법 | dig @<상류> <도메인> +cd 로 정상 응답이면 DNSSEC 실패 |
| 다음 고비 | 2027-01-11 KSK-2017 REVOKE |
| 그 다음 | 2027~2029 ECDSA P-256 알고리즘 전환 |
할 일은 결국 한 줄이다. 운영 중인 검증 resolver의 trust anchor 파일에서 38696을 찾아보고, 없으면 지금 채워 넣는 것이다.
포워더만 쓰고 있다면 그 한 줄을 상류에 대고 물어야 한다. 상류가 공용 DNS면 거기서 끝이고, 사내 resolver면 거기가 진짜 시작점이다.
국내 문의는 KISA 인터넷정보센터로 하면 된다. 전화 061-820-1981, 이메일 in_dnssec@nic.or.kr.
참고자료
- ICANN Publishes Guidance to Help Prepare for the October 2026 Root KSK Rollover (2026-08-11)
- ICANN — Preparing for the Root Zone KSK Rollover: What You Need to Know (2026-07-27)
- IANA — DNSSEC Trust Anchors and Rollovers
- IANA — root-anchors.xml
- APNIC Blog — Geoff Huston, "Rolling the root key" (2026-05-05)
- Verisign — The 2024-2026 Root Zone KSK Rollover: Initial Observations and Early Trends
- Verisign — Unexpected Effects of the 2018 Root Zone KSK Rollover
- ICANN — Review of the 2018 DNSSEC KSK Rollover (2019-03, PDF)
- ICANN — Proposed Root KSK Algorithm Rollover (2026-02-03)
- PowerDNS Recursor — DNSSEC 문서 (RFC 5011 미지원 명시)
- PowerDNS Recursor 5.2.0-beta1 릴리스 노트
- IANA — DNS Security Algorithm Numbers 레지스트리
- RFC 9904 — DNSSEC Cryptographic Algorithm Recommendation Update Process
- RFC 5011 — Automated Updates of DNS Security (DNSSEC) Trust Anchors
- RFC 8145 — Signaling Trust Anchor Knowledge in DNSSEC
- RFC 8509 — A Root Key Trust Anchor Sentinel for DNSSEC
- KISA — 루트 존 DNSSEC 키 서명 키 교체 안내 (2026-09-07)