- Embora o software tenha evoluído rapidamente, o sistema de variáveis de ambiente do sistema operacional ainda mantém uma estrutura de décadas atrás
- As variáveis de ambiente têm a forma de um dicionário global de strings, uma estrutura simples sem namespace nem tipos
- No Linux, as variáveis de ambiente são passadas do processo pai para o filho por meio da chamada de sistema
execve
- Bash, glibc, Python e outros gerenciam variáveis de ambiente respectivamente como hash map, array e wrapper de dicionário
- O padrão POSIX não exige apenas letras maiúsculas nos nomes e, na prática, há regras flexíveis, como a recomendação de usar nomes em minúsculas em certos casos
O que são variáveis de ambiente
- Embora as linguagens de programação tenham evoluído rapidamente, a estrutura base de execução de processos oferecida pelo sistema operacional, especialmente a parte de variáveis de ambiente, quase não mudou
- Ao executar uma aplicação, se você precisa passar parâmetros de runtime sem arquivos separados nem IPC, na prática acaba recorrendo a uma interface baseada em variáveis de ambiente
- As variáveis de ambiente funcionam como um dicionário plano de strings, sem namespace e sem tipos
Estrutura de criação e transmissão das variáveis de ambiente
- As variáveis de ambiente são o método tradicional de transmitir valores entre processos, sendo enviadas junto quando o processo pai executa o processo filho
- Ou seja, trata-se de uma estrutura herdada do processo pai para o filho
- No Linux, a system call
execve recebe como argumentos o executável, os argumentos e o array de variáveis de ambiente (envp)
- Exemplo de comando executado:
ls -lah
- filename:
/usr/bin/ls
- argv:
['ls', '-lah']
- envp:
['PATH=...','USER=...']
- O processo pai pode passar ao filho o ambiente existente como está ou montar um ambiente totalmente novo
- Quase todas as ferramentas (Bash,
subprocess.run do Python, biblioteca C execl etc.) repassam as variáveis de ambiente como estão
- Como exceção, algumas ferramentas, como
login, constroem um novo ambiente
Onde as variáveis de ambiente são armazenadas e como são tratadas internamente
- Ao iniciar um programa, o kernel armazena as variáveis de ambiente na stack, no formato de strings terminadas em null
- Esses dados são difíceis de modificar diretamente pelo programa e, em geral, são copiados para estruturas próprias dentro da aplicação
- Forma de armazenamento das variáveis de ambiente em cada linguagem e shell
- Bash: gerencia em um hash map (dicionário) com estrutura em pilha
- A cada chamada de função, cria um mapa de escopo local
- Apenas variáveis com
export são passadas ao processo filho
- Variáveis declaradas com
local também podem ser passadas ao processo filho via export
- Ex.: com
export PATH, uma alteração local é refletida no filho sem afetar o escopo global
- glibc (biblioteca C): gerencia
environ como uma estrutura de array dinâmico por meio de putenv e getenv
- Como é uma estrutura de array, tanto consulta quanto alteração têm complexidade linear de tempo
- Portanto, não é adequada para armazenar dados que exijam alto desempenho
- Python: internamente expõe isso como um dicionário via
os.environ, mas na prática está integrado ao array environ da biblioteca C
- Ao alterar um valor em
os.environ, os.putenv é chamado e a mudança também é refletida na biblioteca C
- No sentido inverso, não há sincronização, portanto existe unidirecionalidade
Formato e faixa permitida das variáveis de ambiente
- O kernel do Linux e a glibc são muito permissivos quanto ao formato das variáveis de ambiente
- Pode haver nomes duplicados com vários valores para o mesmo nome
- Também é possível registrar entradas sem
= e não há restrição a caracteres especiais como emoji
- Limites de tamanho disponíveis
- Variável individual: 128 KiB (geralmente em ambiente x64)
- Soma total: 2 MiB (compartilhada com os argumentos da linha de comando)
- As variáveis de ambiente são limitadas para não ultrapassar 1/4 do espaço da stack
Peculiaridades e edge cases das variáveis de ambiente
- No Bash, variáveis de ambiente estranhas (nomes duplicados, entradas sem
= etc.) fazem com que nomes duplicados sejam removidos e itens anormais sejam ignorados
- Se o nome da variável contém espaços, o Bash não consegue referenciá-la pelo nome, mas ainda assim ela pode ser passada para processos filhos
- Por exemplo, Nushell e Python conseguem criar variáveis com nomes contendo espaços
- O Bash gerencia esses itens armazenando-os em um hash map separado (
invalid_env)
Formato padrão das variáveis de ambiente e regras para nomes
- O padrão POSIX considera uma entrada como variável desde que o nome não contenha sinal de igual (
=)
- Recomendação oficial: o nome deve permitir apenas letras maiúsculas, números e underscore (o primeiro caractere não pode ser número)
- Variáveis em minúsculas são destinadas a namespaces específicos de aplicações
- Ferramentas padrão usam apenas maiúsculas, mas o uso de variáveis em minúsculas também é permitido
- Na prática, desenvolvedores costumam nomear usando o estilo ALL_UPPERCASE
- Regra recomendada: usar a regex
^[A-Z_][A-Z0-9_]*$ para nomes de variáveis e UTF-8 para os valores
- Se houver preocupação com exceções ou compatibilidade, recomenda-se usar o Portable Character Set (ASCII) do POSIX
Conclusão
- As variáveis de ambiente continuam sendo uma interface antiga, mas essencial, servindo como camada de fronteira entre sistema operacional e aplicações
- Apesar das limitações estruturais, Bash, C e Python continuam a utilizá-las, cada um encapsulando-as de forma diferente
- Em sistemas modernos, torna-se cada vez mais necessária uma forma de gerenciamento de configuração com namespaces claros e sistema de tipos
2 comentários
À primeira vista, parecia estar perdendo importância, mas com a chegada do Docker e da nuvem, tornou-se inevitável novamente.
Comentários do Hacker News
Trabalho com SRE/Sysadmin/DevOps/Whatever, e embora no blog eu tenha falado de forma leve sobre padronização de variáveis de ambiente, quero apontar que as alternativas também acabam provocando frustrações parecidas, especialmente quando segredos estão envolvidos
Uma arquitetura em que a aplicação acessa um cofre específico, como Hashicorp Vault/OpenBao/Secrets Manager, rapidamente cria um forte lock-in de fornecedor, e a substituição se espalha até o nível das bibliotecas, ficando muito difícil
A disponibilidade do Vault passa a ser extremamente crítica, e quando upgrades ou manutenção são necessários, a equipe de operações fica em grande apuro
Quando se passa informação sensível por arquivo de configuração, surge a dificuldade de como armazenar esse segredo, porque arquivos de config geralmente ficam em caminhos públicos
No fim, você acaba dependendo de uma destas opções: "substituir por template antes de um sistema privilegiado entregar ao app" ou "armazenar o arquivo de config inteiro no cofre e entregar ao app"
O trabalho com templates é propenso a erros, e mover o arquivo de config inteiro para o cofre também gera estresse, porque sempre existe o risco de alguém fazer upload errado
Hoje em dia a maioria dos sistemas roda sobre contêineres, e a menos que a empresa cuide muito bem da infraestrutura, os arquivos de config sempre acabam em lugares estranhos, o que torna o processo de montagem ainda mais confuso e cheio de erros
Seja JSON/YAML/TOML ou qualquer outro formato, bugs peculiares são rotina, como o problema da Noruega no YAML
Já vi gente recebendo segredos via Kubernetes Secrets API, mas isso também esbarra em lock-in forte
A menos que você esteja projetando algo especificamente como um sistema no estilo operator, eu não recomendaria esse caminho com entusiasmo
Também já vi problemas surgirem no processo de configurar variáveis de ambiente via subprocesso, mas hoje em dia acho que as equipes preferem sistemas baseados em message bus, que são mais robustos e permitem escalabilidade independente
No nosso time, já tivemos a experiência de criar uma biblioteca leve e genérica de secrets, usando apenas backends específicos de fornecedores, como AWS Secrets Manager, em esquema de plugin
Era possível configurar cache local e opções de bypass de cache por parâmetro, então a lógica realmente dependente do fornecedor ficava só no backend, e tanto a biblioteca quanto a aplicação podiam permanecer independentes do fornecedor
Quando migramos para Vault, bastou adicionar mais um backend e mudar a configuração, e funcionou sem maiores problemas
Tenho curiosidade sobre por que a API de secrets do Kubernetes é vista como um problema de lock-in
Foi algum caso de tentar usar deployment yaml para um propósito que não fosse deploy em Kubernetes?
Na maioria dos apps, você pode montar o secret no contêiner e então injetá-lo na aplicação como variável de ambiente ou arquivo json, lendo e escrevendo de forma independente do ambiente
Pelo que sei, a criptografia do backend etcd também pode ser configurada com KMS
Não entendo muito bem por que receber segredos via Kubernetes Secrets API seria lock-in
Em essência, os secrets do K8s não são armazenados criptografados por padrão, então, na minha visão, isso só faz sentido se você (0) já usa K8s, (1) configurou a criptografia no control plane e (2) obrigatoriamente usa uma solução adicional como CSI driver
E o Secret Store CSI Driver suporta vários backends, como Conjur, então na verdade seria o oposto de lock-in
É por isso que ainda continuo usando config principalmente com env vars e dotenv
A estrutura de configuração baseada em variáveis de ambiente é simples demais, e também é muito compatível com várias ferramentas, incluindo gerenciadores de segredos
Nos últimos anos comecei a me interessar aos poucos por sops baseado em YAML
YAML é realmente intuitivo para expressar a estrutura de configuração da aplicação, e com sops é fácil criptografar e gerenciar só partes do arquivo
Claro que gerenciar chaves GPG pode ser complicado, mas dá para resolver isso com Vault ou OpenBao
Só que aí aparece de novo a questão do lock-in de fornecedor, embora o OpenBao pareça um pouco menos problemático nesse aspecto
Também dá para receber variáveis de ambiente como resultado da execução de um comando, então é possível fazer isso sem lock-in de fornecedor e sem processo de template
Mais uma curiosidade interessante:
setenv()é fundamentalmente quebrado no POSIX, então acho que nunca deveria ser usado em código de bibliotecaMesmo em código de aplicação, deve ser o último recurso e só pode ser usado antes de criar threads
getenv()retorna diretamente um ponteiro para a variável de ambiente original, então quandosetenv()sobrescreve a variável, não existe nenhum mecanismo de proteçãoÉ preciso ter muito cuidado
Acho que a forma correta de configurar variáveis de ambiente é com
execve()Esse método só é apropriado quando a informação é passada por variáveis de ambiente no momento do
exec()ou em torno deleNão entendo por que alguém usaria
setenvem código de bibliotecaO Solaris resolveu esse problema, mas o Linux ainda insiste na mesma abordagem
No NetBSD já existe há muito tempo uma alternativa segura chamada
getenv_r(), e recentemente o FreeBSD também a adotouO macOS provavelmente deve seguir pelo mesmo caminho em breve
Já houve tentativas de colocá-la no glibc ou no POSIX, mas foram rejeitadas
Espero que, quando isso se espalhar por várias plataformas, acabe sendo aceito oficialmente algum dia
Documentação do getenv_r no NetBSD
Commit do FreeBSD
Variáveis de ambiente são frequentemente usadas para passar segredos, mas eu não acho que isso seja uma boa prática
No Linux, todos os processos executados pelo mesmo usuário conseguem inspecionar as variáveis de ambiente uns dos outros
Seja qual for o modelo de ameaça, isso preocupa bastante, especialmente em máquinas de desenvolvedores, onde há muitos processos rodando sob o mesmo usuário
Esse problema fica ainda mais sério quando existem vários processos rodando fora do contêiner, como agentes de LLM
Além disso, variáveis de ambiente normalmente são herdadas pelos processos filhos, então mesmo quando apenas um processo precisa do segredo, ele tende a ficar exposto de forma indiscriminada
O systemd expõe variáveis de ambiente a todos os clientes do sistema via DBUS, e a documentação oficial alerta para não colocar segredos em variáveis de ambiente
Se isso for verdade, significa que variáveis de ambiente configuradas em unidades só para root também podem ficar visíveis para usuários comuns, o que seria bem chocante para muitos administradores de sistemas
No fim, acredito que a única saída para escapar tanto da exposição em variáveis de ambiente quanto em arquivos em texto puro é usar uma estrutura em que o gerenciador de segredos entrega o segredo por meio de arquivo temporário compartilhado, como fazem o
opcli da 1Password, flask e terraformÉ assim que funciona o sistema de credentials do systemd. Mas o suporte ainda é pequeno
Se alguém souber de uma boa forma de passar segredos sem usar variáveis de ambiente nem arquivos em texto puro, eu gostaria que compartilhasse
No caso do cliente
opda 1Password, por exemplo, como ele exige minha aprovação a cada sessão, sinto que é seguro para usar numa sessão de CLI; mesmo que algum processo malicioso invoque o binárioop, ainda será necessária uma aprovação separadaAgora o problema que sobra é: como passar esse segredo para o processo que realmente precisa dele? Parece que voltamos ao ponto de partida
Link da documentação oficial do systemd sobre variáveis de ambiente
Desde algo em torno de 2012, variáveis de ambiente passaram a ser tão seguras quanto a memória comum
Commit relacionado
Para ler as variáveis de ambiente de outro processo, é obrigatório ter permissão de
ptrace; e, se você já consegue ler viaptrace, na prática já consegue ler qualquer segredo, então essa preocupação não faz muito sentidoInformação de linha de comando (
cmdline) é outra história, mas variáveis de ambiente já não ficam mais expostas tão facilmente dessa formaNo modelo de segurança da maioria dos sistemas operacionais, rodar sob um mesmo usuário significa essencialmente entregar por completo todos os privilégios desse usuário
Há exceções com recursos extras de segurança, como capsicum no FreeBSD, landlock no Linux, SELinux, AppArmor e integrity label no Windows, mas a maioria tem limitações claras
No fim das contas, eu posso matar, parar ou depurar livremente meus próprios processos, e segredos dos processos que possuo sempre podem ser acessados com
ptrace/process_vm_readv/ReadProcessMemoryetc.Existem modelos de segurança completamente diferentes, como sistemas operacionais perfeitamente baseados em capability, mas a grande maioria segue esse modelo, então é preciso reconhecer seus limites e responsabilidades
Uma boa forma de passar segredos sem usar variáveis de ambiente nem arquivos em texto puro pode ser
memfd_secretman page do memfd_secret
Ainda não há muito suporte por linguagem ou framework, então talvez valha tentar via FFI, especialmente em Rust ou talvez também em Go
Já cogitei criar um wrapper direto para PHP, mas desisti porque não queria chegar ao ponto de modificar o
php-fpmNa prática, o ideal seria o gerenciador de processos abrir antes um descritor de arquivo de secret e passá-lo ao processo filho, para que ele pudesse usá-lo sem expor nada em memória e afins
O modelo clássico de segurança do Unix ainda é muito usado, ainda que com pequenas melhorias, mas mostra limites claros em ambientes baratos de computação e em cenários modernos
Se você precisa esconder segredos de outros processos, o caminho correto é separá-los para rodar sob outro usuário
Outra opção é acessar tudo remotamente, mas isso também traz desvantagens e complexidade
Hoje em dia, plataformas de contêineres tendem a recomendar o uso de variáveis de ambiente para passar config ou secrets
Dentro do contêiner, ele é projetado de forma que outros processos não consigam inspecionar as variáveis de ambiente
O fato de processos filhos herdarem variáveis de ambiente também é intencional no design, porque quem configura o ambiente com o valor do secret é justamente quem também configura esse ambiente
Eu não vejo a maioria dessas preocupações como grandes problemas, mas, se necessário, posso discutir os pontos concretos
Muitos comentários estão focando no gerenciamento de segredos e seus problemas, mas também vale pensar um pouco nas vantagens das variáveis de ambiente
Variáveis de ambiente são uma forma de "binding dinâmico de variáveis com escopo indefinido" que conecta estruturalmente processos Unix
Em vez de compará-las diretamente com um simples arquivo de texto, vale lembrar que a razão de existir das variáveis de ambiente é o repasse de contexto para transmitir informações com segurança a processos filhos
Quanto mais complexa a estrutura de processos — shells aninhados ou subprocessos de programas complicados, por exemplo — mais esse papel das variáveis de ambiente se destaca
Quero recomendar o Varlock como algo realmente útil
Ele permite definir com clareza quais variáveis de ambiente são obrigatórias ou opcionais em um projeto, seus tipos de dados e até de onde vêm, o que facilita bastante a gestão
Site oficial do Varlock
Pela experiência prática, um exemplo de como variáveis de ambiente podem ficar complexas: em uma empresa onde trabalhei, certa vez tentei depurar de onde vinha uma variável
ENVespecífica e fiquei completamente perdidoNo começo achei que estivesse definida em algum lugar simples, como
.bashrc, mas na verdade ela era configurada por pelo menos 10 camadas: nível da empresa inteira, região, divisão de negócios, time, indivíduo etc.No fim, só consegui rastrear tudo ligando flags de debug do bash e seguindo passo a passo onde ela era definida
Não sei se outras linguagens suportam algo parecido, mas o Node.js adicionou recentemente uma flag de linha de comando que rastreia com precisão acessos e alterações em variáveis de ambiente
Documentação do Node.js --trace-env
Como valores podem ser definidos ou alterados por muitas APIs diferentes, isso parece muito útil em depurações complexas
É um caso que faz pensar: "será que um namespace só não bastaria?"
Faz muito tempo que abandonei o uso de variáveis de ambiente
Hoje eu mantenho um arquivo
dmd.confao lado do compilador, e o próprio compilador lê esse arquivo diretamenteO problema mais grave das variáveis de ambiente é seu caráter implícito e opaco
No mundo *nix, a maioria dos apps tende a depender de variáveis de ambiente
Mesmo quando há suporte adicional a formas explícitas e transparentes de configuração, como arquivos de configuração, serviços remotos ou argumentos de linha de comando, o suporte a variáveis de ambiente continua sendo a tradição da área
No fim, variáveis de ambiente também são um hashmap global, clonado e expandido para processos filhos; em 1979 isso talvez fosse um design razoável, mas hoje muitas vezes acaba sendo um veneno
Por exemplo, o Kubernetes polui por padrão o ambiente do contêiner com variáveis de ambiente de "service link"
Quando variáveis de ambiente esperadas pela aplicação entram em conflito com env vars padrão, a depuração fica extremamente difícil
Documentação oficial do Kubernetes
Além disso, sinto que há muitas práticas de manter estruturas legadas sem reflexão crítica, como
/bin,/usr/bin,/lib,/usr/libReferência: Q&A do Ubuntu sobre manutenção de diretórios legados
hjkltambém podem ser vistas como exemplo dessa tradição antiquadaO
hjkldo vi vem de um terminal burro de 40 anos atrás, que inclusive vendeu pouco(menos até que o Nokia N9)
Sempre me dá uma sensação de insegurança quando vou configurar variáveis de ambiente no Linux
Não existe um método oficialmente padronizado que funcione do mesmo jeito em todas as distribuições, e mesmo seguindo instruções online tudo desaparece depois de reiniciar ou fechar o terminal
Seria ótimo se houvesse um editor gráfico simples de variáveis de ambiente globais, como no Windows
No Windows existe o incômodo de ter que abrir um novo terminal para as mudanças entrarem em vigor, mas fora isso sempre funciona bem
É natural que variáveis de ambiente não persistam entre sessões, então elas precisam ser escritas em algum lugar que seja executado novamente a cada sessão, como no login ou ao abrir o terminal
No login, roda o
.bash_profile; em sessões filhas, roda o.bashrcSe você der
sourcedo.bashrcdentro do.bash_profilee deixar a maior parte das configurações no.bashrc, fica fácil de gerenciarSe você não usa Bash, mas zsh/fish ou outro shell, precisa ajustar conforme esse shell
O Linux não tem um GUI oficial e unificado de variáveis de ambiente que se aplique a todos os terminais
Até daria para criar um GUI com parsing complexo, mas em geral é mais fácil editar em texto mesmo
Do meu ponto de vista, como alguém que usa Linux principalmente, o comportamento do Windows é que parece mais incômodo
Aplicativos demais poluem as variáveis de ambiente, então quando algo falha frequentemente a confusão é porque
$SOFTWAREestava sendo executado a partir de uma pasta estranhaSe você usa systemd, também dá para escrever
KEY=VALUEem/etc/environmentou/etc/environment.d/Na prática, até daria para criar um GUI para esse método
Mas variáveis de ambiente não podem ser injetadas em processos já em execução; é preciso reiniciar para refletir as mudanças, então há esse limite
Referência da documentação oficial do systemd
Quadrinho xkcd Standards
Mostra de forma divertida a situação em que já existem 14 formas concorrentes de configurar variáveis de ambiente no Linux, e alguém propõe "unificar tudo em uma só" — no dia seguinte passam a existir 15 padrões
Minha curiosidade favorita sobre variáveis de ambiente é que coisas como
PS1, que todo mundo assume serem variáveis de ambiente, na verdade não são; são variáveis do shellVocê nem consegue vê-las com o comando
env