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 ou acidente, acho que toda pessoa que desenvolve software já commitou aquela senha de SMTP no repositório, ou a senha do banco de dados.

Até que 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

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 coda um pouco mais, 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?

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?

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.

Mas chegou a hora de mudar isso 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, rodar o BFG pedindo para ele substituir tudo o que está naquele arquivo por uma string qualquer.

Só pra ficar legal, mudo para a pasta anterior ao repositório: cd ..
Agora crio o arquivo com os segredos que estavam commitados, um por linha.

Este é o conteúdo do arquivo segredos.txt:

senha123
BBBCCCDDDAAAFFF111222

Agora, com os comandos abaixo você se livra do que tem que se livrar:

$ bfg -rt segredos.txt segredo
$ cd segredo
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive

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 *