Chapters ▾ 2nd Edition

7.14 Ferramentas do Git - Armazenamento de Credenciais

Armazenamento de Credenciais

Se você usa o transporte SSH para se conectar a remotes, pode ter uma chave sem frase secreta, o que permite transferir dados com segurança sem digitar seu nome de usuário e sua senha. No entanto, isso não é possível com os protocolos HTTP – toda conexão exige um nome de usuário e uma senha. Isso se torna ainda mais difícil em sistemas com autenticação de dois fatores, nos quais o token usado como senha é gerado aleatoriamente e é impronunciável.

Felizmente, o Git possui um sistema de credenciais que pode ajudar com isso. O Git oferece algumas opções prontas para uso:

  • O padrão é não armazenar nada em cache. Toda conexão solicitará seu nome de usuário e sua senha.

  • O modo “cache” mantém as credenciais na memória por determinado período. Nenhuma senha é armazenada em disco, e elas são removidas do cache depois de 15 minutos.

  • O modo “store” salva as credenciais em um arquivo de texto puro no disco, e elas nunca expiram. Isso significa que, enquanto você não alterar sua senha no host Git, nunca mais precisará digitar suas credenciais. A desvantagem dessa abordagem é que suas senhas ficam armazenadas sem criptografia em um arquivo de texto puro no seu diretório pessoal.

  • Se você usa macOS, o Git inclui um modo “osxkeychain”, que armazena as credenciais no chaveiro seguro associado à sua conta do sistema. Esse método armazena as credenciais em disco, e elas nunca expiram, mas são criptografadas pelo mesmo sistema que armazena certificados HTTPS e dados de preenchimento automático do Safari.

  • Se você usa Windows, pode habilitar o recurso Git Credential Manager ao instalar o Git for Windows ou instalar separadamente a versão mais recente do GCM como um serviço independente. Ele é semelhante ao helper “osxkeychain” descrito acima, mas usa o Gerenciador de Credenciais do Windows para controlar informações confidenciais. Ele também pode fornecer credenciais ao WSL1 ou WSL2. Consulte as instruções de instalação do GCM para obter mais informações.

Você pode escolher um desses métodos definindo um valor de configuração do Git:

$ git config --global credential.helper cache

Alguns desses helpers possuem opções. O helper “store” aceita um argumento --file <path>, que personaliza o local em que o arquivo de texto puro é salvo (o padrão é ~/.git-credentials). O helper “cache” aceita a opção --timeout <seconds>, que altera o tempo durante o qual seu daemon permanece em execução (o padrão é “900”, ou 15 minutos). Este é um exemplo de como configurar o helper “store” com um nome de arquivo personalizado:

$ git config --global credential.helper 'store --file ~/.my-credentials'

O Git permite até mesmo configurar vários helpers. Ao procurar credenciais para um host específico, o Git consultará os helpers em ordem e parará assim que receber a primeira resposta. Ao salvar credenciais, o Git enviará o nome de usuário e a senha para todos os helpers da lista, e cada um poderá decidir o que fazer com eles. Este é o aspecto de um arquivo .gitconfig caso você tivesse um arquivo de credenciais em um pen drive, mas quisesse usar o cache em memória para evitar digitação quando o dispositivo não estivesse conectado:

[credential]
    helper = store --file /mnt/thumbdrive/.git-credentials
    helper = cache --timeout 30000

Por Baixo dos Panos

Como tudo isso funciona? O comando principal do Git para o sistema de helpers de credenciais é git credential, que recebe um comando como argumento e mais dados pela entrada padrão.

Talvez seja mais fácil entender com um exemplo. Suponha que um helper de credenciais tenha sido configurado e armazenado credenciais para mygithost. Esta sessão usa o comando “fill”, invocado quando o Git tenta encontrar credenciais para um host:

$ git credential fill (1)
protocol=https (2)
host=mygithost
(3)
protocol=https (4)
host=mygithost
username=bob
password=s3cre7
$ git credential fill (5)
protocol=https
host=unknownhost

Username for 'https://unknownhost': bob
Password for 'https://bob@unknownhost':
protocol=https
host=unknownhost
username=bob
password=s3cre7
  1. Esta é a linha de comando que inicia a interação.

  2. Em seguida, Git-credential aguarda dados na entrada padrão. Fornecemos as informações que conhecemos: o protocolo e o nome do host.

  3. Uma linha em branco indica que a entrada terminou e que o sistema de credenciais deve responder com o que sabe.

  4. Então, Git-credential assume o controle e grava na saída padrão as informações que encontrou.

  5. Se nenhuma credencial for encontrada, o Git solicita ao usuário o nome de usuário e a senha e os devolve na saída padrão do processo que o invocou (aqui, ela está associada ao mesmo console).

Na verdade, o sistema de credenciais invoca um programa separado do próprio Git; qual programa e de que maneira dependem do valor de configuração credential.helper. Ele pode assumir várias formas:

Valor de Configuração Comportamento

foo

Executa git-credential-foo

foo -a --opt=bcd

Executa git-credential-foo -a --opt=bcd

/absolute/path/foo -xyz

Executa /absolute/path/foo -xyz

!f() { echo "password=s3cre7"; }; f

O código depois de ! é avaliado no shell

Portanto, os helpers descritos acima na verdade se chamam git-credential-cache, git-credential-store e assim por diante, e podemos configurá-los para receber argumentos de linha de comando. A forma geral é “git-credential-foo [args] <action>.” O protocolo de entrada e saída padrão é o mesmo de git-credential, mas esses helpers usam um conjunto de ações ligeiramente diferente:

  • get é uma solicitação de um par de nome de usuário e senha.

  • store é uma solicitação para salvar um conjunto de credenciais na memória desse helper.

  • erase remove da memória desse helper as credenciais correspondentes às propriedades fornecidas.

Para as ações store e erase, nenhuma resposta é necessária (o Git a ignora de qualquer forma). Para a ação get, porém, o Git se interessa muito pelo que o helper tem a dizer. Se o helper não souber nada útil, poderá simplesmente encerrar sem produzir saída; se souber, deverá complementar as informações fornecidas com as que armazenou. A saída é tratada como uma série de atribuições; tudo o que for fornecido substituirá o que o Git já sabe.

Este é o mesmo exemplo anterior, mas sem passar por git-credential e usando diretamente git-credential-store:

$ git credential-store --file ~/git.store store (1)
protocol=https
host=mygithost
username=bob
password=s3cre7
$ git credential-store --file ~/git.store get (2)
protocol=https
host=mygithost

username=bob (3)
password=s3cre7
  1. Aqui instruímos git-credential-store a salvar algumas credenciais: o nome de usuário “bob” e a senha “s3cre7” devem ser usados quando https://mygithost for acessado.

  2. Agora recuperaremos essas credenciais. Fornecemos as partes da conexão que já conhecemos (https://mygithost) e uma linha em branco.

  3. git-credential-store responde com o nome de usuário e a senha que armazenamos acima.

Este é o conteúdo do arquivo ~/git.store:

https://bob:s3cre7@mygithost

Ele é apenas uma série de linhas, cada uma contendo uma URL acompanhada de credenciais. Os helpers osxkeychain e wincred usam o formato nativo dos armazenamentos nos quais se apoiam, enquanto cache usa seu próprio formato em memória (que nenhum outro processo consegue ler).

Um Cache de Credenciais Personalizado

Como git-credential-store e programas semelhantes são separados do Git, não é difícil perceber que qualquer programa pode ser um helper de credenciais do Git. Os helpers fornecidos pelo Git atendem a muitos casos de uso comuns, mas não a todos. Por exemplo, suponha que sua equipe tenha credenciais compartilhadas por todos, talvez para fazer deploy. Elas ficam armazenadas em um diretório compartilhado, mas você não quer copiá-las para seu próprio armazenamento de credenciais, pois elas mudam com frequência. Nenhum dos helpers existentes atende a esse caso; vejamos o que seria necessário para criar o nosso. Esse programa precisa ter várias características importantes:

  1. A única ação à qual precisamos prestar atenção é get; store e erase são operações de escrita, portanto simplesmente encerraremos sem erros ao recebê-las.

  2. O formato do arquivo de credenciais compartilhadas é o mesmo usado por git-credential-store.

  3. A localização desse arquivo é bastante padronizada, mas devemos permitir que o usuário passe um caminho personalizado por precaução.

Mais uma vez, escreveremos essa extensão em Ruby, mas qualquer linguagem funcionará, desde que o Git possa executar o produto final. Este é o código-fonte completo do nosso novo helper de credenciais:

#!/usr/bin/env ruby

require 'optparse'

path = File.expand_path '~/.git-credentials' # (1)
OptionParser.new do |opts|
    opts.banner = 'USAGE: git-credential-read-only [options] <action>'
    opts.on('-f', '--file PATH', 'Specify path for backing store') do |argpath|
        path = File.expand_path argpath
    end
end.parse!

exit(0) unless ARGV[0].downcase == 'get' # (2)
exit(0) unless File.exist? path

known = {} # (3)
while line = STDIN.gets
    break if line.strip == ''
    k,v = line.strip.split '=', 2
    known[k] = v
end

File.readlines(path).each do |fileline| # (4)
    prot,user,pass,host = fileline.scan(/^(.*?):\/\/(.*?):(.*?)@(.*)$/).first
    if prot == known['protocol'] and host == known['host'] and user == known['username'] then
        puts "protocol=#{prot}"
        puts "host=#{host}"
        puts "username=#{user}"
        puts "password=#{pass}"
        exit(0)
    end
end
  1. Aqui analisamos as opções da linha de comando, permitindo que o usuário especifique o arquivo de entrada. O padrão é ~/.git-credentials.

  2. Esse programa só responde se a ação for get e o arquivo de armazenamento existir.

  3. Esse laço lê a entrada padrão até encontrar a primeira linha em branco. As entradas são armazenadas no hash known para consulta posterior.

  4. Esse laço lê o conteúdo do arquivo de armazenamento em busca de correspondências. Se o protocolo, o host e o nome de usuário de known corresponderem à linha, o programa imprimirá os resultados na saída padrão e encerrará.

Salvaremos nosso helper como git-credential-read-only, colocaremos o arquivo em algum local do PATH e o marcaremos como executável. Esta é a aparência de uma sessão interativa:

$ git credential-read-only --file=/mnt/shared/creds get
protocol=https
host=mygithost
username=bob

protocol=https
host=mygithost
username=bob
password=s3cre7

Como seu nome começa com “git-”, podemos usar a sintaxe simples no valor de configuração:

$ git config --global credential.helper 'read-only --file /mnt/shared/creds'

Como você pode ver, estender esse sistema é bastante simples e pode resolver alguns problemas comuns para você e sua equipe.