Removendo segredos commitados no Git com BFG Repo-Cleaner

Se você ainda não fez, tenho certeza que um dia fará.
Seja por preguiça, desinformação ou acidente, todo dev já commitou a senha do banco de dados no repositório.

Até que uma hora descobre que isso não é uma boa prática, chega a hora de migrar os segredos para um cofre mas eles ainda estão lá, commitados.
É só fazer checkout para um commit específico, que é possível ter acesso a eles. Se não tiverem sido mudados, é só fazer a festa.

Tive de me livrar de segredos commitados a um tempo atrás e usei o BFG Repo-Cleaner, e vou mostrar nesse post como limpei o repositório com essa ferramentinha simples e eficiente.

Commitando segredos

Primeiro erro — Usando git add .

Supondo que você tenha esse arquivo na sua aplicação, config.php:

<?php
 
return [
    'db_pass' => 'senha123',
];

Depois de criar esse arquivo você coda, coda e conclui a funcionalidade de listar produtos e está pronto para commitar:

# Comando fatal
$ git add .
$ git commit -m "feat: Lista de produtos"

[main (root-commit) f22bb72] feat: Lista de produtos
 2 files changed, 39 insertions(+)
 create mode 100644 config.php
 create mode 100644 lista-produtos.php

$ git push

Aí já vai a primeira dica:
evite ao máximo usar git add ..
Isso irá adicionar todos os arquivos (sejam eles pertencentes ao repositório ou não) e quando você rodar git commit o arquivo irá para o repo. — Você não quer adicionar o telefone do gás que você anotou em qualquer lugar rapidão no repositório, certo?

Segundo erro — git commit -a

A vida vai seguindo assim, e você vai adicionando segredos ao repositório, como a API key do Google Maps.

Seu arquivo config.php tá assim:

<?php
 
return [
    'db_pass' => 'senha123',
    'gmaps_api_key' => 'BBBCCCDDDAAAFFF111222',
];

E a cada commit você roda outro comando fatal:

# você aprendeu que não pode usar git add ., então adiciona certinho agora
$ git add pega-endereco.php
# comando fatal
$ git commit -a -m "Feat: Pega endereço via Google Maps"

[main 3ecdef6] Feat: Pega endereço via Google Maps
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 pega-endereco.php

Segunda dica: Não use a opção -a no comando git commit.
Ela irá adicionar ao commit todos os arquivos já trackeados no repositório. — Você não quer commitar aquela gambeta que você fez pra executar o sistema sem precisar de autenticação, certo?

Resolvendo

Enfim, acho que você já pegou o ponto.
Na vida real nós não trabalhamos na branch “main”, e provavelmente temos o code review, mas o exemplo aqui pode acontecer num freela, por exemplo, ou os reviewers talvez não dêem tanta atenção aos segredos.

Descoberto o problema, chegou a hora de mudar os segredos para um cofre, e você super cuidadoso vai no config.php e faz isso:

<?php
 
function getSecretFromVault(string $key): ?string
{
    return json_decode(file_get_contents("https://localhost:8200/secrets/{$key}"), true)['value'] ?? null;
}
 
return [
    'db_pass' => getSecretFromVault('db_pass'),
    'gmaps_api_key' => getSecretFromVault('gmaps_api_key' ),
];
$ git add config.php
$ git commit -m "internal: Pega os segredos de um cofre no arquivo de configuração"

[main 53fee5d] internal: Pega os segredos de um cofre no arquivo de configuração
 1 file changed, 7 insertions(+), 2 deletions(-)

$ git push

Agora tá bonito, hein? 🙂
Sempre que quisermos pegar um segredo é só chamar a funçãozinha que ela vai lá numa API HTTP, pega o segredo pelo nome e trás pra nós. Sem segredos no código… Certo?

Claro que, a partir daí todos os segredos provavelmente serão modificados, e os outros deixarão de ser um problema, mas ainda sim é legal deixar o repositório limpo.

Todos os commits a partir daqui estarão sem segredos, mas como eu disse no começo do artigo, se alguém fizer checkout para um commit anterior eles estarão lá.

$ git checkout f22bb72

Note: switching to 'f22bb72'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
  git switch -c <new-branch-name>
Or undo this operation with:
  git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at f22bb72 feat: Lista de produtos

$ cat config.php

<?php
 
return [
    'db_pass' => 'senha123',
];

$ git checkout main

Percebeu? É aí que entra o BFG Repo-Cleaner.

Removendo os segredos commitados

Aqui vou usar a mesma solução que eu usei quando precisei.
Criar uma lista de segredos (arquivo “secrets.txt”), rodar o BFG pedindo para ele substituir tudo o que estiver nesse arquivo por uma string qualquer.

Para evitar incidentes, crio esse arquivo na pasta pai do repositório: cd ..
Esse arquivo deve conter todos os segredos que estavam commitados, um por linha.

senha123
BBBCCCDDDAAAFFF111222

Agora, vamos limpar o repositório.

#Pasta do projeto é "meu-sistema"
$ bfg -rt secrets.txt meu-sistema

O comando acima serve para substituir todas as entradas do arquivo “secrets.txt” pela string “***REMOVED***” (padrão da ferramenta).

Feito isso, devemos garantir que o repositório seja limpo, impossibilitando a recuperação dos segredos.

$ cd meu-sistema
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive

O último comando que executamos tem duas partes:

  • Remove imediatamente todas as referências do histórico de mudanças em todo o reposittório (dificultando a recuperação dos segredos através do reflog)
  • Coleta todo o lixo (gc); remove todos os objetos que não são mais referenciados por nenhuma branch ou tag.

Confira o resultado, fazendo checkout para outros commits, outras branches, e no final, empurre as alterações com “git push -f”.

Observação: A ordem dos fatores deve ser essa, pois o BFG não altera o commit atual. Então você primeiro deixa o seu repositório funcional, para depois aplicar essas alterações.

Fim

Última dica:
Use arquivos contendo valores padrões como config.php.dist ou config.php.example.
Adicione o config.php ao .gitignore.

Alguns frameworks tem esqueletos apropriados para guardar os segredos de uma forma melhor que não seja no repositório, use esses esqueletos também!

Esse texto ensina como remover segredos usando só o git. Eu não me aventurei nisso ainda.

Qualquer dúvida, crítica, elogio ou sugestão… Manda!
Até a próxima!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *