En HN: Hosting de sitios web con un servidor web en C
(github.com/cozis)- BlogTech es un toolkit basado en C para administrar sitios web pequeños y medianos, que ofrece HTTPS, hosts virtuales, gestión automática de certificados basada en ACME y un cliente de administración remota del servidor
- Actualmente está en fase de pruebas; con la versión
0.4.xse usó para operarhttps://coz.is/durante varias semanas, pero no se presenta como una versión estable - El servidor se ejecuta en Linux y Windows, pero HTTPS solo es compatible con Linux; en Windows, tanto el cliente como el servidor solo pueden usar HTTP
- La gestión remota de archivos se maneja con
--put,--gety--delete, y las solicitudes que modifican recursos se autentican con una firma HMAC/SHA256 basada en una clave secreta compartida - En entornos de producción se necesita un servidor HTTP en el puerto 80 y registros DNS de dominio; al activar ACME, se generan automáticamente los archivos de certificado y clave privada, y los errores quedan en
acme.log
Propósito y estado actual de BlogTech
- BlogTech es un toolkit para administrar sitios web pequeños y medianos
- Las funciones compatibles son las siguientes
-
HTTPS
-
Hosts virtuales
- Gestión automática de certificados mediante ACME
- Cliente para administración remota del servidor
- Todavía está en fase de pruebas
- Hay un caso en el que, con la versión
0.4.x, se sirvió correctamentehttps://coz.is/durante varias semanas - La versión antigua de un solo archivo está en la rama
single_file
-
Compilación y ejecución básica
- BlogTech se ejecuta en Linux y Windows
- La compilación en Linux requiere las bibliotecas de desarrollo de OpenSSL y
gcc - La compilación en Windows requiere
clang
- La compilación en Linux requiere las bibliotecas de desarrollo de OpenSSL y
- La compilación se realiza con scripts específicos de cada plataforma
- Linux:
./build.sh - Windows:
.\build.bat
- Linux:
- Los artefactos de compilación son los siguientes
- Linux:
blogtech - Windows:
blogtech.exe
- Linux:
- En Linux se puede instalar con
./install.sh - Un ejemplo de ejecución básica del servidor crea el directorio
docrooty especifica--serve,--document-root=docrooty--skip-auth-check - El servidor HTTP básico escucha en
127.0.0.1:8080y sirve el contenido dedocroot - Con
docrootvacío, al acceder ahttp://127.0.0.1:8080/se devuelve un 404
Gestión remota de archivos y autenticación
- BlogTech puede subir archivos al servidor en modo cliente
- Subir archivos con
--put - Descargar archivos remotos con
--get - Eliminar archivos remotos con
--delete
- Subir archivos con
- Funciona de la misma manera tanto si el cliente y el servidor se ejecutan en la misma máquina como si el servidor está en una máquina remota
- Las solicitudes que modifican recursos se firman digitalmente con una firma HMAC/SHA256
- El cliente y el servidor deben compartir la misma clave secreta, que por convención se almacena en el archivo
admin.pwd - El archivo de contraseña se especifica con la opción
--auth-password-file - Si no existe el archivo de contraseña, se rechazan todas las solicitudes que requieren autenticación
- También se rechazan las contraseñas vacías
- Durante el desarrollo, se puede usar
--skip-auth-checkpara tratar todas las solicitudes como autenticadas- Si se usa esta opción,
--auth-password-filese ignora aunque esté especificado - Debe usarse con cuidado
- Si se usa esta opción,
-
Encabezados de autenticación y condiciones de reenvío
- Las solicitudes HTTP autenticadas incluyen encabezados
X-BlogTech- X-BlogTech-Nonce: token elegido aleatoriamente por el clienteX-BlogTech-Timestamp: timestamp UNIX del momento en que se firmó originalmente la solicitudX-BlogTech-Expire: tiempo, en segundos, durante el cual la solicitud es válida desde el momento de la firmaX-BlogTech-Signature: valor codificado en Base64 del HMAC de la información de la solicitud- La firma se obtiene creando una cadena de solicitud normalizada, calculando HMAC/SHA256 con la contraseña de autenticación como clave y codificando el resultado en Base64 con padding
- Los nonces que el servidor ya vio se almacenan en memoria
- Cuando el servidor se recarga, olvida los nonces anteriores, por lo que las solicitudes que aún no hayan expirado podrían reenviarse
- Las solicitudes HTTP autenticadas incluyen encabezados
Hosts virtuales y mapeo de directorios
- BlogTech puede alojar varios sitios web en el mismo servidor
- Los dominios se configuran especificando varias veces la opción
--domain - BlogTech crea directorios por dominio dentro de la raíz de documentos
defaultwebsiteA.comwebsiteB.com
- Las solicitudes que llegan a un host específico consultan el directorio asociado a ese host
- Las solicitudes no asociadas a una carpeta específica consultan el directorio default
- En el ejemplo, los archivos subidos a
websiteA.com,websiteB.comyother.comse guardan en el directorio del dominio correspondiente o en el directoriodefault
HTTPS y configuración de certificados
- HTTPS solo es compatible con Linux
- Esto se debe a que la biblioteca HTTP subyacente, cHTTP, implementa HTTPS con OpenSSL
- En Windows, tanto el cliente como el servidor solo pueden usar HTTP
- Para activar HTTPS, se necesitan las siguientes opciones
--https-enabled--cert-file--cert-key-file
- La dirección de escucha HTTPS predeterminada es
127.0.0.1:8443 - La dirección y el puerto de escucha pueden cambiarse con estas opciones
--https-addr=<addr>--https-port=<port>
- Durante el desarrollo, se puede crear un certificado autofirmado con un comando de OpenSSL
- Los navegadores no permiten explorar servidores HTTPS que usan certificados autofirmados
- cURL puede acceder a servidores con certificados autofirmados usando la bandera
--insecure - Si se necesitan varios certificados, se puede pasar
--extra-certadicionalmente- El certificado predeterminado se proporciona con
--cert-filey--cert-key-file - Los certificados adicionales se usan cuando el cliente solicita un dominio específico
- El certificado predeterminado se proporciona con
- Las opciones largas de línea de comandos pueden trasladarse a un archivo de configuración
Emisión automática de certificados basada en ACME
- El protocolo ACME permite que un servidor web solicite automáticamente la emisión de certificados a una autoridad certificadora
- BlogTech admite un flujo en el que inicia en modo HTTPS sin certificado y luego se genera el certificado
- En entornos de producción se requieren las siguientes condiciones
- Ejecutar el servidor HTTP en el puerto 80
- Crear registros DNS que conecten el dominio con la máquina donde se ejecuta el servidor
- ACME se activa especificando
--acme-enabledjunto con las opciones de HTTPS - A diferencia del modo HTTPS normal, en modo ACME se espera que el certificado no exista
- Para generar el certificado, se necesitan los siguientes valores
--acme-domain--acme-email--acme-country--acme-organization--acme-agree-tos
- Si el proceso se completa correctamente, se generan los siguientes archivos
acme_key.pem: clave privada asociada a la cuenta ACMEcert.pem: certificado emitido o archivo especificado con--cert-filekey.pem: clave privada asociada al certificado o archivo especificado con--cert-key-file
- Si ocurre un error, el mensaje se registra en
acme.log - Si se especifica
--acme-domainvarias veces, ACME puede procesar varios dominios - El certificado resultante incluye todos los dominios especificados
Prueba del cliente ACME
- Para probar el cliente ACME en Linux, se debe instalar Docker y clonar Pebble ACME server
- En
docker-compose.yml, se agrega una entradaextra_hostsque conecte el dominio de prueba conhost-gateway - En
/etc/hosts, se mapea el dominio de prueba a127.0.0.1 - Pebble se inicia con
docker compose up - BlogTech ejecuta una instancia de prueba con
./blogtech -s --config=misc/pebble_blogtech.conf - La interacción con el servidor ACME se imprime en stdout
- Si tiene éxito, se generan
acme_key.pem,cert.pemykey.pem - La prueba de renovación de certificados puede realizarse cambiando el valor de
validityPeriodenpebble/test/config/pebble-config.json - Con la opción
--acme-force-renewal-period=<maximum duration in ms>, se puede indicar que se renueve el certificado aunque no haya expirado
Archivos de configuración y carga automática
- BlogTech puede trasladar cualquier cantidad de argumentos de línea de comandos a un archivo de configuración
- Por ejemplo, se pueden escribir las opciones relacionadas con HTTPS y ACME en
blogtech_server.confy ejecutar con--config=blogtech_server.conf - Si el nombre del archivo de configuración es exactamente
blogtech.conf, BlogTech lo carga automáticamente - En ese caso, se puede ejecutar como
./blogtech --serve - Para ignorar la carga implícita del archivo de configuración, se usa
--no-config
Logs de fallos y demonio systemd
- Si BlogTech se cae en modo servidor, se genera un archivo
crash.bin - En el siguiente inicio del servidor,
crash.binse convierte encrash.log, un stack trace legible para humanos - El proceso de convertir direcciones en nombres de símbolos o números de línea puede ser algo inestable
- BlogTech puede instalarse como demonio systemd
- El ejemplo
blogtech.serviceejecuta/root/blogtech/blogtech -s, reinicia en caso de fallo y establece el directorio de trabajo en/root/blogtech/ - Las opciones del servidor se cargan automáticamente desde
/root/blogtech/blogtech.conf - Después de copiar el archivo de servicio a
/etc/systemd/system/, se habilita e inicia con los siguientes comandossystemctl daemon-reloadsystemctl enable blogtechsystemctl start blogtech
- La gestión del servicio se realiza con
systemctl start,systemctl stop,systemctl restartyjournalctl -u blogtech
1 comentarios
Opiniones en Hacker News
La parte de “no hace falta un proxy inverso” siempre me pareció rara.
Cuando no había una razón particular por la que un proxy inverso ayudara, solía exponer apps Jetty embebidas directamente a internet, sin nada delante, y no tuve problemas.
La gente de infraestructura o seguridad pregunta por qué no pongo nginx delante, pero cuando les pregunto el motivo, solo hablan vagamente de seguridad o rendimiento y les falta concreción. La respuesta más concreta que recibí fue slow loris, pero eso dejó de ser un gran problema hace mucho.
Me pregunto si los proxies inversos se convirtieron colectivamente en un culto cargo, o si me estoy perdiendo alguna buena razón que aplique en general.
En general, permite proteger el servidor de origen y hacer que reciba solo el tráfico relevante. Incluso cuando ofrecemos dominios personalizados a los clientes, el DNS del cliente apunta al proxy inverso y no al servidor de origen, así que si hace falta cambiar el servidor de origen, no hay que preocuparse por cambios en el DNS del cliente.
Cada uno se ejecuta en un puerto distinto, pero quiero que todos sean accesibles públicamente con URLs diferentes y exponer a internet solo el puerto 443.
También quiero renovación automática de certificados TLS para cada dominio. Para el primer requisito necesito un proxy inverso, y Caddy hace ambas cosas.
Si operas un único servidor y ese servidor también hace la terminación TLS, un proxy inverso no es estrictamente necesario.
Como después puedo agregar terminación TLS, reescritura de URL y otras tareas sin mucho esfuerzo, terminé usándolo por costumbre, y hasta ahora esa costumbre me ha pagado bien.
En seguridad, uno quiere usar en todo el sistema la menor cantidad de código posible y un sistema operativo robusto. Una app tipo proxy puede ser mucho más simple que un servidor web/de aplicaciones, y también puede filtrar el tráfico entrante, validar entradas o transformarlas a un formato que sea más seguro y rápido de parsear. También puede ejecutarse sobre sistemas operativos difíciles de atacar, como OpenBSD, GenodeOS o INTEGRITY-178B.
En disponibilidad, a menudo también es más seguro poner el balanceo de carga, el monitoreo y la recuperación en esos sistemas, porque los servidores de aplicaciones pueden caerse con más frecuencia.
En rendimiento, la primera ventaja es que una app simple y enfocada puede optimizarse mucho. Luego se puede acelerar la compresión o el cifrado con CPU o aceleradores de hardware PCI, algo que suele llamarse offloading. La configuración más costo-efectiva suele ser aquella en la que muchos servidores de propósito general se benefician de unos pocos servidores caros de offloading. Algunos reducen el uso de recursos costosos enviando el tráfico entrante, mediante balanceo de carga, al servidor que mejor pueda manejarlo.
En una configuración mínima no hace falta, pero incluso en un entorno operado en un solo host permite rolling releases, compresión, TLS, servir archivos estáticos rápido y posibles pruebas A/B.
Una capa de indirección entre las solicitudes y el servidor puede ser bastante útil.
Genial. Yo también escribí antes mi propio servidor web en C, y abajo está el código fuente. Durante un tiempo incluso lo usé para operar un sitio web comercial.
Es sorprendente lo pequeño y liviano que puede ser un servidor web HTTP/1.1. Ese sitio comercial corría en una máquina con 128 MB de RAM y 1 CPU, y era un sistema de chat interactivo basado en web, de código cerrado, que daba servicio con regularidad a una cantidad importante de escuelas del Reino Unido. Claro que eso fue hace 20 años, cuando internet era menos hostil que ahora.
El artículo dice que los bots funcionan como excelentes fuzzers, pero aun así creo que conviene hacer también algo de fuzzing real.
http://git.annexia.org/?p=rws.git;a=tree
Requisitos:
http://git.annexia.org/?p=c2lib.git;a=tree
http://git.annexia.org/?p=pthrlib.git;a=tree
Mi sitio web https://blessed.rs corre en una VM de 256 MB de RAM, la más pequeña que pude encontrar, pero normalmente usa solo unos 60 MB.
Lo que de verdad me sorprendió fue que, si el mapa de memoria tiene solo cinco páginas de 4 KB, fork en Linux se vuelve tremendamente rápido.
Es un pequeño proyecto que empecé por diversión en mi tiempo libre, y pensé que acá les gustaría :)
Impresionante. Siempre me pareció, y todavía me parece, realmente satisfactorio levantar un servicio mínimo usando las API de sistema de más bajo nivel de esta forma
Es casi mágico, y más todavía cuando lo ves manejar tráfico real. Me sorprendió un poco que un
poll()común pueda alcanzar esos números, aunque quizá sea porque hace mucho que no manejo eventos ni benchmarks a ese nivelMe gusta la forma en que administra las conexiones con una función por conexión, estructuras relacionadas y arreglos, y también el arreglo de descriptores de archivo de
poll. Me recuerda a lo que hacen varios paquetes open source conocidos por su alto rendimiento, como nginx, Redis y memcachedMe hizo pensar que todos deberían conocer e intentar todos los lenguajes posibles, ya sean lenguajes de programación o lenguas naturales. Pensar en un lenguaje es una experiencia única. Los distintos contextos hacen que todo se sienta diferente y, aunque al final hagas cosas parecidas, te cambian la perspectiva
Por ejemplo, para entender de verdad la esencia de Linux o Git, tienes que hablar ese lenguaje y comprender los matices que normalmente se pierden en la traducción. Es parecido a tener que hablar y entender ruso para comprender el verdadero significado subjetivo de la palabra “bosque” en ruso
El contexto cambia la perspectiva y, a veces, lo cambia todo
A mí también me sorprendió que un
poll()común aguante tanto. Pensé que eventualmente tendría que pasar aepoll, peropoll()está funcionando muy bienNo hay nada nuevo bajo el sol
Esto también puede ser interesante: https://news.ycombinator.com/item?id=27431910
A 2024, la instancia de althttpd de sqlite.org maneja más de 500 mil solicitudes HTTP por día, unas 5 a 6 por segundo, y sirve unos 200 GB de contenido diarios, alrededor de 18 Mbit/s, desde un Linode de 40 dólares al mes. El promedio de carga de esta máquina normalmente se mantiene cerca de 0.5. Aproximadamente el 19% de las solicitudes HTTP son CGI hacia varios repositorios de código fuente de Fossil
Si quieres escribir una app en C pero no te sientes cómodo escribiendo directamente la parte que queda expuesta a internet público, Kore es un buen framework
Tiene funciones integradas útiles como gestión de certificados ACME, Pgsql, curl y WebSocket
Básicamente puedes compilar, ejecutar y combinar módulos, y también es posible mezclar Lua/Python con C
https://kore.io/
Por fin apareció un sitio web que no muere al llegar a la primera página
No digo que este proyecto no sea excelente, pero si esto es realmente importante en un entorno de producción y sirves contenido mayormente estático, simplemente usa un CDN. Casi siempre tendrá mejor rendimiento que casi cualquier cosa que escribas tú mismo. Solo que no es tan divertido
El proyecto es limpio y genial, pero creo que la mayoría irá a GitHub, no al enlace que muestra la página web. ¿Me estoy perdiendo algo?
Me gustó la parte que dice: “Disfruto crear mis propias herramientas, y ya estoy un poco cansado de escuchar que todo tiene que estar probado en producción. Entonces, si se muere, ¿qué importa? Los bugs se arreglan :^)”
¿Un servidor HTTP y HTTPS completo con solo 3.4k líneas de C? Honestamente pensé que se necesitaría mucho más para cumplir por completo con la especificación
Si solo aceptas solicitudes GET y configuras
Content-Lengthen la respuesta, eso alcanza para el 99% de los agentes de usuario. ManejarTransfer-Encodingy los encabezados de rango de bytes tampoco aumenta mucho el códigoHTTPS es solo HTTP sobre un socket TLS, y si no implementas tú mismo el cifrado, ese debería ser exactamente el nivel de abstracción. Es interesante y, en la práctica, no está tan mal
httpd(8)[1] de OpenBSD tampoco llega actualmente a 15,000 líneas, incluyendo la documentaciónSi quitas algunas funciones y haces algunas suposiciones, no me sorprendería que entre en el rango de las 5,000 líneas, como este proyecto
El resultado de
$ wc -l *da un total de 14815 líneas[1]: https://man.openbsd.org/httpd.8
Claro que no lo pondría en internet público e implementaba solo una parte muy pequeña de HTTP/1.1, pero funciona y solo necesita
mallocdurante la inicializaciónMe recuerda a una charla del Chaos Communication Congress sobre un blog/servidor web escrito en C
Trataba sobre muchas funciones de seguridad, como almacenamiento inmutable, reducción de privilegios y evitar que el blog pudiera acceder a los certificados TLS: https://www.youtube.com/watch?v=TaE28fJVPTk