2 pontos por GN⁺ 2024-09-26 | 1 comentários | Compartilhar no WhatsApp
  • 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.x tenha sido usada para operar https://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, --get e --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.x serviu https://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 é feito com scripts específicos por plataforma
    • Linux: ./build.sh
    • Windows: .\\build.bat
  • Os artefatos gerados são os seguintes
    • Linux: blogtech
    • Windows: blogtech.exe
  • No Linux, é possível instalar com ./install.sh
  • Um exemplo básico de execução do servidor cria o diretório docroot e usa --serve, --document-root=docroot e --skip-auth-check
  • O servidor HTTP padrão escuta em 127.0.0.1:8080 e serve o conteúdo de docroot
  • Se docroot estiver vazio, acessar http://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
  • 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
  • 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 cliente
    • X-BlogTech-Timestamp: timestamp UNIX do momento em que a requisição foi assinada pela primeira vez
    • X-BlogTech-Expire: tempo em segundos durante o qual a requisição é válida a partir do momento da assinatura
    • X-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

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 --domain várias vezes
  • O BlogTech cria diretórios por domínio dentro da raiz de documentos
    • default
    • websiteA.com
    • websiteB.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.com e other.com são armazenados respectivamente no diretório do domínio ou no diretório default

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 --insecure para acessar um servidor com certificado autoassinado
  • Se forem necessários vários certificados, é possível passar --extra-cert adicionalmente
    • O certificado padrão é fornecido com --cert-file e --cert-key-file
    • Certificados adicionais são usados quando o cliente solicita um domínio específico
  • 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-enabled junto 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 ACME
    • cert.pem: certificado emitido ou o arquivo indicado por --cert-file
    • key.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-domain vá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 entrada extra_hosts que conecte o domínio de teste ao host-gateway
  • Em /etc/hosts, o domínio de teste deve ser mapeado para 127.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.pem e key.pem
  • O teste de renovação do certificado pode ser feito alterando o valor de validityPeriod em pebble/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.conf e 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.bin será convertido em crash.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.service executa /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 comandos
    • systemctl daemon-reload
    • systemctl enable blogtech
    • systemctl start blogtech
  • O gerenciamento do serviço é feito com systemctl start, systemctl stop, systemctl restart e journalctl -u blogtech

1 comentários

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

    • Para mim, um proxy reverso permite que o servidor de origem faça apenas entrega da aplicação. Terminação TLS, balanceamento de carga, reescrita de URL e, se necessário, recursos de segurança como WAF podem ser todos tratados no proxy reverso, criando uma separação de responsabilidades
      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
    • Rodo vários programas de servidor no meu homelab
      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
    • Na maioria dos deploys, o impacto de desempenho de um proxy reverso é desprezível, e a configuração já fica preparada
      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
    • Para responder de forma aplicável aos vários tipos de servidores colocados na frente de aplicações web, há algumas maneiras em que um equipamento adicional ajuda tanto em segurança quanto em desempenho
      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
    • Não acho que exista uma regra geral para todos os servidores
      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

    • Se você quer rodar um servidor web desse tamanho sem se preocupar tanto com a internet hostil, Rust é uma boa escolha
      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
    • Isto parece muito mais prático do que o pequeno e leve servidor web HTTP/1.0 que eu fiz, mas imagino que o rws não seja tão pequeno e leve assim: http://canonical.org/~kragen/sw/dev3/server.s http://canonical.org/~kragen/sw/dev3/httpdito-readme
      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 :)

    • É um exercício interessante passar por bugs antigos de servidores HTTP e CVEs para ver se eles também podem afetar meu código e como eu poderia corrigi-los. Fazer algo assim do zero é divertido
    • Eu justamente queria mexer com a API de concorrência do C11 e, vindo de C++, tinha curiosidade para ver como essas estruturas funcionam em C, então quis escrever algo parecido com um servidor
  • 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ível
    També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 memcached

    • Na universidade, mexer com C/C++ me dava a sensação de que minha cabeça ia explodir. Foi uma experiência muito específica e que dá humildade, com um pouco de tudo que eu gosto: engenharia, história, cultura, linguística etc.
      Isso 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
    • Concordo totalmente. E, quando você usa de verdade, é ainda mais satisfatório. Agora comecei a ficar curioso também sobre o protocolo de e-mail
      Também fiquei surpreso que um poll() comum aguente tanto. Achei que em algum momento eu migraria para epoll, mas poll() está funcionando muito bem
    • Acho que as pessoas esquecem que todas as abstrações ótimas e elegantes, no fundo, acabam fazendo a mesma coisa: abrir um socket, ler e escrever
      Nã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

    • Esse texto me inspirou muito. Fez eu perceber que coisas assim são realmente possíveis
  • 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

    • Com um CDN na frente, qualquer site consegue isso
      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 link não leva ao GitHub? Esse comentário me deixou um pouco confuso
      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

    • HTTP/1.1 é bem simples se você ignorar a maior parte da especificação
      Se você só aceitar requisições GET e definir Content-Length na resposta, isso basta para 99% dos user agents. Lidar com Transfer-Encoding e cabeçalhos de intervalo de bytes também não aumenta muito o código
      HTTPS é 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
    • Esse tamanho parece razoável. O httpd(8) do OpenBSD [1] também tem atualmente um pouco menos de 15.000 linhas, incluindo a documentação
      Removendo 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
    • Em um dos meus experimentos, usei um servidor web C embarcado simples para fornecer uma visualização ao vivo da coleta de dados, e ele tinha menos de 250 linhas
      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 malloc na inicialização
    • Há mais alguns servidores HTTP/1.1 desse tamanho: https://www.acme.com/software/thttpd/benchmarks.html
  • Lembrei 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