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