Pular para conteúdo

0030 — Credencial FCM fora do jar e exigida no boot

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-20 · Decidido em: 2026-09-18

Contexto

A credencial do firebase-admin vivia em src/main/resources/firebase-admin-sdk.json, versionada: chave privada no git e, pelo empacotamento, também dentro do app.jar publicado no registry. A property push.credentials-path já existia e o FcmPushService já lia de disco, mas o default era classpath:firebase-admin-sdk.json — o arquivo embutido.

A migração para native (.ia/017) força a mudança: no native a credencial passa a depender de metadata de recurso, e o jeito de injetá-la muda de qualquer forma. É a hora de tirá-la do build.

Havia ainda um problema de visibilidade. A credencial era aberta na primeira tentativa de push — credencial ausente ou path errado logava WARN só naquele momento. Numa instância que passa dias sem push, uma credencial trocada de lugar fica invisível justamente até a hora em que ela importa. Com a credencial saindo do jar para um secret file montado, "path errado" deixa de ser hipótese remota e passa a ser o modo de falha esperado de um deploy mal configurado.

Decisão

  • A credencial sai do repo e do jar. Em produção ela chega por PUSH_CREDENTIALS_PATH apontando para um secret file montado no container. Nenhuma mudança na assinatura do FcmPushService: a property já existia e o serviço já lia de disco.
  • Com o push ligado, a credencial é conferida no boot e a instância não sobe sem ela. Com push.enabled=true, um @EventListener de StartupEvent abre a credencial e lança se ela não abrir, com mensagem nomeando o path e a saída (PUSH_ENABLED=false).
  • Push desligado não exige credencial, e empresa não reconhecida (que já desliga o push) não derruba o boot.

Isso é o que a norma da casa manda: "property de segredo não declarada → feature desligada; declarada e vazia → boot recusado, com a env nomeada" (engenharia/seguranca, Segredos). PUSH_ENABLED=true é a feature declarada ligada. Não conflita com a decisão 0026, que preteriu recusar o boot por CENTRAL_API_TOKEN: lá a feature fica desligada nas instâncias que não a usam — o primeiro caso da norma, não o segundo.

O gate é @EventListener(StartupEvent) e não @Requires(property=…, pattern=".+") porque a condição não é "a property está vazia" e sim "a credencial abre": o path pode estar preenchido e apontar para um arquivo que não existe, que é exatamente a falha que se quer pegar.

Alternativas preteridas

  • Manter o aviso lazy (WARN no 1º push). Zero mudança, mas a troca para secret file é justo quando um path errado passa despercebido — e passaria dias, até o primeiro push.
  • Avisar no boot com WARN, sem derrubar. Visível no deploy, mas um WARN em log de startup é lido por quem está olhando; o deploy seguiria verde com o push morto.
  • JSON inteiro numa env (PUSH_CREDENTIALS_JSON). Evita arquivo no disco, mas põe chave privada multiline na env do Coolify, visível em docker inspect, e exigiria mudar o FcmPushService.
  • Manter no classpath com a chave rotacionada. Menor mudança de runtime, mas a chave continuaria viajando na imagem do registry — que é o problema de origem.

Consequências

  • A ordem de deploy passa a importar: a env PUSH_CREDENTIALS_PATH e o secret file precisam existir antes do deploy que remove o JSON do jar. Deployar antes derruba as instâncias com PUSH_ENABLED=true — hoje, as de push vivo.
  • Rotacionar a chave exposta no git é pré-requisito, e a rotação é o que invalida o vazamento; tirar do repo sozinho não invalida nada.
  • O resource-config.json do native continua incluindo firebase-admin-sdk.json: é inócuo quando a credencial vem de disco e ainda cobre o caminho de teste.
  • Config: application-config.