Lançado no HN: Hospedando sites com um servidor web em C
(github.com/cozis)- BlogTech é um toolkit baseado em C para gerenciar sites de pequeno a médio porte, oferecendo HTTPS, hosts virtuais, gerenciamento automático de certificados baseado em ACME e um cliente de administração remota do servidor
- No momento está em fase de testes e, embora a versão
0.4.xtenha sido usada para operarhttps://coz.is/por algumas semanas, ela não é apresentada como uma release estável - O servidor roda em Linux e Windows, mas o HTTPS é compatível apenas com Linux; no Windows, tanto o cliente quanto o servidor só podem usar HTTP
- O gerenciamento remoto de arquivos é feito com
--put,--gete--delete, e requisições que alteram recursos são autenticadas com assinaturas HMAC/SHA256 baseadas em segredo compartilhado - Em ambiente de produção, é necessário um servidor HTTP na porta 80 e registros DNS do domínio; ao ativar o ACME, os arquivos de certificado e chave privada são gerados automaticamente, e erros ficam registrados em
acme.log
Objetivo atual e estado do BlogTech
- BlogTech é um toolkit para administrar sites de pequeno a médio porte
- Os recursos suportados são os seguintes
-
HTTPS
-
Hosts virtuais
- Gerenciamento automático de certificados via ACME
- Cliente para administração remota do servidor
- Ainda está em fase de testes
- Há um caso em que a versão
0.4.xserviuhttps://coz.is/com sucesso por algumas semanas - A versão antiga em arquivo único está no branch
single_file
-
Build e execução básica
- BlogTech roda em Linux e Windows
- O build no Linux exige as bibliotecas de desenvolvimento do OpenSSL e
gcc - O build no Windows exige
clang
- O build no Linux exige as bibliotecas de desenvolvimento do OpenSSL e
- O build é feito com scripts específicos por plataforma
- Linux:
./build.sh - Windows:
.\\build.bat
- Linux:
- Os artefatos gerados são os seguintes
- Linux:
blogtech - Windows:
blogtech.exe
- Linux:
- No Linux, é possível instalar com
./install.sh - Um exemplo básico de execução do servidor cria o diretório
docroote usa--serve,--document-root=docroote--skip-auth-check - O servidor HTTP padrão escuta em
127.0.0.1:8080e serve o conteúdo dedocroot - Se
docrootestiver vazio, acessarhttp://127.0.0.1:8080/retorna 404
Gerenciamento remoto de arquivos e autenticação
- No modo cliente, o BlogTech pode enviar arquivos para o servidor
- Upload de arquivo com
--put - Download de arquivo remoto com
--get - Exclusão de arquivo remoto com
--delete
- Upload de arquivo com
- Mesmo executando cliente e servidor na mesma máquina, ele funciona da mesma forma para um servidor em máquina remota
- Requisições que alteram recursos são assinadas digitalmente com HMAC/SHA256
- Cliente e servidor precisam compartilhar a mesma chave secreta, que por convenção é armazenada no arquivo
admin.pwd - O arquivo de senha é indicado pela opção
--auth-password-file - Se o arquivo de senha não existir, todas as requisições que exigem autenticação serão rejeitadas
- Senha vazia também é rejeitada
- Durante o desenvolvimento, é possível tratar todas as requisições como autenticadas com
--skip-auth-check- Ao usar essa opção,
--auth-password-fileé ignorado mesmo que tenha sido definido - Deve ser usada com cuidado
- Ao usar essa opção,
-
Cabeçalhos de autenticação e condições de replay
- Requisições HTTP autenticadas incluem cabeçalhos
X-BlogTech- X-BlogTech-Nonce: token escolhido aleatoriamente pelo clienteX-BlogTech-Timestamp: timestamp UNIX do momento em que a requisição foi assinada pela primeira vezX-BlogTech-Expire: tempo em segundos durante o qual a requisição é válida a partir do momento da assinaturaX-BlogTech-Signature: valor do HMAC das informações da requisição codificado em Base64- A assinatura é obtida ao criar uma string de requisição normalizada, calcular HMAC/SHA256 usando a senha de autenticação como chave e codificar o resultado em Base64 com padding
- Nonces já vistos pelo servidor são armazenados em memória
- Se o servidor for recarregado, ele esquece os nonces anteriores, então requisições ainda não expiradas podem ser reenviadas
- Requisições HTTP autenticadas incluem cabeçalhos
Hosts virtuais e mapeamento de diretórios
- BlogTech pode hospedar vários sites no mesmo servidor
- Os domínios são configurados especificando a opção
--domainvárias vezes - O BlogTech cria diretórios por domínio dentro da raiz de documentos
defaultwebsiteA.comwebsiteB.com
- Requisições recebidas para um host específico consultam o diretório associado àquele host
- Requisições não associadas a uma pasta específica consultam o diretório default
- No exemplo, arquivos enviados para
websiteA.com,websiteB.comeother.comsão armazenados respectivamente no diretório do domínio ou no diretóriodefault
HTTPS e configuração de certificados
- HTTPS é compatível apenas com Linux
- Isso ocorre porque a biblioteca HTTP subjacente, cHTTP, implementa HTTPS com OpenSSL
- No Windows, tanto o cliente quanto o servidor só podem usar HTTP
- Para ativar HTTPS, são necessárias as seguintes opções
--https-enabled--cert-file--cert-key-file
- O endereço padrão de escuta do HTTPS é
127.0.0.1:8443 - O endereço e a porta de escuta podem ser alterados com as seguintes opções
--https-addr=<addr>--https-port=<port>
- Durante o desenvolvimento, é possível criar um certificado autoassinado com um comando do OpenSSL
- Navegadores não permitem navegar em servidores HTTPS que usem certificado autoassinado
- No cURL, é necessário usar a flag
--insecurepara acessar um servidor com certificado autoassinado - Se forem necessários vários certificados, é possível passar
--extra-certadicionalmente- O certificado padrão é fornecido com
--cert-filee--cert-key-file - Certificados adicionais são usados quando o cliente solicita um domínio específico
- O certificado padrão é fornecido com
- Opções longas de linha de comando podem ser movidas para um arquivo de configuração
Emissão automática de certificados via ACME
- O protocolo ACME permite que um servidor web solicite automaticamente a emissão de certificados a uma autoridade certificadora
- O BlogTech suporta o fluxo de iniciar em modo HTTPS sem certificado e depois gerar o certificado
- Em ambiente de produção, são necessárias as seguintes condições
- Executar o servidor HTTP na porta 80
- Criar registros DNS que conectem o domínio à máquina em que o servidor está rodando
- O ACME é ativado especificando
--acme-enabledjunto com as opções de HTTPS - Diferentemente do modo HTTPS comum, no modo ACME espera-se que o certificado ainda não exista
- Os seguintes valores são necessários para gerar o certificado
--acme-domain--acme-email--acme-country--acme-organization--acme-agree-tos
- Se tudo correr bem, os seguintes arquivos são gerados
acme_key.pem: chave privada associada à conta ACMEcert.pem: certificado emitido ou o arquivo indicado por--cert-filekey.pem: chave privada associada ao certificado ou o arquivo indicado por--cert-key-file
- Se ocorrer um erro, as mensagens serão registradas em
acme.log - É possível processar vários domínios com ACME especificando
--acme-domainvárias vezes - O certificado resultante incluirá todos os domínios especificados
Teste do cliente ACME
- Para testar o cliente ACME no Linux, é preciso instalar o Docker e clonar o Pebble ACME server
- Em
docker-compose.yml, deve-se adicionar uma entradaextra_hostsque conecte o domínio de teste aohost-gateway - Em
/etc/hosts, o domínio de teste deve ser mapeado para127.0.0.1 - O Pebble é iniciado com
docker compose up - O BlogTech executa a instância de teste com
./blogtech -s --config=misc/pebble_blogtech.conf - A interação com o servidor ACME é exibida no stdout
- Se tiver sucesso, serão gerados
acme_key.pem,cert.pemekey.pem - O teste de renovação do certificado pode ser feito alterando o valor de
validityPeriodempebble/test/config/pebble-config.json - A opção
--acme-force-renewal-period=<maximum duration in ms>permite instruir a renovação mesmo que o certificado ainda não tenha expirado
Arquivo de configuração e carregamento automático
- BlogTech permite mover qualquer quantidade de argumentos de linha de comando para um arquivo de configuração
- Por exemplo, opções relacionadas a HTTPS e ACME podem ser escritas em
blogtech_server.confe executadas com--config=blogtech_server.conf - Se o nome do arquivo de configuração for exatamente
blogtech.conf, o BlogTech o carrega automaticamente - Nesse caso, ele pode ser executado como
./blogtech --serve - Para ignorar o carregamento implícito do arquivo de configuração, use
--no-config
Logs de crash e daemon systemd
- Se o BlogTech travar no modo servidor, será gerado o arquivo
crash.bin - Na próxima inicialização do servidor,
crash.binserá convertido emcrash.log, um stack trace legível por humanos - O processo de converter endereços em nomes de símbolos ou números de linha pode ser um pouco instável
- O BlogTech pode ser instalado como um daemon systemd
- O exemplo
blogtech.serviceexecuta/root/blogtech/blogtech -s, reinicia em caso de falha e define/root/blogtech/como diretório de trabalho - As opções do servidor são carregadas automaticamente de
/root/blogtech/blogtech.conf - Depois de copiar o arquivo de serviço para
/etc/systemd/system/, ele é ativado e iniciado com os seguintes comandossystemctl daemon-reloadsystemctl enable blogtechsystemctl start blogtech
- O gerenciamento do serviço é feito com
systemctl start,systemctl stop,systemctl restartejournalctl -u blogtech
1 comentários
Opiniões do Hacker News
A parte de “proxy reverso desnecessário” sempre me deixou intrigado
Quando não havia nenhum motivo específico para um proxy reverso ajudar, eu costumava expor diretamente à internet apps Jetty embarcados, sem nada na frente, e não tive problemas
Pessoas de infraestrutura ou segurança perguntam por que não colocar nginx na frente, mas, quando pergunto o motivo, falam de segurança ou desempenho de forma vaga, sem muita concretude. A resposta mais específica foi slow loris, mas isso já não era um grande problema havia muito tempo
Fico me perguntando se proxies reversos viraram coletivamente um culto de carga, ou se estou deixando passar algum bom motivo que se aplica de modo geral
No geral, dá para proteger o servidor de origem e fazer com que ele receba apenas tráfego relevante. Mesmo ao oferecer domínios personalizados a clientes, o DNS do cliente aponta para o proxy reverso, não para o servidor de origem; assim, se for preciso trocar o servidor de origem, não é necessário se preocupar com mudanças no DNS do cliente
Cada um executa em uma porta diferente, mas quero que todos sejam acessíveis publicamente por URLs diferentes e quero expor à internet apenas a porta 443
Também quero renovação automática dos certificados TLS de cada domínio. Para o primeiro requisito, um proxy reverso é necessário, e o Caddy faz os dois
Se você opera apenas um único servidor e esse servidor também faz a terminação TLS, um proxy reverso não é estritamente necessário
Como depois dá para adicionar terminação TLS, reescrita de URL e outras tarefas sem grande esforço, passei a usá-lo por hábito, e até agora esse hábito compensou
No lado da segurança, quero usar no sistema inteiro o mínimo de código possível e um sistema operacional robusto. Um app do tipo proxy pode ser muito mais simples do que um servidor web/de aplicação, e também pode filtrar tráfego de entrada, validar entradas ou transformá-las em um formato que possa ser analisado com mais segurança e rapidez. Também pode rodar em sistemas operacionais difíceis de atacar, como OpenBSD, GenodeOS e INTEGRITY-178B
Do ponto de vista de disponibilidade, muitas vezes é mais seguro colocar balanceamento de carga, monitoramento e recuperação nesses sistemas, porque servidores de aplicação podem cair com mais frequência
Em termos de desempenho, a primeira vantagem é que um app simples e focado pode ser altamente otimizado. Depois, é possível acelerar compressão ou criptografia com CPU ou aceleradores de hardware PCI, o que é comumente chamado de offloading. A configuração com melhor custo-benefício é aquela em que muitos servidores de uso geral se beneficiam de um pequeno número de servidores caros de offloading. Alguns reduzem o uso de recursos caros enviando, por balanceamento de carga, o tráfego de entrada para o servidor que melhor conseguirá processá-lo
Em uma configuração mínima, não é necessário, mas mesmo em um ambiente de operação com um único host ele permite rolling releases, compressão, TLS, entrega rápida de arquivos estáticos e potenciais testes A/B
Uma camada de indireção entre a requisição e o servidor pode ser bastante útil
Legal. Eu também já escrevi um servidor web em C por conta própria, e o código-fonte está abaixo. Por um tempo, ele também rodou um site comercial
É surpreendente quão pequeno e leve dá para fazer um servidor web HTTP/1.1. Esse site comercial rodava em uma máquina com 128 MB de RAM e 1 CPU, e atendia regularmente uma parte considerável das escolas do Reino Unido com um sistema fechado de chat interativo baseado na web. Mas isso foi 20 anos atrás, quando a internet era menos hostil do que é hoje
O texto disse que bots funcionam como excelentes fuzzers, mas ainda assim acho bom fazer um pouco de fuzzing de verdade
http://git.annexia.org/?p=rws.git;a=tree
Dependências:
http://git.annexia.org/?p=c2lib.git;a=tree
http://git.annexia.org/?p=pthrlib.git;a=tree
Meu site https://blessed.rs roda na menor VM que consegui encontrar, com 256 MB de RAM, mas normalmente usa apenas cerca de 60 MB
O que foi realmente surpreendente foi perceber que o fork do Linux fica absurdamente rápido quando o mapa de memória tem apenas cinco páginas de 4 KB
É um pequeno projeto que comecei no tempo livre por diversão, e achei que o pessoal daqui fosse gostar :)
Incrível. Sempre achei, e ainda acho, realmente satisfatório colocar no ar um serviço mínimo usando as APIs de sistema de nível mais baixo desse jeito
Parece quase mágica, ainda mais quando você vê isso lidando com tráfego real. Fiquei um pouco surpreso que um
poll()comum consiga chegar a esses números, mas talvez seja porque faz muito tempo que não trabalho com eventos ou benchmarks nesse nívelTambém gosto da função por conexão, das structs relacionadas, da forma de gerenciar conexões com arrays e do array de descritores de arquivo do
poll. Lembra a abordagem usada em vários pacotes open source conhecidos por alta vazão, como nginx, Redis e memcachedIsso me fez pensar que todo mundo deveria conhecer e experimentar todas as línguas possíveis, sejam linguagens de programação ou línguas naturais. Pensar em uma linguagem é uma experiência única. Contextos diferentes fazem tudo parecer diferente e, no fim, mesmo quando você faz algo parecido, a perspectiva muda
Por exemplo, para realmente entender a essência do Linux ou do Git, você precisa falar essa linguagem e compreender as nuances que normalmente desaparecem na tradução. É parecido com precisar falar e entender russo para compreender o verdadeiro significado subjetivo da palavra “floresta” em russo
O contexto muda a perspectiva e, às vezes, muda tudo
Também fiquei surpreso que um
poll()comum aguente tanto. Achei que em algum momento eu migraria paraepoll, maspoll()está funcionando muito bemNão há nada de novo sob o sol
Isto também pode ser interessante: https://news.ycombinator.com/item?id=27431910
Em 2024, a instância althttpd do sqlite.org atende mais de 500 mil requisições HTTP por dia, cerca de 5 a 6 por segundo, e serve por volta de 200 GB de conteúdo por dia, cerca de 18 Mbit/s, em uma Linode de US$ 40 por mês. A média de carga dessa máquina normalmente fica perto de 0,5. Cerca de 19% das requisições HTTP são CGI para vários repositórios de código-fonte Fossil
Se você quer escrever um app em C, mas não se sente confortável em escrever por conta própria a parte que fica diretamente exposta à internet pública, o Kore é um bom framework
Ele tem recursos internos convenientes como gerenciamento de certificados ACME, Pgsql, curl e WebSockets
Basicamente, você pode criar módulos, executá-los e combiná-los, e também é possível misturar Lua/Python com C
https://kore.io/
Finalmente apareceu um site que não morre ao chegar à primeira página
Não estou dizendo que este projeto não seja excelente, mas, se isso for realmente importante em produção e você servir principalmente conteúdo estático, basta usar um CDN. Ele quase sempre terá desempenho melhor do que praticamente qualquer coisa que você escreva por conta própria. Só não é divertido
O projeto é limpo e legal, mas acho que a maioria vai para o GitHub, não para um link que mostra a página web. Estou deixando passar alguma coisa?
Gosto da parte que diz: “Gosto de criar minhas próprias ferramentas e já estou meio cansado de ouvir que tudo precisa ser testado em batalha. Então, e daí se morrer? Bugs podem ser corrigidos :^)”
Um servidor HTTP e HTTPS completo com apenas 3,4 mil linhas de código C? Sinceramente, achei que seria preciso muito mais para cumprir totalmente a especificação
Se você só aceitar requisições GET e definir
Content-Lengthna resposta, isso basta para 99% dos user agents. Lidar comTransfer-Encodinge cabeçalhos de intervalo de bytes também não aumenta muito o códigoHTTPS é apenas HTTP sobre um socket TLS e, se você não implementar a criptografia por conta própria, esse deve ser exatamente o nível de abstração. É divertido e, na prática, nem é tão ruim
httpd(8)do OpenBSD [1] também tem atualmente um pouco menos de 15.000 linhas, incluindo a documentaçãoRemovendo alguns recursos e fazendo algumas suposições, não seria surpreendente ele entrar na faixa de 5.000 linhas, como este projeto
O resultado de
$ wc -l *dá um total de 14815 linhas[1]: https://man.openbsd.org/httpd.8
Claro que eu não colocaria isso na internet pública e ele implementava apenas uma parte bem pequena do HTTP/1.1, mas funciona e só precisa de
mallocna inicializaçãoLembrei de uma apresentação do Chaos Communication Congress sobre um blog/servidor web escrito em C
Ela falava de muitos recursos de segurança, como armazenamento imutável, redução de privilégios e impedir que o blog acessasse certificados TLS: https://www.youtube.com/watch?v=TaE28fJVPTk