2 puntos por GN⁺ 2024-09-26 | 1 comentarios | Compartir por WhatsApp
  • 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.x se usó para operar https://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, --get y --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ó correctamente https://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 se realiza con scripts específicos de cada plataforma
    • Linux: ./build.sh
    • Windows: .\build.bat
  • Los artefactos de compilación son los siguientes
    • Linux: blogtech
    • Windows: blogtech.exe
  • En Linux se puede instalar con ./install.sh
  • Un ejemplo de ejecución básica del servidor crea el directorio docroot y especifica --serve, --document-root=docroot y --skip-auth-check
  • El servidor HTTP básico escucha en 127.0.0.1:8080 y sirve el contenido de docroot
  • Con docroot vacío, al acceder a http://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
  • 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-check para tratar todas las solicitudes como autenticadas
    • Si se usa esta opción, --auth-password-file se ignora aunque esté especificado
    • Debe usarse con cuidado
  • 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 cliente
    • X-BlogTech-Timestamp: timestamp UNIX del momento en que se firmó originalmente la solicitud
    • X-BlogTech-Expire: tiempo, en segundos, durante el cual la solicitud es válida desde el momento de la firma
    • X-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

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
    • default
    • websiteA.com
    • websiteB.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.com y other.com se guardan en el directorio del dominio correspondiente o en el directorio default

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-cert adicionalmente
    • El certificado predeterminado se proporciona con --cert-file y --cert-key-file
    • Los certificados adicionales se usan cuando el cliente solicita un dominio específico
  • 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-enabled junto 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 ACME
    • cert.pem: certificado emitido o archivo especificado con --cert-file
    • key.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-domain varias 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 entrada extra_hosts que conecte el dominio de prueba con host-gateway
  • En /etc/hosts, se mapea el dominio de prueba a 127.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.pem y key.pem
  • La prueba de renovación de certificados puede realizarse cambiando el valor de validityPeriod en pebble/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.conf y 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.bin se convierte en crash.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.service ejecuta /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 comandos
    • systemctl daemon-reload
    • systemctl enable blogtech
    • systemctl start blogtech
  • La gestión del servicio se realiza con systemctl start, systemctl stop, systemctl restart y journalctl -u blogtech

1 comentarios

 
GN⁺ 2024-09-26
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.

    • Para mí, un proxy inverso permite que el servidor de origen se encargue solo de servir la aplicación. La terminación TLS, el balanceo de carga, la reescritura de URL y, si hace falta, funciones de seguridad como un WAF pueden manejarse todas en el proxy inverso, logrando una separación de responsabilidades.
      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.
    • En mi homelab corro varios programas de servidor.
      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.
    • En la mayoría de los despliegues, el impacto de rendimiento de un proxy inverso es despreciable, y la configuración ya la tengo preparada.
      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.
    • Si hay que dar una respuesta aplicable a varios tipos de servidores que se ponen delante de una aplicación web, hay varias formas en que un equipo adicional ayuda tanto en seguridad como en rendimiento.
      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.
    • No creo que exista una regla general para todos los servidores.
      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

    • Si quieres correr un servidor web de este tamaño y no preocuparte demasiado por una internet hostil, Rust es una buena opción.
      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.
    • Esto parece mucho más práctico que mi pequeño y liviano servidor web HTTP/1.0, aunque no creo que rws sea tan pequeño y liviano: http://canonical.org/~kragen/sw/dev3/server.s http://canonical.org/~kragen/sw/dev3/httpdito-readme
      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 :)

    • Revisar bugs antiguos de servidores HTTP y CVE para ver si también podrían afectar mi código, y cómo podría corregirlos, se vuelve un ejercicio interesante. Hacer cosas así uno mismo es divertido.
    • Justo quería jugar con la API de concurrencia de C11, y viniendo de C++ tenía curiosidad por ver cómo funcionaban esas estructuras en C, así que quise escribir algo parecido a un servidor.
  • 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 nivel
    Me 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 memcached

    • En la universidad, trabajar con C/C++ me hacía sentir que me explotaba la cabeza. Fue una experiencia muy particular y que te pone humilde, con un poco de ingeniería, historia, cultura, lingüística y otras cosas que me gustan
      Me 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
    • Totalmente de acuerdo. Y cuando lo usas de verdad, es todavía más satisfactorio. Ahora me está empezando a dar curiosidad también el protocolo de email
      A mí también me sorprendió que un poll() común aguante tanto. Pensé que eventualmente tendría que pasar a epoll, pero poll() está funcionando muy bien
    • Parece que la gente olvida que todas las abstracciones geniales y elegantes, en el fondo, terminan haciendo exactamente lo mismo: abrir un socket, leer y escribir
      No 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

    • Ese artículo me inspiró mucho. Me hizo darme cuenta de que algo así sí es posible en la práctica
  • 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

    • Con un CDN delante, cualquier sitio puede lograr eso
      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 enlace no apunta a GitHub? Este comentario me confunde un poco
      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

    • HTTP/1.1 es muy simple si ignoras la mayor parte de la especificación
      Si solo aceptas solicitudes GET y configuras Content-Length en la respuesta, eso alcanza para el 99% de los agentes de usuario. Manejar Transfer-Encoding y los encabezados de rango de bytes tampoco aumenta mucho el código
      HTTPS 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
    • Ese tamaño me parece razonable. httpd(8) [1] de OpenBSD tampoco llega actualmente a 15,000 líneas, incluyendo la documentación
      Si 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
    • En uno de mis experimentos usé un servidor web embebido sencillo en C para ofrecer una vista en vivo de recolección de datos, y tenía menos de 250 líneas
      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 malloc durante la inicialización
    • Hay algunos servidores HTTP/1.1 más de este tamaño: https://www.acme.com/software/thttpd/benchmarks.html
  • Me 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