aboutsummaryrefslogtreecommitdiff
path: root/tw/services/secrets.scm
diff options
context:
space:
mode:
Diffstat (limited to 'tw/services/secrets.scm')
-rw-r--r--tw/services/secrets.scm174
1 files changed, 46 insertions, 128 deletions
diff --git a/tw/services/secrets.scm b/tw/services/secrets.scm
index 41c26678..1895700d 100644
--- a/tw/services/secrets.scm
+++ b/tw/services/secrets.scm
@@ -1,6 +1,6 @@
(define-module (tw services secrets)
#:use-module (gnu)
- #:use-module (gnu packages guile-xyz)
+ #:use-module (gnu packages golang)
#:use-module (gnu services)
#:use-module (gnu services configuration)
#:use-module (guix gexp)
@@ -15,7 +15,8 @@
(define-configuration/no-serialization secret
(encrypted-file file-like "The file in the Guix store containing the
-encrypted secret.")
+encrypted secret. The file must have been encrypted to the @code{host-key}
+specified in the host's @code{secrets-configuration} record.")
(destination string "The file path into which the secret will be decrypted.")
(user (string "root") "The UNIX user owning the resulting decrypted file.")
(group (string "root") "The UNIX group owning the resulting decrypted file.")
@@ -27,75 +28,48 @@ decrypted file. Accessible only by the file's owning user by default."))
(every secret? thing)))
(define-configuration/no-serialization secrets-configuration
- (host-key (string "/etc/secrets.key") "The path to a file containing the
-decryption key for the given secrets.")
- (secrets (list-of-secrets '()) "A list of @code{secret} records "))
+ (host-key (string "/etc/ssh/ssh_host_ed25519_key") "The path to a file
+containing the decryption key for the given secrets.")
+ (secrets (list-of-secrets '()) "A list of @code{secret} records to
+install on the host."))
(define (secrets-activation config)
+ (define (secret-install-invocation secret)
+ (match-record secret <secret> (encrypted-file destination user group permissions)
+ ;; Call the `install' function defined in the gexp below.
+ #~(install #$encrypted-file #$destination #$user #$group #$permissions)))
+
(match-record config <secrets-configuration> (host-key secrets)
- (with-imported-modules (source-module-closure
- '((guix base64)
- (guix build utils)))
- (with-extensions (list guile-sodium)
- #~(begin
- (use-modules (ice-9 format)
- (ice-9 ports)
- (ice-9 binary-ports)
- (ice-9 textual-ports)
- (rnrs bytevectors)
- (srfi srfi-26)
- (guix base64)
- ((guix build utils) #:select (mkdir-p))
- (sodium stream))
- (define (install contents destination user group permissions)
- (format (current-error-port) "Installing secret (~4,'0o ~a:~a) at ~a~%"
- permissions user group destination)
- (mkdir-p (dirname destination))
- (let ((port (open-file destination "wb")))
- (with-exception-handler
- (lambda (exn)
- (close port)
- (raise-exception exn))
- (lambda ()
- ;; Change permissions before writing contents to avoid exposing
- ;; the secret in the meantime.
- (chown port (passwd:uid (getpw user)) (group:gid (getgr group)))
- (chmod port permissions)
- (put-bytevector port contents)
- (close port)))))
- ;; Generate a new host key if none exists yet.
- ;; This allows instantiating this service with an empty list of
- ;; secrets to generate a host key, and later add secrets.
- (unless (file-exists? #$host-key)
- (format (current-error-port)
- "No host key found at ~a; creating one now~%" #$host-key)
- (install (call-with-input-file "/dev/urandom"
- (cut get-bytevector-n <> (crypto-stream-chacha20-ietf-keybytes)))
- #$host-key "root" "root" #o600))
- (define host-key
- (call-with-input-file #$host-key get-bytevector-all #:binary #t))
- (unless (= (crypto-stream-chacha20-ietf-keybytes)
- (bytevector-length host-key))
- (error "Invalid key detected; expected ~d bytes but got ~d bytes."
- (crypto-stream-chacha20-ietf-keybytes)
- (bytevector-length host-key)))
- (define nonce
- ;; In practice, `crypto-stream-chacha20-ietf-xor' is limited to
- ;; 256 GiB of data for each (key, nonce) pair, but secrets are
- ;; expected to be small, so it's fine to use the same nonce.
- (make-bytevector (crypto-stream-chacha20-ietf-noncebytes) 0))
- (define (decrypt encrypted-file)
- (crypto-stream-chacha20-ietf-xor
- #:message
- (base64-decode
- (string-delete ; `base64-decode' doesn't tolerate any whitespace
- char-whitespace?
- (call-with-input-file encrypted-file get-string-all)))
- #:nonce nonce #:key host-key))
- #$@(map (lambda (secret)
- (match-record secret <secret> (encrypted-file destination user group permissions)
- #~(install (decrypt #$encrypted-file) #$destination #$user #$group #$permissions)))
- secrets))))))
+ (with-imported-modules (source-module-closure '((guix build utils)))
+ #~(begin
+ (use-modules (ice-9 format)
+ (ice-9 popen)
+ ((guix build utils) #:select (mkdir-p dump-port)))
+ (define (install encrypted-file destination user group permissions)
+ (format (current-error-port) "Installing secret (~4,'0o ~a:~a) at ~a~%"
+ permissions user group destination)
+ (mkdir-p (dirname destination))
+ (let ((port (open-file destination OPEN_WRITE)))
+ ;; Change permissions before writing contents to avoid exposing
+ ;; the secret in the meantime.
+ (chown port (passwd:uid (getpw user)) (group:gid (getgr group)))
+ (chmod port permissions)
+ (let ((stream (open-pipe* OPEN_READ #$(file-append age "/bin/age")
+ "-d" "-i" #$host-key encrypted-file)))
+ (dump-port stream port)
+ (close-pipe stream))
+ (close port)))
+ ;; Generate a new host key if none exists yet.
+ ;; This allows instantiating this service with an empty list of
+ ;; secrets to generate a host key, and later add secrets.
+ (unless (file-exists? #$host-key)
+ (format (current-error-port)
+ "No host key found at ~a; creating one now~%" #$host-key)
+ (unless (status:exit-val
+ (system* #$(file-append age-keygen "/bin/age-keygen")
+ "-o" #$host-key))
+ (error "Failed to generate host key at:" #$host-key)))
+ #$@(map secret-install-invocation secrets)))))
(define secrets-service-type
(service-type
@@ -105,65 +79,9 @@ decryption key for the given secrets.")
(compose concatenate)
;; ...then `extend' combines the extensions with the initial config.
(extend (lambda (config more-secrets)
- (secrets-configuration
- (inherit config)
- (secrets (append (secrets-configuration-secrets config)
- more-secrets)))))
+ (match-record config <secrets-configuration> (secrets)
+ (secrets-configuration
+ (inherit config)
+ (secrets (append secrets more-secrets))))))
(default-value (secrets-configuration))
(description "Install files containing secrets on the system.")))
-
-;; The following can be installed e.g. in "~/.local/bin" and used to import
-;; secrets into local-files in a Guix channel.
-(define encsecret-program
- (program-file "encsecret"
- (with-imported-modules (source-module-closure
- '((guix base64)))
- (with-extensions (list guile-sodium)
- #~(begin
- (use-modules (ice-9 match)
- (ice-9 ports)
- (ice-9 binary-ports)
- (rnrs bytevectors)
- (guix base64)
- (sodium stream))
- (define (main key-port)
- (let ((cryptotext
- (crypto-stream-chacha20-ietf-xor
- #:message (get-bytevector-all (current-input-port))
- #:nonce (make-bytevector (crypto-stream-chacha20-ietf-noncebytes) 0)
- #:key (get-bytevector-all key-port))))
- (base64-encode cryptotext 0 (bytevector-length cryptotext)
- #f #f base64-alphabet (current-output-port)))
- (newline (current-output-port)))
-
- (define (help-message program-name)
- (string-append "\
-usage: " (basename program-name) " [-h] KEY_FILE
-
-This utility encrypts data passed on stdin to stdout using the given key file,
-in a way that the output can be decrypted with the same key file by the Guix
-secrets-service-type. Symmetric encryption is used. Processing this utility's
-base64-decoded output with the same key results in the original plaintext.
-
-arguments:
- -h, --help show this message and exit
- KEY_FILE the file containing the encryption key; required
-"))
-
- (match (program-arguments)
- ((program-name
- . (? (lambda (args)
- (or (member "-h" args)
- (member "--help" args)))
- _))
- (display (help-message program-name)))
-
- ((_ key-file)
- (call-with-input-file key-file main #:binary #t))
-
- ((program-name . _)
- (display "error: invalid number of arguments\n\n"
- (current-error-port))
- (display (help-message program-name)
- (current-error-port))
- (exit 1))))))))