blob: 70d2b9faa4847ed396b0be676070fa331b4297a8 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
(define-module (epistemia services age-secret)
#:use-module (guix gexp)
#:use-module (guix records)
#:use-module (gnu services)
#:use-module (gnu services shepherd)
#: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>
age-secret-configuration make-age-secret-configuration
age-secret-configuration?
(secrets age-secret-configuration-secrets
(default '())))
(define-record-type* <age-secret>
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-shepherd-service config)
(list (shepherd-service
(provision '(age-secret))
(requirement '())
(one-shot? #t)
(start #~(lambda () #t))
(documentation "Age secret decryption marker service."))))
(define age-secret-service-type
(service-type
(name 'age-secret)
(extensions
(list (service-extension activation-service-type
age-secret-activation)
(service-extension shepherd-root-service-type
age-secret-shepherd-service)))
(description
"Decrypts age-encrypted files at boot using host keys and assigns ownership.")
(default-value (age-secret-configuration))))
|