Khi một script client-connect thoát với mã khác không, OpenVPN từ chối máy khách, và người vận hành thường cho rằng script client-disconnect tương ứng sẽ thu dọn mọi thứ. Thường thì nó không bao giờ chạy, vì việc xử lý disconnect phụ thuộc vào những gì giai đoạn connect đã hoàn tất. Coi việc từ chối và hoàn tác là một cơ chế duy nhất chính là điều biến một lần đăng nhập thất bại thành một quy tắc tường lửa bị rò rỉ hoặc một bản ghi mồ côi. Đây là hai nghĩa vụ riêng biệt, và chỉ một trong hai là tự động. Hãy đọc đường thoát của chính hook trước khi đổ lỗi cho máy chủ vì đã để lại trạng thái.

Thoát với mã khác không sẽ từ chối máy khách; nó không hoàn tác công việc của bạn

Trạng thái thoát của client-connect là quyết định kết nạp. Không có nghĩa là kết nạp, bất cứ điều gì khác có nghĩa là dỡ bỏ phiên bản trước khi nó được thiết lập đầy đủ. Không có gì trong hợp đồng đó kiểm tra những gì script đã làm với thế giới bên ngoài. Nếu script đã thêm một quy tắc iptables, ghi một dòng hoặc tăng một bộ đếm, những thay đổi đó tồn tại qua lần từ chối.

Sự bất đối xứng tương tự cũng áp dụng cho plugin, nơi nhiều hàm connect có thể chạy cho một phiên bản. Một lỗi về sau không hủy bỏ hồi tố một thành công trước đó, và hàm trước đó không có cách nào biết rằng một hàm anh em đã thất bại.

Từ chối là tự động, dọn dẹp thì không

Hướng dẫn OpenVPN 2.6 gắn việc xử lý client-disconnect với việc xử lý connect trước đó thành công. Một ngoại lệ được ghi lại bao gồm các chuỗi xếp tầng: nếu một bước connect đã trả về thành công, các bước disconnect tương ứng vẫn chạy khi phiên bản bị dỡ bỏ, ngay cả khi một bước connect sau đó thất bại. Ngoại lệ đó hẹp và phụ thuộc vào số callback được cấu hình, vì vậy kiểm thử một script shell không cho bạn biết gì về một chuỗi.

Hãy lập bản đồ cấu hình thực tế trước khi thiết kế xoay quanh nó: script nào, plugin nào và theo thứ tự nào chúng chạy. Bảng dưới đây liệt kê những gì một thiết lập dở dang để lại.

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

Mỗi hàng kết thúc giống nhau với máy khách và khác nhau với máy chủ. Máy khách không thể biết trường hợp nào đã xảy ra, nên hook phải hoàn tác những gì nó đã làm trước khi trả về trạng thái lỗi.

Bù trừ bên trong script đã thất bại

Hãy xác thực mọi thứ có thể trước khi chạm vào trạng thái chia sẻ: các trường chứng chỉ, tính sẵn có của địa chỉ, nhóm mà máy khách thuộc về. Lần từ chối rẻ nhất là lần xảy ra trước mọi việc cấp phát. Khi thiết lập bao gồm nhiều thao tác bên ngoài, hãy ghi lại những thao tác đã hoàn tất và hoàn tác chúng theo thứ tự ngược lại trước khi trả về trạng thái lỗi.

Một trường hợp cụ thể: script tạo một mục tường lửa cho mỗi máy khách, rồi thất bại khi ghi dòng trạng thái mô tả nó. Nhánh thất bại nên xóa mục vừa tạo rồi sau đó thoát với mã khác không. Chờ một sự kiện disconnect có thể không bao giờ được lên lịch chính là cách một quy tắc sống lâu hơn phiên mà nó dành cho.

Làm cho việc bù trừ có tính idempotent và có chủ sở hữu

Việc dọn dẹp phải an toàn khi chạy hai lần và chính xác về những gì nó được phép xóa. Một sự kiện muộn từ lần thử trước không nên xóa việc cấp phát của một phiên mới hơn chỉ vì cả hai chia sẻ một tên chung hoặc một địa chỉ đường hầm được tái sử dụng. Hãy ghi lại handle hoặc id thao tác cụ thể cùng với tài nguyên, và xóa theo khóa đó thay vì theo tên. Giao dịch cơ sở dữ liệu chỉ giúp với những thay đổi của chính cơ sở dữ liệu; một quy tắc tường lửa nằm ngoài ranh giới đó cần một bước bù trừ rõ ràng.

Giả định rằng tiến trình có thể chết giữa thay đổi và dọn dẹp, và thêm một bộ đối soát so sánh trạng thái bên ngoài với danh sách máy khách được kết nạp theo lịch.

Kiểm thử ba con đường thực sự khác nhau

Hãy luyện ba trường hợp: từ chối trước khi bất cứ điều gì được cấp phát, từ chối sau khi cấp phát một phần, và một phiên được kết nạp bình thường kết thúc sau đó. Ba trường hợp này tạo ra các dòng nhật ký khác nhau và phần dư khác nhau. Hãy xác minh trạng thái bên ngoài sau mỗi trường hợp, không chỉ thông báo lỗi của máy khách, vì lỗi của máy khách là kết quả mong đợi trong hai trên ba trường hợp.

Ghi lại giai đoạn của hook, trạng thái thoát và những gì đã được cấp phát hoặc giải phóng, không kèm thông tin xác thực. Nhật ký hook nêu tên tài nguyên mà nó tạo ra chính là điều khiến một quy tắc bị rò rỉ có thể tìm thấy sáu tháng sau.

Bài viết liên quan