Pular para o conteúdo

Identidade e enrollment

O RootPilot Agent se autentica por mTLS. Esta página é sobre a credencial que ele usa para isso: como ele a obtém, como ela se mantém viva, e — a pergunta que custa caro quando ninguém a fez antes — o que acontece quando o container é substituído.

Vale separar as duas coisas desde já, porque elas se confundem:

  • Como a identidade nasce — três caminhos, e você escolhe um.
  • Como ela sobrevive — renovação (dentro do processo) e persistência (através da troca de container).

A segunda metade é a que decide se um deploy exige alguém no navegador.

Caminho Quando usar O que você configura
Atestação de nuvem Onde a plataforma prova identidade sozinha: GKE, EC2/EKS RUNNER_ATTESTATION_MODE + RUNNER_ENROLL_URL
Bootstrap token Onde não há identidade de plataforma: VPS, host próprio, primeiro piloto RUNNER_BOOTSTRAP_TOKEN + RUNNER_ENROLL_URL
Certificado provisionado Ponte: nós emitimos, você monta RUNNER_CERT_PEM + RUNNER_KEY_PEM

A atestação de nuvem é a melhor onde existe, e a razão é concreta: não há segredo para mintar, entregar ou girar. Os modos disponíveis e o que cada um exige estão em Atestação.

O boot é uma máquina de estados: liveness → config → secrets → enroll → connect → ready. O passo enroll faz isto:

  1. Gera um par de chaves EC P-256. A chave privada existe só em memória, em PKCS#8.
  2. Gera um nonce e monta um CSR com ele no campo challengePassword, que é a prova de posse.
  3. Coleta a atestação da plataforma, conforme o RUNNER_ATTESTATION_MODE.
  4. POST para /api/enroll com { version, attestation, csr, challengeNonce }.
  5. O control-plane valida a atestação, assina o CSR e devolve o certificado com o SPIFFE id do tenant dentro: spiffe://rootpilot/tenant/<slug>/runner.
  6. O Agent abre o túnel WSS com mTLS usando esse certificado.

O tenant vem do certificado, não da configuração

Seção intitulada “O tenant vem do certificado, não da configuração”

Vale insistir nisso porque é a garantia que sustenta o resto: quem determina o tenant é o material de atestação, não uma variável de ambiente. O enrollment devolve um certificado com o SPIFFE id dentro, e é ele que o control-plane lê em cada chamada.

RUNNER_TENANT, que aparece nos scripts de frota, não escolhe tenant nenhum: escolhe a pasta do cofre de onde ler as credenciais. Errar esse slug não conecta você na organização errada — e, se o slug declarado discordar do que a credencial enrollou, o Agent recusa subir em vez de servir a organização errada.

O certificado tem vida curta — 15 minutos por padrão. Isso é de propósito: um certificado que vale pouco tempo vale pouco para quem o roubar.

RUNNER_CERT_RENEWAL=on é o default, e a renovação dispara a 2/3 do tempo que resta, não perto do vencimento. A razão é operacional: renovar em cima do notAfter transformaria qualquer instabilidade transitória de rede em perda de identidade. Renovando a 2/3, sobra um terço da vida como janela de retry.

O retry tem backoff com teto de 5 minutos e jitter de ±25% — sem o jitter, a frota inteira bateria no control-plane no mesmo segundo. Se o servidor responder Retry-After, ele vence o cálculo local.

A renovação exige RUNNER_ENROLL_URL: é para lá que ela fala.

O Agent verifica de 30 em 30 segundos se já é hora de renovar, comparando o relógio do sistema com o vencimento do certificado. Ele não arma um despertador para daqui a 40 minutos.

A diferença aparece quando o host para de contar o tempo: uma máquina suspensa, uma VM restaurada de snapshot, um container sem CPU por quota. Um despertador longo conta tempo de processo e não avança enquanto o host dorme — ele acordaria achando que ainda faltam minutos para renovar um certificado que morreu horas antes. Perguntando ao relógio do sistema a cada 30 segundos, o Agent descobre a situação real logo depois de voltar:

  • Voltou antes do vencimento — renova na hora. O certificado é salvo.
  • Voltou depois — declara a perda e sai, para o supervisor subir um substituto (ver Troubleshooting), em vez de passar meia hora tentando reconectar com uma identidade morta.

A renovação não é um RUNNER_ATTESTATION_MODE. Renovar não é escolha de configuração: é o que o Agent faz quando já tem certificado.

Ele assina o nonce do novo CSR com a chave privada atual e envia o certificado atual junto. O control-plane valida a cadeia contra a CA, confere a assinatura com a chave pública do certificado e extrai o tenant do certificado, nunca do corpo da requisição. Se lesse do corpo, renovar viraria uma via de trocar de tenant.

Aqui está a diferença que mais custa caro se ninguém a configurar.

A renovação vive dentro do processo. Substituído o container, a identidade morre com ele — e o fallback é enrollment do zero. Onde a identidade nasceu de um bootstrap token, isso significa um token novo, mintado à mão, a cada deploy (o token é de uso único).

RUNNER_IDENTITY_DIR resolve isso: aponta para um diretório onde o Agent grava a identidade (identity.json, modo 600). No boot seguinte ele renova a partir dela em vez de enrolar do zero.

volumes:
- rootpilot-identity:/identity # volume nomeado: herda a posse certa da imagem
environment:
RUNNER_IDENTITY_DIR: /identity

É só isso: a variável basta sozinha. O Agent cria, na primeira vez que usa o diretório, um id de dono que fica gravado ali (instance-id, modo 600) — então a identidade continua sendo reconhecida como dele depois de o container ser recriado, que é o ponto inteiro da feature.

O RUNNER_INSTANCE_ID continua valendo a pena, mas por outro motivo: é ele que identifica a instância nos logs e no Fleet. Não tem mais relação com a persistência.

Três coisas que valem saber antes de ligar:

  • O diretório precisa ser gravável pelo uid do Agent (a imagem roda como não-root, uid 1000). Um volume nomeado resolve sozinho: a imagem traz /identity com a posse certa, e o volume a herda ao ser criado. Um bind mount não herda — a posse é a do host. Se não for gravável, o boot não falha: ele avisa e segue, e você só descobre no deploy seguinte, com could not persist identity — next boot will re-enroll from scratch.
  • Um diretório por Agent. Dois containers no mesmo volume dividiriam identidade.
  • A janela tem limite. O que sobrevive é a renovação, e ela vale até 6 minutos além do vencimento do certificado (5 de graça + 1 de margem de relógio). Um deploy normal cabe folgado; um host desligado a noite inteira, não — aí é enrollment do zero.

Passados os 6 minutos, o control-plane responde presented cert expired beyond the renewal grace window, e vai responder isso para sempre: a atestação de renovação prova posse com o certificado que morreu.

O comportamento então é sair do processo, para que o supervisor suba um Agent novo que enrolla do zero. Sem essa saída, o processo viraria zumbi: de pé, sem servir, e invisível para qualquer probe.

identity expired beyond the renewal grace window — runner needs re-enrollment

O RootPilot emite um certificado para o seu tenant e você o monta em /certs (ou passa inline em RUNNER_CERT_PEM/RUNNER_KEY_PEM/RUNNER_CA_PEM). Não exige atestação de plataforma nem egress para o endpoint de enrollment, e é a ponte para ambientes onde nenhum dos outros dois se aplica ainda.

O custo é declarado: ele não se renova. Quando vencer, a frota para de conectar, e o Agent não sai do processo nem avisa — ele fica tentando reconectar indefinidamente, com erro de TLS que não diz que é isso. A data está no notAfter da linha de boot; anote-a no calendário no dia em que receber o certificado.

O schema exige pelo menos um caminho de identidade, e falha rápido se não houver nenhum:

either provide RUNNER_CERT_PEM+RUNNER_KEY_PEM (provisioned cert) or RUNNER_ENROLL_URL (enrollment)