Ken Muse

How to Dynamically Authenticate With Git


In the last post, I delved into Git authentication and how it works. I explored credential helpers and how they are implemented. If you need to authenticate with multiple accounts or across multiple systems, then that may have been all the details you needed. However, there’s another common scenario: needing to authenticate with a dynamic set of credentials. In this post, I’ll explore how that can be done.

The challenge

Sometimes, you need to work with multiple sets of repositories that require different credentials. With GitHub Actions, you can use the actions/checkout action to check out the repository, providing a token for each environment. In most cases, this is sufficient. There are sometimes cases, however, where you need to authenticate with a set of credentials that are dynamically provided or not known until runtime. You might also need to authenticate with repositories that are not part of GitHub and can’t use a token for authentication. In extreme cases, you might even need to determine the repository and credentials during the process. You can see where, in these cases, a credential helper might be helpful.

The credential store

One way to meet this requirement is to use the git-store credential helper and git-credential-store. The git-store helper is a built-in feature that reads and writes credentials to a file. You can either specify a path to the file or use the default path, which is ~/.git-credentials:

1git config --global credential.helper 'store --file /credentials/mycreds'

When the credential helper needs to read or store credentials, it will use the specified file. For each context the helper is tracking, it will store a record in the file in this format:

1https://username:password@hostname/path

While you can manually populate the file, a command-line tool is also provided. You can use git credential-store to read, write, or erase credentials. For example, to store a credential, you can run:

1git credential-store --file /credentials/mycreds store

You will then provide the credential details in the traditional Git input format:

1protocol=https
2host=github.com
3username=kenmuse
4password=supersecret

The empty line at the end is important. It tells the command that you are done providing input.

There is a problem with this approach, however. The file contents are stored in plaintext. That means that anyone with access to the file can read the credentials. This is definitely not a good practice! We need a better way to retrieve the credentials.

The credential helper

Because the credential helper is launched as a child process, it will have access to any environment variables. That means that not only can you build any process you want to retrieve the credentials, you can also use any of the available environment variables in the process. For example, assume you have an environment variable, SECRET_TOKEN, that contains the credentials for a user “anyuser.” You could use a Bash script to always return that value as the password. The script would look like this:

1#!/bin/bash
2
3test "$1" = "get" && printf "username=anyuser\npassword=${SECRET_TOKEN}"

This script would return the credentials using the environment variable in response to any get command. It would also ignore store and erase commands, so it can’t store credentials after connecting or remove credentials on failure.

This approach could also be used to retrieve credentials from a secrets vault, such as Azure Key Vault or AWS Secrets Manager. For example, if you have a secret in Azure Key Vault, you could use the Azure CLI to retrieve the secret and return it as the password. The script would look like this:

1#!/bin/bash
2
3# Assumes $SECRET_NAME and $KEYVAULT are set as environment variables
4test "$1" = "get" && \
5printf "username=anyuser\npassword=$(az keyvault secret show --name $SECRET_NAME --vault-name $KEYVAULT --query value -o tsv)"

Credential helper scripts

For simple cases like this, it turns out there’s another option for credential helpers. If the credential helper starts with !, Git will treat it as an inline script and invoke it using the shell (sh). Instead of requiring a separate file, you can provide the contents inline. For example:

1git config --global credential.helper '! \
2  getResults() { \
3    test "$1" = "get" && printf "username=anyuser\npassword=${SECRET_TOKEN}"; \
4  }; \
5  getResults'

Since it starts with !, it’s an inline script. This script declares a function, getResults, that will return the credentials. After that, the function getResults is called. Since Git will then append the command to the script, making it like calling getResults get.

Surrounded by options

With these techniques, you can dynamically authenticate with Git in a way that suits your specific needs. Whether you’re working with environment variables, secrets vaults, or inline scripts, Git’s credential helper system provides the flexibility to adapt to various scenarios. By understanding these options, you can securely manage credentials and streamline your workflows. Keep experimenting and exploring to find the best approach for your use case, and don’t hesitate to dive deeper into Git’s documentation to uncover even more possibilities!