summaryrefslogtreecommitdiff
path: root/epistemia/services/age-secret.scm
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))))