dnsmasq에서 OpenVPN 클라이언트 이름: 스크립트인가 정적 항목인가?
클라이언트 주소는 바뀌지만 서비스 이름은 바뀌지 않는다. VPN 세션에서 클라이언트 이름을 생성하고, 서비스 이름은 drop-in 파일에 고정한 다음, 클라이언트가 그것을 사용하게 하라.
거의 모든 소규모 OpenVPN 게이트웨이에서 두 가지 이름 문제가 나타난다. 연결된 노트북은 인증서에 적힌 이름으로 접근할 수 없고, 터널 뒤의 서비스에는 이름이 아예 없다. 둘은 하나의 문제처럼 보이지만, 한 주소는 세션마다 움직이고 다른 주소는 움직이지 않기 때문에 정반대 처리가 필요하다. 무엇이든 편집하기 전에 어떤 종류의 이름을 추가하는지 결정하라.
게이트웨이가 아니라 이름별로 결정하라
시험은 간단하다: 클라이언트가 다시 연결할 때 이 주소가 바뀌는가? 그렇다면 이름은 VPN 세션에서 생성해야 한다. 아니라면 읽고 검토할 수 있는 정적 항목에 속한다.
| Name kind | Source of truth | Where it lives |
|---|---|---|
| Connected client | The address OpenVPN just assigned | Generated hosts file, rewritten per session |
| Service behind the tunnel | A fixed address you chose | Static address= drop-in |
| Anything with a certificate name | The certificate, not the pool | Either, but the name must match the certificate |
둘을 섞는 것이 흔한 실패다. 생성 파일에 고정한 서비스는 스크립트가 처음 다시 쓸 때 사라지고, 정적 항목에 적은 클라이언트는 그 주소를 마지막으로 가진 쪽을 가리킨다. 두 실수 모두 이름이 잘못된 기계로 해석될 때까지 보이지 않는다. 그때쯤 디버깅하는 사람은 DNS가 고장 났다고 생각하지 파일을 의심하지 않는다.
동적 경로: learn-address와 풀
OpenVPN은 할당을 알고 있으니 알려 주게 하라. 이 서버 옵션들은 할당을 지속 가능하고 스크립트 가능하게 만든다:
client-config-dir /etc/openvpn/ccd
ifconfig-pool-persist /etc/openvpn/ip-pool 60
script-security 2
learn-address /etc/openvpn/learn-address.sh
learn-address는 주소가 추가, 갱신, 삭제될 때마다 스크립트를 실행하고 작업, 주소, 공통 이름을 넘긴다. 최소 스크립트는 클라이언트마다 한 줄을 덧붙이고 삭제 시 그 줄을 지운다:
#!/bin/sh
HOSTS=/etc/openvpn/hosts.lan
case "$1" in
add|update) echo "$2 $3" >> "$HOSTS" ;;
delete) sed -i "/^$2 /d" "$HOSTS" ;;
esac
kill -HUP $(cat /var/run/dnsmasq.pid)
tun 장치에서는 두 번째 인수가 클라이언트의 터널 주소이므로 그 줄을 바로 쓸 수 있다. tap 장치에서는 OpenVPN이 대신 클라이언트의 MAC 주소를 넘기는데, tap 세그먼트는 IPv4 외의 프로토콜도 실을 수 있기 때문이며, 그때는 무엇이든 쓰기 전에 공통 이름으로 풀 파일에서 주소를 찾아야 한다.
생성 파일은 지금 누가 연결되어 있는지의 스냅숏이라는 점을 분명히 하라. 연결이 끊기면 이름이 사라지는데, 네트워크 사이를 옮겨 다니는 노트북에는 바로 원하는 동작이고 밤새 켜 두는 서버에는 바로 원하지 않는 동작이다.
정적 경로: address=와 도메인 확장
시스템 hosts 파일 대신 dnsmasq를 생성 파일로 향하게 하고, 도메인을 주어 짧은 이름이 정규화된 이름으로 확장되게 하라:
domain=corp.example
no-hosts
addn-hosts=/etc/openvpn/hosts.lan
listen-address=127.0.0.1,10.8.0.1
bind-interfaces
domain가 설정되면 10.8.0.6 alice 같은 줄이 alice와 alice.corp.example 모두에 응답한다. listen-address는 터널 게이트웨이와 loopback으로만 제한하라(LAN 인터페이스에 실수로 공개된 리졸버는 제공할 생각이 없던 서비스다).
풀에서 오지 않는 서비스는 자체 항목을 갖는다. 배포판 설정 디렉터리의 별도 drop-in 파일에 넣어 패키지 업그레이드가 덮어쓰지 못하게 하라:
address=/files.corp.example/10.8.0.20
address=/db.corp.example/10.9.0.5
리졸버를 넘기지 않으면 아무 일도 일어나지 않는다
아무도 묻지 않는 리졸버는 아무것도 바꾸지 않는다. 서버가 그것을 알려야 한다:
push "dhcp-option DNS 10.8.0.1"
push "dhcp-option DOMAIN corp.example"
Windows 클라이언트는 두 옵션을 스스로 적용하지만 대부분의 Linux 클라이언트는 그렇지 않다. 배포판의 update-resolv-conf 훅이나 NetworkManager의 DNS 처리 같은 지원되는 리졸버 통합, 또는 수동 검색 도메인과 네임서버가 필요하다. push가 지켜졌다고 가정하지 말고 클라이언트의 실제 리졸버를 확인하라(무시된 push는 응답하지 않는 서버와 똑같이 보인다).
세 계층에서 검증하라
먼저 서버에 직접 묻고, 다음으로 클라이언트에, 마지막으로 애플리케이션에 물어라:
dig +short @10.8.0.1 alice.corp.example
getent hosts files.corp.example
직접 질의가 응답하고 애플리케이션이 응답하지 않으면 원인은 존이 아니라 클라이언트로의 전달에 있다. 이름이 해석되는데 연결이 계속 실패하면 이름은 정상이고 다음에 볼 것은 경로나 방화벽이다.
두 범주를 설정 옆에 적어 두라: 어떤 이름이 생성되고, 어떤 것이 고정되며, 각각이 어느 인증서와 일치해야 하는지. 다음에 서버를 추가하는 사람은 가장 먼저 찾은 패턴을 그대로 복사할 것이다.