Deploy no token do João: como criar conta de serviço que não depende de ninguém
Troque tokens pessoais por identidades próprias para CI, servidor e banco, com permissão mínima, dono humano, rotação e um inventário que mostra onde cada uma é usada.
Sexta, 17h40. O pipeline de deploy da loja falha com "Bad credentials". Ninguém mexeu no pipeline. O que mudou foi que o João saiu da empresa na quarta, o TI desativou a conta dele no GitHub, e o token pessoal que o João criou em 2023 para "destravar o deploy rapidinho" morreu junto. O final alternativo é pior: ninguém desativa nada, e o João continua com um token capaz de publicar na produção de uma empresa onde ele não trabalha mais.
A resposta curta: robô não usa credencial de gente. Cada sistema automático (pipeline, aplicação, script de backup) ganha uma identidade própria, com a menor permissão que resolve, um dono humano responsável e um registro de onde ela é usada. Quando uma pessoa entra ou sai, nada automático quebra e nada automático fica com o poder dela.
Por que o token pessoal parece prático e vira problema
Token pessoal é o caminho de menor esforço: a pessoa já tem acesso, gera o token em dois cliques, cola no segredo do CI e o deploy volta. O problema é que esse token herda tudo o que a pessoa pode fazer. Se o João era administrador da organização, o pipeline também virou administrador. Um vazamento do segredo do CI vira acesso de administrador.
Além do excesso de poder, a credencial carrega o ciclo de vida da pessoa. Ela vence quando a pessoa sai, troca de equipe ou perde um acesso. Os registros de auditoria mostram "João publicou" quando quem publicou foi o robô, o que confunde qualquer investigação. E, como o token está "no nome do João", ninguém mais se sente responsável por trocá-lo.
O mesmo padrão aparece fora do GitHub: aplicação conectando no banco com o usuário pessoal do DBA, serviço rodando no Linux como o usuário de quem fez a instalação, script de backup usando a chave SSH do notebook de alguém.
Que tipo de identidade usar em cada lugar
- Dentro do GitHub Actions, o próprio GitHub entrega um
GITHUB_TOKENtemporário por execução, com permissões que você restringe no workflow. Para ações no próprio repositório, ele basta. - Para acessar outros repositórios ou a API em nome da organização, prefira um GitHub App instalado na organização, com permissões escolhidas e tokens de curta duração.
- Para um servidor buscar um repositório específico, uma deploy key: chave SSH ligada a um único repositório, somente leitura por padrão.
- Para o pipeline falar com nuvem (AWS, Azure, Google Cloud), a federação por OIDC do GitHub Actions troca um token de curta duração por credencial temporária, sem segredo fixo guardado.
- No servidor e no banco, um usuário de sistema e um usuário de banco por serviço, nunca o de uma pessoa.
Tokens pessoais de granularidade fina, com prazo e repositórios escolhidos, são melhores que os clássicos, mas continuam presos a uma pessoa. Servem para uso pessoal, não para robô.
Como migrar a loja, passo a passo
- Faça o inventário. Liste todo segredo do CI, toda variável de ambiente com senha ou token, todo usuário de banco e todo serviço do servidor. Para cada um, anote de quem é a credencial.
- Crie uma identidade por uso. Uma para o deploy, outra para a aplicação acessar o banco, outra para o backup. Identidade compartilhada entre usos não pode ser revogada sem derrubar todos.
- Dê a permissão mínima. O deploy escreve na pasta do site; a aplicação lê e grava em tabelas; o backup só lê.
- Nomeie um dono e um substituto. Pessoas, não "o time". São elas que recebem o aviso de expiração e fazem a troca.
- Guarde no cofre, nunca na pasta pessoal de ninguém, nem no repositório.
- Troque e revogue a antiga. Só depois de a nova estar funcionando, e sempre nessa ordem.
- Prove que funciona. Desative temporariamente a conta pessoal de quem configurou e rode o pipeline.
O workflow sem token pessoal
name: publicar
on:
push:
branches: [main]
permissions:
contents: read
jobs:
publicar:
runs-on: ubuntu-latest
environment: producao
steps:
- uses: actions/checkout@v4
- name: Enviar para o servidor
env:
CHAVE_DEPLOY: ${{ secrets.DEPLOY_SSH_KEY }}
HOST_CONHECIDO: ${{ vars.SSH_KNOWN_HOSTS }}
run: |
install -m 700 -d ~/.ssh
printf '%s\n' "$CHAVE_DEPLOY" > ~/.ssh/deploy
printf '%s\n' "$HOST_CONHECIDO" > ~/.ssh/known_hosts
chmod 600 ~/.ssh/deploy
rsync -az --delete -e "ssh -i ~/.ssh/deploy" ./public/ deploy@loja.exemplo.com.br:/srv/loja/
O bloco permissions limita o GITHUB_TOKEN a leitura do código. O environment: producao permite exigir aprovação e restringir o segredo a esse ambiente. A chave SSH não é de ninguém: foi gerada só para isso, e a parte pública está no usuário deploy do servidor. O known_hosts vem de uma variável para que a conexão confira a identidade do servidor em vez de aceitar qualquer um. Como gerar e instalar a chave está em chave SSH na VM.
No servidor e no banco
# quem publica: precisa de shell e de home para o authorized_keys
sudo useradd --system --create-home --shell /bin/bash deploy
sudo chown -R deploy:deploy /srv/loja
# quem roda a aplicação: sem login interativo
sudo useradd --system --no-create-home --shell /usr/sbin/nologin loja
CREATE USER 'loja_app'@'10.0.0.%' IDENTIFIED BY 'senha-gerada-pelo-cofre';
GRANT SELECT, INSERT, UPDATE, DELETE ON loja.* TO 'loja_app'@'10.0.0.%';
CREATE USER 'loja_backup'@'10.0.0.%' IDENTIFIED BY 'outra-senha-do-cofre';
GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT ON loja.* TO 'loja_backup'@'10.0.0.%';
Separar deploy de loja significa que uma falha na aplicação não dá permissão de reescrever o próprio código. No systemd, o serviço roda com User=loja. No banco, a aplicação não consegue apagar tabelas e o backup não consegue alterar dados. No MySQL 8, rode o mysqldump desse usuário com --no-tablespaces; sem isso ele pede o privilégio global PROCESS, que você não precisa dar. As senhas nos exemplos são marcadores: a real é gerada e guardada no cofre, e a aplicação a recebe por variável de ambiente ou arquivo de segredo.
O inventário que salva a próxima troca
Mantenha um registro simples, num lugar que o time consulta, com uma entrada por identidade:
identidade: deploy-loja (chave SSH, usuário deploy no servidor)
usada em: GitHub Actions do repo loja, segredo DEPLOY_SSH_KEY
permissão: escrita em /srv/loja
dono: Marina (substituto: Carlos)
criada em: 2026-03-02
troca até: 2026-09-02
Sem isso, a rotação vira caça ao tesouro: você troca a senha, e três dias depois descobre um cron esquecido num servidor que parou de fazer backup. A troca em si segue a ordem descrita em rotação que tem dois passos.
Erros comuns
- Token pessoal no CI. O deploy quebra quando a pessoa sai ou, pior, continua funcionando com o poder dela depois que ela saiu.
- Uma conta de serviço para tudo. Para revogar o acesso do backup, você derruba também o deploy e a aplicação.
- Conta de serviço sem dono. O aviso de expiração vai para uma caixa que ninguém lê, e a credencial vence numa sexta à noite.
- Permissão de administrador "para não dar erro". Qualquer vazamento do segredo vira controle total do repositório ou do banco.
- Revogar antes de trocar. A identidade nova ainda não foi testada, e a antiga já não funciona.
- Segredo colado no código "temporariamente". Ele fica no histórico do Git mesmo depois de apagado, como explica token de bot que o Git não pode ver.
Checklist
- Nenhum segredo do CI pertence a uma conta pessoal.
- O workflow declara
permissionscom o mínimo necessário. - Cada uso (deploy, aplicação, backup) tem identidade própria.
- A aplicação roda num usuário de sistema sem login interativo.
- Usuários de banco têm só os privilégios que o serviço usa.
- Toda identidade tem dono, substituto e data de troca no inventário.
- O pipeline foi testado com a conta pessoal de quem o configurou desativada.
Abra agora a lista de segredos do seu CI e, para cada um, responda: se a pessoa que o criou fosse desligada hoje, o deploy de amanhã ainda funcionaria por mérito próprio?