Hi Guix!

Until recently I reconfigured my home & system by setting the GUILE_LOAD_PATH env var, but I am now trying to transition to using the -L argument.

I have a configuration repo that's broken down into separate modules, (mostly) like so:

--8<---------------cut here---------------start------------->8---
lib
└── rsent
    ├── constants
    │   └── wireguard.scm
    ├── home
    │   └── pathfinder.scm
    ├── system
    │   └── pathfinder.scm
    └── utils
        └── secrets.scm
--8<---------------cut here---------------end--------------->8---

wireguard.scm contains code that fetches secret values (private+preshared keys) from my password store and defines a service using that secret value. The code looks something like this:

--8<---------------cut here---------------start------------->8---
(define wireguard-lan-secret-service
  (service
   (wireguard-configuration
    ...
    (private-key
     (plain-file "private.key"
                 (get-secret*
                  "System/WireGuard/LAN/private.key"))))))
--8<---------------cut here---------------end--------------->8---

I've noticed that when I run `guix home reconfigure -L lib lib/rsent/home/pathfinder.scm`, (get-secret* ...) is still evaluated, meaning that I'm prompted for a password when I don't need to enter one (home-environment doesn't support wireguard-service). I don't have this problem if I run `GUILE_LOAD_PATH=lib guix home reconfigure lib/rsent/home/pathfinder.scm`, presumably because Guix's -L doesn't just add to the load path, but also evaluates every file for possible package definitions.

I suspect I need to replace (plain-file ...) with another option, perhaps (computed-file), as well as rework (get-secret*) into a gexp. I'm struggling with the syntax though, so any help in this would be appreciated. Or if there's a better solution, that would be amazing!

*On another note*, should Guix have another command line flag that behaves identically to `$ guile -L`? Adds directory to the load path, but does not do anything else? The distinction between Guile and Guix's -L isn't emphasized enough in my experience. To a beginner, these two sound identical.

Guile: -L DIRECTORY add DIRECTORY to the front of the module load path Guix: -L, --load-path=DIR prepend DIR to the package module search path

Apologies for the wall of text, hopefully someone has some thoughts.
Richard
(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 #<unspecified>
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))
(define-module (rsent constants wireguard)
  #:use-module (gnu services)
  #:use-module (gnu services shepherd)
  #:use-module (gnu services vpn)
  #:use-module (guix records)
  #:use-module (guix gexp)
  #:use-module (gnu system)
  #:use-module (rsent utils services)
  #:use-module (rsent utils secrets)
  #:export (wireguard-nickleslan-secret-service)
  #:export (wireguard-nickleslan-operating-system))

(define wireguard-nickleslan-secret-service
  (secret-service
   (auto-start-disabled wireguard-service-type)
   (wireguard-configuration
    (interface "wg-nicklesbread")
    (private-key
     (plain-file "private.key"
                 (get-secret*
                  "System/WireGuard/NicklesBread/private.key")))
    (addresses '("10.169.222.4/24"))
    (dns '("10.1.1.1"))
    (peers
     (list
      (wireguard-peer
       (name "nickleslan")
       (endpoint "nicleary.com:51820")
       (public-key "EHoPXGJvQVVpQ6PZ/XQtHx0p5FWEVCS3y2oI2O+Y9zo=")
       (preshared-key
        (plain-file "preshared.key"
                    (get-secret*
                     "System/WireGuard/NicklesBread/preshared.key")))
       (allowed-ips '("10.1.1.0/24"))))))))

(define (wireguard-nickleslan-operating-system base-operating-system)
  (operating-system
    (inherit base-operating-system)
    (services
     (append (remove-unused-secret-services (list 
wireguard-nickleslan-secret-service))
             (operating-system-user-services base-operating-system)))))

Reply via email to