Chạy hành động sau khi kết nối VPN với tư cách người dùng, không phải daemon
Một biến môi trường ghi USER=alice không thay đổi tài khoản mà hook chạy dưới. Tách sự kiện, trình kích hoạt và hành động, rồi chứng minh UID.
Các hook kết nối trên một daemon VPN chạy với tư cách daemon, và thường điều đó có nghĩa là chúng chạy với tư cách root. Ngay lúc một trong những hook đó ghi vào thư mục nhà của ai đó hoặc khởi động lại một trợ lý màn hình, bạn đã trao cho một sự kiện mạng khả năng hành động như bất kỳ người dùng cục bộ nào. Cách tắt thông thường, đọc tên người dùng từ một biến môi trường rồi chuyển tiếp, không khắc phục được điều đó. Nó chỉ dán nhãn lại hành động mà không thay đổi tài khoản thực thi. Điều hiệu quả là một bàn giao ba phần, trong đó bước có đặc quyền nói điều gì đã xảy ra và một đơn vị riêng thực hiện hành động dưới đúng danh tính.
Tên người dùng trong môi trường không phải công tắc danh tính
Unix quyết định tiến trình được chạm vào gì dựa trên ID người dùng hiệu lực của nó, không dựa trên các chuỗi trong môi trường. Đặt USER=alice hay xuất LOGNAME thay đổi những gì phần mềm bất cẩn in ra trong đầu ra; nó không thay đổi gì trong các kiểm tra quyền sở hữu tệp. Một hook chạy với tư cách root cùng biến đó vẫn mở tệp của Alice với tư cách root.
Tên cũng không phải cùng một tên. Đăng nhập truy cập từ xa, tài khoản Unix cục bộ và tên chung của chứng chỉ là ba định danh thường trùng nhau và đôi khi không. Một người dùng xác thực với tư cách a.smith có thể không có tài khoản cục bộ nào, hoặc có thể được ánh xạ sang asmith qua một dịch vụ thư mục. Bất kỳ thiết kế nào giả định rằng một chuỗi phục vụ cả ba vai trò sẽ hỏng ngay ở nhà thầu đầu tiên.
Vì vậy câu hỏi đầu tiên không phải «người dùng nào đang kết nối» mà là «tài khoản cục bộ nào được phép thực hiện hành động này». Hãy trả lời điều đó từ cơ sở dữ liệu tài khoản của chính bạn, không phải từ những gì máy khách đã gửi:
id -u alice
getent passwd alice | cut -d: -f1,3,6
stat -c '%U:%G %a %n' /home/alice/.local/state/vpn-status.json
Lệnh thứ ba là lệnh mà mọi người bỏ qua. Nếu tệp đích thuộc sở hữu của root vì hook trước đã tạo ra nó, một hành động người dùng được cấu hình đúng sẽ thất bại với lỗi quyền trông giống sự cố systemd và không phải vậy.
Daemon ở yên chỗ của nó
Đổi User= của chính dịch vụ VPN thành Alice là đòn bẩy sai. Nó thay đổi cách daemon liên kết giao diện, đọc khóa, ghi tệp trạng thái và quản lý tuyến đường, và chuyển toàn bộ rủi ro đó vào một đơn vị đang hoạt động tốt. Daemon cần đặc quyền của nó; hành động tiếp theo thì không.
Hãy xem đơn vị thực sự làm gì trước khi chạm vào nó. Hai lệnh cho bạn biết danh tính thực thi và các dòng lệnh chính xác đang có hiệu lực, bao gồm mọi thứ mà một phần ghi đè đã thêm vào:
systemctl show -p User -p Group -p ExecStart openvpn-server@server
systemctl cat openvpn-server@server
Trong bản cài đặt gói mặc định, daemon là root và tập lệnh --up thừa hưởng điều đó. Hãy xác nhận thay vì giả định, vì một bản phân phối được tăng cường có thể đã chuyển nó sang một tài khoản chuyên dụng, và một drop-in viết tay có thể chạy hook qua một lớp bao với các quy tắc khác.
Ba phần có tên, một trình kích hoạt hẹp
Cách bố trí tồn tại qua bảo trì có một sự kiện, một trình kích hoạt và một hành động. Daemon báo rằng một phiên có tên đã lên. Trình kích hoạt chấp nhận sự thật nhỏ đó và yêu cầu một hành động cố định. Hành động chạy dưới tài khoản cục bộ sở hữu các tệp mà nó chạm vào. Không có gì trong chuỗi chấp nhận một đường dẫn lệnh tùy ý.
Bản thân hành động là một đơn vị dịch vụ bình thường, và danh tính là một chỉ thị bên trong nó:
[Service]
Type=oneshot
User=alice
Group=alice
ExecStart=/usr/local/bin/refresh-vpn-status --slot %i
Lưu ý rằng User=alice áp dụng cho toàn bộ đơn vị, và systemd ghi lại một ngoại lệ đáng biết: một lệnh có tiền tố + vẫn chạy với đầy đủ đặc quyền bất kể điều đó. Tiền tố đó là công cụ hợp lệ cho một bước có đặc quyền duy nhất và là cách thuận tiện để hoàn tác sự tách biệt bạn vừa xây, nên hãy coi mọi + trong đơn vị đó là thứ phải biện minh khi rà soát.
Giữ đầu vào của trình kích hoạt tối thiểu và không bí mật. Định danh phiên, tài khoản được ánh xạ và dấu thời gian thường là tất cả những gì cần. Không bao giờ chuyển qua ranh giới một mẩu shell, một đường dẫn tệp thực thi hay một khối môi trường do máy khách cung cấp, vì phía nhận không có cách nào phân biệt giá trị hợp lệ với giá trị bị chèn.
Xác minh danh tính thực sự đã chạy
Rà soát cấu hình không phải bằng chứng. Sau một kết nối thử đã được phê duyệt, hãy xác nhận UID đã chạm vào tệp và UID mà daemon vẫn đang dùng:
stat -c '%U %G %y %n' /home/alice/.local/state/vpn-status.json
ps -o user=,pid=,cmd= -p "$(pgrep -o -f 'openvpn --daemon')"
systemd-run --uid=alice --pipe /usr/local/bin/refresh-vpn-status --dry-run
Dòng đầu tiên phải cho thấy Alice và một dấu thời gian trong cửa sổ thử của bạn. Dòng thứ hai vẫn phải cho thấy root hoặc tài khoản dịch vụ của gói, điều đó chứng minh daemon không di chuyển. Dòng thứ ba là cách rẻ nhất để thử quyền của hành động mà không chờ một lần kết nối lại thật.
Nếu chủ sở hữu tệp là Alice nhưng nhóm hoặc chế độ của nó đã thay đổi giữa các lần chạy, hãy tìm một UMask= hoặc một khác biệt về quyền thư mục thay vì lỗi logic. Nếu dấu thời gian không hề nhúc nhích, trình kích hoạt chưa bao giờ kích hoạt, và bước tiếp theo hữu ích là journalctl -u cho đơn vị hành động chứ không phải một lần kết nối lại nữa của máy khách.
Điều mà một sự kiện kết nối vẫn chưa chứng minh
Một đường hầm đã thiết lập là một sự thật duy nhất. Nó không có nghĩa là phiên màn hình đã được mở khóa, bus phiên có thể truy cập, hay tài nguyên riêng mà hành động muốn đã sẵn có. Một đơn vị oneshot dưới UID của Alice có danh tính của cô ấy và không có gì từ phiên của cô ấy, vì vậy mọi thứ cần bus đồ họa phải đi qua cơ chế phiên người dùng được hỗ trợ và định nghĩa điều gì xảy ra khi cô ấy đăng xuất.
Các lần kết nối lại cũng lặp lại. Một máy tính xách tay ngủ trên tàu có thể tạo ra hàng chục sự kiện kết nối trong một giờ, và một hook thêm một dòng mỗi lần biến tệp trạng thái thành một nhật ký không ai đọc được. Hãy làm cho hành động có tính idempotent, hoặc để nó thay thế một bản ghi duy nhất được khóa theo phiên.
Cuối cùng, giới hạn bán kính thiệt hại ngay trong đơn vị: một chế độ xem hệ thống tệp chỉ đọc ngoài thư mục trạng thái, không có đặc quyền mới, và một thời gian chờ rõ ràng. Sự tách biệt giữa daemon và hành động người dùng đáng giá tự thân, nhưng đáng giá hơn nhiều khi hành động không thể làm gì khác ngoài điều duy nhất bạn đã phê duyệt.