Docker의 OpenVPN: 최소 클라이언트에 필요한 런타임 계약
검토된 이미지, 보호된 프로필 하나, TUN 액세스와 NET_ADMIN. 컨테이너가 얻는 것, 얻지 말아야 할 것, 터널이 작동함을 증명하는 방법.
VPN 클라이언트를 컨테이너에 넣으면 내부에 터널이 있는 네임스페이스 하나를 얻지만, 모든 트래픽을 VPN으로 라우팅하는 호스트를 얻는 것은 아닙니다. 이 둘은 별개의 프로젝트이며, 둘을 하나로 취급하는 것이 컨테이너가 실수로 권한을 갖게 되는 경로입니다. 이 안내서는 첫 번째만 다루며, 검토된 이미지 하나, 보호된 프로필 하나, 그리고 자체 네트워크 네임스페이스에 국한된 클라이언트 프로세스입니다.
누군가 이미 승인한 산출물에서 시작하되, 아래 단계는 openvpn 실행 파일과 인증서 및 키 자료가 마운트 안에서 이미 해결된 완전한 클라이언트 프로필을 포함하는 이미지를 가정합니다. 소프트웨어를 빌드하지 않고 자격 증명을 발급하지 않으며 root 없는 런타임과 네이티브 Windows 컨테이너는 다루지 않습니다. 각각이 답을 바꾸기 때문입니다.
컨테이너가 얻는 것과 얻지 않는 것
프로필과 그것이 참조하는 모든 것을 제한된 권한의 로컬 디렉터리에 두고 읽기 전용으로 마운트하되, 프로필 내부의 상대 경로는 그 마운트 안에서 해결되어야 합니다. 인증서가 프로필보다 한 디렉터리 위에 있고 컨테이너가 그것을 결코 보지 못할 때 사람들이 놓치는 세부 사항입니다.
services:
vpn:
image: ${VPN_CLIENT_IMAGE:?set the reviewed image reference}
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ./vpn:/vpn:ro
working_dir: /vpn
entrypoint: ["openvpn"]
command: ["--config", "/vpn/client.ovpn"]
그 파일은 세 가지를 제공하고 그 밖의 것은 요구하지 않습니다. 배포 정책이 그렇게 정한다면 이미지 참조를 다이제스트로 고정해야 하며, 프로필은 검토된 것이어야지 호스트에서 누군가 그 자리에서 편집한 사본이어서는 안 됩니다.
여기서 읽기 전용이란 컨테이너가 호스트의 파일을 변경할 수 없다는 뜻이며, 마운트한 개인 키를 읽을 수 없다는 뜻이 아닙니다. 그것이 마운트의 핵심이며, 파일 권한과 소유권, 그리고 비밀이 호스트에 어떻게 들어왔는지가 여전히 중요한 이유입니다. 암호를 명령줄에 전달하거나 이미지 계층에 구워 넣어서는 안 되며, 둘 다 나중에 지울 수 없는 곳에 남습니다.
기능, entrypoint, 그리고 그 사이의 함정
두 가지 설정이 필요하며 서로 무관한 일을 합니다. NET_ADMIN는 컨테이너의 네임스페이스 안에서 인터페이스와 경로를 조작할 수 있게 합니다. /dev/net/tun 장치 항목은 프로세스가 터널 장치를 아예 열 수 있게 하며, 하나만 주면 분명한 실패가 나오므로 편리합니다. TUN 장치가 없으면 열 때 실패하고, 기능이 없으면 클라이언트가 인터페이스를 구성하려 할 때 실패합니다.
둘 다 privileged: true를 정당화하지 않습니다. 권한이 있는 컨테이너는 모든 기능과 장치 액세스를 받는데, 이는 이 워크로드에 필요한 것보다 훨씬 큰 허용입니다. 런타임 권한과 기능 참조는 그 허용이 어디까지 미치는지 설명합니다. chart나 블로그 글이 그것을 요구한다면, 요구 사항이 사실은 빠진 장치 경로나 빠진 기능이었는지 먼저 확인하십시오.
entrypoint를 재정의하는 것이 물어뜯는 부분입니다. openvpn를 직접 호출하는 것은 일반 이미지에는 맞고, 시작 스크립트가 데몬을 실행하기 전에 방화벽 규칙, 리졸버 파일, 자격 증명을 준비하는 모든 제품 이미지에는 틀립니다. 그 스크립트를 재정의하면 준비가 건너뛰어지고 절반만 구성된 네임스페이스에서 올라온 터널을 디버그하게 됩니다.
클라이언트를 포그라운드에서 실행하고 컨테이너의 주 프로세스가 되게 하십시오. 데몬화하는 프로세스는 즉시 종료되고, 컨테이너는 루프로 재시작되며, 필요한 로그는 모두 똑같아 보이는 재시작 사이에 흩어집니다.
네임스페이스 안에서 검증하고 호스트에서 검증하지 마십시오
스택을 올리고 클라이언트가 연결된 상태에 도달했음을 로그에서 확인한 다음, 실제로 터널을 보유한 네임스페이스를 검사하십시오.
docker compose up -d
docker compose logs --tail 40 vpn
docker compose exec vpn ip -br addr
docker compose exec vpn ip route
주소 목록에는 풀에서 온 주소를 가진 터널 인터페이스가 보여야 하고, 경로 테이블에는 그것을 가리키는 푸시된 목적지가 보여야 합니다. 인터페이스가 없으면 클라이언트는 그것을 만드는 단계에 도달하지 못한 것입니다. 그것이 있는데도 어떤 경로도 사용하지 않는다면 살펴볼 곳은 push 또는 경로 옵션입니다.
주소 목록에는 풀에서 온 주소를 가진 터널 인터페이스가 보여야 하고, 경로 테이블에는 그것을 가리키는 푸시된 목적지가 보여야 합니다. 인터페이스가 없으면 클라이언트는 그것을 만드는 단계에 도달하지 못한 것이며, 그것이 있는데도 어떤 경로도 사용하지 않는다면 살펴볼 곳은 push 또는 경로 옵션입니다.
연결이 끊긴 상태도 계약의 일부입니다
그다음 중요한 목적지를 터널을 사용해야 하는 네임스페이스에서, ping만이 아니라 애플리케이션으로 시험하십시오. 이름 해석은 별개의 실패이며 명시적으로 시험할 가치가 있습니다. 푸시된 DNS 주소는 컨테이너의 리졸버를 스스로 갱신하지 않으며, push를 무시하는 독립형 클라이언트는 이미지가 구성한 것을 통해 이름을 해석합니다.
워크로드가 일반 경로를 절대 사용해서는 안 된다면 방화벽 규칙과 네임스페이스 내부의 준비 신호로 그것을 명시적으로 설계하십시오. Kill switch는 직접 작성하고 시험하는 정책이며, 컨테이너에서 클라이언트를 실행해 물려받는 속성이 아닙니다.
재연결 동작에는 자체 점검이 필요하며, 재시작 정책은 프로세스가 재시작됨을 증명할 뿐 그 사이에 트래픽이 어떻게 되는지는 말해 주지 않으므로, 터널을 멈추고 의도한 목적지를 시도해 보면서 연결이 거부되는지, 멈추는지, 아니면 컨테이너의 일반 경로로 조용히 성공하는지 관찰해야 하고, 마지막 결과가 중요한 것이며 건강한 로그에서는 보이지 않습니다.포워딩, 변환 및 방화벽 규칙은 외부 경로의 일부로 남고, 게시된 포트는 VPN 인터페이스가 아닙니다.