Ghim một chương trình vào VPN trên Linux chủ yếu là vấn đề định tuyến, và quy tắc tường lửa chỉ quyết định điều gì xảy ra sau khi tuyến được chọn. Nếu bạn khớp UID của chương trình trong OUTPUT trước khi đường hầm nắm tuyến mặc định, quy tắc sẽ bỏ đúng luồng lưu lượng mà bạn muốn bảo vệ. Chính quy tắc đó cũng không thấy tiến trình phân giải trả lời các truy vấn DNS của chương trình. Sắp xếp đúng thứ tự và một tài khoản không có đặc quyền trở thành bộ chọn đường hầm chia tách đáng tin cậy.

Cho tài khoản một tuyến trước khi phán xét

Việc khớp chủ sở hữu trả lời một câu hỏi: socket cục bộ thuộc UID này có dựng gói không? Nó không chọn giao diện và không thể di chuyển lưu lượng mà bảng chính đã gửi ra qua NIC vật lý. Vì vậy một quy tắc UID không có tuyến tương ứng sẽ chặn chương trình thay vì chuyển hướng nó.

Đưa tài khoản đi qua bảng riêng của nó trước, rồi để tường lửa xác nhận kết quả. Cách này hoạt động trên một máy chủ mà iproute2 hiểu uidrange, điều mà ip rule help báo cáo trong một dòng. Bảng 100 giữ tuyến mặc định qua đường hầm, và quy tắc chỉ gửi UID 1000 vào bảng đó. Mọi thứ khác vẫn ở bảng chính và tiếp tục dùng liên kết vật lý.

ip rule add uidrange 1000-1000 table 100
ip route add default dev tun0 table 100

Tài khoản thứ hai cần một đường hầm khác nhận bảng 101 và quy tắc riêng, vì hai hồ sơ không thể cùng sở hữu tuyến mặc định trong một bảng. Hồ sơ NetworkManager là toàn hệ thống, nên phần này vẫn thủ công, và các quy tắc biến mất khi khởi động lại trừ khi có gì đó khôi phục chúng. Hãy ghi các bảng vào script khởi động cạnh định nghĩa đường hầm để việc kết nối lại không thể để một tài khoản đi sai đường.

Vì sao ngoại lệ loopback giết đường hầm

Hầu hết các bản sao của quy tắc đều bỏ mọi gói không đi ra qua loopback. Đường hầm không phải loopback, nên dạng đó cũng bỏ lưu lượng của đường hầm, và kết quả trông như một VPN chết chứ không phải lỗi tường lửa.

Khi OUTPUT chạy, nhân đã chọn giao diện ra, nên -o được so với câu trả lời của tuyến chứ không phải tiêu đề gói. Việc khớp chủ sở hữu chỉ được xem xét trong OUTPUT và POSTROUTING, và các gói được chuyển tiếp không mang socket nào để nó đọc. Sự kết hợp đó nghĩa là ngoại lệ phải nêu tên giao diện đường hầm và phủ định việc khớp đó, thay vì phủ định loopback.

Loopback vẫn xứng đáng có ACCEPT riêng phía trên DROP, vì gói DNS đầu tiên từ chương trình đi tới stub cục bộ qua lo. Đặt hai quy tắc theo thứ tự này và tài khoản tới được stub, rồi tới đường hầm, và không gì khác.

iptables -I OUTPUT 1 -o lo -m owner --uid-owner vpnapp -j ACCEPT
iptables -A OUTPUT -m owner --uid-owner vpnapp ! -o tun0 -j DROP

IPv6 là một bộ quy tắc riêng, và cặp chỉ dành cho v4 để ip6tables nguyên vẹn. Một chương trình ưu tiên bản ghi AAAA sẽ đi ra qua liên kết vật lý mà không bao giờ tới DROP. Phản chiếu cả hai quy tắc trong ip6tables, hoặc tắt IPv6 trên máy chủ khi đường hầm không mang địa chỉ v6 nào. Các quy tắc thêm thủ công cũng biến mất khi khởi động lại, nên hãy lưu chúng bằng netfilter-persistent save hoặc dựng lại cùng logic đó thành bảng nftables do đơn vị khởi động nạp.

Câu trả lời DNS đến từ tiến trình khác

Truy vấn của chương trình tới 127.0.0.53 đi qua ACCEPT loopback, và đó là nơi việc khớp UID dừng lại. Bộ phân giải stub chuyển tiếp lên trên từ tài khoản dịch vụ của chính nó, nên quy tắc gắn với vpnapp không bao giờ thấy truy vấn thứ hai. Nếu bộ phân giải chọn một máy chủ LAN cho liên kết, câu trả lời quay về qua NIC vật lý trong khi các gói của chính chương trình vẫn ở trong đường hầm. Kết quả nhìn thấy là một đường hầm hoạt động với tên bị hỏng, hoặc những cái tên do một máy chủ bạn không chọn trả lời.

Sửa việc phân giải tên trên liên kết đường hầm, không phải trong ứng dụng. Cấp cho hồ sơ đường hầm một máy chủ DNS và một miền định tuyến, rồi xác nhận bằng resolvectl query và resolvectl status tun0 rằng bộ phân giải dùng nó cho những tên quan trọng. Bên trong một không gian tên mạng, vấn đề biến mất, vì bộ phân giải của không gian chỉ biết đường hầm.

Kiểm tra quy tắc từ chính tài khoản

Hỏi nhân xem UID nhận tuyến nào, rồi gửi yêu cầu với tư cách tài khoản đó và so địa chỉ ra với địa chỉ của tài khoản bạn. Nếu cả hai báo cùng một địa chỉ, quy tắc đang khớp với thứ khác chứ không phải lưu lượng bạn đã thử. Lệnh đầu tiên bên dưới hiển thị việc tra tuyến mà không gửi gói, điều này tách lỗi định tuyến khỏi lỗi tường lửa.

ip route get 1.1.1.1 uid 1000
sudo -u vpnapp curl -s https://ifconfig.co

Bộ đếm cho thấy một quy tắc đã từng kích hoạt hay chưa. Chạy iptables -vnL OUTPUT, đọc số gói cạnh từng quy tắc, và coi số không cạnh DROP sau lưu lượng thật là lỗi khớp.

Lưu lượng được chuyển tiếp nằm ngoài thiết kế này. Một container hay VM sau cầu không có socket cục bộ thuộc sở hữu của vpnapp, nên việc khớp chủ sở hữu không bao giờ kích hoạt và các gói đó theo chính sách FORWARD. Nếu khối lượng công việc đó phải được bao phủ, hãy thử riêng và viết một chính sách gắn với cầu hoặc địa chỉ container. Thiết kế đứng vững khi bảng, ngoại lệ giao diện, bộ phân giải và các quy tắc v6 đều chỉ cùng một hướng; bỏ sót bất kỳ cái nào và tài khoản hoặc rò rỉ hoặc mất kết nối.

Bài viết liên quan