Git
Chapters ▾ 2nd Edition

7.14 Herramientas de Git - Almacenamiento de credenciales

Almacenamiento de credenciales

Si usa protocolo SSH para conectar a los remotos, es posible tener una llave sin clave, lo que permite tranferir la data sin tener que escribir el nombre de usuario y la clave cada vez. Sin embargo, esto no es posible por el protocolo HTTP - cada conexión necesita usuario y contraseña. Incluso se vuelve más complicado para sistemas con autenticación de dos pasos, donde el token que se usa para la clave es generado al azar y no puede ser reutilizado.

Afortunadamente, Git tiene un sistema de credenciales que lo ayuda con esto. Git tiene las siguientes funciones disponibles:

  • El “default” es no guardar cache para nada. Cada conexión solicitará el usuario y contraseña.

  • El modo “cache” mantiene las credenciales en memoria por un cierto período de tiempo. Ninguna de las claves es guardada en disco, y son borradas del cache tras 15 minutos.

  • El modo “store” guarda las credenciales en un archivo de texto plano en disco, y nunca expiran. Esto quiere decir que hasta que se cambie la contraseña en el host Git, no se necesitará escribir las credenciales de nuevo. La desventaja de este método es que sus claves son guardadas en texto plano en un archivo dentro de su máquina.

  • Si está usando Mac, Git viene con el modo “osxkeychain”, el cual guarda en cache las credenciales en el llavero que está conectado a su cuenta de sistema. Este método guarda las claves en disco, y nunca expiran, pero están encriptadas con el mismo sistema que guarda los certificados HTTPS y los auto-completar de Safari.

  • Si está en Windows, puede instalar un ayudante llamado “winstore.” Éste es similar al ayudante de “osxkeychain” descrito arriba, pero usa Windows Credential Store para controlar la información sensible. Se puede encontrar en https://gitcredentialstore.codeplex.com.

Se puede elegir cualquiera de estos métodos mediante el valor de configuración de Git:

$ git config --global credential.helper cache

Algunos de estos ayudantes tienen opciones. El modo “store” puede tomar un argumento --file <ruta>', el cual personaliza la ubicación final del archivo en texto plano (el default es `~/.git-credentials).

El modo “cache” acepta la opción --timeout <segundos>, la cual cambia la cantidad de tiempo que el “demonio” se mantiene en ejecución (el default es “900”, o 15 minutos). Aquí hay un ejemplo de cómo configurar el modo “store” con un nombre de archivo personalizado:

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

Git incluso permite configurar varios modos. Cuando se buscan por credenciales para un host en particular, Git las mostrará en orden, y se detendrá después que la primer respuesta sea entregada. Cuando se guardan credenciales, Git mandará el usuario y contraseña a todos los modos en la lista, y se podrá elegir qué hacer con ellos. Aquí se muestra cómo se vería un archivo .gitconfig si tuviera un archivo de credenciales en una memoria, pero quisiera usar lo almacenado en cache cuando la memoria no esté conectada:

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

Bajo el sombrero

¿Cómo funciona todo esto? El comando raíz de Git para el asistente de credenciales es git credential, el cual toma un comando como argumento, y luego más inputs por medio de “stdin”.

Esto podría ser más fácil de entender con un ejemplo. Supongamos que un modo de credenciales ha sido configurado, y que el asistente ha guardado credenciales para mygithost. Aquí hay una sesión que usa el comando “fill”, el cual es invocado cuando Git está intentando encontrar credenciales para el 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. Este es el comando que inicia la interacción.

  2. Git-credential entonces espera por un input en “stdin”. Nosotros lo proveemos con lo que conocemos: el protocolo y el nombre de host.

  3. Una línea en blanco indica que el input está completo, y el sistema de credenciales debería responder con lo que conoce.

  4. Git-credential entonces entra en acción, y escribe en “stdout” los bits de información que encontró.

  5. Si no se encuentran credenciales, Git pregunta al usuario por el usuario y la contraseña, y los entrega de vuelta a “stdout” (aquí ya están conectados a la misma consola).

El sistema de credenciales en realidad está invocando un programa que está separado de Git; el que figura en el valor de configuración credential.helper. Hay varias formas que puede tomar:

Configuration Value Behavior

foo

Runs git-credential-foo

foo -a --opt=bcd

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

/absolute/path/foo -xyz

Runs /absolute/path/foo -xyz

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

Code after ! evaluated in shell

Así, los modos descritos arriba en realidad se llaman git-credential-cache, git-credential-store, y en adelante, los podemos configurar para que tomen argumentos de línea de comando. La forma general para conseguirlo es “git-credential-foo [args] <acción>.” El protocolo “stdin/stdout” es el mismo que “git-credential”, pero usan un conjunto ligeramente distinto de acciones:

  • get es una petición para un par usuario/contraseña.

  • store es una petición para guardar un grupo de credenciales en la memoria del modo.

  • erase purga las credenciales para las propiedades entregadas de la memoria del modo.

Para las acciones store y erase, no es necesaria una respuesta (Git la ignora de todos modos). Para la acción get, sin embargo, Git está muy interesado en lo que el modo tiene que decir. Si el modo no sabe nada útil, puede simplemente salir sin mostrar inforamción, pero si sabe algo, debería aumentar la información provista con la información que ha almacenado. El output es tratado como una serie de declaraciones de asignación; nada provisto remplazará lo que Git ya conoce.

Aquí hay un ejemplo de lo explicado, pero saltando git-credential y yendo directo a 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. Aquí decimos con git-credential-store que guarde las credenciales: username “bob” y la clave “s3cre7”, que serán usadas cuando se accese a https://mygithost.

  2. Ahora vamos a recibir las credenciales. Proveemos las partes de la conexión que ya conocemos (https://mygithost), y una línea en blanco.

  3. git-credential-store responde con el usuario y la contraseña que guardamos al comienzo.

Aquí se muestra cómo se vería ~/git.store:

https://bob:s3cre7@mygithost

Es solamente una serie de líneas, cada una conteniendo una URL con credenciales. Los modos osxkeychain y winsoter usan el formato nativo de sus almacenamientos, mientras cache usa su propio formato en memoria (el cual no puede ser leído por ningún proceso).

Un cache de credenciales personalizado

Dado que git-credential-store y amigos son programas separados de Git, no es difícil de notar que cualquier programa puede ser un asistente de credenciales de Git. Los modos provistos por Git cubren muchos de los casos de uso, pero no todos. Por ejemplo, supongamos que tu equipo tiene credenciales que son compartidas con el equipo entero, tal vez para despliegue. Éstas están guardadas en un directorio compartido, pero no deseas copiarlas a tu almacén de credenciales, porque cambian de manera seguida. Ninguno de los modos existentes cubre este caso; veamos lo que tomaría para escribir tu propio modo. Existen muchas funcionalidades clave que necesita tener este programa:

  1. La única acción que necesitamos vigilar es get, en tanto que store y erase son operaciones de escritura, así que sólo saldremos limpiamente cuando sean recibidas.

  2. El formato de archivo de la credencial compartida es el mismo que se usa por git-credential-store.

  3. La ubicación de ese archivo es relativamente estándar, pero deberías permitir al usuario entregar una ruta alterna, por si acaso.

Una vez más, vamos a escribir esta extensión en Ruby, pero cualquier lenguaje funcionará siempre y cuando Git pueda ejecutar el producto final. Aquí está el código de nuestro nuevo asistente de credenciales:

#!/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.exists? 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'] then
        puts "protocol=#{prot}"
        puts "host=#{host}"
        puts "username=#{user}"
        puts "password=#{pass}"
        exit(0)
    end
end
  1. Aquí analizamos las opciones de la línea de comando, permitiendo al usuario especificar un archivo. El default es ~/.git-credentials.

  2. Este programa sólo responde si la acción es get y el archivo de almacenamiento existe.

  3. Este bucle lee de “stdin” hasta que se encuentre la primer línea en blanco. Los inputs son guardados en el hash known para una posterior referencia.

  4. Este bucle lee el contenido del archivo de almacenamiento, buscando por concordancias. Si el protocolo y el host de known concuerdan con la línea, el programa imprime el resultado a “stdout” y sale.

Guardaremos nuestro modo como git-credential-read-only, ponlo en algún lugar en nuestro PATH y lo marcamos como ejecutable. Aquí se muestra cómo se vería una sesión interactiva:

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

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

Dado que su nombre comienza con “git-”, podemos usar la sintaxis simple para el valor de configuración:

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

Como se puede apreciar, extender este sistema es bastante sencillo, y puede resolver algunos problemas comunes para ti y tu equipo.