Ir para o conteúdo

Blogue ·

Um canário que corre contra servidores reais

Um simulador não prova a autenticação. Dois defeitos de monitorização que só servidores reais podiam mostrar, e o que uma página de estado pode contar.

Por Provibr

Uma verificação de monitorização que inicia sessão numa base de dados é um pedaço pequeno de código de protocolo com uma propriedade desagradável: quase todas as formas de o testar testam a coisa errada.

O teste evidente é um servidor simulado que fala o protocolo. Funciona, e prova bastante: o enquadramento, os tempos de espera, o que acontece se a ligação cai a meio de um aperto de mão, se uma recusa produz o código de erro certo. O que não consegue provar é a autenticação, e por uma razão estrutural. Um simulador que recalcula o nosso próprio embaralhamento usa a nossa própria implementação nas duas pontas do fio. Se trocarmos dois argumentos num resumo, os dois lados trocam-nos, e o teste fica verde sobre código que nenhum servidor real aceitaria.

Essa falha ficou anotada como assunto em aberto no final da ronda anterior de monitorização: o SCRAM só tinha sido medido contra uma implementação que também tínhamos escrito nós. Um canário fecha-a.

O que é aqui um canário

Um canário é um conjunto pequeno de provas que correm contra a coisa real em vez de contra um modelo dela. Contentores descartáveis no nosso próprio servidor, ligados a localhost, com um PostgreSQL 17, um Redis 7, um MySQL 8.4 e um MariaDB 11 verdadeiros lá dentro. Os adaptadores são chamados diretamente e não através do envelope de comandos, porque o assunto é o fio e não a canalização à volta. Onze provas, todas ignoradas por omissão, e cada uma recusa correr enquanto o seu endereço não estiver realmente definido.

Esse segundo ferrolho não é redundante com o primeiro. Se alguém correr a coleção inteira levantando a marca de omissão, uma prova ligaria em silêncio a 127.0.0.1:0 e produziria uma falha que se lê como um defeito do nosso código em vez de uma variável de ambiente em falta.

O que as onze cobrem:

ServidorO que fica provado
PostgreSQL 17SCRAM com a palavra-passe certa e com uma errada, md5, trust, TLS exigido contra um servidor com TLS desligado, verificação completa do certificado contra uma âncora estranha
Redis 7Autenticação por palavra-passe e um utilizador com ACL, mais a lista permitida de INFO
MySQL 8.4caching_sha2 com cache fria e com cache quente, e uma palavra-passe errada
MariaDB 11.8O mesmo aperto de mão contra outra implementação dele

A distinção entre cache fria e quente importa porque são dois caminhos de código diferentes. O MySQL usa em caching_sha2_password um embaralhamento barato assim que o servidor já viu a conta, e pede autenticação completa da primeira vez depois de um reinício. Só um servidor real reinicia.

Dois defeitos que nenhum simulador podia mostrar

PostgreSQL, palavra-passe errada. Com a palavra-passe errada, o PostgreSQL não responde à primeira resposta SASL com uma continuação. Responde com um erro que leva o SQLSTATE 28P01, palavra-passe inválida. O adaptador tinha ali uma desestruturação nua, que transformava tudo o que não fosse uma continuação num erro de protocolo: este serviço não respondeu como um servidor PostgreSQL. O SQLSTATE era deitado fora. Uma cliente cujas credenciais de monitorização tinham expirado lia que a sua base de dados estava avariada.

Um simulador nunca passa por esse ramo, porque um simulador que testa o caminho feliz envia a continuação. Os dois pontos onde agora pode chegar um erro, depois do primeiro passo e depois do segundo, mantêm-no como falha de autenticação.

Redis, um campo vazio. O adaptador lê exatamente dois campos de INFO: a versão e o papel. Pedia a secção server, e role não está nessa secção. Está em replication. O campo estava vazio desde a ronda que o introduziu, e nada o dizia, porque o simulador respondia com um bloco INFO escrito por nós próprios em que role aparecia amavelmente onde o esperávamos.

A correção são duas chamadas INFO separadas e não uma única que nomeie ambas as secções, porque o Redis só aceita várias secções num INFO a partir da versão 7.0. Em 6.x isso é um erro de sintaxe, e como um INFO falhado propositadamente não põe a verificação a vermelho, o campo teria simplesmente voltado a ficar vazio em servidores antigos. Uma degradação silenciosa a substituir uma degradação silenciosa.

Os dois defeitos partilham a forma. Nenhum dos dois produziu um erro. Um produziu o erro errado e o outro não produziu nada, e ambos eram invisíveis para uma coleção de testes que estava verde.

MySQL, e a biblioteca que não foi acrescentada

A mesma ronda acrescentou MySQL e MariaDB como tipo de verificação. Não há biblioteca de cliente por trás, e isso é uma repetição deliberada de uma decisão anterior e não uma decisão nova.

Para o PostgreSQL a escolha tinha sido não escrever um SCRAM próprio, porque o SCRAM tem um passo em que o servidor assina algo que o cliente deveria verificar, e uma implementação que salta esse passo fica verde contra um servidor real e igualmente verde contra um falsificado. É esse o atalho perigoso: nunca falha em voz alta.

O MySQL não tem tal passo. A autenticação nativa por palavra-passe é uma cadeia de resumos SHA1 e o caminho rápido do caching_sha2_password é uma cadeia de resumos SHA256, e em nenhum dos dois o servidor assina algo cuja verificação pudéssemos omitir. Sobram o enquadramento e dois resumos, e ambas as bibliotecas de resumo já estavam na árvore. Também não era preciso um construtor de consultas: há uma consulta, SELECT 1, está como constante, e a resposta esperada também.

Esta última lê-se como uma funcionalidade em falta. Acima de uma consulta fixa, uma expectativa configurável é um botão em que qualquer posição menos a predefinida faz a verificação falhar, e um botão desses ensina a quem administra que os botões não significam nada. O seu lugar será no dia em que existir uma segunda consulta fixa, e nem um dia antes. A versão também não é uma segunda consulta: o MySQL envia-a sem que lha peçam no pacote de aperto de mão, e a segunda consulta é sempre aquela a que daqui a um ano cresce um campo de texto livre.

Há duas coisas recusadas de imediato. A capacidade de cliente que permite a um servidor mandar o cliente ler um ficheiro do seu disco e enviá-lo não é pedida, e o byte de resposta correspondente é um erro de protocolo em vez de um ramo que faz alguma coisa. E a autenticação caching_sha2 completa só se faz sobre TLS: a alternativa numa linha nua é ir buscar a chave pública do servidor por essa mesma linha não autenticada, e quem estiver no meio responde com a sua. A recusa nomeia a reparação, que é ligar o TLS.

Uma página de estado conta ticks, não resultados

A mesma ronda colocou uma página de estado pública à frente de tudo isto, e a aritmética dessa página exigia uma decisão que sai mal com facilidade.

Cada execução devida de uma regra é um tick. Um tick pode acabar como up, degraded ou down, e também pode acabar como source_unavailable (a nossa própria fonte de medição não estava lá), configuration_error (a regra não fazia sentido), unsupported (o agente não conhecia aquele tipo de verificação) ou perdido. Estes últimos quatro não dizem nada sobre o serviço da clientela.

Se o denominador de uma disponibilidade diária fosse cada tick devido, uma avaria nossa baixaria o número na página de estado da clientela. Por isso o denominador é up mais degraded mais down: os ticks em que alguém olhou realmente para o serviço. Se disso não sobrar nada, a resposta desse dia é que não há resposta, explicitamente nem zero nem cem por cento. Na própria página esses quatro estados aparecem como desconhecido e nunca como down.

Degraded conta metade. É uma escolha e não uma lei da natureza: um serviço que responde o dia todo mas se mantém acima do seu limiar de latência não está em baixo nem está bem, e contá-lo para qualquer um dos lados deixa um dos dois sem sentido.

Os buracos são mostrados como buracos. Uma regra criada na semana passada não tem noventa dias de historial, e uma barra que preenche isso com o valor do dia vizinho afirma algo que nunca foi medido.

O que nos custou uma coleção de testes verde

Cada um dos defeitos acima estava debaixo de um teste aprovado. A coleção de simuladores não estava errada; respondia a uma pergunta diferente daquela que toda a gente julgava. O valor do canário não é ser uma prova melhor. É ser uma prova em que a outra ponta do fio foi escrita por alguém que não partilha as nossas suposições.

Isso vale seis contentores descartáveis, e vale a pena escrever que são descartáveis: correm na nossa própria máquina, são removidos quando terminam, e as provas recusam correr se os endereços não estiverem lá.

Do trabalho de monitorização da Provibr. Porque uma sonda alojada aceita exatamente um tipo de mensagem está em uma sonda que não se deixa convencer de nada, e porque um segredo de monitorização viaja uma só vez está em um segredo que viaja uma vez.

Comece com um host e um agente

Crie uma conta, instale o agente com um único comando e ligue o seu primeiro host Proxmox. O período experimental dura 14 dias e não pede quaisquer dados de pagamento.