오류 없이 실행되면서도 파일이 없다고 보고하는 훅은 대개 제 일을 실패하는 것이 아니라 엉뚱한 곳을 보고 있다. 스크립트의 상대 경로는 프로세스의 작업 디렉터리를 기준으로 해석되고, 서비스는 당신이 고르지 않은 작업 디렉터리로 시작한다. 스크립트 옆의 파일과 프로세스가 여는 파일이 같은 것이 되는 것은 무언가가 둘을 고정할 때뿐이다. 이 간극이 권한 문제로 잘못 읽히는 훅 실패의 상당 부분을 설명한다.

권한보다 먼저 경로를 확인해야 한다, 왜냐하면 잘못된 파일에 대한 권한 오류도 결국 잘못된 파일의 오류일 뿐이기 때문이다.

상대 경로는 파일이 아니라 프로세스를 기준으로 해석된다

스크립트가 open("policy.json") 를 호출하면 커널은 그 이름을 프로세스의 현재 작업 디렉터리를 기준으로 해석한다. 스크립트 자체의 위치는 그 탐색에 전혀 관여하지 않는다. OpenVPN 은 자기가 고른 디렉터리에서 훅을 시작하고 cd 옵션으로 그것을 바꿀 수 있으므로, 자기 폴더에서 손으로 시험한 스크립트는 데몬이 실행할 때 다르게 동작한다. 수동 시험은 통과하고 서비스 실행은 실패하며, 로그 줄은 사라진 이름만 언급한다.

Python 은 스크립트의 위치를 __file__ 로 드러내며, 바로 그것이 고정을 가능하게 한다. Path(__file__).resolve().parent / "policy.json" 는 프로세스가 아니라 파일에서 경로를 만든다. 그 한 줄이 권한에는 아무것도 건드리지 않고 작업 디렉터리 의존성을 없앤다.

경로를 의도적으로 고정하고, 그다음 resolve 가 무엇을 하는지 확인한다

resolve() 는 심볼릭 링크를 따라가므로, 훅을 제자리에 링크하는 설치에서는 링크를 담은 디렉터리가 아니라 링크 대상에 고정된다. 버전이 붙은 배포에서는 대개 그것이 원하는 동작이고, 심볼릭 링크 옆에 데이터를 두는 배치에서는 틀린 동작이다. 배포가 어느 쪽을 뜻하는지 정하고, 운영에서 차이를 발견하는 대신 그에 맞게 코드를 작성한다. Path(__file__).parent 없이 resolve() 는 링크 자체의 디렉터리를 유지한다.

데이터가 프로그램이 아니라 인스턴스에 속한다면 설정에서 절대 경로를 가져온다. 위치를 하드코딩하면 훅이 하나의 호스트 배치에 묶인다.

네 경계를 차례로 지나간다

작업 디렉터리에서 시작해 유닛의 WorkingDirectory, OpenVPN 의 cd 옵션, exec 전에 디렉터리를 바꾸는 래퍼를 비교한다. 그다음 해석된 위치를 확인한다, 다른 배포에 있는 같은 이름의 파일이 탐색에 응답해 오류를 숨길 수 있기 때문이다. 다음은 파일 시스템의 시점으로, chroot 나 서비스의 마운트 네임스페이스는 호스트의 절대 경로를 그냥 없게 만들 수 있다. 마지막은 접근 신원이며, 이는 가시성과 별개의 질문이다.

ProtectHome 와 PrivateTmp 는 서로 다른 위치를 숨기고, 둘 다 호스트에서 가져온 디렉터리 목록에는 나타나지 않는다. 셸에서 root 에게 존재하는 경로가 유닛의 네임스페이스 안에서는 보이지 않을 수 있고, 그래서 서비스 밖에서 같은 명령을 시험하는 것이 그토록 적게 증명한다.

서비스를 넓히지 않고 탐색을 고친다

경로를 고정하는 것은 첫 번째 경계만 해결한다. 서비스가 숨기는 디렉터리를 드러내지도 않고, 권한 없는 계정에 root 전용 파일 접근을 주지도 않는다. script-security 를 올리는 것은 OpenVPN 이 어떤 스크립트를 실행할지 바꾼다; 상대 이름이 어디에서 해석되는지에는 아무 영향이 없다. 서비스 전체의 작업 디렉터리를 바꾸는 것은 문제보다 넓다, 다른 모든 상대 인증서, 로그, 보조 경로가 함께 움직이기 때문이다.

좁은 수정을 선호한다, 즉 스크립트 안의 결정적 경로, 인스턴스 데이터에 대한 명시적 설정 값, 그리고 훅이 실제로 실행되는 계정에 맞는 파일 소유권이다.

경계를 지목하는 증거를 기록한다

쓸모 있는 실패 보고에는 세 가지가 들어간다, 즉 훅이 시도한 정확한 경로, 그것을 만들어 낸 환경, 그리고 어느 경계가 거부했는지다. 파일 내용을 쏟아 내지 않고 해석된 경로와 예외를 출력한다. 이 세 가지가 잘못된 고정과 숨은 마운트와 권한 거부를 갈라내며, 셋은 서로 다른 수정을 요구한다.

root 가 터미널에서 파일을 읽을 수 있다는 주장은 그 질문 중 하나에만 답하고 나머지 어느 것에도 답하지 않는다. 대신 서비스 자체의 문맥에서 시험해야 한다.

관련 글