
Secrets with SOPS & Age
Photo Credits: Unsplash and SBTS2018 on FlatIcon
Introduction
sops
+ age
:
- Enables the secure storage of encrypted secrets on your local filesystem or in your repositories.
- Provides fully transparent access to the encrypted data through
sops
; Creating, reading, and editing the encrypted data is no more difficult than using your text editor on unencrypted data. - Reduces manual secrets management overhead to a single secret to be stored and transferred out-of-band. Cloud-based keys (e.g. AWS, GCP, etc.) are supported.
Additional features (that will not be covered in this article):
- key updates, additions, rotation
- key sharding via 'key groups'
- a built-in 'key service' for sharing
sops
keys over a socket connection - native auditing
- integrations with various automation tools - such as
terraform
- to retrieve secrets fromsops
-encrypted files with a minimum of configuration
About The Tools
SOPS: Secrets OPerationS
SOPS is "an editor of encrypted files that supports YAML, JSON, ENV, INI and BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault, age, and PGP."
sops
(once configured) allows us to create, read, and update encrypted files with a single command.
DEK/KEK Encryption
SOPS uses a DEK/KEK model for encryption. It encrypts data with a 256-bit data encryption key (DEK), then encrypts that data key with one or more key encryption key(s) (KEK) provided by an external key service or encryption library (in the case of this tutorial, age
).
Multiple KEKs can be used, allowing multiple private keys to decrypt the secrets. This is accomplished by storing multiple encrypted copies of the DEK in the encrypted data file, each associated with its own public key.
FiloSottile/age
age is "a simple, modern, and secure file encryption tool, format, and Go library."
Tutorial
Start a Dev Environment
To run the demo in a containerized environment:
docker run -it debian:latest /bin/bash
Install
SOPS is not included in mainstream package repositories. You can download binaries from their Github releases page or build from source (see below).
Install golang, git, and make (if not installed)
apt update
apt install -y wget git make
# see https://go.dev/doc/install
wget https://go.dev/dl/go1.24.4.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.4.linux-amd64.tar.gz
cat << EOF >> ~/.bashrc
export PATH="$PATH:/usr/local/go/bin"
export PATH="${PATH}:~/go/bin"
export GOPATH=~/go
EOF
source ~/.bashrc
mkdir $GOPATH
Build sops
from source
mkdir -p $GOPATH/src/github.com/getsops/sops/
git clone https://github.com/getsops/sops.git $GOPATH/src/github.com/getsops/sops/
cd $GOPATH/src/github.com/getsops/sops/
make install
Install age
apt install -y age
Configure
Create a private key
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
Create a config file
The .sops.yaml file tells SOPS knows what key to use
cat << EOF > .sops.yaml
creation_rules:
- age: "<YOUR PUBLIC KEY>"
EOF
To use different keys for different repositories:
creation_rules:
- path_regex: '<path1>'
age: 'pubkey1'
- path_regex: '<path2'
age: '<pubkey2>'
Alternative: Config via ENV
Alternatively, both the public and private keys can be provided via environment variables:
export SOPS_AGE_RECIPIENTS="YOUR PUBLIC KEY"
export SOPS_AGE_KEY="YOUR SECRET KEY"
Test
sops test.yaml
Encrypted File Format
SOPS can create and edit arbitrary filetypes, but 'targets' encryption for supported structured filetypes, such as yaml. Essentially, it will only encrypt the values of our yaml file and not the keys. From the README:
sops uses the file extension to decide which encryption method to use on the file content. YAML, JSON, ENV, and INI files are treated as trees of data, and key/values are extracted from the files to only encrypt the leaf values. The tree structure is also used to check the integrity of the file.
After saving the file, you can view the encrypted version with a cat test.yaml
:
hello: ENC[AES256_GCM,data:fNNoqMbZLW2n5vz4nd9KyBPpkGbdJTE7YZZeH4sJ6t3/GRlIF/QzEnD7PndClQ==,iv:2li3yVay4KBGK/EgFoDD4vXtAffnffruGJj8huUMFZo=,tag:f60yzaNqSL2KYJHpSX82og==,type:str]
example_key: ENC[AES256_GCM,data:Wt03RCvFzBHfapwFIw==,iv:e9o8thLfXbsc76C0FPLyQNBaDyChBcbtby/WnPYApIg=,tag:RM5NEUYM2UK+hNXcuOVyzA==,type:str]
#ENC[AES256_GCM,data:kqFjXMan8Dn1qRYQ2k/hCw==,iv:N/2QmyeoGqzuw7w35FWiC53jYoRJAFnho9C0s91DCZM=,tag:KWOtfFmraQBfCoUK2mnsjw==,type:comment]
example_array:
- ENC[AES256_GCM,data:55u6LOQSVag/7uxm8L0=,iv:tDlUjrMSNaNCtbT6wrL8zEWLiVO/VDc8N08cLbv033o=,tag:1W001s6bNZDemofAVDYIzw==,type:str]
- ENC[AES256_GCM,data:GU8Ur24YxlA0EQtcIy4=,iv:lNYb9YKlDTfZ2g7+qFrYwaQnO5eWn2ulu+/UItkfr14=,tag:qZy8qqKNbD5O5c2i6lwY3Q==,type:str]
example_number: ENC[AES256_GCM,data:WewtHzjhHj5FBQ==,iv:dfmOOYAOPIV0cVnc6E5S4VaEjb+MU+DZhaP7Ldi9DVI=,tag:t0H09QMUieIjKXe0MZALpw==,type:float]
example_booleans:
- ENC[AES256_GCM,data:A+6Flg==,iv:/5qpoHX70v3X4hywCBvvzXVQQLYNKFactbkwite+EHs=,tag:CKgbO5lNXIo9GYLoMrOPhQ==,type:bool]
- ENC[AES256_GCM,data:KDYY+i8=,iv:G/hEXLZ8G9b6CHjajWkHNm0XIO7vRrxsE+bOS8joPQU=,tag:z0Bp71+2+4gxyrJkqLLHbg==,type:bool]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1rwrumcadz6jk99j2gxjw3nsywdcyuwyc9858gqg5k3wvq8w6gqvqn8qj47
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWMUtBcStYdCt4TGkrYlVL
MkhIWlIwVzBNRkFqb3pZd2dNaE9ib2JQYm5VCnZCUURvdmJFS01CSE9qbTcvVnhw
Nlk4QjQ0SXdKVVo4RjF4MkFPckFIaTgKLS0tIFFSeVhxZEZ4cGpLeGsrWnpQMEg2
eDR0Z1hCZjdmUFdoaHlyS1ZsdTV6Mm8KxskZTV+o2yJ506pijDfXYHPeNuLkeSsD
qJnq1M5A5/WnfKBn29AuthMTP45oo4zj3rFB3JQnqipaken0YijhTQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2023-08-29T00:28:56Z"
mac: ENC[AES256_GCM,data:ykzKTV5NPo547Uywc+3NhrrOhuyc3FoxmZH0DdAV2fpSSYYFGilCTWwvsS1QcRFaI4XCUtzVNu9pnhvNNkbn9soHcjQNCW4lB08rNVjBm2fCYh+dkULWzeG52NZT+szbynmAvxYcgitnu+pBB4h/oZ+BvJLLb9LrOiXQXu6TH4E=,iv:jzfxc0N13DDLiAUV+cWj4E+86OsUSijDUdGVE3XDcZc=,tag:PfAdq92sSmXEQz8ESQHjxg==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.8.0-rc.1
You can edit the file again with sops test.yaml
, or decrypt it to stdout with sops -d test.yaml
.