age-secret.scm (3951B)
1 (define-module (epistemia services age-secret) 2 #:use-module (guix gexp) 3 #:use-module (guix records) 4 #:use-module (gnu services) 5 #:use-module (gnu services shepherd) 6 #:use-module (gnu packages golang-crypto) 7 #:use-module (ice-9 match) 8 #:export (age-secret-service-type 9 age-secret-configuration 10 age-secret)) 11 12 (define-record-type* <age-secret-configuration> 13 age-secret-configuration make-age-secret-configuration 14 age-secret-configuration? 15 (secrets age-secret-configuration-secrets 16 (default '()))) 17 18 (define-record-type* <age-secret> 19 age-secret make-age-secret 20 age-secret? 21 (file age-secret-file) 22 (name age-secret-name) 23 (owner age-secret-owner (default "root")) 24 (group age-secret-group (default "root")) 25 (mode age-secret-mode (default #o400))) 26 27 (define (age-secret-activation config) 28 (let ((secrets (age-secret-configuration-secrets config))) 29 (with-imported-modules '((guix build utils)) 30 #~(begin 31 (use-modules (guix build utils) 32 (ice-9 match) 33 (ice-9 format)) 34 35 ;; TODO: make the let vars configurable if I ever upstream this 36 (let ((secret-dir "/run/secrets") 37 (key-path "/etc/ssh/ssh_host_ed25519_key") 38 (age-bin #$(file-append age "/bin/age"))) 39 40 (mkdir-p secret-dir) 41 (chmod secret-dir #o711) 42 43 (for-each 44 (match-lambda 45 ((source output-name owner-name group-name mode) 46 (let ((dest (string-append secret-dir "/" output-name))) 47 ;; decrypt if missing 48 (unless (file-exists? dest) 49 (if (file-exists? key-path) 50 (begin 51 (format #t "Decrypting secret: ~a -> ~a...~%" source dest) 52 (let ((status (system* age-bin "-d" "-i" key-path "-o" dest source))) 53 (unless (zero? status) 54 (format (current-error-port) "Failed to decrypt ~a.~%" output-name)))) 55 (format (current-error-port) "Missing host key: ~a~%" key-path))) 56 57 ;; apply permissions and ownership 58 (when (file-exists? dest) 59 (catch 'system-error 60 (lambda () 61 (let ((uid (passwd:uid (getpwnam owner-name))) 62 (gid (group:gid (getgrnam group-name)))) 63 (chown dest uid gid) 64 (chmod dest mode))) 65 (lambda args 66 (format (current-error-port) 67 "Failed to set owner ~a:~a on ~a: ~a~%" 68 owner-name group-name dest (strerror (system-error-errno args))))))))) 69 70 ;; serialize record list for gexp 71 '#$(map (lambda (s) 72 (list (age-secret-file s) 73 (age-secret-name s) 74 (age-secret-owner s) 75 (age-secret-group s) 76 (age-secret-mode s))) 77 secrets))))))) 78 79 (define (age-secret-shepherd-service config) 80 (list (shepherd-service 81 (provision '(age-secret)) 82 (requirement '()) 83 (one-shot? #t) 84 (start #~(lambda () #t)) 85 (documentation "Age secret decryption marker service.")))) 86 87 (define age-secret-service-type 88 (service-type 89 (name 'age-secret) 90 (extensions 91 (list (service-extension activation-service-type 92 age-secret-activation) 93 (service-extension shepherd-root-service-type 94 age-secret-shepherd-service))) 95 (description 96 "Decrypts age-encrypted files at boot using host keys and assigns ownership.") 97 (default-value (age-secret-configuration))))