OpenVPN 자격 증명 파일인가 관리 소켓인가: 무엇을 스크립트할까
auth-user-pass는 계정 프롬프트를, 관리 소켓은 네 가지 모두를 담당합니다. 노출, 스크립트 비용, 서비스에서 각각 어떻게 실패하는지 비교합니다.
OpenVPN을 시작하고도 절대 연결되지 않는 스크립트는 대개 네트워크 문제가 아니라 클라이언트가 터미널 없이 프롬프트 앞에 앉아 있는 것입니다. OpenVPN에는 사람 없이 그 프롬프트에 답하는 지원되는 방법이 정확히 두 가지 있는데, auth-user-pass 뒤의 자격 증명 파일과 관리 소켓이며 둘은 서로 바꿀 수 없습니다. 파일은 지시문 하나로 프롬프트 하나에 답하지만, 소켓은 모든 프롬프트에 답하고 loopback 포트를 열 수 있는 무엇이든 인증 없는 제어 채널을 갖게 합니다. 어느 쪽이 배포에 맞는지는 실제로 어느 프롬프트가 막고 있는지, 그리고 어떤 비밀을 휴면 상태로 둘 여유가 있는지에 달려 있습니다.
메커니즘을 고르기 전에 프롬프트의 이름을 붙이세요
네 가지 프롬프트는 터미널에서 똑같아 보입니다. 계정 쌍은 서버의 PAM 또는 auth-user-pass-verify 설정에서 오고 auth-user-pass가 답합니다. 개인 키 암호는 키 파일에 속하며 --askpass로 읽거나 손으로 입력합니다. PKCS#12 번들은 자체 암호를 요구합니다. 프록시 인증은 계정 자격 증명이 아니라 --http-proxy가 받는 auth 파일을 사용합니다.
OpenVPN이 systemd, PHP 또는 Node의 자식으로 실행될 때는 터미널이 없으므로 이들 중 어느 것이든 조용히 막힙니다. 프로세스는 그대로 앉아 있고 로그는 암호 줄 뒤에서 멈추며 유닛은 결국 시간 초과되는데, 사람들이 어느 프롬프트가 대기 중인지 보는 대신 재시도 루프나 sleep을 추가하는 이유가 그 침묵입니다. 네 가지 모두에 답할 수 있는 것은 관리 소켓뿐입니다. 자격 증명 파일은 첫 번째에 답하고 그 밖에는 아무것도 하지 않습니다.
auth-user-pass: 파일 하나, 프롬프트 하나
파일은 첫 줄에 사용자 이름을, 둘째 줄에 암호만을 담고 따옴표도 키 이름도 주석도 없습니다. 모드를 600으로 설정하고 소유자를 openvpn을 실행하는 계정으로 하되, 서비스에서는 로그인 계정이 아니라 전용 사용자인 경우가 많습니다. /root 아래의 root 소유 파일은 셸에서 동작하는 명령과 부팅 시 실패하는 유닛의 차이입니다.
두 가지 세부가 올바른 파일을 프롬프트로 바꿉니다. CRLF로 저장하는 편집기는 암호에 캐리지 리턴을 붙여 두므로 서버는 파일에서 맞아 보이는 암호를 거부하며, 눈이 아니라 cat -A나 od -c로 확인하세요. 그리고 프로필 안의 인자 없는 auth-user-pass는 설계상 «사용자에게 묻기»를 뜻하며, 프로필은 있는 자리에서 분석되므로 명령줄의 두 번째 지시문이 이를 덮어쓰지 못할 수 있습니다.
옵션 순서가 세 번째 함정이며 버전에 따라 다릅니다. 일부 OpenVPN 2.4 빌드는 --config 앞에 오는 --auth-user-pass를 무시하므로 같은 두 옵션이 반대 순서에서는 연결됩니다. 지시문을 프로필 안에 두거나 명령줄에서 --config 뒤에 두고, 다른 것을 바꾸기 전에 프로필의 모든 출현을 grep 하세요.
auth-nocache는 이 메커니즘에 속하지만 아무것도 제공하지 않습니다. 클라이언트가 자격 증명을 메모리에 유지하는 시간을 제한하며, 크래시 후에는 도움이 되고 파일이 없을 때는 전혀 도움이 되지 않습니다. 서버 측 세션 시간 초과 후 재연결은 파일을 다시 읽으므로 부팅 시 동작한 자격 증명도 하루 중간에 만료될 수 있습니다.
관리 소켓: 모든 프롬프트, 더 넓은 표면
리스너와 함께 클라이언트를 시작하고 프롬프트가 그 위에서 보고될 것임을 명시하세요. OpenVPN 2.6 설명서에 문서화된 지시문을 사용합니다:
management 127.0.0.1 3030
management-query-passwords
bash에서는 연결을 여분의 파일 디스크립터에 유지하고 위치가 아니라 프롬프트 이름으로 답하세요. 재연결은 다른 순서로 물을 수 있기 때문입니다:
exec 3<>/dev/tcp/127.0.0.1/3030
while read -r -u 3 line; do
case "$line" in
*"Need 'Auth' username/password"*)
echo 'username "Auth" alice' >&3
echo 'password "Auth" secret' >&3 ;;
*"Need 'Private Key' password"*)
echo 'password "Private Key" secret' >&3 ;;
esac
done
경계는 전송입니다. 관리 프로토콜은 loopback의 TCP 위 평문이므로 원격 컨트롤러가 닿도록 리스너를 LAN이나 터널 주소에 게시해서는 안 되며, 원격 컨트롤러는 더 넓은 바인드가 아니라 SSH 전달을 뜻합니다. 커뮤니티 데몬은 한 번에 하나의 관리 클라이언트만 받으므로 GUI나 모니터가 스크립트와 소켓을 공유할 수 없습니다. 두 번째 연결은 대기열에 넣지 않고 거부되며, 그 실패는 귀가 먹은 스크립트처럼 보입니다.
그 소켓은 제어 채널이기도 합니다. 관리 암호가 없으면 127.0.0.1:3030에 연결할 수 있는 무엇이든 상태를 읽고, 터널을 죽이고, 스스로 프롬프트에 답할 수 있습니다. 관리 암호와 자격 증명 파일은 같은 부류의 비밀이며 둘 다 같은 주의를 받을 자격이 있습니다.
배포에 맞는 쪽
유일한 프롬프트가 계정 쌍이고 클라이언트가 안정적인 소유자의 서비스로 실행되며 통제하지 않는 백업 밖에 600 파일을 둘 수 있다면 자격 증명 파일을 고르세요. 키 암호나 PKCS#12 암호가 관련되거나, 래퍼가 이미 프로세스를 감독하거나, 재연결을 디버그하면서 상태와 프롬프트를 지켜봐야 한다면 관리 소켓을 고르세요.
두 조건이 결정을 움직입니다. 래퍼가 /bin/sh이거나 바이너리를 직접 호출하는 systemd 유닛이나 최소 컨테이너라면 bash 전용 소켓 글루를 쓸 수 없고 파일이 더 싼 답입니다. 비밀이 자주 회전한다면 어느 파일도 좋은 집이 아니므로 작은 래퍼에서 키링을 조회하고 유닛이 래퍼를 호출하게 하면 회전은 한곳에, 암호는 셸 기록 밖에 남습니다.
프로세스 치환은 둘 사이에 있고 어느 쪽에도 속하지 않습니다. 아래 한 줄이 동작하는 것은 openvpn이 시작되기 전에 bash가 확장하기 때문이며, 클라이언트가 문법을 이해하기 때문이 아닙니다:
openvpn --config client.ovpn --auth-user-pass <(printf "%s
%s
" "$USER" "$PASS")
/bin/sh 아래에서, 셸을 우회하는 유닛에서, bash나 /dev/fd가 없는 이미지에서 깨집니다. 대화형 테스트용으로 남기고, 서비스가 의존하는 계약으로는 절대 쓰지 마세요.
비밀을 보호하고, 그다음 로그로 검증하세요
두 메커니즘 모두 OpenVPN 프로세스가 읽을 수 있는 비밀을 남깁니다. ps와 systemctl이 모든 로컬 사용자에게 보여 주므로 암호를 명령줄이나 유닛 파일에 두지 말고 파일 경로를 넘기며 그 안의 값은 넘기지 마세요. 인증서가 계정 쌍을 대체할 수 있는 곳에서는 프롬프트 범주를 숨기는 대신 없앱니다.
올바른 실행은 프롬프트 순서가 완료되고 세션이 «Initialization Sequence Completed»에 도달하는 것을 보여 줍니다. 클라이언트가 같은 프롬프트에서 멈추면 일치에 실패한 것이므로 소켓이 보낸 정확한 줄을 인쇄하고 따옴표와 프롬프트 이름을 확인하고 sleep을 추가하는 대신 패턴을 고치세요. 성공을 보고하면서 아무것도 나르지 않는 터널은 라우팅이나 push 문제이지 자격 증명 문제가 아닙니다.
그다음 첫 시작만이 아니라 재연결을 시험하세요. 서버 측 세션을 끊거나 그 시간 초과를 기다리면 클라이언트는 다시 묻고, 부팅 시 맞아 보인 파일이 재연결하는 계정에 여전히 읽히는지가 바로 거기서 드러납니다.