My recipe to handle sensitive data in VCS: SOPS + age
Imagine this: you are working on a project but must store sensitive information. In my case, the Secret Key of my Cloud provider. Imagine all the things that an attacker could do with that Secret Key. It’d leave me in bankruptcy. The attacker could add how many resources they want, add more CPU to my machines, disk space, increase the size of the machine to the more advanced types, etc. Guess what could happen at the end of the month…
This case never happened to me, but it is a possible scenario if you expose your sensitive information in your Version Control System. It does not matter what kind of sensitive information you have (API Keys, M2M keys, passwords, etc.); you should (or have?) exclude them from your VCS and securely handle them. In this article, I’m going to publish my approach and the tools that I use:
The perfect combination (for me):
Glossary:
VCS: Version Control System.
I’m not going to explain in detail what SOPS and age are; you can check its documentation. But briefly:
- SOPS is a CLI tool to encrypt and decrypt data. That’s it.
- age is an encryption tool. It generates a public key and a private key that SOPS requires.
Ok, too much talk… Let’s work!
1. Install SOPS and age
(I’m assuming you are on Mac, but if you are on Windows or Linux, use your OS’s package manager.)
brew install sops age
2. Generate the key with age
age-keygen -o key.txt
Now that you have a key.txt, check it out. It should look like:
2. Configure a .sops file
Although we can pass all the configurations we will write in this file through the CLI, it is tedious and not versionable. That’s why SOPS can handle all its configuration with a .sops file. Mine looks like this:
Where:
-
patch_regex is the path where SOPS will look for your encrypted file. In my case, I store my encrypted secrets in a file called secrets.encrypted.yamls
-
age This is your public key generated by age in step #2.
It is safe to push this file to your VCS.
3. Encrypting a file
SOPS supports JSON, YAML, and binaries, among other formats, as inputs for your sensitive data. I choose YAML. My file that contains the sensitive information looks like this:
aws_api_key: james
ssh_secret_key: rodriguez
a_password: my_password_xyz
Now, let’s encrypt it:
sops --encrypt secrets.yaml > secrets.encrypted.yaml
sops will know that you are using age, because of the configuration in .sops.yaml
It’ll generate a secrets.encrypted.yaml that will look like something like this:
aws_api_key: ENC[AES256_GCM,data:blahblahtag:gJV4VqWNQVT4Srv88oJJUQ==,type:str]
aws_api_key: ENC[AES256_GCM,data:blahblahtag:gJV4VqWNQVT4Srv88oJJUQ==,type:str]
a_password: ENC[AES256_GCM,data:blahblahtag:gJV4VqWNQVT4Srv88oJJUQ==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: agethe-public-key-generated-by-age
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
...
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-07-13T14:30:06Z"
mac: ENC[AES256_GCM,data:...,tag:43WAnpwgLkVmhPBjKapnsQ==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.7.3
4. Decrypting a file
SOPS needs to know the path where you have stored your private key generated in step #1 with age. SOPS will look for this file with the path in the environment variable SOPS_AGE_KEY_FILE
.
export SOPS_AGE_KEY_FILE="$(PWD)/key.txt" && \
sops --decrypt secrets.encrypted.yaml
And boom 💫💫 ! You get the output of the decrypted data in your console:
aws_api_key: james
ssh_secret_key: rodriguez
a_password: my_password_xyz
To take into account
SOPS and age have more configuration options like key_groups to handle multiple key management tools. You can check all its configuration and options in its official documentation. This article refers to my own recipe. Use it or modify it according to your needs.