From d730c554e80ac835072e16e99682a5b79e0b425f Mon Sep 17 00:00:00 2001 From: Vineet Kumar Date: Sun, 1 Feb 2026 22:09:09 -0500 Subject: make equivalent of agenix but for guix --- epistemia/services/age-secret.scm | 86 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 epistemia/services/age-secret.scm diff --git a/epistemia/services/age-secret.scm b/epistemia/services/age-secret.scm new file mode 100644 index 0000000..08926ec --- /dev/null +++ b/epistemia/services/age-secret.scm @@ -0,0 +1,86 @@ +(define-module (epistemia services age-secret) + #:use-module (guix gexp) + #:use-module (guix records) + #:use-module (gnu services) + #:use-module (gnu packages golang-crypto) + #:use-module (ice-9 match) + #:export (age-secret-service-type + age-secret-configuration + age-secret)) + +(define-record-type* + age-secret-configuration make-age-secret-configuration + age-secret-configuration? + (secrets age-secret-configuration-secrets + (default '()))) + +(define-record-type* + age-secret make-age-secret + age-secret? + (file age-secret-file) + (name age-secret-name) + (owner age-secret-owner (default "root")) + (group age-secret-group (default "root")) + (mode age-secret-mode (default #o400))) + +(define (age-secret-activation config) + (let ((secrets (age-secret-configuration-secrets config))) + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils) + (ice-9 match) + (ice-9 format)) + + ;; TODO: make the let vars configurable if I ever upstream this + (let ((secret-dir "/run/secrets") + (key-path "/etc/ssh/ssh_host_ed25519_key") + (age-bin #$(file-append age "/bin/age"))) + + (mkdir-p secret-dir) + (chmod secret-dir #o711) + + (for-each + (match-lambda + ((source output-name owner-name group-name mode) + (let ((dest (string-append secret-dir "/" output-name))) + ;; decrypt if missing + (unless (file-exists? dest) + (if (file-exists? key-path) + (begin + (format #t "Decrypting secret: ~a -> ~a...~%" source dest) + (let ((status (system* age-bin "-d" "-i" key-path "-o" dest source))) + (unless (zero? status) + (format (current-error-port) "Failed to decrypt ~a.~%" output-name)))) + (format (current-error-port) "Missing host key: ~a~%" key-path))) + + ;; apply permissions and ownership + (when (file-exists? dest) + (catch 'system-error + (lambda () + (let ((uid (passwd:uid (getpwnam owner-name))) + (gid (group:gid (getgrnam group-name)))) + (chown dest uid gid) + (chmod dest mode))) + (lambda args + (format (current-error-port) + "Failed to set owner ~a:~a on ~a: ~a~%" + owner-name group-name dest (strerror (system-error-errno args))))))))) + + ;; serialize record list for gexp + '#$(map (lambda (s) + (list (age-secret-file s) + (age-secret-name s) + (age-secret-owner s) + (age-secret-group s) + (age-secret-mode s))) + secrets))))))) + +(define age-secret-service-type + (service-type + (name 'age-secret) + (extensions + (list (service-extension activation-service-type + age-secret-activation))) + (description + "Decrypts age-encrypted files at boot using host keys and assigns ownership.") + (default-value (age-secret-configuration)))) -- cgit v1.2.3