client-connect 스크립트가 영이 아닌 코드로 종료되면 OpenVPN은 클라이언트를 거부하며, 운영자는 보통 대응하는 client-disconnect 스크립트가 정리해 줄 것이라고 가정합니다. 그러나 실제로는 거의 실행되지 않습니다. disconnect 처리가 connect 단계가 무엇을 완료했는지에 조건부로 달려 있기 때문입니다. 거부와 롤백을 하나의 메커니즘으로 보는 것이 실패한 로그인을 유출된 방화벽 규칙이나 고아 레코드로 바꾸는 이유입니다. 이 둘은 별개의 의무이며, 그중 하나만 자동입니다. 서버가 상태를 남겼다고 비난하기 전에 hook 자체의 종료 경로를 읽으십시오.

영이 아닌 종료는 클라이언트를 거부하지만 작업을 되돌리지는 않는다

client-connect의 종료 상태는 입장 결정입니다. 영은 입장을 뜻하고, 그 밖의 값은 인스턴스가 완전히 확립되기 전에 해체한다는 뜻입니다. 그 계약 어디에도 스크립트가 외부 세계에 이미 한 일을 검사하는 부분은 없습니다. 스크립트가 iptables 규칙을 추가했거나 행을 썼거나 카운터를 올렸다면, 그 변경은 거부 이후에도 살아남습니다.

같은 비대칭은 플러그인에도 적용됩니다. 하나의 인스턴스에 대해 여러 connect 함수가 실행될 수 있기 때문입니다. 나중의 실패는 앞선 성공을 소급해 취소하지 않으며, 앞선 함수는 형제 함수가 실패했다는 것을 알 방법이 없습니다.

거부는 자동이지만 정리는 아니다

OpenVPN 2.6 매뉴얼은 client-disconnect 처리를 앞선 connect 처리가 성공했는지에 연결합니다. 문서화된 예외는 연쇄 사슬을 다룹니다. 한 connect 단계가 이미 성공을 반환했다면, 이후 connect 단계가 실패했더라도 인스턴스 해체 시 해당 disconnect 단계는 여전히 실행됩니다. 그 예외는 좁고 구성된 콜백 수에 따라 달라지므로, 셸 스크립트 하나를 시험해 봐도 사슬에 대해서는 아무것도 알 수 없습니다.

설계하기 전에 실제 구성을 지도화하십시오. 어떤 스크립트가, 어떤 플러그인이, 어떤 순서로 실행되는지입니다. 아래 표는 절반만 끝난 설정이 무엇을 남기는지 보여 줍니다.

What the hook did before failingWhat the client seesWhat stays behind
Created a firewall entryA rejection, with no tunnelA rule matching an address nobody owns
Wrote a state rowA rejection, with no tunnelA record of a session that never started
Allocated a tunnel addressA rejection, with no tunnelAn address marked busy until something releases it

모든 행은 클라이언트에게는 같은 방식으로, 호스트에게는 다른 방식으로 끝납니다. 클라이언트는 어느 경우가 일어났는지 알 수 없으므로, hook은 실패 상태를 반환하기 전에 자기가 한 일을 되돌려야 합니다.

실패한 스크립트 안에서 보상하라

공유 상태를 건드리기 전에 검증할 수 있는 것은 모두 검증하십시오. 인증서 필드, 주소 가용성, 클라이언트가 속한 그룹입니다. 가장 값싼 거부는 어떤 할당도 일어나기 전의 거부입니다. 설정이 여러 외부 작업에 걸치면 어느 것이 완료되었는지 기록하고, 실패 상태를 반환하기 전에 역순으로 되돌리십시오.

구체적인 사례: 스크립트가 클라이언트별 방화벽 항목을 만들고 나서 그것을 설명하는 상태 행을 쓰는 데 실패합니다. 실패한 분기는 방금 만든 항목을 제거한 뒤 영이 아닌 코드로 종료해야 합니다. 예정되지 않을 수도 있는 disconnect 이벤트를 기다리는 것이 바로 규칙이 원래 의도한 세션보다 오래 사는 방식입니다.

보상을 멱등적으로 만들고 소유권을 부여하라

정리는 두 번 실행해도 안전해야 하고 무엇을 삭제해도 되는지 정확해야 합니다. 이전 시도의 늦은 이벤트가 단지 공통 이름이나 재활용된 터널 주소를 공유한다는 이유로 더 새로운 세션의 할당을 제거해서는 안 됩니다. 구체적인 핸들이나 작업 ID를 리소스와 함께 기록하고, 이름이 아니라 그 키로 삭제하십시오. 데이터베이스 트랜잭션은 데이터베이스 자체의 변경에만 도움이 됩니다. 그 경계 밖의 방화벽 규칙에는 명시적인 보상 단계가 필요합니다.

프로세스가 변경과 정리 사이에 죽을 수 있다고 가정하고, 일정에 따라 외부 상태를 승인된 클라이언트 목록과 비교하는 조정기를 추가하십시오.

실제로 다른 세 경로를 시험하라

세 가지 사례를 연습하십시오. 아무것도 할당되기 전의 거부, 부분 할당 후의 거부, 그리고 나중에 종료되는 정상적인 승인 세션입니다. 이 셋은 서로 다른 로그 줄과 서로 다른 잔여물을 만듭니다. 각각 후에 외부 상태를 검증하고, 클라이언트의 실패 메시지만 보지 마십시오. 셋 중 둘에서는 클라이언트의 실패가 기대되는 결과이기 때문입니다.

hook 단계, 종료 상태, 무엇이 할당되거나 해제되었는지를 자격 증명을 빼고 기록하십시오. 자기가 만든 리소스를 명시하는 hook 로그가 바로 유출된 규칙을 여섯 달 뒤에도 찾을 수 있게 합니다.

관련 글