Pular para o conteúdo

Enrollment e mTLS

O runner sobe sem segredo na imagem. Ele constrói a própria identidade no boot, prova quem é para o control-plane e recebe um certificado mTLS de vida curta. É a única forma de uma frota cattle ter identidade sem espalhar chave por aí.

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, e nunca é escrita em disco.
  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 runner 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.

O certificado vale no máximo 1 hora. RUNNER_CERT_RENEWAL=on é o default.

A renovação dispara a 2/3 da vida do certificado, 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.

A renovação não é um RUNNER_ATTESTATION_MODE. Renovar não é escolha de configuração: é o que o runner 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.

O modo configurado continua valendo para o enrollment inicial e para o caso de a renovação falhar até o vencimento.

O control-plane dá uma graça de 5 minutos para um certificado recém-expirado na atestação de renovação. Somada a 1 minuto de margem para skew de relógio, a identidade é considerada perdida após 6 minutos além do vencimento.

Passado isso, o control-plane responde presented cert expired beyond the renewal grace window, e vai responder isso para sempre. O runner não tem como se reerguer: 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 runner novo que enrolla do zero. Sem essa saída, o processo viraria zumbi: de pé, sem servir, e invisível para qualquer probe.

Existe um segundo caminho de identidade, e ele é só para desenvolvimento: fornecer um certificado pronto por RUNNER_CERT_PEM + RUNNER_KEY_PEM (+ RUNNER_CA_PEM). Quando os dois primeiros estão presentes, o runner pula o enrollment inteiro.

Terminal window
npm run dev:certs # minta CA + cert do túnel + cert cliente em dev-certs/, 30 dias

O log denuncia o atalho:

using provisioned cert (dev/test shortcut) {spiffeId: "spiffe://rootpilot/tenant/demo/runner"}

Com certificado provisionado a renovação não se aplica: não há enrollment para repetir.

O schema exige um dos dois caminhos, e falha rápido se faltarem os dois:

either provide RUNNER_CERT_PEM+RUNNER_KEY_PEM (dev shortcut) or RUNNER_ENROLL_URL (enrollment)