(define-module (rsent utils secrets) #:use-module (gnu services) #:use-module (gnu services shepherd) #:use-module (gnu services docker) #:use-module (ice-9 popen) #:use-module (ice-9 rdelim) #:use-module (guix memoization) #:export (get-secret*) #:export (remove-unused-secret-services) #:export (when-using-secrets) #:export (secret-service)) (define (sudo-user) "Returns a @code{string} representing the user the pass commands should run as. This is necessary because reconfigure requires sudo to be used, which would run a sudoless pass command as root. This causes pass to miss the password store." (or (getenv "SUDO_USER") (getenv "USER"))) (define (use-secrets) "Returns #t if secrets should be used." (not (getenv "RSENT_NO_SECRETS"))) (define-syntax when-using-secrets (syntax-rules () "When macro with a specific test. Must be written as a macro instead of a function to avoid evaluating b1. b1 would be evaled before being passed to the function, and trying to cheat with a sexp and (eval) fails because the function doesn't have visibility to the record definition fields." ((_ b1 ...) (if (use-secrets) (begin b1 ...))))) (define-syntax secret-service (syntax-rules () "A secret service is a service that is replaced with # when (use-secrets) is false." ((_ a b c c* ...) (syntax-error "Too many arguments")) ((_ service-type service-configuration ...) (when-using-secrets (service service-type service-configuration ...))))) (define (get-secret key) "Returns the associated secret for key. Key should be in the form of a password-store path." ;; TODO: Throw error if pass fails. (format #t "~!Fetching secret for ~a... " key) (let* ((port (open-input-pipe (string-append "sudo -u " (sudo-user) " pass ls " key))) (str (read-line port))) ; from (ice-9 rdelim) (close-pipe port) (format #t "~!done\n") str)) ;; Memoize because guix seems to call this function twice per secret, ;; with a nontrivial time delay between them. This technically makes ;; it less secure since it leaves the secret in memory after it's ;; used, but the secrets are being written to the store anyway and ;; this particular attack vector would require a high level of access ;; to the machine. (define get-secret* (memoize get-secret)) (define (remove-unused-secret-services services) "Wrap the list of services with this when using services that may include secrets." (filter (lambda (val) (not (unspecified? val))) services))