From 3aad285fc3c8a83a868da10f7d46a84ae45d2413 Mon Sep 17 00:00:00 2001 From: Timo Wilken Date: Wed, 8 Nov 2023 19:23:27 +0100 Subject: Use age for secrets encryption --- tw/home.scm | 3 - tw/services/secrets.scm | 174 ++++++++---------------------- tw/system.scm | 12 ++- tw/system/files/wireguard/lap-fp4.psk.enc | 9 +- tw/system/files/wireguard/lap-lud.psk.enc | 9 +- tw/system/files/wireguard/lap-pi3.psk.enc | 9 +- tw/system/files/wireguard/lap-vin.psk.enc | 9 +- tw/system/files/wireguard/lap.key.enc | 9 +- tw/system/lap.scm | 1 + 9 files changed, 94 insertions(+), 141 deletions(-) (limited to 'tw') diff --git a/tw/home.scm b/tw/home.scm index 39d903df..12661015 100644 --- a/tw/home.scm +++ b/tw/home.scm @@ -18,8 +18,6 @@ #:use-module (tw packages ci) #:use-module (tw packages games) #:use-module (tw packages xorg) - #:use-module ((tw services secrets) - #:select (encsecret-program)) #:use-module (tw system) #:use-module (tw theme)) @@ -172,7 +170,6 @@ (simple-service 'common-files home-files-service-type `((".infokey" ,(local-file "home/files/infokey")) - (".local/bin/encsecret" ,encsecret-program) ;; With #:recursive? #t, Guix keeps the files' permission bits, i.e. makes them executable. (".local/bin/ppscm" ,(local-file "home/files/ppscm" #:recursive? #t)))) ; pretty-print scheme files diff --git a/tw/services/secrets.scm b/tw/services/secrets.scm index 41c26678..1895700d 100644 --- a/tw/services/secrets.scm +++ b/tw/services/secrets.scm @@ -1,6 +1,6 @@ (define-module (tw services secrets) #:use-module (gnu) - #:use-module (gnu packages guile-xyz) + #:use-module (gnu packages golang) #:use-module (gnu services) #:use-module (gnu services configuration) #:use-module (guix gexp) @@ -15,7 +15,8 @@ (define-configuration/no-serialization secret (encrypted-file file-like "The file in the Guix store containing the -encrypted secret.") +encrypted secret. The file must have been encrypted to the @code{host-key} +specified in the host's @code{secrets-configuration} record.") (destination string "The file path into which the secret will be decrypted.") (user (string "root") "The UNIX user owning the resulting decrypted file.") (group (string "root") "The UNIX group owning the resulting decrypted file.") @@ -27,75 +28,48 @@ decrypted file. Accessible only by the file's owning user by default.")) (every secret? thing))) (define-configuration/no-serialization secrets-configuration - (host-key (string "/etc/secrets.key") "The path to a file containing the -decryption key for the given secrets.") - (secrets (list-of-secrets '()) "A list of @code{secret} records ")) + (host-key (string "/etc/ssh/ssh_host_ed25519_key") "The path to a file +containing the decryption key for the given secrets.") + (secrets (list-of-secrets '()) "A list of @code{secret} records to +install on the host.")) (define (secrets-activation config) + (define (secret-install-invocation secret) + (match-record secret (encrypted-file destination user group permissions) + ;; Call the `install' function defined in the gexp below. + #~(install #$encrypted-file #$destination #$user #$group #$permissions))) + (match-record config (host-key secrets) - (with-imported-modules (source-module-closure - '((guix base64) - (guix build utils))) - (with-extensions (list guile-sodium) - #~(begin - (use-modules (ice-9 format) - (ice-9 ports) - (ice-9 binary-ports) - (ice-9 textual-ports) - (rnrs bytevectors) - (srfi srfi-26) - (guix base64) - ((guix build utils) #:select (mkdir-p)) - (sodium stream)) - (define (install contents destination user group permissions) - (format (current-error-port) "Installing secret (~4,'0o ~a:~a) at ~a~%" - permissions user group destination) - (mkdir-p (dirname destination)) - (let ((port (open-file destination "wb"))) - (with-exception-handler - (lambda (exn) - (close port) - (raise-exception exn)) - (lambda () - ;; Change permissions before writing contents to avoid exposing - ;; the secret in the meantime. - (chown port (passwd:uid (getpw user)) (group:gid (getgr group))) - (chmod port permissions) - (put-bytevector port contents) - (close port))))) - ;; Generate a new host key if none exists yet. - ;; This allows instantiating this service with an empty list of - ;; secrets to generate a host key, and later add secrets. - (unless (file-exists? #$host-key) - (format (current-error-port) - "No host key found at ~a; creating one now~%" #$host-key) - (install (call-with-input-file "/dev/urandom" - (cut get-bytevector-n <> (crypto-stream-chacha20-ietf-keybytes))) - #$host-key "root" "root" #o600)) - (define host-key - (call-with-input-file #$host-key get-bytevector-all #:binary #t)) - (unless (= (crypto-stream-chacha20-ietf-keybytes) - (bytevector-length host-key)) - (error "Invalid key detected; expected ~d bytes but got ~d bytes." - (crypto-stream-chacha20-ietf-keybytes) - (bytevector-length host-key))) - (define nonce - ;; In practice, `crypto-stream-chacha20-ietf-xor' is limited to - ;; 256 GiB of data for each (key, nonce) pair, but secrets are - ;; expected to be small, so it's fine to use the same nonce. - (make-bytevector (crypto-stream-chacha20-ietf-noncebytes) 0)) - (define (decrypt encrypted-file) - (crypto-stream-chacha20-ietf-xor - #:message - (base64-decode - (string-delete ; `base64-decode' doesn't tolerate any whitespace - char-whitespace? - (call-with-input-file encrypted-file get-string-all))) - #:nonce nonce #:key host-key)) - #$@(map (lambda (secret) - (match-record secret (encrypted-file destination user group permissions) - #~(install (decrypt #$encrypted-file) #$destination #$user #$group #$permissions))) - secrets)))))) + (with-imported-modules (source-module-closure '((guix build utils))) + #~(begin + (use-modules (ice-9 format) + (ice-9 popen) + ((guix build utils) #:select (mkdir-p dump-port))) + (define (install encrypted-file destination user group permissions) + (format (current-error-port) "Installing secret (~4,'0o ~a:~a) at ~a~%" + permissions user group destination) + (mkdir-p (dirname destination)) + (let ((port (open-file destination OPEN_WRITE))) + ;; Change permissions before writing contents to avoid exposing + ;; the secret in the meantime. + (chown port (passwd:uid (getpw user)) (group:gid (getgr group))) + (chmod port permissions) + (let ((stream (open-pipe* OPEN_READ #$(file-append age "/bin/age") + "-d" "-i" #$host-key encrypted-file))) + (dump-port stream port) + (close-pipe stream)) + (close port))) + ;; Generate a new host key if none exists yet. + ;; This allows instantiating this service with an empty list of + ;; secrets to generate a host key, and later add secrets. + (unless (file-exists? #$host-key) + (format (current-error-port) + "No host key found at ~a; creating one now~%" #$host-key) + (unless (status:exit-val + (system* #$(file-append age-keygen "/bin/age-keygen") + "-o" #$host-key)) + (error "Failed to generate host key at:" #$host-key))) + #$@(map secret-install-invocation secrets))))) (define secrets-service-type (service-type @@ -105,65 +79,9 @@ decryption key for the given secrets.") (compose concatenate) ;; ...then `extend' combines the extensions with the initial config. (extend (lambda (config more-secrets) - (secrets-configuration - (inherit config) - (secrets (append (secrets-configuration-secrets config) - more-secrets))))) + (match-record config (secrets) + (secrets-configuration + (inherit config) + (secrets (append secrets more-secrets)))))) (default-value (secrets-configuration)) (description "Install files containing secrets on the system."))) - -;; The following can be installed e.g. in "~/.local/bin" and used to import -;; secrets into local-files in a Guix channel. -(define encsecret-program - (program-file "encsecret" - (with-imported-modules (source-module-closure - '((guix base64))) - (with-extensions (list guile-sodium) - #~(begin - (use-modules (ice-9 match) - (ice-9 ports) - (ice-9 binary-ports) - (rnrs bytevectors) - (guix base64) - (sodium stream)) - (define (main key-port) - (let ((cryptotext - (crypto-stream-chacha20-ietf-xor - #:message (get-bytevector-all (current-input-port)) - #:nonce (make-bytevector (crypto-stream-chacha20-ietf-noncebytes) 0) - #:key (get-bytevector-all key-port)))) - (base64-encode cryptotext 0 (bytevector-length cryptotext) - #f #f base64-alphabet (current-output-port))) - (newline (current-output-port))) - - (define (help-message program-name) - (string-append "\ -usage: " (basename program-name) " [-h] KEY_FILE - -This utility encrypts data passed on stdin to stdout using the given key file, -in a way that the output can be decrypted with the same key file by the Guix -secrets-service-type. Symmetric encryption is used. Processing this utility's -base64-decoded output with the same key results in the original plaintext. - -arguments: - -h, --help show this message and exit - KEY_FILE the file containing the encryption key; required -")) - - (match (program-arguments) - ((program-name - . (? (lambda (args) - (or (member "-h" args) - (member "--help" args))) - _)) - (display (help-message program-name))) - - ((_ key-file) - (call-with-input-file key-file main #:binary #t)) - - ((program-name . _) - (display "error: invalid number of arguments\n\n" - (current-error-port)) - (display (help-message program-name) - (current-error-port)) - (exit 1)))))))) diff --git a/tw/system.scm b/tw/system.scm index 48ab680a..51146eed 100644 --- a/tw/system.scm +++ b/tw/system.scm @@ -8,14 +8,16 @@ #:use-module (tw channels) #:use-module (tw services wireguard)) -(use-package-modules acl admin avahi backup certs curl disk file-systems linux lsof man - moreutils python rsync search shells version-control vpn) +(use-package-modules acl admin avahi backup certs curl disk file-systems + golang linux lsof man moreutils python rsync search + shells version-control vpn) (use-service-modules mcron monitoring networking ssh vpn) (define-public %base-system-packages - (cons* acl acpi btrfs-progs cpupower curl efibootmgr exfat-utils git glibc-locales - hddtemp htop lshw lsof man-db man-pages man-pages-posix mlocate - moreutils nss-certs nss-mdns python restic rsync strace wireguard-tools + (cons* acl acpi age btrfs-progs cpupower curl efibootmgr exfat-utils git + glibc-locales hddtemp htop lshw lsof man-db man-pages man-pages-posix + mlocate moreutils nss-certs nss-mdns python restic rsync strace + wireguard-tools %base-packages)) (define-public %british-keyboard diff --git a/tw/system/files/wireguard/lap-fp4.psk.enc b/tw/system/files/wireguard/lap-fp4.psk.enc index ef979978..170235ce 100644 --- a/tw/system/files/wireguard/lap-fp4.psk.enc +++ b/tw/system/files/wireguard/lap-fp4.psk.enc @@ -1 +1,8 @@ -ThP5USmpvehDO14PVZjqyYaBCy8n3jDIQoojYN95O98wvjOHq+Cs+t2NvDD3 +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWamMrRWw2RWc5WEErdnJs +UnhySUJOaDIwSktpWVFtYUNOL1g5L0d4UkZvCitvdWF0QkNLQzdPT2NHSzAwSnM1 +RWVwMnJuaUxJMUhSKzl6Q3NkOXVyQkkKLS0tIGtmakJBaUxHZmp4UmJCbE03K2xF +Yi9Bbk5XZGdlUXNURkwrcy9ydm9ORjQK2J0gYNONcSb0DpGFFkxZ2XRQLC5lRysY +O6MZeSm1sin4Bj5ZOxluWxpvR2fLoxuHJcd1F4ylHxPMQ2TWKjQuHZXaFXnZ6VYY +/+jvJ7g= +-----END AGE ENCRYPTED FILE----- diff --git a/tw/system/files/wireguard/lap-lud.psk.enc b/tw/system/files/wireguard/lap-lud.psk.enc index d976d90a..15ba1599 100644 --- a/tw/system/files/wireguard/lap-lud.psk.enc +++ b/tw/system/files/wireguard/lap-lud.psk.enc @@ -1 +1,8 @@ -XTD6NjCM0JYnYU9+Z6q+9ZbpUBshnEPPMagwf/wCKMxBsXCXrt63oeqM1TD3 +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrKytNRncwdWJNRHZPSlZ0 +ZWJLNnE3WGxDQ2hCYkRkdUZFSU10aVBWc1M4CjY5QThSZERpUnpNcyt5VjdWZFI1 +SzNyRnd4ejV2NkFjWEd3THRZZ3ZhSEUKLS0tIDBhNi9FdUJmckh3MHRNeVo2aEF3 +N3FlWXVzMGpTcloxcWZLVi9VQXp4VjQKUmehShAWGRDMGIkVv4gcvf9TCO9wEgVk +doVPsp8a5AbEUerD4/RHuaOJjA0jNVp799xHISt89rwgTydw3vmuqgRXTEStWOCe +VnDxSVs= +-----END AGE ENCRYPTED FILE----- diff --git a/tw/system/files/wireguard/lap-pi3.psk.enc b/tw/system/files/wireguard/lap-pi3.psk.enc index 2d318777..00d75345 100644 --- a/tw/system/files/wireguard/lap-pi3.psk.enc +++ b/tw/system/files/wireguard/lap-pi3.psk.enc @@ -1 +1,8 @@ -XgTTJRaGle1fGipxe7i78Yr9HhUP3DiOQrctYMA6H/NcmzSV7uC/8d/XjjD3 +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5TGFUek56c2RDd3diTlRx +VnY4N1hkWFZsYmcrczBCRzhEcDJoVWNzQ3pFCkJDSkRnWkovcmhTM0NpSDY1Z0xX +NFpmWjNMVCtYb1VZUkpZNDJpOXFtbzQKLS0tIGZTYldyRFBGaUZpSk5ubHRhU0Zv +M2gxZFc0SUU2K2lTU3VHS1hRWHNLalEKoqVMqXTweXjV4JutcoN6reXECegeY6iX +fzF8aRrczJMYpLxzpW0Oo5RmUumOvNXdm4tcO6g2QpDHQXFp7O6jGAKeyP0GQ7kg +lf5ZW9w= +-----END AGE ENCRYPTED FILE----- diff --git a/tw/system/files/wireguard/lap-vin.psk.enc b/tw/system/files/wireguard/lap-vin.psk.enc index c84938a9..a335cc14 100644 --- a/tw/system/files/wireguard/lap-vin.psk.enc +++ b/tw/system/files/wireguard/lap-vin.psk.enc @@ -1 +1,8 @@ -EQriKiyMh5x0Ak1WRqzg6q79DjcZ+jXNW50RTog5HqRMmSu3sNKf2seijjD3 +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1UmVGZG9VMlVubk1nVmNE +VnFONXZ1V01nb1BTaFNaMldoRFYvMlZKU0Y4CndvUmxHZEJ0KzZQWHlPeUgvdjJS +VWF3bkNNMHhWenVLdFdnYVhQcUNCTXcKLS0tIEk4aE9weDNKRFI1RzQ3NXBwYWNz +MU8vSlhkSS80M0w0bWFhNzkxY2d2SmcKUUMsAD+yY6wGjaSTxRgzjABQ/qPwjKNE ++Pz0nnyJkXPrwlHFS+g5n+VUz6NzKi2zxdaDpgsKkGrSkqSHij1z77ZjdKwcy/uv +7auCjMM= +-----END AGE ENCRYPTED FILE----- diff --git a/tw/system/files/wireguard/lap.key.enc b/tw/system/files/wireguard/lap.key.enc index 200bf821..ce7bac3b 100644 --- a/tw/system/files/wireguard/lap.key.enc +++ b/tw/system/files/wireguard/lap.key.enc @@ -1 +1,8 @@ -ESqNLhK1ubNRY1JDVYuR1tGJDTUvnkKOTawBQcISO8Ntqgil59nRoOSNkjD3 +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOUHZYQVJFenpUbjdQOVVx +dW1mT0VIelR3aTA2ZVRnR01TU21zcE5LZ1VFCkdwQk9VczJjV0psK1Mza2UrUk1H +V3k4R2ovQjhFb0k3NzVueHlkTWk1UUUKLS0tIFdxUTllcmNwSkxzYzBWT0ZRcE5m +RlBqTWVyQ3RJY3ZTb3Y0ZjZsc0xFc0EKJvJ6KrnyxHqucgTydIsnX2dwKqQQwdrg +OHrWGorh3v44xHpHJrS94gnC5AzCblKVVNt5/93esUaUsXYRwaAhQu5TVoUeFdjP +b9POXvk= +-----END AGE ENCRYPTED FILE----- diff --git a/tw/system/lap.scm b/tw/system/lap.scm index 33154d5b..b6049ad3 100644 --- a/tw/system/lap.scm +++ b/tw/system/lap.scm @@ -449,6 +449,7 @@ EndSection (service secrets-service-type (secrets-configuration + (host-key "/etc/secrets.key") ; we have no SSH host keys, so use a custom key (secrets (list (secret -- cgit v1.2.3