- The user creates a file called
secrets.env
anywhere in the repository. This file is dotenv-formatted as simple key-value pairs where the value is a secret to be passed into a Stack or container. We want to be able to read the "shape" of the file (the names of the keys), even when the file is encrypted. - The
.gitattributes
file assignes the following properties to files calledsecrets.env
:filter=sops
which tells the local git instance to run the "sops" filter configured in.git/config
.diff=sops
which tells the local git instance to run the "sops" diff command instead of the normalgit diff
when diffingsecrets.env
files.
- The
.git/config
is configured with the "sops" filter and diff blocks referenced above.filter.sops.smudge=
will run the thing after the=
when checking out the repository. This should decrypt our secrets, making them "dirty" again. We calldecrypt-filter.sh
and pass%f
as a positional argument, which renders to the path of the file being filtered, relative to the repo root (e.g.homelab/stacks/books/secrets.env
).filter.sops.clean=
will run the thing after the=
when staging changes to commit. This should encrypt our secrets, making them "clean". We callencrypt-filter.sh
and pass%f
as a positional argument, which renders to the path of the file being filtered, relative to the repo root (e.g.homelab/stacks/books/secrets.env
).filter.sops.required=true
will ensure that if the scripts fail, the commit will error out.diff.sops.textconv=
will use the command after the=
instead ofgit diff
when comparing files. This affects whether files are considered "modified".
- When the user stages a new
secrets.env
file athomelab/stacks/books/secrets.env
, we automatically runencrypt.sh homelab/stacks/books/secrets.env
. This implictly passes the contents ofsecrets.env
to the script at/dev/stdin
. The script takes the following steps to generate the encrypted content of the file:- Assert privatekey existence at
~/.age/key
. - Determine file extension. For now, we're only working with dotenv-formatted files, but sometimes we also need to support binary files. We use this information to set the
--input-type
parameter. - Encrypt the file contents. This command infers recipients from
.sops.yaml
. The command uses the privatekey (at~/.age/key
) of the local user to encrypt the secret. We use Shamir's secret sharing with sops key groups to require at least two of: author key, CI/CD key, and deploy key. Of course, the original privatekey can always decrypt the secret.
- Assert privatekey existence at
- Our new
secrets.env
file is committed to the repository. Encrypted and json-formatted (why not dotenv-formatted?). - Now we want to deploy our new
homelab/stacks/books
Stack. We'll focus on the sops-related aspects of this process.- Our CI/CD environment is configured with an age keypair. Additional CI/CD environments will each be configured with their own keypairs.
- Our deploy environments (hosts) are each configured with an age keypair.
- Our CI/CD script is configured to decrypt secrets and export the variables before bringing up the Stack.
git ls-files |\
git check-attr -a --stdin |\
grep 'filter: sops' |\
cut -d':' -f1