unofficial mirror of help-guix@gnu.org 
 help / color / mirror / Atom feed
* Deferring evaluation of a get-secret procedure so -L doesn't evaluate it unless needed for build
@ 2024-01-04 17:28 Richard Sent
  2024-01-05 12:17 ` Tomas Volf
  0 siblings, 1 reply; 2+ messages in thread
From: Richard Sent @ 2024-01-04 17:28 UTC (permalink / raw)
  To: help-guix

[-- Attachment #1: Type: text/plain, Size: 2499 bytes --]

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

[-- Attachment #2: secrets.scm --]
[-- Type: text/plain, Size: 2559 bytes --]

(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))

[-- Attachment #3: wireguard.scm --]
[-- Type: text/plain, Size: 1467 bytes --]

(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)))))

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2024-01-05 12:18 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-01-04 17:28 Deferring evaluation of a get-secret procedure so -L doesn't evaluate it unless needed for build Richard Sent
2024-01-05 12:17 ` Tomas Volf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).