Dale Hagglund là tại chỗ. Vì vậy, tôi sẽ nói điều tương tự nhưng theo một cách khác, với một số chi tiết và ví dụ cụ thể. ☺
Điều đúng đắn cần làm trong thế giới Unix và Linux là:
- để có một chương trình nhỏ, đơn giản, dễ nghe, chạy như siêu người dùng và liên kết ổ cắm nghe;
- để có một chương trình nhỏ, đơn giản, dễ kiểm toán khác, bỏ các đặc quyền, được sinh ra bởi chương trình đầu tiên;
- để có được dịch vụ, trong một chương trình thứ ba riêng biệt , chạy trong tài khoản không phải là siêu người dùng và chuỗi được tải bởi chương trình thứ hai, hy vọng đơn giản sẽ kế thừa một bộ mô tả tệp mở cho ổ cắm.
Bạn có ý tưởng sai về nơi có nguy cơ cao. Nguy cơ cao nằm ở việc đọc từ mạng và hành động theo những gì được đọc không phải trong các hành động đơn giản là mở ổ cắm, ràng buộc nó với một cổng và gọi listen()
. Đó là một phần của dịch vụ truyền thông thực tế có rủi ro cao. Các phần mở, bind()
và listen()
, và thậm chí (ở một mức độ nào đó) phần đó accepts()
không phải là rủi ro cao và có thể được chạy dưới sự bảo trợ của siêu người dùng. Họ không sử dụng và hành động (ngoại trừ địa chỉ IP nguồn trong accept()
trường hợp) dữ liệu nằm dưới sự kiểm soát của những người lạ không tin cậy qua mạng.
Có nhiều cách để làm việc này.
inetd
Như Dale Hagglund nói, "siêu máy chủ mạng" cũ inetd
thực hiện điều này. Tài khoản mà quy trình dịch vụ được chạy là một trong những cột trong inetd.conf
. Nó không tách phần nghe và phần đặc quyền thả thành hai chương trình riêng biệt, nhỏ và dễ nghe, nhưng nó tách mã dịch vụ chính thành một chương trình riêng biệt, exec()
trong một quy trình dịch vụ mà nó sinh ra với một bộ mô tả tệp mở cho ổ cắm.
Khó khăn của kiểm toán không phải là vấn đề lớn, vì người ta chỉ phải kiểm toán một chương trình. inetd
Vấn đề lớn của không phải là kiểm toán quá nhiều mà là nó không cung cấp kiểm soát dịch vụ thời gian chạy chi tiết đơn giản, so với các công cụ gần đây.
UCSPI-TCP và daemontools
Các gói UCSPI-TCP và daemontools của Daniel J. Bernstein được thiết kế để thực hiện việc này cùng nhau. Người ta có thể thay thế sử dụng bộ công cụ mã hóa daemontools tương đương phần lớn của Bruce Guenter .
Chương trình để mở bộ mô tả tệp ổ cắm và liên kết với cổng cục bộ đặc quyền là tcpserver
từ UCSPI-TCP. Nó làm cả listen()
và accept()
.
tcpserver
sau đó sinh ra một chương trình dịch vụ tự hủy bỏ quyền root (vì giao thức được phục vụ bao gồm bắt đầu là siêu người dùng và sau đó "đăng nhập", ví dụ như trường hợp FTP hoặc daemon SSH) hoặc setuidgid
là một trình nền SSH chương trình nhỏ độc lập và dễ kiểm tra, chỉ bỏ các đặc quyền và sau đó tải chuỗi cho chương trình dịch vụ phù hợp (không có phần nào chạy với các đặc quyền siêu người dùng, như trường hợp, như, nói, qmail-smtpd
).
Do đó, một run
tập lệnh dịch vụ sẽ là ví dụ (tập lệnh này cho dummyidentd để cung cấp dịch vụ IDENT null):
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
quà vặt
Gói nosh của tôi được thiết kế để làm điều này. Nó có một setuidgid
tiện ích nhỏ , giống như những người khác. Một điểm khác biệt nhỏ là nó có thể sử dụng được với systemd
các dịch vụ "LISTEN_FDS" kiểu cũng như với các dịch vụ UCSPI-TCP, vì vậy tcpserver
chương trình truyền thống được thay thế bằng hai chương trình riêng biệt: tcp-socket-listen
và tcp-socket-accept
.
Một lần nữa, các tiện ích đơn mục đích sinh sản và chuỗi tải nhau. Một điều thú vị của thiết kế là người ta có thể bỏ các đặc quyền siêu người dùng sau listen()
nhưng thậm chí trước đó accept()
. Đây là một run
kịch bản cho qmail-smtpd
điều đó thực sự làm chính xác điều đó:
#!/bin/nosh
fdmove -c 2 1
clearenv --keep-path --keep-locale
envdir env/
softlimit -m 70000000
tcp-socket-listen --combine4and6 --backlog 2 ::0 smtp
setuidgid qmaild
sh -c 'exec \
tcp-socket-accept -v -l "${LOCAL:-0}" -c "${MAXSMTPD:-1}" \
ucspi-socket-rules-check \
qmail-smtpd \
'
Các chương trình chạy dưới sự bảo hộ của superuser là những công cụ chuỗi nạp dịch vụ-agnostic nhỏ fdmove
, clearenv
, envdir
, softlimit
, tcp-socket-listen
, và setuidgid
. Tại điểm sh
được bắt đầu, ổ cắm được mở và ràng buộc với smtp
cổng và quá trình không còn có đặc quyền siêu người dùng.
s6, s6-mạng và thực thi
Các gói mạng s6 và s6 của Laurent Bercot được thiết kế để thực hiện việc này cùng nhau. Các lệnh có cấu trúc rất giống với các lệnh và UCSPI-TCP. daemontools
run
các kịch bản sẽ giống nhau, ngoại trừ việc thay thế s6-tcpserver
cho tcpserver
và s6-setuidgid
cho setuidgid
. Tuy nhiên, người ta cũng có thể chọn để sử dụng M. Bercot của execline công cụ cùng một lúc.
Dưới đây là ví dụ về dịch vụ FTP, được sửa đổi nhẹ từ bản gốc của Wayne Marshall , sử dụng chương trình execline, s6, s6 và chương trình máy chủ FTP từ publicfile :
#!/command/execlineb -PW
multisubstitute {
define CONLIMIT 41
define FTP_ARCHIVE "/var/public/ftp"
}
fdmove -c 2 1
s6-envuidgid pubftp
s6-softlimit -o25 -d250000
s6-tcpserver -vDRH -l0 -b50 -c ${CONLIMIT} -B '220 Features: a p .' 0 21
ftpd ${FTP_ARCHIVE}
ipsvd
Ipsvd của Gerrit Pape là một bộ công cụ khác chạy dọc theo cùng dòng với mạng ucspi-tcp và s6. Các công cụ này chpst
và tcpsvd
lần này, nhưng chúng làm điều tương tự, và mã rủi ro cao thực hiện việc đọc, xử lý và viết các thứ được gửi qua mạng bởi các khách hàng không tin cậy vẫn nằm trong một chương trình riêng biệt.
Đây là ví dụ của M. Pape về việc chạy fnord
trong một run
kịch bản:
#!/bin/sh
exec 2>&1
cd /public/10.0.5.4
exec \
chpst -m300000 -Uwwwuser \
tcpsvd -v 10.0.5.4 443 sslio -v -unobody -//etc/fnord/jail -C./cert.pem \
fnord
systemd
systemd
, hệ thống giám sát và khởi tạo dịch vụ mới có thể được tìm thấy trong một số bản phân phối Linux, được dự định để làm những gì inetd
có thể làm . Tuy nhiên, nó không sử dụng một bộ các chương trình nhỏ khép kín. Người ta phải kiểm toán systemd
toàn bộ, thật không may.
Với systemd
một người tạo các tệp cấu hình để xác định ổ cắm systemd
nghe và dịch vụ systemd
bắt đầu. Tệp "đơn vị" dịch vụ có các cài đặt cho phép người ta kiểm soát rất nhiều quá trình dịch vụ, bao gồm cả những gì người dùng chạy.
Với người dùng đó được đặt là không phải là siêu người dùng, systemd
thực hiện tất cả công việc mở ổ cắm, liên kết nó với một cổng và gọi listen()
(và, nếu được yêu cầu accept()
) trong quy trình số 1 với tư cách là siêu người dùng và quy trình dịch vụ mà nó xử lý sinh sản chạy mà không có đặc quyền siêu người dùng.