banner

Secrets with SOPS & Age

Published 
Last Updated 

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 from sops-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.

source

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>'

source

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"

source

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.