Git integration usable to store encrypted secrets in the git repository while having the plaintext available in the working tree. An alternative to git-crypt using age instead of GPG.
Do not use this tool unless you understand the security implications. I am by no mean a security expert and this code hasn't been audited. Use at your own risk.
Short answer: you probably shouldn't. Before considering this approach, take a look at SOPS and Hashicorp Vault if they are better suited for the problem at hand. They have a clear security advantage over git-agecrypt
.
The one use-case where it makes sense to use git-agecrypt
instead is when you want to keep some files secret on a (potentially public) git remote, but you need to have the plaintext in the local working tree because you cannot hook into the above tools for your workflow. Being lazy is not an excuse to use this software.
I have written this to have a more portable and easy to set up alternative to git-crypt
.
-
First setup
git-agecrypt
integration for a repository:$ git-agecrypt init
This command configures the necessary hooks to encrypt and decrypt git objects and to generate clear-text output for
git diff
,log
etc. -
Next step is to configure rules to map encryption keys to file paths:
$ git-agecrypt config add -r "$(cat ~/.ssh/id_ed25519.pub)" -p path/to/secret.1 path/to/secret.2
An arbitrary number of recipients (public keys) and files can be specified using a single command. Keys can be Age keys, ed25519 SSH keys or stubs generated by Age plugins, e.g. for keys stored on Yubikey PIV module. It is enough to have only one secret key to decrypt the files later.
Configuration is saved to
git-agecrypt.toml
file inside the root of the repository -
After that, edit
.gitattributes
to actually use these filters. This is currently a manual step.path/to/secret.1 filter=git-agecrypt diff=git-agecrypt path/to/secret.2 filter=git-agecrypt diff=git-agecrypt
Files can be specified in the same way as for
.gitignore
but keep in mind that filters are only applied for files, not directories, so that you need to write/secrets/**
instead of/secrets/
to encrypt each file under thesecrets
directory. -
Finally, configure the locations of age identities (private keys) which can be used to decrypt files
$ git-agecrypt config add -i ~/.ssh/id_ed25520
Location of secret keys are stored outside of version control in
.git/config
to support having them in different location for each checkout.
This application hooks into git using smudge
clean
and textconv
filters. Issuing git-agecrypt init
adds them to the repository local .git/config
:
[filter "git-agecrypt"]
required = true
smudge = /path/to/git-agecrypt smudge -f %f
clean = /path/to/git-agecrypt clean -f %f
[diff "git-agecrypt"]
textconv = /path/to/git-agecrypt textconv
These filters are assigned to repository files in .gitattributes
. When configured, they are being called for each file when touching the index. Encryption is non-deterministic, so each time git status
, git add
, etc is run a new ciphertext would be generated. To circumvent this, a blake3 hash is calculated for the plaintext and stored under .git/git-agecrypt/
directory. While the hashes stored match with the file contents in the working tree, git-agencrypt
loads the previous ciphertext from the index when git asks for it.
Encryption can work without access to private keys (what Age calls identities). In order to pull remote changes of encrypted files or to see plain diff of files, these have to be configured with git-agecrypt config
. They are stored in .git/config
conforming to standard git config format:
[git-agecrypt "config"]
identity = /home/vlaci/.ssh/id_ed25519
identity = ...
The following limitations can be easily improved upon, but they are not blockers for my use-case.
-
The application is started once for each file for every git operation. It can cause slowdown when the repository contains many encrypted files. A possible mitigation for this issue could be the implementation of the long-running process protocol but it is usable as it is for a couple of small files.
-
During encryption/decryption the whole file is loaded into memory. This can cause issues when encrypting large files.