Kea DDNS 구축: BIND 9 TSIG·정방향·역방향 자동 업데이트
EdwardMoon
Kea DDNS 구축. DHCP가 주소를 빌려줄 때 호스트 이름의 A 레코드와 IP의 PTR 레코드를 BIND 9에 자동으로 등록하는 구성이다. Kea DHCPv4가 DNS 존 파일을 직접 수정하는 것이 아니라, Name Change Request를 Kea D2에 보내고 D2가 RFC 2136 동적 업데이트를 TSIG로 서명해 BIND 9 primary에 전달한다.
이 글은 10.20.30.0/24 네트워크와 lab.example.internal 내부 도메인을 예로 든다. 기존 ISC DHCP의 ddns-update-style 예제를 Kea에 그대로 옮기면 동작하지 않는다. 역할 분리, 키 보호, 정방향·역방향 존의 일치, DHCID 충돌 처리와 서비스 시작 순서를 함께 맞춰야 한다. 모든 명령과 설정은 코드 블록으로 구분했다.

Kea DDNS 구축 구성 요소와 데이터 흐름
이 실습은 Rocky Linux 9의 10.20.30.53 한 서버에 Kea DHCPv4·D2·BIND를 함께 설치한다. 따라서 키 생성·로컬 nsupdate·서비스 명령은 모두 이 서버에서 실행하며, 클라이언트 검증만 별도 시험 단말에서 실행한다. 운영에서 DHCP와 DNS를 분리하면 TSIG 비밀을 보호된 관리 채널로 D2 호스트에 따로 배포하고 D2의 DNS 대상 주소·방화벽을 맞춰야 한다.
| 구성 요소 | 예시 주소 | 역할 |
|---|---|---|
| Kea DHCPv4 | 10.20.30.53 | 주소 임대와 호스트 이름 정책, D2로 NCR 전송 |
| Kea D2 | 127.0.0.1:53001 | NCR을 DNS UPDATE 메시지로 변환하고 TSIG 서명 |
| BIND 9 primary | 10.20.30.53:53 | 정방향·역방향 dynamic zone의 권한 서버 |
| 클라이언트 | 10.20.30.100~200 | DHCPREQUEST에서 이름 또는 FQDN 제공 |
- 정방향 존 lab.example.internal.에는 호스트 이름에서 IPv4 주소로 찾는 A 레코드가 생성된다.
- 역방향 존 30.20.10.in-addr.arpa.에는 IPv4 주소에서 FQDN으로 찾는 PTR 레코드가 생성된다.
- DHCID 레코드는 다른 DHCP 클라이언트가 같은 이름을 덮어쓰는 충돌을 줄이는 데 사용된다.
- TSIG는 업데이트 메시지의 인증과 무결성을 제공하지만 DNS 조회 응답 자체를 암호화하지는 않는다.
Kea DDNS 구축 1단계: 사전 점검과 패키지 확인
Kea와 BIND 9는 지원되는 배포판 패키지 또는 승인된 ISC 저장소에서 설치한다. 저장소를 무작정 추가하지 말고 후보 버전과 서명을 먼저 확인한다. 아래는 EL9 계열의 패키지 이름 예시이다.
# Kea DHCP 구축 글과 같은 ISC 3.0 LTS 저장소를 먼저 준비합니다.
curl --fail --location --proto '=https' --tlsv1.2 \
https://dl.cloudsmith.io/public/isc/kea-3-0/setup.rpm.sh \
--output /tmp/isc-kea-3-0-setup.rpm.sh
less /tmp/isc-kea-3-0-setup.rpm.sh
sudo bash /tmp/isc-kea-3-0-setup.rpm.sh
sudo dnf --showduplicates list isc-kea-dhcp4 isc-kea-dhcp-ddns bind bind-utils
sudo dnf install -y isc-kea-dhcp4 isc-kea-dhcp-ddns bind bind-utils policycoreutils-python-utils
rpm -q isc-kea-dhcp4 isc-kea-dhcp-ddns bind bind-utils
kea-dhcp4 -V
named -V
systemctl show kea-dhcp-ddns -p User -p Group -p ExecStart
호스트 이름, 시간, 인터페이스, 기존 DHCP·DNS 리스너 충돌을 먼저 확인한다.
hostnamectl
chronyc tracking
ip -br address
sudo ss -luntp | grep -E ':(53|67|53001)\b' || true
getent passwd named
systemctl show kea-dhcp-ddns -p User -p Group
Kea DDNS 구축 2단계: TSIG 비밀키 생성과 권한 분리
BIND 9과 Kea D2가 같은 TSIG secret을 사용해야 한다. 키를 화면이나 셸 기록에 출력하지 말고 root만 생성한 뒤, BIND용 key 블록과 Kea용 secret 파일을 각각 최소 권한으로 배치한다. 키 이름의 끝점까지 양쪽 설정에서 정확히 일치시킨다.
sudo install -d -o root -g named -m 0750 /etc/named/keys
sudo sh -c 'umask 077; tsig-keygen -a hmac-sha256 kea-ddns.lab.example.internal. > /etc/named/keys/kea-ddns.key'
sudo chown root:named /etc/named/keys/kea-ddns.key
sudo chmod 0640 /etc/named/keys/kea-ddns.key
Kea 2.5.8 이상에서는 설정 파일에 secret을 직접 넣는 대신 secret-file을 사용할 수 있다.
# D2의 실제 실행 계정/그룹을 조회합니다. systemd User가 비면 root입니다.
D2_USER=$(systemctl show kea-dhcp-ddns -p User --value)
D2_USER=${D2_USER:-root}
D2_GROUP=$(systemctl show kea-dhcp-ddns -p Group --value)
D2_GROUP=${D2_GROUP:-$(id -gn "$D2_USER")}
sudo install -d -o root -g "$D2_GROUP" -m 0750 /etc/kea/secrets
sudo install -o root -g "$D2_GROUP" -m 0640 /dev/null /etc/kea/secrets/kea-ddns.secret
sudo awk -F'"' '/secret/{print $2}' /etc/named/keys/kea-ddns.key \
| sudo tee /etc/kea/secrets/kea-ddns.secret >/dev/null
sudo restorecon -Rv /etc/kea/secrets
sudo stat -c '%U:%G %a %n' /etc/named/keys/kea-ddns.key /etc/kea/secrets/kea-ddns.secret
Kea DDNS 구축 3단계: BIND 9 dynamic zone 설정
/etc/named.conf에 TSIG key 파일을 포함하고, 정방향·역방향 primary zone을 정의한다.
include "/etc/named/keys/kea-ddns.key";
zone "lab.example.internal" IN {
type primary;
file "dynamic/db.lab.example.internal";
allow-update { key "kea-ddns.lab.example.internal."; };
};
zone "30.20.10.in-addr.arpa" IN {
type primary;
file "dynamic/db.10.20.30";
allow-update { key "kea-ddns.lab.example.internal."; };
};
allow-update는 해당 키에 존 전체 업데이트 권한을 준다. 더 세밀한 이름·레코드 제한이 필요하면 BIND의 update-policy를 설계하되 두 옵션을 같은 존에 동시에 사용하지 않는다.
/etc/named.conf의 기존 options 블록 안에서 다음 항목을 수정한다. 새로운 options 블록을 중복 추가하지 않는다. 이 서버를 내부 클라이언트의 재귀 DNS로도 쓰므로 질의·재귀 허용 범위를 실습망으로 제한한다.
listen-on port 53 { 127.0.0.1; 10.20.30.53; };
listen-on-v6 port 53 { ::1; };
allow-query { localhost; 10.20.30.0/24; };
recursion yes;
allow-recursion { localhost; 10.20.30.0/24; };
아래 두 존 파일은 현재 작업 디렉터리에 각각 db.lab.example.internal과 db.10.20.30으로 저장한 뒤 다음 install 명령으로 배치한다.
정방향 존 초기 파일
$TTL 300
@ IN SOA dns01.lab.example.internal. hostmaster.lab.example.internal. (
2026072101 ; serial
3600 ; refresh
900 ; retry
604800 ; expire
300 ) ; minimum
IN NS dns01.lab.example.internal.
dns01 IN A 10.20.30.53
역방향 존 초기 파일
$TTL 300
@ IN SOA dns01.lab.example.internal. hostmaster.lab.example.internal. (
2026072101 ; serial
3600 ; refresh
900 ; retry
604800 ; expire
300 ) ; minimum
IN NS dns01.lab.example.internal.
53 IN PTR dns01.lab.example.internal.
sudo install -d -o named -g named -m 0770 /var/named/dynamic
sudo install -o named -g named -m 0660 db.lab.example.internal /var/named/dynamic/
sudo install -o named -g named -m 0660 db.10.20.30 /var/named/dynamic/
sudo restorecon -Rv /etc/named/keys /var/named/dynamic
sudo named-checkconf /etc/named.conf
sudo named-checkzone lab.example.internal /var/named/dynamic/db.lab.example.internal
sudo named-checkzone 30.20.10.in-addr.arpa /var/named/dynamic/db.10.20.30
dynamic zone의 저널 파일은 named가 생성한다. SELinux를 끄거나 /var/named 전체를 0777로 바꾸지 말고, 패키지 기본 컨텍스트와 dynamic 디렉터리만 쓰기 가능하게 유지한다.
sudo systemctl enable --now named
sudo systemctl --no-pager --full status named
sudo journalctl -u named -b --no-pager | tail -n 100
dig @127.0.0.1 SOA lab.example.internal +norecurse
dig @127.0.0.1 SOA 30.20.10.in-addr.arpa +norecurse
Kea DDNS 구축 4단계: BIND 동적 업데이트 단독 시험
Kea를 연결하기 전에 nsupdate로 BIND와 TSIG만 시험하면 장애 구간을 분리할 수 있다. 이 시험은 임시 A·PTR 레코드를 추가하고 확인한 뒤 삭제한다.
sudo nsupdate -k /etc/named/keys/kea-ddns.key <<'EOF'
server 127.0.0.1
zone lab.example.internal.
update add ddns-test.lab.example.internal. 300 A 10.20.30.250
send
zone 30.20.10.in-addr.arpa.
update add 250.30.20.10.in-addr.arpa. 300 PTR ddns-test.lab.example.internal.
send
EOF
dig @127.0.0.1 ddns-test.lab.example.internal A +short
dig @127.0.0.1 -x 10.20.30.250 +short
sudo journalctl -u named --since '-5 minutes' --no-pager
sudo nsupdate -k /etc/named/keys/kea-ddns.key <<'EOF'
server 127.0.0.1
update delete ddns-test.lab.example.internal. A
send
update delete 250.30.20.10.in-addr.arpa. PTR
send
EOF
Kea DDNS 구축 5단계: Kea D2 설정
/etc/kea/kea-dhcp-ddns.conf를 다음과 같이 구성한다. DNS 서버 주소는 BIND 9 primary를 가리켜야 한다.
{
"DhcpDdns": {
"ip-address": "127.0.0.1",
"port": 53001,
"dns-server-timeout": 1000,
"ncr-protocol": "UDP",
"ncr-format": "JSON",
"tsig-keys": [
{
"name": "kea-ddns.lab.example.internal.",
"algorithm": "HMAC-SHA256",
"secret-file": "/etc/kea/secrets/kea-ddns.secret"
}
],
"forward-ddns": {
"ddns-domains": [
{
"name": "lab.example.internal.",
"key-name": "kea-ddns.lab.example.internal.",
"dns-servers": [ { "ip-address": "10.20.30.53", "port": 53 } ]
}
]
},
"reverse-ddns": {
"ddns-domains": [
{
"name": "30.20.10.in-addr.arpa.",
"key-name": "kea-ddns.lab.example.internal.",
"dns-servers": [ { "ip-address": "10.20.30.53", "port": 53 } ]
}
]
},
"loggers": [
{ "name": "kea-dhcp-ddns", "severity": "INFO" }
]
}
}
sudo kea-dhcp-ddns -t /etc/kea/kea-dhcp-ddns.conf
sudo systemctl enable --now kea-dhcp-ddns
sudo systemctl --no-pager --full status kea-dhcp-ddns
sudo ss -lunp | grep ':53001'
sudo journalctl -u kea-dhcp-ddns -b --no-pager | tail -n 100
Kea DDNS 구축 6단계: DHCPv4에서 D2 연결
이 예제는 클라이언트가 갱신을 요청하는 경우 Kea가 A와 PTR을 모두 관리하도록 ddns-override-client-update: true를 사용한다. 클라이언트의 갱신 금지 요청은 ddns-override-no-update: false로 존중한다. 따라서 모든 단말에 무조건 A/PTR이 생성되는 것은 아니며 FQDN 옵션과 실제 NCR 로그를 함께 확인한다.
다음 예시는 /etc/kea/kea-dhcp4.conf의 핵심 부분이다. 인터페이스, 라우터, DNS 주소와 풀은 실제 네트워크에 맞게 바꾼다. dhcp-ddns.enable-updates와 ddns-send-updates가 모두 활성화되어야 D2로 요청이 전송된다.
{
"Dhcp4": {
"interfaces-config": { "interfaces": [ "ens192" ] },
"lease-database": {
"type": "memfile",
"persist": true,
"name": "/var/lib/kea/kea-leases4.csv"
},
"renew-timer": 900,
"rebind-timer": 1800,
"valid-lifetime": 3600,
"dhcp-ddns": {
"enable-updates": true,
"server-ip": "127.0.0.1",
"server-port": 53001,
"ncr-protocol": "UDP",
"ncr-format": "JSON"
},
"ddns-send-updates": true,
"ddns-override-no-update": false,
"ddns-override-client-update": true,
"ddns-replace-client-name": "when-not-present",
"ddns-generated-prefix": "host",
"ddns-qualifying-suffix": "lab.example.internal",
"ddns-update-on-renew": false,
"ddns-conflict-resolution-mode": "check-with-dhcid",
"subnet4": [
{
"id": 100,
"subnet": "10.20.30.0/24",
"pools": [ { "pool": "10.20.30.100 - 10.20.30.200" } ],
"option-data": [
{ "name": "routers", "data": "10.20.30.1" },
{ "name": "domain-name-servers", "data": "10.20.30.53" },
{ "name": "domain-name", "data": "lab.example.internal" }
]
}
],
"loggers": [ { "name": "kea-dhcp4", "severity": "INFO" } ]
}
}
ddns-replace-client-name은 이름이 없을 때만 생성하도록 설정했다. 관리 정책상 서버가 모든 이름을 강제로 정해야 한다면 always를 검토할 수 있지만, 기존 클라이언트 이름과 충돌하지 않는 예약·명명 규칙을 먼저 설계한다.
sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf
sudo systemctl enable --now kea-dhcp4
sudo systemctl --no-pager --full status kea-dhcp4
sudo ss -lunp | grep -E ':(67|53001)\b'
sudo journalctl -u kea-dhcp4 -b --no-pager | tail -n 100
Kea DDNS 구축 7단계: 실제 A·PTR·DHCID 검증
테스트 클라이언트에서 DHCP 임대를 새로 받은 뒤, 실제 할당 주소와 FQDN을 기준으로 정방향·역방향을 모두 조회한다. 운영 인터페이스의 임대 갱신은 연결을 끊을 수 있으므로 콘솔 또는 별도 테스트 단말에서 수행한다.
# 테스트 클라이언트에서 실제 환경에 맞는 방법으로 임대를 갱신
sudo dhclient -r ens192
sudo dhclient -v ens192
ip -4 address show dev ens192
dig @10.20.30.53 client01.lab.example.internal A +noall +answer
dig @10.20.30.53 -x 10.20.30.101 +noall +answer
dig @10.20.30.53 client01.lab.example.internal DHCID +noall +answer
sudo journalctl -u kea-dhcp4 -u kea-dhcp-ddns -u named \
--since '-10 minutes' --no-pager
| 증상 | 먼저 확인할 항목 | 대표 원인 |
|---|---|---|
| A와 PTR 모두 없음 | DHCPv4 로그와 53001 UDP | D2 연결 비활성화, 이름 정책, D2 중지 |
| A만 생성 | reverse-ddns 도메인과 역방향 존 | 역방향 존 이름 또는 DNS 서버 주소 불일치 |
| NOTAUTH | BIND primary와 zone 선언 | secondary로 전송, 존 이름 불일치 |
| NOTAUTH 또는 REFUSED | TSIG key 이름·secret·algorithm | 키 불일치, allow-update 미허용 |
| YXDOMAIN·충돌 | 기존 A·DHCID와 클라이언트 식별자 | 다른 클라이언트가 같은 이름을 소유 |
Kea DDNS 운영: dynamic zone 파일을 직접 수정하지 않기
named가 실행 중인 dynamic zone은 .jnl 저널과 함께 관리된다. 존 파일을 직접 편집하면 변경 내용이 덮어써지거나 저널과 불일치할 수 있다. 수동 변경이 꼭 필요하면 먼저 동기화하고 해당 존을 freeze한 뒤 검증 후 thaw한다.
sudo rndc freeze lab.example.internal
sudoedit /var/named/dynamic/db.lab.example.internal
sudo named-checkzone lab.example.internal /var/named/dynamic/db.lab.example.internal
sudo rndc thaw lab.example.internal
sudo rndc zonestatus lab.example.internal
방화벽·SELinux·권한 점검
클라이언트와 권한 DNS 서버 사이에는 DNS 53/TCP·UDP가, DHCP 서버와 클라이언트 사이에는 DHCP 포트가 필요하다. D2의 53001/UDP는 같은 호스트의 loopback에만 바인딩했으므로 외부에 열지 않는다.
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --permanent --zone=internal --add-service=dns
sudo firewall-cmd --permanent --zone=internal --add-service=dhcp
sudo firewall-cmd --reload
sudo firewall-cmd --zone=internal --list-all
sudo ss -luntp | grep -E ':(53|67|53001)\b'
sudo ausearch -m AVC -ts recent | tail -n 50
고가용성·백업·모니터링 체크리스트
- Kea HA의 두 DHCP 서버가 D2와 authoritative DNS에 도달하는지 failover 시험으로 검증한다.
- DNS secondary는 primary의 NOTIFY·IXFR/AXFR로 동기화하고 D2 업데이트 대상은 primary로 명확히 지정한다.
- Kea 설정, lease 데이터베이스, BIND 설정·zone·journal, TSIG 키를 서로 다른 보안 등급으로 백업한다.
- 키 복원 권한을 제한하고 정기적으로 키 교체와 서비스 재시작 순서를 연습한다.
- DHCP 임대 성공뿐 아니라 D2 queue, DNS UPDATE 결과, A·PTR 불일치와 존 serial을 관측한다.
sudo systemctl is-active kea-dhcp4 kea-dhcp-ddns named
sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf
sudo kea-dhcp-ddns -t /etc/kea/kea-dhcp-ddns.conf
sudo named-checkconf -z /etc/named.conf
sudo rndc status
sudo rndc zonestatus lab.example.internal
함께 읽을 글
정리
안전한 Kea DDNS 구축. DHCPv4, D2, BIND 9을 한 번에 시작하는 것이 아니라 BIND 존 검증, TSIG 단독 시험, D2 연결, 실제 DHCP 임대 시험 순으로 경계를 나눠 확인해야 한다. 정방향 A와 역방향 PTR, DHCID를 함께 관찰하고 secret 권한·dynamic zone 저널·백업과 키 교체 절차까지 운영 기준에 포함해야 자동화가 데이터 정합성을 해치지 않는다.