Set up SOPS in a CUE repository¶
To keep secrets in a CUE repository, encrypt them with SOPS to your own key and to each host's, and load the encrypted files into sysdef: secrets.
CUE reads the files still encrypted, so cue vet and cue export work without any age key; syslet decrypts them on the host.
Plain JSON specs can carry secrets too, but each secret spec holds the whole encrypted file as one string that you'd have to regenerate after every edit. In CUE, the spec is always built from the file as it is in the repository.
For a guided walkthrough of the same setup, see the tutorial Set up SOPS encryption. For how the keys fit together, see Secret encryption.
Prerequisites¶
- A CUE repository as in Set up CUE repository.
- sops, age and ssh-to-age on your workstation.
- On each host, an ed25519 SSH host key at
/etc/ssh/ssh_host_ed25519_key, or a dedicated decryption key.
Get your admin key¶
If you already have an age key, print its public key:
| Bash | |
|---|---|
Otherwise, create one where sops looks for it by default:
On macOS, sops looks in ~/Library/Application Support/sops/age/keys.txt instead; use that path, or point SOPS_AGE_KEY_FILE at the file.
Back up the private key and keep it out of the repository: without it, you can't edit the secrets anymore. If several people edit the secrets, each uses their own key and sends you the public half.
Get each host's public key¶
Convert the host's public SSH host key to an age recipient:
| Bash | |
|---|---|
With a dedicated decryption key, convert /etc/syslet/age_ed25519.pub instead.
Repeat this for every host that deploys secrets.
Configure sops¶
Create .sops.yaml in the repository root and list the public keys from above:
| .sops.yaml | |
|---|---|
sops applies this rule to every file ending in .enc.yaml when it encrypts one, so you never pass recipients on the command line.
Keep the suffix: the CUE config below loads secret files by it.
If the repository describes several hosts, use one rule per host directory instead, so each host only decrypts its own secrets (see Manage several hosts).
Load the secret files in CUE¶
Choose how to turn files into secret specs:
- Load every file by name when the files can sit next to the CUE file and their names can be the spec names. This is the common case: adding a secret means adding a file.
- Load a single file when a file lives in another directory, or its spec needs a name of its own.
You can combine both in one repository.
Load every file by name¶
#SysdefSecretsFromEmbeddedFiles turns each creds-<name>.enc.yaml next to the CUE file into the secret spec <name>.
Add @extern(embed) as the first line of syslet.cue, and the secretFiles and sysdef: secrets lines:
@extern(embed) has to be the first line of every file that uses @embed.
allowEmptyGlob keeps the config valid while there are no secret files.
The secret files must sit in the same directory as the CUE file that embeds them.
With a glob into a subdirectory, the directory stays part of each file name, so the creds- prefix isn't stripped and the spec name contains the path.
To keep one secret file per host directory, move these lines into each host's CUE file (see Manage several hosts).
Load a single file¶
Embed the file into the spec directly:
| Text Only | |
|---|---|
The path is relative to the CUE file's directory and can't contain ...
The file that contains this line needs @extern(embed) as its first line, too.
Add the first secret¶
Write the values in plain text first, with a name that matches your .sops.yaml rule and CUE glob:
| creds-webapp.enc.yaml | |
|---|---|
The file must be a flat mapping of string values, and key names may only contain a-z, 0-9 and -, since each key becomes the podman secret <spec name>-<key>, here webapp-db-password.
Encrypt it in place before you commit it:
| Bash | |
|---|---|
The sops block at the end of the file lists one recipient per key from .sops.yaml.
If a host is missing there, it can't decrypt the file: fix the rule, then run sops updatekeys creds-webapp.enc.yaml.
From now on, edit the file with sops edit creds-webapp.enc.yaml; to remove the secret, delete the file.
Check the setup¶
Check that CUE picks up the file, without any key:
| Bash | |
|---|---|
The output has an entry webapp whose spec.ciphertext is the encrypted file.
Then check that the host can decrypt it:
| Bash | |
|---|---|
The plan decrypts the file on the host and lists its keys under Secret changes, with values hidden:
If the host isn't a recipient of the file, the plan reports secret "webapp": decryption failed and syslet applies nothing.
To use the secret, reference it from a container as in Pass a secret to a container.