~fishinthecalculator/sops-guix

Atomic, secure, reproducible secret provisioning with Guix and SOPS

f9e7aeb Fix guix pull.

7 days ago

66bc806 sops: services: sops: Age has been moved to (golang crypto).

21 days ago

#SOPS Guix

builds.sr.ht status

This project aims at implementing secure provisioning of secrets with Guix and SOPS. It was strongly inspired from NixOS' sops-nix.

#Secure secret provisioning with Guix

This channels exposes the sops-secrets-service-type Guix service and the sops-secret record to safely handle secrets with Guix. It works by putting encrypted secrets in the store and by adding a one-shot Shepherd service that decrypts them at startup in a ramfs/tmpfs filesystem. This means that clear text secrets never hit the disk and that you can (and actually are encouraged to) check in your SOPS secrets in the same version control system you use to track you Guix configurations.

Assuming that the right private keys are also provided, sops-secrets can be included in Guix images, deployed with guix deploy and included in Guix System/Home containers.

#Creating secrets with SOPS

First of all you need to create encrypted secrets with SOPS. To do so I'm assuming you already have a GPG key for yourself and the machines you want to deploy secrets to. You should be able to list the private keys you have in your keyring with

user1@home:~ $ gpg --list-secret-keys
/home/user1/.gnupg/pubring.kbx
------------------------
sec   ed25519 2023-12-01 [SC] [expires: 2907-11-30]
      8D1060B96BB8B7249AED41CC193B701E2SODIJNS
uid           [ultimate] user1@example.org
ssb   cv25519 2023-12-01 [E]

pub   rsa3072 1970-01-01 [SCE]
      8C3E4F6EB38828939029AE7BE9B6AF0CD39DD935
uid           [ unknown] root (Imported from SSH) <root@localhost>

pub   rsa3072 1970-01-01 [SCE]
      ZZ3E4VREB38800039029AE7BE9B6AF0CD39AALH9
uid           [ unknown] root (Imported from SSH) <root@localhost>

If you don't have a suitable set of GPG keys it's pretty simple to find online how generate them. Once you have a suitable set of keys for yourself and your machines you are ready to create the only configuration you need for SOPS: a .sops.yaml file that you will place in your project's root directory, or anyway in the same directory where you keep your system configuration. In this file you define which keys will be able to access your secrets files, it may very well be something like:

keys:
    - &user_user1 8D1060B96BB8B7249AED41CC193B701E2SODIJNS
    - &host_host1 8C3E4F6EB38828939029AE7BE9B6AF0CD39DD935
    - &host_host2 ZZ3E4VREB38800039029AE7BE9B6AF0CD39AALH9

creation_rules:
    - path_regex: .*common\.yaml$
      key_groups:
          - pgp:
                - *user_user1
                - *host_host1
                - *host_host2
    - path_regex: .*host1\.yaml$
      key_groups:
          - pgp:
                - *user_user1
                - *host_host1

In this file we define three keys called user_user1, host_host1 and host_host2 . The prefixes host_ and user_ are just a convention to indicate that some GPG keys belong to users and some belong to machines.

We now have defined two secrets file names patterns and we declared permissions for each key, it should be possible now to run the following in your projects root directory:

sops common.yaml

This will open your default editor with an example content to define your secrets value. You can edit it or delete it and your own content for example:

wireguard:
    private: MYPRIVATEKEY

after saving and closing the file you can see by catting the secret file that sops encrypted it before saving it, so you are free to check it in your VCS.

#Making sure the right host keys are in the configured GnuPG keyring

For hosts to be able to decrypt secrets you need to provide in the root user keyring (or anyway the keyring located at the configured gnupg-homedir) the keys you defined in your .sops.yaml. So based on the above example you'd need to provide 8C3E4F6EB38828939029AE7BE9B6AF0CD39DD935's private key on host1 and ZZ3E4VREB38800039029AE7BE9B6AF0CD39AALH9's private key on host2 .

To check that your key is correctly imported into the keyring run:

user1@host1:~ $ sudo gpg --list-secret-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 1970-01-01 [SCE]
      8C3E4F6EB38828939029AE7BE9B6AF0CD39DD935
uid           [ unknown] root (Imported from SSH) <root@localhost>

By setting generate-key? to #t in sops-service-configuration a GPG key will be automatically derived for you from your system's /etc/ssh/ssh_host_rsa_key and added to the configured keyring. It is discouraged to do so and you are more than encouraged to autonomally provide a key in your configured keyring.

#Adding secrets to your operating-system record

Now, supposing you have your operating-system file in the same directory where you have your .sops.yaml and common.yaml files, you can simply add the following to your configuration:

(use-modules (sops secrets)
             (sops services sops)
             (guix utils))

(define project-root
  (current-source-directory))

(define sops.yaml
  (local-file (string-append project-root "/.sops.yaml")
              ;; This is because paths on the store
              ;; can not start with dots.
              "sops.yaml"))

(define common.yaml
  (local-file (string-append project-root "/common.yaml")))

(operating-system
  [...]
  (services
    (list
       [...]
       (service sops-secrets-service-type
                (sops-service-configuration
                  (gnupg-homedir "/mnt/.gnupg")
                  (generate-key? #t)
                  (config sops.yaml)
                  (secrets
                    (list
                      (sops-secret
                        (key '("wireguard" "private"))
                        (file common.yaml)
                        (user "user1")
                        (group "users")
                        (permissions #o400)))))))))

Upon reconfiguration, this will yield the following content at /run/secrets:

user1@host1:~ $ sudo ls -la /run/secrets/
total 12
drwxr-xr-x 1 root root    50 Jan  2 12:44 .
drwxr-xr-x 1 root root   254 Jan  2 12:44 ..
lrwxrwxrwx 1 root root    53 Jan  2 12:44 .sops.yaml -> /gnu/store/lyhyh91jw2n2asa1w0fc0zmv93yxkxip-sops.yaml
-r-------- 1 user1 users  44 Jan  2 12:44 wireguard
user1@host1:~ $ cat /run/secrets/wireguard/private
MYPRIVATEKEY

#Adding secrets to your home-environment record

sops-guix also provides a Guix Home service that is able to provide most feature of the system service. Most significant limitations are:

  • AFAIK home-environments can't configure ramfs mount points hence the secrets-directory option is not available. Secrets are stored in /run/user/$UID/secrets which usually is mounted on tmpfs.
  • sops-secret-user and sops-secrets-group are ignored. All secrets belong to the user running the Guix comman line but you can still set permissions.
  • There's no option to automatically generate GPG keys since probably users can easily generate one.

Now, supposing you have your home-environment file in the same directory where you have your .sops.yaml and your secrets files, you can simply add the following to your configuration:

(use-modules (sops secrets)
             (sops home services sops)
             (guix utils))

(define project-root
  (current-source-directory))

(define sops.yaml
  (local-file (string-append project-root "/.sops.yaml")
              ;; This is because paths on the store
              ;; can not start with dots.
              "sops.yaml"))

(define user1.yaml
  (local-file (string-append project-root "/user1.yaml")))

(home-environment
  [...]
  (services
    (list
       [...]
       (service home-sops-secrets-service-type
                (home-sops-service-configuration
                  (gnupg-homedir (string-append (getenv "HOME") "/.gnupg"))
                  (config sops.yaml)
                  (secrets
                    (list
                      (sops-secret
                        (key '("wireguard" "private"))
                        (file user1.yaml)
                        (permissions #o400)))))))))

Upon reconfiguration, this will yield the following content at /run/secrets/$YOUR_UID/secrets:

user1@host1:~ $ ls -la /run/user/$(id -u)/secrets
total 12
drwxr-xr-x 1 user1 users    50 Jan  2 12:44 .
drwxr-xr-x 1 user1 users   254 Jan  2 12:44 ..
lrwxrwxrwx 1 user1 users    53 Jan  2 12:44 .sops.yaml -> /gnu/store/lyhyh91jw2n2asa1w0fc0zmv93yxkxip-sops.yaml
-r-------- 1 user1 users    44 Jan  2 12:44 wireguard
user1@host1:~ $ cat /run/user/$(id -u)/secrets/wireguard/private
MYPRIVATEKEY

#Configure

To configure Guix for using this channel you need to create a .config/guix/channels.scm file with the following content:

(cons* (channel
        (name 'sops-guix)
        (url "https://git.sr.ht/~fishinthecalculator/sops-guix")
        (branch "main")
        ;; Enable signature verification:
        (introduction
         (make-channel-introduction
          "0bbaf1fdd25266c7df790f65640aaa01e6d2dbc9"
          (openpgp-fingerprint
           "8D10 60B9 6BB8 292E 829B  7249 AED4 1CC1 93B7 01E2"))))
       %default-channels)

Otherwise, if you already have a .config/guix/channels.scm you can simply prepend this channel to the preexisting ones:

(cons* (channel
        (name 'sops-guix)
        (url "https://git.sr.ht/~fishinthecalculator/sops-guix")
        (branch "main")
        ;; Enable signature verification:
        (introduction
         (make-channel-introduction
          "0bbaf1fdd25266c7df790f65640aaa01e6d2dbc9"
          (openpgp-fingerprint
           "8D10 60B9 6BB8 292E 829B  7249 AED4 1CC1 93B7 01E2"))))
       (channel
        (name 'nonguix)
        (url "https://gitlab.com/nonguix/nonguix")
        ;; Enable signature verification:
        (introduction
         (make-channel-introduction
          "897c1a470da759236cc11798f4e0a5f7d4d59fbc"
          (openpgp-fingerprint
           "2A39 3FFF 68F4 EF7A 3D29  12AF 6F51 20A0 22FB B2D5"))))
       %default-channels)

#What is a Guix channel?

A channel is roughly the Guix equivalent of the AUR or container registries. It's a software repository providing Guix package and service definitions.

You can search for package and service definitions from this channel any many others at toys.whereis.みんな.

#Contributing

All contributions are welcome. You can open issues at todo.sr.ht/~fishinthecalculator/sops-guix and send contributions at lists.sr.ht/~fishinthecalculator/sops-guix-devel. If you have commit access please remember to setup the authentication hook with

cp etc/git/pre-push .git/hooks/pre-push

#License

Unless otherwise stated all the files in this repository are to be considered under the GPL 3.0 terms. You are more than welcome to open issues or send patches.