· Oleksandr Oleniuk · Development  · 14 min de leitura

Deixe de guardar tokens de desenvolvimento no .bashrc: um fluxo de trabalho prático em Linux com pass e GPG



Deixe de guardar tokens de desenvolvimento no .bashrc: um fluxo de trabalho prático em Linux com pass e GPG

«O meu portátil fica em casa. Ninguém vai assaltar a casa para roubar o meu token do npm.»

Provavelmente é verdade. Mas abrir o .bashrc durante uma partilha de ecrã exige bastante menos planeamento do que um assalto.

As credenciais de quem desenvolve software tendem a acabar no ficheiro que permite executar o próximo comando. Depois de npm login, o seu ~/.npmrc pode conter um token em texto simples (este exemplo é fictício):

//registry.npmjs.org/:_authToken=npm_example_token_not_real

Outras credenciais acabam no .bashrc, disponíveis em cada novo terminal:

export API_KEY="..."
export GITLAB_TOKEN="..."
export DATABASE_PASSWORD="..."

É prático — até esses valores acabarem num repositório de dotfiles, numa cópia de segurança ou no ambiente de um programa que nunca precisou deles.

A shell de um programador não deve incluir automaticamente todas as suas credenciais. Com pass e GPG, pode manter os segredos cifrados no disco e obtê-los para os comandos que deles precisam.

O que este fluxo de trabalho realmente protege

Este fluxo de trabalho reduz a exposição de valores em texto simples nos ficheiros de configuração e nas cópias de segurança, e limita os processos que recebem credenciais. Ajuda a evitar erros do dia a dia: copiar um dotfile, partilhar a saída do terminal ou exportar um token para todas as shells.

Não torna segura uma estação de trabalho comprometida nem protege uma credencial do programa que a utiliza. Software malicioso pode pedir a decifragem através de um agente desbloqueado, e o processo que recebe o valor continua a poder registá-lo, transmiti-lo ou passá-lo aos seus processos filhos.

Os tokens de registos de pacotes são o nosso principal exemplo, mas uma estação de trabalho típica tem vários tipos de segredos que vale a pena gerir desta forma:

  • Credenciais de registos de pacotes: tokens para npm, Nexus ou Artifactory, e credenciais para fontes privadas de pacotes PyPI ou NuGet.
  • Tokens de alojamento Git: tokens de acesso pessoal do GitHub ou GitLab para repositórios privados e acesso à API.
  • Chaves de API de terceiros: chaves da OpenAI, chaves de teste da Stripe ou credenciais para APIs internas.
  • Credenciais de bases de dados: palavras-passe e cadeias de ligação para bases de dados de desenvolvimento ou de pré-produção.
  • Chaves de cifragem de aplicações: chaves que um serviço local utiliza para decifrar a sua configuração no arranque.

Guarde os segredos no pass

O pass é um gestor de palavras-passe para a linha de comandos que guarda palavras-passe, tokens e outros segredos em ficheiros cifrados com GPG. Cada entrada fica em ~/.password-store. A cifragem utiliza a sua chave pública; a decifragem exige a chave privada correspondente.

Instale GnuPG e pass através do gestor de pacotes da sua distribuição. Existem pacotes para Debian, Ubuntu, Arch Linux e Fedora:

# Debian / Ubuntu
sudo apt install gnupg pass

# Arch Linux (btw)
sudo pacman -S gnupg pass

# Fedora
sudo dnf install gnupg2 pass

Após a instalação, o fluxo de trabalho é o mesmo em todas as distribuições. Crie uma chave se ainda não tiver uma adequada:

gpg --full-generate-key
gpg --list-secret-keys --keyid-format=long

Escolha uma configuração de chave que permita cifrar e proteja-a com uma frase-passe forte. Copie a impressão digital completa da chave a partir da listagem e substitua o marcador abaixo:

pass init YOUR_GPG_KEY_FINGERPRINT
pass insert dev/northstar-encryption-key
pass insert dev/nexus-northstar-token
pass insert dev/nexus-orbit-token
pass insert dev/npm-personal-token

Introduza cada segredo num prompt que oculta os caracteres. Utilize tokens de registo apenas de leitura para instalar dependências e limite cada entrada a uma linha: apenas o segredo. Não cole os valores nos próprios comandos, pois podem ficar registados no histórico da shell. Estas entradas fictícias correspondem à função auxiliar abaixo; utilize nomes adequados aos seus projetos.

Faça uma cópia de segurança do armazenamento cifrado e mantenha uma cópia de segurança separada e protegida da chave privada GPG. Teste a recuperação antes de depender dela. O armazenamento, por si só, não permite recuperar o acesso sem a chave. Os nomes das entradas e dos diretórios continuam visíveis, por isso evite incluir informações sensíveis nesses nomes.

Torne a decifragem mais prática

Para os prompts no terminal, adicione o seguinte ao seu .bashrc interativo, seguindo as instruções de configuração do agente GPG:

export GPG_TTY="$(tty)"

Esta variável identifica o seu terminal; não contém nenhum segredo. Certifique-se de que a sua distribuição também tem um programa Pinentry funcional para pedir a frase-passe.

O gpg-agent guarda a frase-passe em cache depois de desbloquear a chave, para que os comandos seguintes não tenham de a pedir sempre. A cache está ativa por predefinição. Para definir explicitamente os seus tempos limite, crie ou edite ~/.gnupg/gpg-agent.conf e adicione estas linhas, substituindo quaisquer entradas existentes para as mesmas opções:

default-cache-ttl 600
max-cache-ttl 7200

Estes são os valores predefinidos documentados: um prazo de 10 minutos de inatividade e uma duração máxima de duas horas. A utilização de uma entrada em cache reinicia a contagem de inatividade, mas não prolonga a duração máxima. Recarregue o agente para aplicar a configuração:

gpgconf --reload gpg-agent

O próximo comando que decifrar um segredo pedirá a sua frase-passe e voltará a guardá-la em cache. Prazos de cache mais longos tornam a utilização mais prática, mas prolongam o período durante o qual é possível decifrar sem um novo pedido. Comece pelos valores predefinidos e ajuste-os apenas se necessário. Consulte as opções de cache do agente e as instruções relativas ao ficheiro de configuração.

Carregue os segredos para um único comando

Remova as exportações de tokens dos seus ficheiros de arranque. Coloque esta função auxiliar Bash no .bashrc:

with-secrets() {
  NORTHSTAR_ENCRYPTION_KEY="$(pass show dev/northstar-encryption-key)" \
  NEXUS_NORTHSTAR_TOKEN="$(pass show dev/nexus-northstar-token)" \
  NEXUS_ORBIT_TOKEN="$(pass show dev/nexus-orbit-token)" \
  NPM_PERSONAL_TOKEN="$(pass show dev/npm-personal-token)" \
    "$@"
}

alias ws='with-secrets'

Cada atribuição fornece uma variável de ambiente ao comando passado através de "$@". As aspas preservam os argumentos, incluindo os espaços. As duas variáveis da aplicação leem a mesma chave de cifragem guardada, porque este exemplo pressupõe que as ferramentas locais a esperam com ambos os nomes. Adapte os nomes ao que as suas aplicações realmente leem.

Passe sempre um comando a esta função auxiliar. Esta versão compacta pressupõe que todas as consultas são bem-sucedidas: uma falha em pass show não impede que o comando arranque com um valor vazio. Adicione verificações explícitas às consultas se o comando tiver de parar quando não for possível obter um segredo.

Abra uma nova shell e execute:

ws npm install
# Or, in a Yarn project:
ws yarn install

Nestas invocações, as atribuições fornecem credenciais ao comando sem as exportar permanentemente para a sua shell interativa. Os processos descendentes podem herdá-las, e os processos em segundo plano que continuem a executar podem retê-las depois de o comando original terminar. Trata-se de uma disponibilização temporária, sem garantia de eliminação.

Esta função auxiliar carrega as cinco variáveis para cada comando que executa. Para um controlo mais rigoroso, divida-a em funções auxiliares para a instalação de pacotes e para o arranque da aplicação, e carregue apenas as credenciais de que cada projeto precisa. A instalação a partir de um registo não tem motivo para receber uma chave de cifragem da aplicação ou um token de publicação.

Remova também as exportações antigas das sessões existentes, ou reinicie o terminal e qualquer IDE que as tenha herdado. A função não limpa um ambiente que já contém essas credenciais. Mantenha o rastreio da shell (set -x) desativado durante a obtenção dos segredos: pode imprimir as atribuições com os valores expandidos.

Os segredos passam do pass através de with-secrets para o comando e os seus processos filhos.


Mantenha a configuração do npm e do Yarn livre de tokens

A configuração habitual começa com npm login: autentica-se no seu registo, a instalação de pacotes funciona e regressa ao desenvolvimento. Nos bastidores, o npm guarda essas credenciais no seu ficheiro de configuração de utilizador, normalmente ~/.npmrc. A autenticação fica resolvida, mas uma credencial em texto simples passa a estar num ficheiro de configuração de uso corrente.

O passo seguinte é substituir esse valor guardado por uma referência a uma variável de ambiente e deixar que a função forneça o token a partir de pass. Para instalar dependências, utilize o token apenas de leitura guardado anteriormente. Remova também da sua configuração de utilizador qualquer credencial em texto simples existente para esse registo; adicionar uma referência ao .npmrc de um projeto não remove a cópia em ~/.npmrc.

Para npm, o .npmrc de um projeto pode conter:

//registry.npmjs.org/:_authToken=${NPM_PERSONAL_TOKEN?}

Esta entrada utiliza o registo público do npm e a variável NPM_PERSONAL_TOKEN da função auxiliar. Para um registo privado, altere o host e a referência à variável para corresponderem ao registo; inclua também o caminho do registo, se existir.

O ? indica às versões atuais do npm que devem substituir a referência por uma cadeia vazia quando a variável não está definida. A forma simples ${NPM_PERSONAL_TOKEN} deixa a referência por substituir quando a variável não está definida. Este exemplo segue a documentação de configuração do npm v11; confirme a compatibilidade se o seu projeto utilizar uma versão anterior fixa do npm.

Para Yarn 4, mantenha os pacotes públicos no registo predefinido do Yarn ou utilize diretamente o registo público do npm. Encaminhe os seus pacotes privados para um registo separado. Este exemplo de .yarnrc.yml utiliza uma empresa fictícia, Northstar:

# Yarn's default public registry
npmRegistryServer: 'https://registry.yarnpkg.com'
# Alternatively, use npm's public registry directly:
# npmRegistryServer: 'https://registry.npmjs.org'

npmRegistries:
  'https://nexus.northstar.example/repository/npm-public':
    npmAlwaysAuth: true
    npmAuthToken: '${NEXUS_NORTHSTAR_TOKEN:-}'

npmScopes:
  northstar-platform:
    npmRegistryServer: 'https://nexus.northstar.example/repository/npm-public'
    npmAlwaysAuth: true
    npmAuthToken: '${NEXUS_NORTHSTAR_TOKEN:-}'

npmRegistries configura a autenticação para o URL de um registo. npmScopes encaminha pacotes como @northstar-platform/ui para o registo privado; aqui, os nomes dos âmbitos omitem o @. Os restantes pacotes utilizam o registo público, onde as transferências de pacotes públicos não exigem um token. npmAlwaysAuth: true indica ao Yarn que envie credenciais nos pedidos ao registo privado, incluindo os de leitura. Consulte a referência de configuração do Yarn.

A forma ${NAME:-} do Yarn utiliza uma cadeia vazia quando a variável está em falta ou vazia. A forma simples ${NAME} lança um erro quando a variável está em falta, podendo bloquear até comandos de compilação sem relação com ela durante a análise da configuração.

A função auxiliar já fornece NEXUS_NORTHSTAR_TOKEN a partir de dev/nexus-northstar-token. Este projeto só precisa dessa credencial do registo privado, pelo que uma função específica do projeto pode omitir as outras quatro variáveis. O exemplo pressupõe autenticação com um bearer token.

O fluxo de trabalho diário passa a ser:

yarn build
yarn test
ws yarn install

O recurso a um valor vazio não concede acesso a pacotes privados. As operações autenticadas continuam a precisar da função; as compilações e os testes habituais podem ser executados sem ela, desde que não obtenham, por si próprios, dependências privadas.

E um serviço que permanece em execução?

Um serviço pode precisar de uma chave uma única vez, para decifrar um ficheiro de configuração no arranque. Guarde essa chave separadamente com pass insert work/config-key e, em seguida, inicie o serviço a partir de uma subshell:

(
  CONFIG_KEY=$(pass show work/config-key) || exit 1
  [ -n "$CONFIG_KEY" ] || exit 1
  export CONFIG_KEY
  exec node server.js
)

A aplicação pode remover a entrada do ambiente depois de a ler. Aqui, decryptConfig representa a rotina síncrona de decifragem já existente na sua aplicação:

function loadConfig() {
  const key = process.env.CONFIG_KEY;
  delete process.env.CONFIG_KEY;

  if (!key) throw new Error('CONFIG_KEY is required');
  return decryptConfig(key);
}

const config = loadConfig();

O serviço continua a utilizar a configuração decifrada em memória. Eliminar a propriedade do ambiente reduz a exposição acidental quando o ambiente é registado mais tarde e impede que a propriedade seja herdada por processos filhos iniciados posteriormente. Não apaga a cadeia local key, as cópias anteriores nem a configuração decifrada. Execute este passo antes de iniciar workers ou processos filhos que não precisem da chave.


O token acompanha a árvore de processos

ws yarn install entrega um token a mais do que um cliente HTTP. Os gestores de pacotes podem executar scripts do ciclo de vida das dependências:

As credenciais fornecidas ao Yarn são herdadas pelos scripts de instalação das dependências e pelos seus processos filhos.

Tanto o executor de scripts do npm como a configuração do ambiente de scripts do Yarn transportam o ambiente do processo pai para a execução dos scripts. Considere que o código executado durante uma instalação pode ler o token fornecido.

Utilize uma credencial apenas de leitura, limitada aos pacotes de que precisa. Guarde as credenciais de publicação numa entrada separada e obtenha-as apenas para publicar. As definições de tokens granulares do npm permitem restringir os pacotes, definir permissões apenas de leitura e estabelecer prazos de validade. Ative também a autenticação de dois fatores (2FA) na conta, sem esquecer que isso não torna inofensivo um bearer token roubado.

Inclua os lockfiles no controlo de versões, reveja as alterações às dependências e questione a utilização de pacotes desconhecidos ou desnecessários. Sempre que for viável, desative os scripts de instalação das dependências, tendo em conta os pacotes que precisam de compilação nativa ou de ficheiros gerados. Estas medidas reduzem o risco; o armazenamento cifrado não consegue proteger um token de código malicioso executado enquanto esse token está disponível.

O mesmo fluxo de trabalho em macOS e Windows

A ferramenta de armazenamento pode mudar, mantendo-se o fluxo de trabalho: obter uma credencial quando é necessária e passá-la ao processo que a utiliza.

  • macOS: o Keychain disponibiliza armazenamento integrado para palavras-passe e outros segredos. Uma função auxiliar da shell pode obter uma credencial guardada no arranque e fornecê-la a um comando, mantendo o valor fora de .zshrc e da configuração do projeto.
  • Windows: o PowerShell SecretManagement com SecretStore disponibiliza uma interface para scripts assente num cofre local cifrado. Um programa de arranque pode obter um valor com Get-Secret e colocá-lo no ambiente do processo filho, mantendo as credenciais fora dos perfis do PowerShell e das variáveis de ambiente persistentes do utilizador.
  • Em várias plataformas: se já utiliza 1Password, a 1Password CLI oferece uma alternativa prática. Guarde referências op:// num ficheiro de ambiente e utilize op run --env-file=./dev.env -- yarn install para as resolver e fornecer os segredos a esse comando. O ficheiro contém referências; os valores reais permanecem no seu cofre até serem pedidos.

Aplicam-se os mesmos limites: o processo que recebe as credenciais e os seus processos filhos podem lê-las ou divulgá-las.

Utilize os segredos de forma deliberada nos fluxos de trabalho com agentes

Um agente de programação pode precisar de um token de registo para instalar dependências privadas. No entanto, iniciar todo o agente através de with-secrets dá à sua sessão e aos processos filhos todas as credenciais da função auxiliar. Divida a função para que a instalação receba apenas o token necessário, com permissões apenas de leitura, enquanto os restantes comandos são executados sem ele.

Para um fluxo de trabalho mais simples, instale as dependências através da função e, em seguida, inicie o agente a partir de uma shell sem credenciais exportadas. Se o agente alterar as dependências, faça uma nova instalação autenticada. Mantenha os valores dos segredos fora dos prompts, dos ficheiros gerados e da saída dos comandos.

Os scripts de instalação continuam a herdar o token. Além disso, um agente que possa invocar livremente pass enquanto a sua chave está desbloqueada pode obter segredos por si próprio. A função limita a exposição habitual; impor restrições de acesso exige controlos sobre as ferramentas ou o ambiente de execução do agente.

Mantenha os segredos fora dos dotfiles

Os aliases, as funções auxiliares, as referências a variáveis e os caminhos de entradas que não contenham informações sensíveis podem ficar num repositório de dotfiles. As credenciais reais, os ficheiros .env com segredos, a configuração decifrada, as chaves privadas e as palavras-passe incluídas em URLs devem ficar fora dele.

Há também uma pequena vantagem no dia a dia: depois de remover os valores sensíveis, pode fazer uma cópia de segurança de todo o seu .bashrc sem o tratar como um cofre. Este ficheiro contém os seus utilitários, aliases e preferências da shell, pelo que restaurar a configuração habitual numa nova estação de trabalho deixa de implicar copiar também as suas credenciais.

Se uma credencial tiver sido incluída num commit ou exposta de outra forma, revogue-a ou substitua-a. Remover a linha do ficheiro atual não altera o histórico do Git, as cópias de segurança nem a saída anteriormente copiada.

Antes
  .bashrc → credenciais em texto simples → todas as shells e os respetivos processos filhos

Depois
  pass / GPG → credenciais cifradas em disco
  .bashrc → funções auxiliares e aliases
  configuração do npm / Yarn → referências a variáveis
  ws <comando> → credenciais selecionadas para essa árvore de processos

As suas ferramentas acabam por precisar de acesso aos segredos. Torne esse acesso deliberado: mantenha as credenciais cifradas em repouso, indisponíveis por predefinição e limitadas aos comandos que delas precisam.

Back to Blog

Related Posts

View All Posts »