Skip to content
Content only available in english

How to set up SOPS with age keys ​

This guide walks through setting up the local toolchain needed to create and work with encrypted files in a CoB project. Once set up, you will be able to encrypt new secret files that cob-cli can deploy and have decrypted on the server.

Prerequisites ​

Install SOPS and age:

sh
# macOS
brew install sops age

# Windows
winget install --id FiloSottile.age
winget install --id SecretsOPerationS.SOPS

Generate an age key pair and save it to 1Password ​

Copy and paste this script to generate the key and store it directly in 1Password without writing it to disk:

sh
age_key_output=$(age-keygen)
age_private_key=$(echo "$age_key_output" | grep -o 'AGE-SECRET-KEY-[[:alnum:]]*')
age_public_key=$(echo "$age_key_output" | grep -o 'age1[[:alnum:]]*')

op item create --category=Login --title="cob-cli key" --vault="Employee" \
   "username=$age_public_key" \
   "password=$age_private_key"

WARNING

The Vault Employee was named Private in older versions of 1password so the command above may give an error for not finding the vault.

This is a known naming inconsistency in 1Password. They renamed Teams and Business "private vaults" to "employee vaults" to reduce confusion between work and personal accounts, but no functionality actually changed — just the name. In this case you can chek that the UUID of the vault is the same but the label changed. Solution is changing Employee to Privatein the command above

The public key (saved as username) is safe to share and will be added to the repository configuration. The private key (stored as password) never touches disk.

Add your key to .sops.yaml ​

The repository should already contain a .sops.yaml at its root, with the server's key. Open it and add your public key as a new entry:

yaml
creation_rules:
  - age:
      # server
      - age1lggyhqjt3nu3tqe5ld8stpkdqe...
      # <your name>
      - age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7...

Replace the second key with your own public key from the previous step, and update the comment to identify yourself. Commit the change — .sops.yaml contains only public keys and is safe to version-control.

TIP

If the file does not exist please contact Cult Of Bits support.

Scope keys per environment ​

If the server has more than one machine environment — for example prod and qual — you will usually want each environment's secrets encrypted with a different server key, so that the quality machine cannot decrypt production secrets and vice versa. Developers still need to read every environment, so their keys appear in every rule.

Environment-specific encrypted files follow the usual <filename>.ENV__<env>__.<extension> naming convention. SOPS picks a rule by matching the file path against each path_regex, top to bottom, and uses the first rule that matches. Order the rules from most specific to least, and leave the last rule without a path_regex so it becomes the default — aligning with cob-cli's precedence, where production is the default environment:

yaml
creation_rules:
  # Quality secrets: *.ENV__qual__.* → qual server + developers
  - path_regex: \.ENV__qual__\.
    age:
      # qual server
      - age1qual0server0public0key...
      # <your name>
      - age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7...

  # Default: *.ENV__prod__.* and any file without an env marker → prod server + developers
  - age:
      # prod server
      - age1lggyhqjt3nu3tqe5ld8stpkdqe...
      # <your name>
      - age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7...

With this configuration:

  • secret.ENV__qual__.yml matches the first rule and is encrypted for the qual server and every developer.
  • secret.ENV__prod__.yml, or a common file with no ENV__ marker, falls through to the last rule and is encrypted for the prod server and every developer.

TIP

Keep the rule without a path_regex last — it matches every path, so placing it first would make all files use the production keys regardless of their environment marker. Add one path_regex rule per extra environment above it.

WARNING

.sops.yaml rules are applied only when a file is first encrypted. After changing the rules — or adding a developer key — re-encrypt the affected files with sops updatekeys <file> so they pick up the new key set. A secret that must be decryptable on every machine needs a per-environment copy, since the default rule only grants the prod server access.

Make the private key available to SOPS ​

Set SOPS_AGE_KEY_CMD to have SOPS retrieve the private key from 1Password on demand:

sh
export SOPS_AGE_KEY_CMD='op read "op://Employee/cob-cli key/password"'

SOPS will run this command each time it needs the private key, prompting 1Password to authenticate if the session has expired.

Add this to your shell profile.

Before relying on the setup, verify that op can retrieve the key correctly:

sh
eval $SOPS_AGE_KEY_CMD

This should print the AGE-SECRET-KEY-... line. If 1Password prompts for authentication, sign in and run it again.

Alternative: key file

If you are not using 1Password, generate the key to a file instead:

sh
age-keygen -o age-key.txt

Then point SOPS at it:

sh
export SOPS_AGE_KEY_FILE=/path/to/age-key.txt

Add this to your shell profile. Keep the key file outside the repository directory. Do not set both SOPS_AGE_KEY_CMD and SOPS_AGE_KEY_FILE at the same time.

Verify the setup ​

Check that SOPS can encrypt and decrypt with your key. In a server_* local repo, with an existing .sops.yaml file:

sh
echo "test: secret" > /tmp/test.yml
sops --encrypt /tmp/test.yml > /tmp/test.yml.enc
sops --decrypt --input-type yaml --output-type yaml /tmp/test.yml.enc

If the last command prints test: secret, the setup is complete.