aboutsummaryrefslogtreecommitdiff
path: root/tw/services/wireguard.scm
blob: b2a34b1f482d1568d3b35bfb27bc8280cb33cf7f (about) (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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
(define-module (tw services wireguard)
  #:use-module (ice-9 format)
  #:use-module (ice-9 match)
  #:use-module (ice-9 regex)
  #:use-module ((srfi srfi-1) #:select (append-map every))
  #:use-module ((srfi srfi-26) #:select (cut))
  #:use-module (gnu services)
  #:use-module (gnu services base)
  #:use-module (gnu services configuration)
  #:use-module (gnu services vpn)
  #:use-module (guix gexp)
  #:use-module ((guix records) #:select (match-record))
  #:use-module ((guix utils) #:select (current-source-directory))
  #:use-module (tw services secrets)
  #:export (%wireguard-peers
            tw-wireguard-configuration
            tw-wireguard-service-type))

(define %wireguard-peers
  ;; Order in the following list is significant! It determines what IPs are assigned.
  (let ((peers '(("lap" "lap/DvCb8xXLUCqcaPEx8kCRcoeV4ScTMVZW5hvvNzA=" #f)
                 ("lud" "lud/9sbXVdOYXxOkRgAB+b/17QxbwllfJY/pbA3/MkE=" 58921)
                 ("vin" "vin/Im+sOszZFE01UF1+QlyxLP1PsPXJgTz4KmgvL3Y=" 58921)
                 ("fp4" "fp4/aLAVBADTy+UGmNh011w1CFOOwq70Df6EWlZRkAs=" #f)
                 ("pi3" "pi3/ThUH4qDTuyvNQIiiyy2dbziF/xLRTwO0+vcUoVY=" 58922)
                 ("frm" "frm/YGu1BfXUl4jrN0PTFMNdTQXWPSuY1wEpz5W9C2Y=" #f)
                 ("btl" "btl/kAgD+DVXsApNn53JCZdgZ9iJvVpFZVpa3Z+rrj4=" #f))))

    (map (match-lambda*
           ((i (short-name public-key port))
            (cons (string-append short-name ".twilken.net")
                  (wireguard-peer
                   (name (string-append short-name ".wg"))
                   (endpoint (and port (format #f "~a.twilken.net:~d" short-name port)))
                   (public-key public-key)
                   (preshared-key (string-append "/etc/wireguard/" short-name ".psk"))
                   (allowed-ips (list (format #f "10.0.0.~d/32" (+ i 1))
                                      (format #f "fc00::~d/128" (+ i 1)))))))
           (args (error "Unknown peer spec" args)))
         (iota (length peers)) peers)))

(define (wireguard-peers-list? object)
  (and (list? object)
       (every (compose string? car) object)
       (every (compose wireguard-peer? cdr) object)))

(define-configuration/no-serialization tw-wireguard-configuration
  (this-host
   (string)
   "The host name of the machine being configured.")
  (peers
   (wireguard-peers-list %wireguard-peers)
   "An alist of WireGuard peers to install.")
  (private-key-file
   (string "/etc/wireguard/private.key")
   "Where to store this host's private key."))

(define (other-peers this-host peers)
  (delq (assoc-ref peers this-host) (map cdr peers)))

(define (tw-wireguard-service config)
  "Create a full WireGuard config from the personal network CONFIG."
  (match-record config <tw-wireguard-configuration> (this-host peers private-key-file)
    (unless (assoc this-host peers)
      (error "No peer config found for host" this-host))
    (match-record (assoc-ref peers this-host) (@@ (gnu services vpn) <wireguard-peer>) (endpoint allowed-ips)
      (wireguard-configuration
       (addresses
        (map (lambda (cidr)
               (let ((ipv4 (string-match "/32$" cidr))
                     (ipv6 (string-match "/128$" cidr)))
                 (cond
                  (ipv4 (regexp-substitute #f ipv4 'pre "/24"))
                  (ipv6 (regexp-substitute #f ipv6 'pre "/64"))
                  (#t cidr))))
             allowed-ips))
       (port
        (if endpoint
            (string->number (cadr (string-split endpoint #\:)))
            58921))
       (private-key private-key-file)
       (peers (other-peers this-host peers))))))

(define (cut-string-at-char str char-pred)
  "Return the first part of STR up to the first occurrence of CHAR-PRED."
  (substring str 0 (string-index str char-pred)))

(define (peer->ips peer)
  "Extract IP addresses assigned to the given `wireguard-peer' PEER."
  (map (cut cut-string-at-char <> #\/)
       (wireguard-peer-allowed-ips peer)))

(define (tw-wireguard-hosts config)
  "Generate a hosts file entries from the personal WireGuard network CONFIG."
  (define (peer->entries peer)
    (map (cut host <> (wireguard-peer-name peer))
         (peer->ips peer)))
  (append-map (compose peer->entries cdr)
              (tw-wireguard-configuration-peers config)))

(define (tw-wireguard-secrets config)
  "Install secrets for the host's private key and preshared keys with peers."
  (define (local-file-here path)
    (local-file
     (canonicalize-path
      (string-append
       (current-source-directory) "/" path))))
  (match-record config <tw-wireguard-configuration> (this-host peers private-key-file)
    (define short-host (cut-string-at-char this-host #\.))
    (define private-key
      (secret
       (encrypted-file
        (local-file-here (string-append "files/wireguard/" short-host ".key.enc")))
       (destination private-key-file)))
    (define (peer->secret peer)
      (let ((short-peer (cut-string-at-char (wireguard-peer-name peer) #\.)))
        (secret
         (encrypted-file
          (local-file-here
           (string-append "files/wireguard/" short-host "-" short-peer ".psk.enc")))
         (destination
          (string-append "/etc/wireguard/" short-peer ".psk")))))
    (cons private-key (map peer->secret (other-peers this-host peers)))))

(define tw-wireguard-service-type
  (service-type
   (name 'tw-wireguard)
   (description "Set up my personal WireGuard network.")
   (extensions
    (cons* (service-extension hosts-service-type tw-wireguard-hosts)
           (service-extension secrets-service-type tw-wireguard-secrets)
           ;; FIXME: `wireguard-service-type' cannot be extended, so copy its
           ;; service-extensions directly.
           (map (lambda (ext)
                  (service-extension (service-extension-target ext)
                                     (compose (service-extension-compute ext)
                                              tw-wireguard-service)))
                (service-type-extensions wireguard-service-type))))))