Jafner.net/.sops
Joey Hafner bb337129f0
Some checks are pending
Stacks CICD / Setup (push) Waiting to run
#3 Rotate Gitea runner key, re-encrypt all keys
Also removes git-crypt encryption from all remaining secrets.
2024-08-29 14:29:51 -07:00
..
age-author-pubkeys #3 Set up sops at repo root 2024-08-23 00:12:20 -07:00
decrypt-filter.sh Update filter scripts to use absolute path of .sops.yaml 2024-08-27 21:56:30 -07:00
encrypt-filter.sh #3 Rotate Gitea runner key, re-encrypt all keys 2024-08-29 14:29:51 -07:00
README.md #3 Switch back from git-crypt to sops 2024-08-27 21:19:54 -07:00
sops-setup.sh #3 Remove textconv filter so that we can see encrypted content when staged 2024-08-28 13:14:39 -07:00

sops Encryption/Decryption Workflow in Git

  1. 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.
  2. The .gitattributes file assignes the following properties to files called secrets.env:
    1. filter=sops which tells the local git instance to run the "sops" filter configured in .git/config.
    2. diff=sops which tells the local git instance to run the "sops" diff command instead of the normal git diff when diffing secrets.env files.
  3. The .git/config is configured with the "sops" filter and diff blocks referenced above.
    1. filter.sops.smudge= will run the thing after the = when checking out the repository. This should decrypt our secrets, making them "dirty" again. We call decrypt-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).
    2. filter.sops.clean= will run the thing after the = when staging changes to commit. This should encrypt our secrets, making them "clean". We call encrypt-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).
    3. filter.sops.required=true will ensure that if the scripts fail, the commit will error out.
    4. diff.sops.textconv= will use the command after the = instead of git diff when comparing files. This affects whether files are considered "modified".
  4. When the user stages a new secrets.env file at homelab/stacks/books/secrets.env, we automatically run encrypt.sh homelab/stacks/books/secrets.env. This implictly passes the contents of secrets.env to the script at /dev/stdin. The script takes the following steps to generate the encrypted content of the file:
    1. Assert privatekey existence at ~/.age/key.
    2. 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.
    3. 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.
  5. Our new secrets.env file is committed to the repository. Encrypted and json-formatted (why not dotenv-formatted?).
  6. Now we want to deploy our new homelab/stacks/books Stack. We'll focus on the sops-related aspects of this process.
    1. Our CI/CD environment is configured with an age keypair. Additional CI/CD environments will each be configured with their own keypairs.
    2. Our deploy environments (hosts) are each configured with an age keypair.
    3. Our CI/CD script is configured to decrypt secrets and export the variables before bringing up the Stack.