Using openssl to encrypt development scripts that have secrets
I like having scripts that can pull production data down to development – the best kind of fake data is real data. Usually that involves taking a database dump and downloading any uploaded files. Unfortunately, that also means remembering long command line arguments, and dealing with credentials for both the database and the file server.
Up until now, I’ve kept a secure notepad of command line arguments with secrets in my password manager, but that doesn’t really scale if you want to be able to supply different arguments in different contexts (The number of times I’ve forgotten to change an argument…) – what I really wanted was a script that I could call that abstracted the nasty command line call with long arguments to a slightly less nasty command line call with shorter arguments. Ideally it could also be stored in source control so other could use it.
The app I’m currently working on has a little helper bash script that does common tasks, like bringing up docker, running bundler – things that cost me valuable keypresses – and I really wanted to add a sync from production script. And this is how I did it.
The bash script (let’s call it /bin/lazy.sh) is just a stupid set of functions and case statements:
command = $1
function run {
local command = $1
shift
case $command in
backend)
docker-compose run backend $@
;;
frontend)
docker-compose run frontend $@
;;
}
case $command in
run)
run $@
;;
esac
This allows me to build up a nice little DSL of helper commands, meaning I don’t have to type so much.
$ lazy.sh run backend
Now, the fancy bit. Create another script, called lazy.secret (not stored in source control)
command = $1
function sync {
local command = $1
shift
case $command in
database)
PGPASSWORD=supersecretpassword pgdump -h -h database-server.aaaaaa.ap-southeast-2.rds.amazonaws.com -d database_name -U username
;;
uploads)
AWS_ACCESS_KEY_ID=amazonaccesskey AWS_SECRET_ACCESS_KEY=longstringofrandomcharacters s3_get bucket
;;
}
case $command in
sync)
sync $@
;;
esac
And then encrypt it:
openssl enc -aes-256-cbc -salt -in lazy.secret -out /bin/lazy.encrypted
When this happens, openssl will ask you to provide a passcode to decrypt the file. The encrypted file that has the secrets can by stored in source control (it’s not readable without the passkey), and the passcode can be stored in your password manager.
If you want to be slightly more paranoid, delete lazy.secret – you can decrypt the file if you need to modify it.
openssl enc -aes-256-cbc -d -in $dir/lazy.encrypted -out lazy.secret
Of course, remembering the command line argument to decrypt the file is way too hard, so let’s get the original /bin/lazy.sh script do that for us (it will ask you for the passkey first):
command = $1
function sync {
# This tells bash to look for the file in the same directory as the source script, not the current working dir
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Decrypt the file, and pipe it into bash. We need to supply /dev/stdin so the command line arguments passthrough.
openssl enc -aes-256-cbc -d -in $dir/lazy.encrypted | /bin/bash /dev/stdin $@
}
function run {
local command = $1
shift
case $command in
backend)
docker-compose run backend $@
;;
frontend)
docker-compose run frontend $@
;;
}
case $command in
run)
run $@
;;
sync)
sync $@
;;
esac
Kablamo!
$ /bin/lazy.sh sync database
enter aes-256-cbc decryption password:
Limitations
It’s not perfect, but it’s a nice simple way to share complicated scripts that require credentials with your team. Things to consider:
- Running ps while the script is running will expose all the secrets. But this also a problem when running the scripts by hand
- Everyone needs to know the passkey – Perhaps openssl has a way of having different keys unlock the same script? Let me know in comments
- If the key becomes exposed, your source control has a history that allows an attacker to decrypt and get the secrets at that point in time – in this case, just rotate all the credentials in the script, change the passkey and push. You should be doing this each time a team member arrives or leaves anyway.