Ir para o conteúdo

Blogue ·

Uma sonda que não se deixa convencer

Monitorizar a partir de fora da rede de um cliente obriga a ter máquinas nossas. Eis como a plataforma impede que uma delas faça outra coisa.

Por Provibr

Verificar se a máquina de um cliente está acessível só tem significado a partir de um sítio onde a máquina não está. Um agente dentro da rede sabe dizer que o serviço está à escuta; não sabe dizer que a rota até ele está partida. Por isso colocam-se máquinas nossas na internet pública e deixa-se que sejam elas a estabelecer a ligação.

Isso é, no mesmo fôlego, uma funcionalidade e um passivo. Uma sonda é hardware nosso, num centro de dados partilhado com desconhecidos, a manter uma ligação de controlo aberta com a nossa plataforma. Se alguma coisa nessa ligação a conseguir persuadir a fazer outra coisa que não medir, ela passa a ser o ponto de apoio mais útil de todo o produto.

Um só binário, e é o token que decide o que se é

Uma sonda não é um programa à parte. É o agente que os nossos clientes correm, arrancado com probe enroll ou probe run em vez de run, sobre o mesmo motor de monitorização e o mesmo RPC de sessão. Uma compilação reduzida não conseguiria sequer executar um comando de hipervisor, o que parece mais seguro, mas implica uma segunda implementação da repetição, do recuo, da rotação de certificados e do motor de verificações, e a segunda recebe menos atenção a cada mês que passa. Ficámos com um binário e mudámos a fronteira para um sítio onde é possível testá-la.

A maneira óbvia de deixar um binário inscrever-se como duas coisas é então um campo no pedido de inscrição a dizer qual delas. É a maneira errada, e vale a pena ser direto quanto ao motivo: um cliente que declara a sua própria espécie declara os seus próprios privilégios, e todas as verificações posteriores assentam numa cadeia de caracteres fornecida por aquilo que está a ser verificado.

Por isso esse campo não existe. Há duas tabelas de tokens: enrollment_tokens, com uma chave estrangeira não nula para agents, e monitoring_probe_enrollment_tokens, com outra para monitoring_probes. Um token existe exatamente numa delas, pelo que aquilo que fica inscrito decorre de algo que nós emitimos e não de algo que quem chama disse. As duas falhas devolvem exatamente a mesma recusa, porque mensagens diferentes diriam a um atacante que espécie de token tinha encontrado.

O certificado que volta traz um nome comum da forma probe:<id> e, como nome alternativo do sujeito, um URI sem organização nenhuma lá dentro, ao passo que o de um agente nomeia o cliente. Isso é para um operador que lê um certificado e não é, explicitamente, a fronteira: um nome comum é uma cadeia de caracteres que uma autoridade de certificação escreve. A fronteira é que uma impressão digital se resolve em agents ou em monitoring_probes, nunca nas duas, porque são duas tabelas.

Um veredicto para cada mensagem, e não uma lista das permitidas

O protocolo de sessão são duas mensagens de envelope, cada uma a transportar uma de um conjunto de alternativas. Uma lista de permissão que nomeie apenas aquilo que uma sonda pode enviar fica verde para sempre, inclusive no dia em que alguém acrescentar um quinto tipo de mensagem, e nesse dia existe um tipo de mensagem que nada menciona e que, portanto, nada recusa. Por isso as tabelas no gateway nomeiam todos os campos dos dois envelopes com um veredicto explícito: vinte e duas entradas no sentido ascendente, das quais são aceites três mensagens verdadeiras mais um número de sequência, e catorze no sentido descendente, das quais são aceites uma instrução verdadeira e três confirmações de sessão. Um teste lê o TypeScript gerado e falha se um campo não tiver veredicto, e falha também no sentido inverso quando um nome da tabela deixa de existir no protocolo, porque uma regra que nada restringe lê-se exatamente como cobertura. Acrescentar uma mensagem de sessão custa agora uma linha, e essa linha é uma decisão com o nome de alguém no commit.

No sentido ascendente, uma sonda pode enviar três coisas: uma saudação, uma pulsação e o resultado de uma verificação. A recusa que vale a pena explicar é a dos resultados de comandos: o despachante associa um resultado a um comando pelo identificador e não pelo remetente, pelo que uma sonda capaz de enviar um deles poderia encerrar um comando pertencente a qualquer agente do parque. No sentido descendente, são recusadas dez entradas, e quatro delas são a razão de ser de todo este desenho:

MensagemO que transportariaPorque uma sonda nunca a pode ver
Envio de credenciaisOs acessos ao painel e ao hipervisor de um clienteUma sonda não tem cofre nem integração; não há estado nenhum em que isto signifique alguma coisa
Esquecimento de credenciaisUma instrução para apagar uma dessas credenciaisA mesma razão, ao contrário
Diretiva de arranque pela redeO que servir a uma máquina que arranca pela redeUma sonda não aprovisiona nada
Abertura de automatizaçãoUm ambiente isolado que corre scripts do clienteUma sonda não executa código de clientes

O registo em que uma sonda não está

A tabela acima é a metade demonstrável da promessa. A metade que aguenta o peso é não haver caminho nenhum. O despacho de comandos encontra o seu destino pelo identificador de agente; as sondas vivem num registo próprio, indexado pelo identificador de sonda, e não têm identificador de agente nenhum. Nenhuma pesquisa pode devolver uma sessão de sonda ao código que envia credenciais, mesmo que alguém acrescentasse uma nova espécie de instrução e se esquecesse por completo da tabela.

A única função que escreve para uma sonda verifica o veredicto na origem e não no recetor, porque uma mensagem que escapasse chegaria a uma máquina nossa a correr o mesmo binário que um agente de cliente, que poderia simplesmente executá-la. Faz também uma verificação que a tabela não consegue exprimir: command é aceite, mas um pedido de comando é ele próprio uma escolha entre vinte e quatro alternativas, das quais todas menos três são famílias de instruções de fornecedor, pelo que, sem ela, «uma sonda pode receber comandos» significaria em silêncio «uma sonda pode receber um comando de hipervisor». A carga útil tem de ser exatamente um campo, e esse campo tem de ser a verificação de monitorização. O teste percorre todos os campos de carga útil no código gerado e afirma que todos menos um são recusados.

Escolher uma sonda, e passar para a seguinte quando falha

Cada localização pode ter mais do que uma sonda, portanto alguma coisa tem de decidir. Uma função de dispersão módulo o número de sondas está errada no dia em que essa contagem muda: quase todas as regras mudam de sítio ao mesmo tempo, e gráficos de latência por todo o produto dão um salto no mesmo dia sem razão visível. O rendezvous hashing move apenas aquilo que estava ligado à sonda que saiu. A chave é a revisão da regra e a localização. A revisão é imutável, pelo que uma regra que o cliente edite pode legitimamente ir parar a outro sítio, ao passo que a escolha dentro de uma revisão nunca deriva. O slot está deliberadamente fora da chave: com o número do ciclo lá dentro, medições consecutivas cairiam em sondas diferentes, e uma diferença entre dois pontos podia muito bem ser uma diferença entre duas máquinas. A comparabilidade ao longo do tempo vale mais do que distribuir carga.

Uma sonda pode falhar de duas maneiras, notadas em momentos diferentes: pode ter desaparecido no momento do envio, ou pode aceitar o comando e nunca responder, o que só se manifesta depois de passado o tempo limite da própria verificação. Ambas produzem mais uma tentativa dentro do mesmo ciclo, e nunca um segundo ciclo. Passar para outra sonda não é uma segunda medição e, se o fosse, uma localização com azar duplicaria em silêncio a contagem de amostras. O teto são duas tentativas físicas, porque em conjunto têm de caber dentro do prazo do ciclo.

Quando uma localização não tem sonda saudável nenhuma, o slot é reclamado à mesma e a fonte fica registada como indisponível. Nunca como em baixo: uma localização nossa estar ausente não diz nada sobre a máquina do cliente, e chamar-lhe uma interrupção seria um alarme falso gerado pela nossa própria infraestrutura.

Três sítios que recusam um endereço privado

Uma regra de monitorização é um campo onde alguém indica um destino e uma máquina nossa se liga a ele todos os minutos. Sem limites, isso é um motor de falsificação de pedidos com uma morada de faturação associada. O alvo é uma atribuição de endereço pertencente ao próprio servidor do cliente e não texto livre, o que retira a maior parte do problema; o resto é uma lista de gamas em que uma sonda alojada não pode tocar, por cima das que estão fechadas em todo o lado:

GamaPorque está fechada
10/8, 172.16/12, 192.168/16Espaço privado. Numa sonda alojada isso é o centro de dados, não o cliente
100.64/10NAT de operador, onde os fornecedores penduram balanceadores de carga e caminhos de gestão
192.0.0/24, 192.0.2/24, 198.18/15, 198.51.100/24, 203.0.113/24, 192.88.99/24Atribuições de protocolo, aferição de desempenho, documentação, um anycast de retransmissão descontinuado. Nenhuma delas pode ser uma máquina de cliente, pelo que o que quer que responda está mais perto do que o alvo
fc00::/7, 2001:db8::/32A primeira e a terceira linhas acima, em IPv6: espaço local único e espaço de documentação

As formas IPv4 mapeadas são desembrulhadas primeiro, sob pena de todas as regras acima terem uma grafia que passa ao lado delas.

A verificação corre em três sítios e são precisos os três. A plataforma recusa antes de enviar, para que um alvo mau nunca chegue a uma máquina capaz de o executar. A sonda recusa no momento da execução, porque só ali se sabe para onde um nome se resolveu de facto, e isso pode variar de execução para execução. E a plataforma volta a ler o endereço comunicado quando o resultado chega, o que transforma uma sonda que mediu outro sítio numa resposta inválida em vez de num ponto de dados. Um destino privado é um erro de configuração e nunca uma falha: nada foi medido, portanto não há nada a dizer sobre o alvo.

O que registaríamos

  1. Nunca deixar o cliente nomear a sua própria espécie. Derivá-la de algo que tenha sido emitido por nós, e manter as duas recusas idênticas para que o erro não seja um oráculo.
  2. Enumerar o protocolo inteiro, e não a parte permitida. Deixar que um teste leia o código gerado e exija um veredicto por campo, nos dois sentidos, para que um novo tipo de mensagem seja um teste vermelho e não uma lacuna.
  3. Pôr a fronteira verdadeira onde não possa ser esquecida. Um registo separado sem identificador de agente significa que não há caminho, diga a tabela o que disser.
  4. Verificar a carga útil além do envelope. «Os comandos são permitidos» não é a mesma afirmação que «este comando é permitido».
  5. Manter a chave de atribuição livre do relógio e fazer da passagem para outra sonda uma tentativa a mais e não uma amostra a mais, para que uma mudança nos dados nunca possa ser confundida com uma mudança no instrumento.

Para ser claro: a monitorização alojada vive atrás de um sinalizador de lançamento que está desligado por predefinição, não existe por baixo dela camada de direitos nenhuma, e nada disto é comprável hoje. O que está por provar também merece ser nomeado. O subcomando da sonda nunca correu de ponta a ponta, porque um dos seus componentes de rede não compila na máquina onde isto foi escrito; as políticas de localização só foram exercitadas na sua forma mais simples; o IPv6 não foi medido em lado nenhum. A ligação em si foi conduzida a sério, da inscrição até uma sessão e uma verificação que voltou com resposta, e uma mensagem proibida nessa ligação foi recusada.

Do trabalho de monitorização no Provibr. A razão pela qual um nome de anfitrião tem de ser provado antes de poder ser um alvo está em provar um nome de anfitrião sem certificado, e a razão pela qual um incidente não fecha só porque as medições pararam está em um incidente que o silêncio nunca fecha.

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.