어떤 자격 증명 원본을 사용할지는 세 가지가 결정합니다. 콘솔에 사람이 앉아 있는지, 비밀이 디스크에 남아도 되는지, 다른 프로세스가 프롬프트에 답해야 하는지입니다. 잘못 고르면 비밀번호는 완벽히 유효한데도 클라이언트가 stdin 문제를 보고하고, 이 때문에 대부분의 사람이 이유 없이 서버를 의심하게 됩니다. 먼저 경로를 고르세요. 오류 메시지는 어느 경로가 그것을 만들었는지 알 때에만 의미가 있습니다.

이를 결정하는 세 조건

콘솔 프롬프트는 사람이 필요한 유일한 경로이므로 systemd 유닛이나 컨테이너에서 조용히 실패하는 유일한 경로이기도 합니다. 사람이 클라이언트를 실행해 비밀번호를 입력하면 파일이 개입하지 않고 민감한 정보가 디스크에 남지 않습니다. 클라이언트가 부팅 시 실행되면 같은 프롬프트가 멈춤이 되고, 시간 초과는 보통 자격 증명 오류가 아니라 일반적인 연결 실패로 나타납니다.

자격 증명 파일은 프로세스가 무인으로 시작되고 통제된 권한 집합 아래에서 비밀이 디스크에 남아도 될 때 올바른 답입니다. 비밀을 전혀 기록해서는 안 되는 경우, 즉 보관소 전용 정책이나 단명 토큰의 경우에는 클라이언트가 열 수 있는 스트림이 필요하며, 실제로는 프로세스 치환 또는 파일 디스크립터에 쓰는 도우미를 뜻합니다. 관리 인터페이스는 네 번째 경우를 담당합니다. 터널의 수명 주기를 이미 관리하고 소켓으로 질문에 답할 수 있는 컨트롤러입니다.

  • 콘솔에 사람이 있음, 자동화 없음: 대화형 프롬프트.
  • 무인 시작, 비밀 저장 가능: 모드 0600의 자격 증명 파일.
  • 무인 시작, 비밀 저장 불가: 프로세스 치환 또는 시크릿 에이전트가 쓰는 fd.
  • 다른 프로세스가 클라이언트를 소유함: 관리 인터페이스.

두 답이 모두 맞을 때는 비밀을 argv와 셸 기록 밖에 두는 쪽을 고르세요. 프로그램 인자로 넘긴 비밀번호는 프로세스 목록을 통해 모든 로컬 사용자가 읽을 수 있고, 그 노출은 되돌리기 어렵습니다.

각 입력 경로가 실제로 하는 일

--auth-user-pass 단독으로는 콘솔에서 사용자 이름과 비밀번호를 묻습니다. 파일 이름을 주면 클라이언트는 첫 줄을 사용자 이름으로, 둘째 줄을 비밀번호로 사용합니다. 따옴표, 주석, 키-값 구문은 지원하지 않습니다. 이것이 형식의 전부이므로 INI 파일을 기대하고 편집하는 사람은 그것을 깨뜨립니다. 인라인 <auth-user-pass> 블록은 프로필 안에 같은 두 줄을 담고, 프로필 자체를 비밀로 만들며 개인 키와 같은 취급 등급에 놓습니다.

나머지 두 경로는 스트림과 소켓입니다. OpenVPN은 읽을 수 있는 파일 디스크립터만 필요하므로 프로세스 치환, 파이프로 뒷받침되는 /dev/fd 항목, 실제로 FIFO인 파일 이름을 받아들입니다. 관리 인터페이스는 성격이 다릅니다. 클라이언트가 값을 직접 읽는 대신 감독 프로세스에 묻습니다. 이 차이는 다시 연결할 때 중요합니다. 자격 증명 파일, 시크릿 도우미가 다시 쓸 수 있는 FIFO, 관리 인터페이스는 사람 없이 다시 공급할 수 있지만 프로세스 치환은 그럴 수 없습니다. 디스크립터가 첫 읽기에서 소비되기 때문입니다.

파일은 정확히 두 줄이어야 합니다

깨진 자격 증명 파일의 대부분은 두 가지 실패 양상으로 설명됩니다. Windows 편집기가 CRLF 줄바꿈을 남기면 캐리지 리턴이 비밀번호의 일부가 되어, 화면에서는 맞아 보이는 값을 서버가 거부합니다. 둘째 줄이 비어 있는 경우, 즉 비밀번호가 있어야 할 자리가 빈 줄이면 클라이언트는 빈 비밀번호를 갖게 되고, 화면의 값이 맞아 보여도 서버는 거부합니다. 비밀번호 뒤의 추가 개행은 무해합니다. 파일은 편집기가 아니라 셸에서 쓰고 바이트를 확인하세요.

install -m 600 /dev/null /etc/openvpn/client.creds
printf '%s\n%s\n' "$VPN_USER" "$VPN_PASS" > /etc/openvpn/client.creds
od -c /etc/openvpn/client.creds | head -3

경로는 내용만큼 중요합니다. 상대 경로는 프로세스의 작업 디렉터리를 기준으로 풀리는데, 서비스에서는 테스트한 디렉터리와 거의 일치하지 않습니다. 절대 경로를 쓰고, 파일을 클라이언트를 실행하는 계정 소유로 하고, 그룹과 다른 사용자가 읽지 못하도록 모드 0600을 유지하세요. OpenVPN 2.6 설명서가 디렉티브의 허용 형식을 나열합니다. 세 번째 형식을 만들기 전에 확인하세요.

서버를 탓하기 전에 파일을 시험하세요. openvpn --config client.ovpn --auth-user-pass /etc/openvpn/client.creds --verb 4 를 실행하면 파일이 열렸는지, 나중에 자격 증명이 받아들여졌는지 출력되어 다음에 어느 계층을 바꿀지 알 수 있습니다.

스트림, stdin, 그리고 그들이 만드는 오류

비밀번호를 파이프로 넣는 것은 대부분이 가장 먼저 시도하는 방법이며 가장 많은 의외성이 있는 경로입니다. 프로세스 치환이 되는 이유는 클라이언트가 읽을 수 있는 디스크립터만 필요하기 때문입니다. openvpn --config client.ovpn --verb 3 --auth-user-pass <(printf '%s\n%s\n' "$VPN_USER" "$VPN_PASS") . /dev/stdin 를 넘기는 것은 클라이언트가 묻는 순간 stdin이 아직 열려 있고 읽을 수 있을 때만 동작하며, 이미 그것을 소비한 파이프라인에서는 사실이 아닙니다.

ERROR: could not read Auth username from stdin 에는 두 가지 다른 원인이 있습니다. 일부 2.3.x 클라이언트에서는 이 메시지가 stdin 처리 결함에서 나왔고 이후 2.3 릴리스가 고쳤으므로, 그렇게 오래된 클라이언트는 업그레이드로만 나아집니다. 현재 클라이언트에서는 같은 문구가 클라이언트가 콘솔 입력을 요청했고 stdin이 터미널이 아니었음을 뜻합니다. 바로 systemd 유닛, cron 작업, -t 없이 시작한 컨테이너의 상황입니다.

auth-nocache 는 깨진 비밀번호처럼 보이는 세 번째 고리를 더합니다. 사용 후 자격 증명을 메모리에서 버리므로 네트워크 변경으로 촉발된 재연결에는 다시 재생할 것이 없고 프롬프트가 돌아옵니다. 이것은 설계대로 동작하는 디렉티브입니다. auth-nocache 는 클라이언트가 메모리에 유지하는 것을 바꾸며, 서버나 ID 공급자가 계정에 대해 생각하는 것을 바꾸지 않습니다. 따라서 해결책은 같은 파일을 다시 공급하는 것이고, 재시작 시 다시 읽힙니다. 관리되는 연결에서는 그 공급이 승인된 시크릿 저장소에서 와야 하며 스크립트 안의 비밀번호가 아닙니다.

다른 프로세스가 프롬프트를 소유할 때의 관리 인터페이스

터널을 이미 감독하는 컨트롤러는 그 질문에도 답할 수 있습니다. 클라이언트를 --management 127.0.0.1 7505 로 시작하고 상태 줄을 읽고 >PASSWORD:Need 'Auth' username/password 를 지켜보세요. 컨트롤러는 별도의 명령 줄에서 username "Auth" alice 에 이어 password "Auth" ... 로 답합니다. 'Auth' 토큰은 요청의 식별자를 그대로 돌려주며 일치해야 하고, 그렇지 않으면 응답이 무시되고 클라이언트는 기다립니다.

대가는 이제 여러분이 소유한 상태 기계입니다. 연결 순서, 클라이언트 재시작, 그리고 호스트의 다른 사용자가 닿아서는 안 되는 로컬 소켓입니다. 얻는 것은 런타임에 공급되는 자격 증명이며, 디스크의 파일도, 멈출 수 있는 프롬프트도 없습니다. 스택의 다른 무엇도 아직 이 프로토콜을 말하지 않는다면, 이 경로는 유지해야 할 구성 요소를 하나 더합니다.

자격 증명 문제처럼 보이는 실패

끝나지 않는 TLS 핸드셰이크나 VERIFY ERROR: depth=0, error=certificate signature failure 는 신뢰 문제이며 어떤 사용자 이름도 읽히기 전에 일어납니다. UDP 리스너를 향한 proto tcp 클라이언트도 같은 지점에서 실패합니다. 패킷이 인증 단계에 도달하지 못하므로 모든 비밀번호가 연상 작용으로 틀려 보입니다. 자격 증명을 보내기 전에 인증서 검증을 켜 두세요. 익숙한 프롬프트는 누가 비밀번호를 받는지에 대해 아무것도 말해 주지 않습니다. 자격 증명을 건드리기 전에 인증서 체인, 일반 이름, 전송 방식을 확인하세요.

프로필을 조립하는 스크립트의 셸 버그도 또 하나의 거짓 단서입니다. [ ! -f "$file" ] 안의 공백이 빠지면 command not found 가 생기고, 스크립트는 일찍 멈추며, 프로필은 다시 만들어지지 않고, 클라이언트는 이전 것으로 시작합니다. 그 오류는 인증이 아니라 셸에 속하며, 비밀번호를 몇 번 다시 입력해도 지워지지 않습니다.

이 흐름의 모든 프롬프트가 이 자격 증명은 아닙니다. 개인 키 암호 문구, HTTP 프록시 로그인, 서비스 비밀번호는 서로 다른 단계에서 요구되는 세 가지 다른 비밀입니다. 키 암호 문구는 보통 auth-user-pass가 아니라 클라이언트나 에이전트가 처리합니다. TLS 세션이 서기 전에 프롬프트가 나타난다면 잘못된 질문에 답하는 것이고 계정 초기화는 도움이 되지 않습니다. 잘못된 프롬프트를 배제한 뒤에는 남은 점검이 가장 이른 계층에서 시작합니다.

  • 마지막 자격 증명 줄이 아니라 첫 프로토콜 오류를 읽으세요. TLS, 그다음 인증, 그다음 push입니다.
  • 파일이 두 줄이고, 캐리지 리턴이 없으며, 절대 경로로 참조되는지 확인하세요.
  • 프로필과 명령 줄이 디렉티브에 대해 어긋나지 않는지 확인하세요.
  • "자격 증명이 받아들여졌다"와 "트래픽이 흐른다"를 구분하세요. 라우팅과 DNS는 이후 계층입니다.

클라이언트가 push 단계에 도달하면 자격 증명을 보는 것을 멈추고 서버가 보낸 경로와 DNS 서버를 읽기 시작하세요. 그것이 다음 문제이며, 방금 고친 비밀번호와는 아무 관련이 없습니다.

관련 글