From 7409fef3cbe6bba6c66ce8b03aef6c2d9dc6c7e7 Mon Sep 17 00:00:00 2001 From: Timo Wilken Date: Sun, 5 Nov 2023 01:03:55 +0100 Subject: Add secrets service Allow managing secrets and passwords using Guix. Secrets are encrypted in the Guix channel repository and decrypted using a single host key at activation time. --- tw/home.scm | 3 + tw/services/secrets.scm | 169 ++++++++++++++++++++++++++++++ tw/system/files/wireguard/lap-fp4.psk.enc | 1 + tw/system/files/wireguard/lap-lud.psk.enc | 1 + tw/system/files/wireguard/lap-pi3.psk.enc | 1 + tw/system/files/wireguard/lap-vin.psk.enc | 1 + tw/system/files/wireguard/lap.key.enc | 1 + tw/system/lap.scm | 21 ++++ 8 files changed, 198 insertions(+) create mode 100644 tw/services/secrets.scm create mode 100644 tw/system/files/wireguard/lap-fp4.psk.enc create mode 100644 tw/system/files/wireguard/lap-lud.psk.enc create mode 100644 tw/system/files/wireguard/lap-pi3.psk.enc create mode 100644 tw/system/files/wireguard/lap-vin.psk.enc create mode 100644 tw/system/files/wireguard/lap.key.enc (limited to 'tw') diff --git a/tw/home.scm b/tw/home.scm index 17cb7da7..6b0402db 100644 --- a/tw/home.scm +++ b/tw/home.scm @@ -18,6 +18,8 @@ #: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)) @@ -170,6 +172,7 @@ (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 new file mode 100644 index 00000000..41c26678 --- /dev/null +++ b/tw/services/secrets.scm @@ -0,0 +1,169 @@ +(define-module (tw services secrets) + #:use-module (gnu) + #:use-module (gnu packages guile-xyz) + #:use-module (gnu services) + #:use-module (gnu services configuration) + #:use-module (guix gexp) + #:use-module (guix modules) + #:use-module (guix packages) + #:use-module ((guix records) #:select (match-record)) + #:use-module (srfi srfi-1) + #:export (secrets-service-type + secrets-configuration + secret + encsecret-program)) + +(define-configuration/no-serialization secret + (encrypted-file file-like "The file in the Guix store containing the +encrypted secret.") + (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.") + (permissions (integer #o600) "UNIX file permissions for the resulting +decrypted file. Accessible only by the file's owning user by default.")) + +(define (list-of-secrets? thing) + (and (list? thing) + (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 ")) + +(define (secrets-activation config) + (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)))))) + +(define secrets-service-type + (service-type + (name 'secrets) + (extensions (list (service-extension activation-service-type secrets-activation))) + ;; `compose' is applied to unify all extensions into one first, ... + (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))))) + (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/files/wireguard/lap-fp4.psk.enc b/tw/system/files/wireguard/lap-fp4.psk.enc new file mode 100644 index 00000000..ef979978 --- /dev/null +++ b/tw/system/files/wireguard/lap-fp4.psk.enc @@ -0,0 +1 @@ +ThP5USmpvehDO14PVZjqyYaBCy8n3jDIQoojYN95O98wvjOHq+Cs+t2NvDD3 diff --git a/tw/system/files/wireguard/lap-lud.psk.enc b/tw/system/files/wireguard/lap-lud.psk.enc new file mode 100644 index 00000000..d976d90a --- /dev/null +++ b/tw/system/files/wireguard/lap-lud.psk.enc @@ -0,0 +1 @@ +XTD6NjCM0JYnYU9+Z6q+9ZbpUBshnEPPMagwf/wCKMxBsXCXrt63oeqM1TD3 diff --git a/tw/system/files/wireguard/lap-pi3.psk.enc b/tw/system/files/wireguard/lap-pi3.psk.enc new file mode 100644 index 00000000..2d318777 --- /dev/null +++ b/tw/system/files/wireguard/lap-pi3.psk.enc @@ -0,0 +1 @@ +XgTTJRaGle1fGipxe7i78Yr9HhUP3DiOQrctYMA6H/NcmzSV7uC/8d/XjjD3 diff --git a/tw/system/files/wireguard/lap-vin.psk.enc b/tw/system/files/wireguard/lap-vin.psk.enc new file mode 100644 index 00000000..c84938a9 --- /dev/null +++ b/tw/system/files/wireguard/lap-vin.psk.enc @@ -0,0 +1 @@ +EQriKiyMh5x0Ak1WRqzg6q79DjcZ+jXNW50RTog5HqRMmSu3sNKf2seijjD3 diff --git a/tw/system/files/wireguard/lap.key.enc b/tw/system/files/wireguard/lap.key.enc new file mode 100644 index 00000000..200bf821 --- /dev/null +++ b/tw/system/files/wireguard/lap.key.enc @@ -0,0 +1 @@ +ESqNLhK1ubNRY1JDVYuR1tGJDTUvnkKOTawBQcISO8Ntqgil59nRoOSNkjD3 diff --git a/tw/system/lap.scm b/tw/system/lap.scm index 516de321..33154d5b 100644 --- a/tw/system/lap.scm +++ b/tw/system/lap.scm @@ -21,6 +21,7 @@ #:use-module (nonguix licenses) #:use-module (tw channels) #:use-module (tw packages scanner) + #:use-module (tw services secrets) #:use-module (tw services wireguard) #:use-module (tw system)) @@ -446,6 +447,26 @@ EndSection (list vin) (list lud)))))) + (service secrets-service-type + (secrets-configuration + (secrets + (list + (secret + (encrypted-file (local-file "files/wireguard/lap.key.enc")) + (destination "/etc/wireguard/private.key")) + (secret + (encrypted-file (local-file "files/wireguard/lap-fp4.psk.enc")) + (destination "/etc/wireguard/fp4.psk")) + (secret + (encrypted-file (local-file "files/wireguard/lap-lud.psk.enc")) + (destination "/etc/wireguard/lud.psk")) + (secret + (encrypted-file (local-file "files/wireguard/lap-pi3.psk.enc")) + (destination "/etc/wireguard/pi3.psk")) + (secret + (encrypted-file (local-file "files/wireguard/lap-vin.psk.enc")) + (destination "/etc/wireguard/vin.psk")))))) + (modify-services (append %system-channel-services %desktop-services) ;; Let sane find the airscan backend. ipp-usb needs to be running separately. (sane-service-type _ => sane-backends/airscan) -- cgit v1.2.3