Using git while on-the-run
This post describes how to use a custom git configuration, including SSH and GPG keys, stored on a an external drive such as a USB stick. This is nothing fancy but comes in handy when the user does not want to store their keys persistently on the machine.
This setup originates from situations where I would like to work on my personal git repositories, yet I don't want to store my git configuration, my SSH key for authentication or my GPG key for signing commits on the device that I'm currently working with. This is for example often the case at work where I use a company laptop, virtual machines and servers on which I do not want my key material to persist on the system nor to conflict with the existing configuration.
This is of course intended for machines that are trusted enough to load key material onto. By no means I recommend plugin such a USB stick into non-trusted devices.
Generating and storing the secret keys
For those who haven't got working pairs of SSH and GPG keys already, I recommend looking up the best practices on generating them. The following resources are a good starting point:
- GPG: Riseup, Marcus Holtz and Mike Ross
- SSH: Brandon Checketts
I store my git configuration file, my SSH key used for git authentication, my GPG subkey to sign commits on LUKS filesystems that are unlocked by using a passphrase. Here is how I structure the files on them:
/mnt/usb/
├── alias.sh
├── git/
│ └── config
├── keys/
│ ├── gpg/
│ │ ├── public.asc
│ │ ├── public.gpg
│ │ ├── revocation.asc
│ │ ├── subkey.asc
│ │ └── subkey.gpg
│ └── ssh/
│ ├── skywhi_ed25519
│ └── skywhi_ed25519.pub
└── signing_keyring/
├── gpg.conf
└── pubring.kbx
Working on a new device
The usage of environment variables, as opposed to aliases, will not only apply
the new settings to git but will also propagate them throughout the shell
session to other programs such as make and custom scripts. The environment
variables that we will set are GIT_CONFIG_GLOBAL, GIT_SSH_COMMAND and
GNUPGHOME.
GIT_CONFIG_GLOBALtells git to use a custom configuration file,GIT_SSH_COMMANDspecifies the SSH command used by git to authenticate to remote repositories.GNUPGHOMEpoints towards a GPG keyring and configuration file used when git calls gpg to sign commits.
$ man git | grep -A1 'GIT_CONFIG_GLOBAL' GIT_CONFIG_GLOBAL, GIT_CONFIG_SYSTEM Take the configuration from the given files instead from global or system-level configuration files. If GIT_CONFIG_SYSTEM is set, the system config file defined at build time (usually /etc/gitconfig) will not be read. Likewise, if GIT_CONFIG_GLOBAL is set, neither $HOME/.gitconfig nor $XDG_CONFIG_HOME/git/config will be read. Can be set to /dev/null to skip reading configuration files of the respective level. $ man git | grep 'GIT_SSH_COMMAND' GIT_SSH, GIT_SSH_COMMAND If either of these environment variables is set then git fetch and git push will use the specified command instead of ssh when they need to connect to a remote. The command-line parameters passed to the configured command are determined by the ssh variant. See ssh.variant option in git-config(1) for details. $GIT_SSH_COMMAND takes precedence over $GIT_SSH, and is interpreted by the shell, which allows additional arguments to be included. $GIT_SSH on the other hand must be just the path to a program (which can be a wrapper shell script, if additional arguments are needed). Usually it is easier to configure any desired options through your personal .ssh/config file. Please consult your ssh documentation for further details. $ man gpg | grep -A1 'GNUPGHOME$' GNUPGHOME If set directory used instead of "~/.gnupg".
These environment variables are set and unset using shell functions defined in /mnt/usb/alias.sh:
setupgit() { local KEY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; # If the variable is already defined, make a backup before updating it and # export both. [[ ! -z ${GIT_CONFIG_GLOBAL+x} ]] && export BAK_GIT_CONFIG_GLOBAL=$GIT_CONFIG_GLOBAL export GIT_CONFIG_GLOBAL="${KEY_DIR}/git/config" [[ ! -z ${GIT_SSH_COMMAND+x} ]] && export BAK_GIT_SSH_COMMAND=$GIT_SSH_COMMAND export GIT_SSH_COMMAND="ssh -i ${KEY_DIR}/keys/ssh/skywhi_ed25519" [[ ! -z ${GNUPGHOME+x} ]] && export BAK_GNUPGHOME=$GNUPGHOME export GNUPGHOME="${KEY_DIR}/signing_keyring" } unsetupgit() { # If there is a backup of the variable, then restore the old value and # export it. Otherwise, clean the variable. [[ ! -z ${BAK_GIT_CONFIG_GLOBAL+x} ]] \ && { export GIT_CONFIG_GLOBAL=${BAK_GIT_CONFIG_GLOBAL} ; export -n BAK_GIT_CONFIG_GLOBAL ; } \ || export -n GIT_CONFIG_GLOBAL [[ ! -z ${BAK_GIT_SSH_COMMAND+x} ]] \ && { export GIT_SSH_COMMAND=${BAK_GIT_SSH_COMMAND} ; export -n BAK_GIT_SSH_COMMAND ; } \ || export -n GIT_SSH_COMMAND [[ ! -z ${BAK_GNUPGHOME+x} ]] \ && { export GNUPGHOME=${BAK_GNUPGHOME} ; export -n BAK_GNUPGHOME ; } \ || export -n GNUPGHOME }
So whenever I need to do some git operations using the configuration files on the drive /dev/sdb, I use the following commands:
# Open and mount the LUKS-encrypted device /dev/sdb $ sudo cryptsetup open /dev/sdb cool-pendrive $ sudo mount /dev/mapper/cool-pendrive /mnt/usb # Load the environment variables $ . /mnt/usb/alias.sh $ setupgit # Do some git things... $ git commit -m 'My commit' # Will be signed with my GPG key $ git push # And pushed with my SSH key # Restore the environment and unmount the external drive $ unsetupgit $ sudo umount /mnt/usb $ sudo cryptsetup close cool-pendrive