Dale Hagglund es perfecto. Así que voy a decir lo mismo pero de una manera diferente, con algunos detalles y ejemplos. ☺
Lo correcto en los mundos de Unix y Linux es:
- tener un programa pequeño, simple y fácilmente auditable que se ejecute como superusuario y se una a la toma de escucha;
- tener otro programa pequeño, simple, fácilmente auditable, que elimine privilegios, generado por el primer programa;
- para que la carne del servicio, en un tercer programa separado , se ejecute bajo una cuenta que no sea de superusuario y se cargue en cadena por el segundo programa, esperando simplemente heredar un descriptor de archivo abierto para el socket.
Tienes una idea equivocada de dónde está el alto riesgo. El alto riesgo está en leer desde la red y actuar sobre lo que se lee, no en los simples actos de abrir un socket, vincularlo a un puerto y llamar listen()
. La parte de un servicio que hace la comunicación real es el alto riesgo. Las partes que se abren, bind()
y listen()
, e incluso (en una medida) la parte que accepts()
, no son los de alto riesgo y se pueden ejecutar bajo los auspicios de la superusuario. No utilizan ni actúan sobre (a excepción de las direcciones IP de origen en el accept()
caso) datos que están bajo el control de extraños no confiables a través de la red.
Hay muchas maneras de hacer esto.
inetd
Como dice Dale Hagglund, el viejo "servidor de red" inetd
hace esto. La cuenta bajo la cual se ejecuta el proceso de servicio es una de las columnas en inetd.conf
. No separa la parte de escucha y la parte de privilegios de caída en dos programas separados, pequeños y fácilmente auditables, pero separa el código de servicio principal en un programa separado, exec()
editado en un proceso de servicio que genera con un descriptor de archivo abierto para el zócalo
La dificultad de auditar no es un gran problema, ya que uno solo tiene que auditar un programa. inetd
El principal problema no es auditar tanto, sino que no proporciona un control de servicio de tiempo de ejecución simple, en comparación con las herramientas más recientes.
UCSPI-TCP y daemontools
Los paquetes UCSPI-TCP y daemontools de Daniel J. Bernstein fueron diseñados para hacer esto en conjunto. Alternativamente, se puede usar el conjunto de herramientas daemontools-encore en gran medida equivalente de Bruce Guenter .
El programa para abrir el descriptor del archivo de socket y enlazar al puerto local privilegiado es tcpserver
, desde UCSPI-TCP. Hace tanto el listen()
como el accept()
.
tcpserver
luego genera un programa de servicio que elimina los privilegios de raíz (porque el protocolo que se está sirviendo implica comenzar como superusuario y luego "iniciar sesión", como es el caso, por ejemplo, de un demonio FTP o SSH) o setuidgid
que es un programa autónomo pequeño y fácilmente auditable que solo elimina privilegios y luego encadena las cargas al programa de servicio propiamente dicho (ninguna parte se ejecuta con privilegios de superusuario, como es el caso, por ejemplo qmail-smtpd
).
Un run
script de servicio sería, por ejemplo, (este para dummyidentd para proporcionar un servicio IDENT nulo):
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
comida
Mi paquete nosh está diseñado para hacer esto. Tiene una pequeña setuidgid
utilidad, al igual que las demás. Una ligera diferencia es que se puede usar con los systemd
servicios "LISTEN_FDS" de estilo, así como con los servicios UCSPI-TCP, por lo que el tcpserver
programa tradicional se reemplaza por dos programas separados: tcp-socket-listen
y tcp-socket-accept
.
Nuevamente, las utilidades de un solo propósito se generan y se encadenan entre sí. Una peculiaridad interesante del diseño es que uno puede abandonar los privilegios de superusuario después listen()
pero incluso antes accept()
. Aquí hay un run
script para qmail-smtpd
eso que hace exactamente eso:
#!/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 \
'
Los programas que se ejecutan bajo los auspicios del superusuario son las pequeñas herramientas de la cadena de carga de servicio agnóstica fdmove
, clearenv
, envdir
, softlimit
, tcp-socket-listen
, y setuidgid
. En el momento en que sh
se inicia, el socket está abierto y vinculado al smtp
puerto, y el proceso ya no tiene privilegios de superusuario.
s6, s6-networking y execline
Los paquetes de redes s6 y s6 de Laurent Bercot fueron diseñados para hacer esto en conjunto. Los comandos son estructuralmente muy similares a los de daemontools
UCSPI-TCP.
run
los guiones serían muy parecidos, excepto por la sustitución de s6-tcpserver
for tcpserver
y s6-setuidgid
for setuidgid
. Sin embargo, uno también podría optar por utilizar el conjunto de herramientas execlinas de M. Bercot al mismo tiempo.
Aquí hay un ejemplo de un servicio FTP, ligeramente modificado del original de Wayne Marshall , que utiliza execline, s6, s6-networking y el programa de servidor FTP de 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
El ipsvd de Gerrit Pape es otro conjunto de herramientas que se ejecuta en la misma línea que ucspi-tcp y s6-networking. Las herramientas son chpst
y tcpsvd
esta vez, pero hacen lo mismo, y el código de alto riesgo que hace la lectura, el procesamiento y la escritura de cosas enviadas a través de la red por clientes no confiables todavía está en un programa separado.
Aquí está el ejemplo de M. Pape de ejecutar fnord
en un run
script:
#!/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
, el nuevo sistema de supervisión de servicios e init que se puede encontrar en algunas distribuciones de Linux está destinado a hacer lo que inetd
puede hacer . Sin embargo, no utiliza un conjunto de pequeños programas independientes. Uno tiene que auditar systemd
en su totalidad, desafortunadamente.
Con systemd
uno crea archivos de configuración para definir un socket que systemd
escucha y un servicio que se systemd
inicia. El archivo de "unidad" de servicio tiene configuraciones que le permiten a uno tener un gran control sobre el proceso de servicio, incluido el usuario con el que se ejecuta.
Con ese usuario configurado para no ser un superusuario, systemd
realiza todo el trabajo de abrir el socket, vincularlo a un puerto y llamar listen()
(y, si es necesario accept()
) , en el proceso n. ° 1 como superusuario, y el proceso de servicio que genera carreras sin privilegios de superusuario.