We have some shared secrets we need to set up our infrastructure. Those shared secrets are (currently) mainly technical accounts (like our CI/CD systems and other build tools).
age
- A simple, modern, and secure encryption tool (and Go library) with small explicit keys, no config options, and UNIX-style composability.
In this example, we use age
to encrypt sensitive files and values required to set up the infrastructure.
Side-note: Since we missed this detail for a long time in our project, please note: The author pronounces it [aɡe̞], like the Italian “aghe”.
Prepare the Basic SOPS Setup
Let us kick-start the workspace from Alice and Bob, who want to share a repo with credentials.
git init workspace-Alice
git init workspace-Bob
They decided to use age encryption to store the development secrets in the shared repository.
mkdir secrets
age-keygen -o secrets/age-key.txt
Since this is the private key, Alice uses .gitignore
to prevent accidentally publishing the key.
echo 'secrets/age-key.txt' >> .gitignore
Both generated public keys are stored in public-age-keys.txt
as potential recipients for the encryption.
echo 'age1ls2ftwdzyu0ptdqe8mysde7mzwjdagqvvepr3hk2ysduhlz47enqxtymfh,age17aeun7qfjz470e4k4r3650h6hxdghtnh3z73llczzzzhvhlal44q4h8lsq' > public-age-keys.txt
Check the Setup with the sops CLI
Alice uses a secret.json
locally...
echo -n '{
"password": "42"
}' > secret.json
and stores the encrypted version in the repo manually:
# Alice
export SOPS_AGE_RECIPIENTS=$(<public-age-keys.txt)
sops --encrypt --age ${SOPS_AGE_RECIPIENTS} secret.json > secret.json.enc
git add secret.json.enc
git commit -m "Add encrypted shared secret"
With his key added to the list of recipients, Bob can manually decode and use the secret locally:
# Bob
export SOPS_AGE_KEY_FILE=$(pwd)/secrets/age-key.txt
sops --decrypt --input-type json --output-type json secret.json.enc > secret.json
Capture the Commands in a Script
Bod decided to capture the encryption and decryption steps in shell scripts to kick-start the automation (with git). The basic idea is to reuse the scripts in clean and smudge filters with Git later...
mkdir scripts
The encrypt.sh
script...
echo -n '#!/bin/bash
scriptDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd "${scriptDir}/.." || exit 1
export SOPS_AGE_RECIPIENTS=$(<public-age-keys.txt)
exec 3<<< "$(cat $1)"
sops --encrypt --input-type json --output-type json --age ${SOPS_AGE_RECIPIENTS} --encrypted-regex "^(user|password)$" /dev/fd/3
' >> scripts/encrypt.sh
chmod u+x scripts/encrypt.sh
and decrypt.sh
script:
echo -n '#!/bin/bash
scriptDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd "${scriptDir}/.." || exit 1
export SOPS_AGE_KEY_FILE=$(pwd)/secrets/age-key.txt
exec 3<<< "$(cat $1)"
sops --decrypt --input-type json --output-type json /dev/fd/3
' >> scripts/decrypt.sh
chmod u+x scripts/decrypt.sh
Bob adds both scripts to the shared repository:
# Bob
git add scripts
git commit -m "Add SOPS encryption/decryption scripts"
Test Encryption and Decryption Scripts
Bob adds the latest changes to secret.json
, encrypts the file with his script, and pushes the result...
# Bob
./scripts/encrypt.sh secret.json > secret.json.enc
git add secret.json.enc
git commit -m "Update encrypted shared secret"
Alice can use the provided script to easily decrypt the file without having to remember the command-line options:
# Alice
./scripts/decrypt.sh secret.json.enc > secret.json
Git Filters - Automagically SOPS All The Things
With the scripts provided, Alice is equipped with the last missing pieces to automate the overall process. Instead of semi-manually decrypting the file before using a Git filter can do the trick:
# Alice
git config --local filter.sops.smudge $(pwd)/scripts/decrypt.sh
git config --local filter.sops.clean $(pwd)/scripts/encrypt.sh
git config --local filter.sops.required true
Check the freshly created section in .git/config
(optional):
grep -A 3 sops .git/config
Enable the filter for the plain file...
echo 'secret.json filter=sops' > .gitattributes
git add .gitattributes
git commit -vm 'Hook secret.json into Git Filter sops'
and finally, replace the encrypted file:
# Alice
git add secret.json
git rm secret.json.enc
git commit -vm 'Switch to Git Filter for shared secret'
To opt in into the cool new magic world, Bob configures his Git alike:
# Bob
git config --local filter.sops.smudge $(pwd)/scripts/decrypt.sh
git config --local filter.sops.clean $(pwd)/scripts/encrypt.sh
git config --local filter.sops.required true
git pull
Tip: Run as if git was started in
path
:git -C <path>
This option allows for operating with both repositories within a single terminal.
git -C workspace-Alice git push
git -C workspace-Bob git pull
If unsure, you can inspect the shared secret in your local git-workspace with the following command:
GIT_HASH=$(git rev-list --objects -g --no-walk --all | grep secret.json | cut -d ' ' -f 1)
git cat-file -p ${GIT_HASH}
Eves Session - just for the record
Without a proper age key, the session from Yves will lead to nothing but encrypted values...
git clone repository
cd repository
shows the encrypted shared secret:
{
"password": "ENC[AES256_GCM,data:09GJdw==,iv:nWF898ZCjkwjiYQSEQiTpxXeZwkD+Cn0Z9PJlgdEJ0Q=,tag:jhrwmqLObZdA6wyu7o+odA==,type:str]",
"sops": {
}
}
TODO - Explore usage of .sops.yaml
This could be a nice alternative to store for public age keys and configuration
echo -n 'creation_rules:
- shamir_threshold: 1
path_regex: "secret.json"
encrypted_regex: "^(user|password)$"
key_groups:
- age:
- age1t2c8jft25k5nnr7m2zln473dkxegwvx5ge2pfgarfnaepepmzpzszz63qy
' >> workspace/.sops.yaml
Photo by James Coleman on Unsplash