Cách tìm ra sandbox systemd nào đã từ chối hook OpenVPN của bạn
Hook chạy được khi chạy bằng tay với quyền root và thất bại dưới systemd vì unit giữ ít khả năng hơn, /dev nhỏ hơn và các đường dẫn chỉ đọc.
Hook hoạt động khi bạn chạy nó bằng tay với quyền root và trả về Operation not permitted ngay lúc dịch vụ khởi động nó. Tệp không thay đổi giữa các lần chạy đó; môi trường mới thay đổi. systemd trao cho tiến trình một thế giới nhỏ hơn: ít khả năng hơn, một /dev khác, các đường dẫn hệ thống tệp chỉ đọc và một giới hạn tác vụ. Hãy đọc lỗi như một ranh giới đã từ chối một thao tác cụ thể, không phải như một script bị hỏng.
Bắt đầu từ thao tác đã bị từ chối
Một dòng log nói rằng unit thất bại gần như không cho bạn biết gì. Dòng hữu ích nêu tên lời gọi đã bị từ chối: một nút thiết bị không thể mở, một thư mục không thể ghi, một namespace không thể tạo. Hãy tìm lời gọi đó trước khi chạm vào bất kỳ chỉ thị nào, vì mỗi lời gọi ánh xạ tới một hạn chế khác nhau. Sau đó đọc unit thực sự đã chạy, kể cả drop-in, thay vì tệp mà gói đã gửi.
Truy vấn dưới đây in đường dẫn fragment, các thư mục drop-in và các thuộc tính quan trọng đối với việc từ chối thiết bị hoặc hệ thống tệp. Hãy đọc tất cả trước khi quyết định ranh giới nào là liên quan:
systemctl show "[email protected]" -p FragmentPath -p DropInPaths -p PrivateDevices -p DevicePolicy -p DeviceAllow -p ProtectHome -p ProtectSystem -p TasksMax
journalctl -u "[email protected]" -b --no-pager | grep -iE 'operation not permitted|permission denied|cannot open|failed'Không có gì trong khối đó ghi. Nếu lời gọi bị từ chối không có trong journal, hãy tăng mức độ chi tiết của client cho một lần chạy thay vì đoán các chỉ thị. Một tiến trình đang chạy chứng minh unit đã khởi động, không phải hook đã chạy với các đặc quyền cần thiết.
Một namespace cần quyền mount mà thiết bị TUN không bao giờ dùng
Hai thao tác trông giống nhau trong cùng một hook có hồ sơ quyền khác nhau. Việc mở /dev/net/tun và tạo giao diện cần quản trị mạng cộng với quyền truy cập vào nút đó. Việc tạo một namespace mạng có tên cũng cần công việc liên quan đến mount, vì công cụ namespace mount một /sys góc nhìn mới và gắn một thư mục trạng thái vào vị trí. Unit client thượng nguồn cố ý để việc quản trị mount ra ngoài tập giới hạn của nó, vì vậy bước namespace bị từ chối trong khi bản thân tunnel sẽ hoạt động.
Đó là lý do việc cấp nút thiết bị không cứu được script. Một giấy phép thiết bị trả lời một câu hỏi, liệu tiến trình này có được mở nút này không, còn câu hỏi namespace là câu hỏi khác. Hãy khớp lời gọi thất bại với khả năng của nó trước khi mở rộng bất cứ điều gì.
Một thử nghiệm trong shell của bạn thừa hưởng mọi khả năng mà root không hạn chế có, và unit thừa hưởng những gì tập giới hạn của nó giữ lại. Hai lần chạy là hai thử nghiệm khác nhau ngay cả khi tệp nhị phân giống hệt nhau.
Thiết bị, đường dẫn được bảo vệ và giới hạn tác vụ
Khi đã biết thao tác bị từ chối, ba nhóm chỉ thị giải thích hầu hết các thất bại. Quyền truy cập thiết bị được chi phối bởi PrivateDevices, thứ thay thế /dev bằng một tập thiết bị giả tối thiểu, và bởi DevicePolicy với DeviceAllow, chúng quyết định cgroup cho phép gì. /dev/net/tun không thuộc tập tối thiểu đó, nên một unit có PrivateDevices=yes không thể mở nó bất kể chính sách thiết bị nói gì.
Bảo vệ hệ thống tệp trả lời câu hỏi thứ hai. ProtectHome=yes làm cho /home, /root và /run/user không thể truy cập, và ProtectSystem=full làm cho /usr, /boot và /etc chỉ đọc. Vì vậy một hook ghi bên cạnh tệp cấu hình của chính nó hoạt động trong shell của bạn và thất bại dưới unit, với cùng hình dạng quyền như một khả năng bị thiếu. Hãy chuyển đầu vào có thể ghi tới một đường dẫn mà chính sách cho phép, hoặc thêm một ReadWritePaths= mục hẹp.
Giới hạn tài nguyên là nhóm thứ ba, và ít rõ ràng nhất. TasksMax= giới hạn số tác vụ mà unit có thể tạo, và giới hạn bộ mô tả tệp hoạt động tương tự với socket và handle. Một triển khai đã vượt giới hạn sẽ thất bại với lỗi tài nguyên thay vì lỗi quyền, đó là lý do thông điệp chính xác vẫn quan trọng.
Đưa bước namespace ra khỏi hook
Việc chuẩn bị cần một đặc quyền mà client không được giữ thuộc về unit riêng của nó. Một oneshot chạy trước client, giữ kết quả của nó và được sắp xếp trước kết nối sẽ trao cho bước namespace những gì nó cần mà không giao các quyền đó cho tiến trình tunnel. Sau đó client tham chiếu unit đó thay vì gọi script từ up hook của nó.
Mẫu dưới đây là một hình dạng khởi đầu, không phải thứ để áp dụng mà không đọc. Nó giữ việc chuẩn bị ở nơi đặc quyền sống và để unit tunnel không bị chạm vào:
# /etc/systemd/system/[email protected]
[Unit]
Description=Namespace preparation for %i
Before=openvpn-client@%i.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/netns-up.sh %i
CapabilityBoundingSet=CAP_NET_ADMIN CAP_SYS_ADMIN
[Install]
WantedBy=multi-user.targetThêm thứ tự bằng một drop-in thay vì sửa unit đã đóng gói, để một bản nâng cấp không thể âm thầm bỏ thay đổi của bạn. Thư mục được khóa theo tên instance, nên hồ sơ thứ hai cần tệp riêng:
sudo mkdir -p /etc/systemd/system/[email protected]
sudo tee /etc/systemd/system/[email protected]/10-namespace.conf >/dev/null <<'CONF'
[Unit]
[email protected]
[email protected]
CONF
sudo systemctl daemon-reloadLặp lại cùng lời gọi sau thay đổi
Một bản sửa chỉ được xác nhận khi cùng lời gọi tạo ra kết quả khác. Khởi động lại cả hai unit, chạy thao tác đã thất bại, và so sánh kết quả của nó với dòng journal bạn đã lưu trước thay đổi. Một unit khởi động trong khi hook không bao giờ chạy lại không phải là bản sửa.
Chuỗi dưới đây giữ so sánh trung thực, vì nó kiểm tra lời gọi bị từ chối chứ không phải màu của unit. Lưu đầu ra của nó bên cạnh dòng journal bạn đã chụp trước thay đổi:
sudo systemctl restart "[email protected]" "[email protected]"
systemctl status "[email protected]" --no-pager
journalctl -u "[email protected]" -b --no-pager | grep -c 'Operation not permitted'
ip -brief addr show tun0Hãy đếm các lần từ chối thay vì đọc dòng cuối, vì một hook có thể thất bại, thử lại và thành công, và chỉ con số cho thấy ranh giới có dịch chuyển hay không. Để lại một chú thích trong drop-in nêu tên chỉ thị bạn đã nới lỏng và lý do, vì bản nâng cấp tiếp theo so sánh cấu hình của bạn với mặc định đã đóng gói. Một unit chỉ khởi động sau khi mọi bảo vệ bị tắt chưa được sửa.