gnu: python-aiohttp-socks: Update to 0.7.1.
[jackhill/guix/guix.git] / gnu / services / vpn.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu>
3 ;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
4 ;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
5 ;;; Copyright © 2021 Guillaume Le Vaillant <glv@posteo.net>
6 ;;; Copyright © 2021 Solene Rapenne <solene@perso.pw>
7 ;;; Copyright © 2021 Domagoj Stolfa <ds815@gmx.com>
8 ;;; Copyright © 2021 Tobias Geerinckx-Rice <me@tobias.gr>
9 ;;; Copyright © 2021 Raghav Gururajan <rg@raghavgururajan.name>
10 ;;; Copyright © 2021 jgart <jgart@dismail.de>
11 ;;; Copyright © 2021 Nathan Dehnel <ncdehnel@gmail.com>
12 ;;; Copyright © 2022 Cameron V Chaparro <cameron@cameronchaparro.com>
13 ;;;
14 ;;; This file is part of GNU Guix.
15 ;;;
16 ;;; GNU Guix is free software; you can redistribute it and/or modify it
17 ;;; under the terms of the GNU General Public License as published by
18 ;;; the Free Software Foundation; either version 3 of the License, or (at
19 ;;; your option) any later version.
20 ;;;
21 ;;; GNU Guix is distributed in the hope that it will be useful, but
22 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
23 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ;;; GNU General Public License for more details.
25 ;;;
26 ;;; You should have received a copy of the GNU General Public License
27 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
28
29 (define-module (gnu services vpn)
30 #:use-module (gnu services)
31 #:use-module (gnu services configuration)
32 #:use-module (gnu services dbus)
33 #:use-module (gnu services shepherd)
34 #:use-module (gnu system shadow)
35 #:use-module (gnu packages admin)
36 #:use-module (gnu packages vpn)
37 #:use-module (guix packages)
38 #:use-module (guix records)
39 #:use-module (guix gexp)
40 #:use-module (guix i18n)
41 #:use-module (srfi srfi-1)
42 #:use-module (ice-9 match)
43 #:use-module (ice-9 regex)
44 #:export (openvpn-client-service
45 openvpn-server-service
46 openvpn-client-service-type
47 openvpn-server-service-type
48 openvpn-client-configuration
49 openvpn-server-configuration
50 openvpn-remote-configuration
51 openvpn-ccd-configuration
52 generate-openvpn-client-documentation
53 generate-openvpn-server-documentation
54
55 strongswan-configuration
56 strongswan-service-type
57
58 wireguard-peer
59 wireguard-peer?
60 wireguard-peer-name
61 wireguard-peer-endpoint
62 wireguard-peer-allowed-ips
63 wireguard-peer-public-key
64 wireguard-peer-keep-alive
65
66 wireguard-configuration
67 wireguard-configuration?
68 wireguard-configuration-wireguard
69 wireguard-configuration-interface
70 wireguard-configuration-addresses
71 wireguard-configuration-port
72 wireguard-configuration-dns
73 wireguard-configuration-private-key
74 wireguard-configuration-peers
75
76 wireguard-service-type))
77
78 ;;;
79 ;;; Bitmask.
80 ;;;
81
82 (define-public bitmask-service-type
83 (service-type
84 (name 'bitmask)
85 (description "Setup the @uref{https://bitmask.net, Bitmask} VPN application.")
86 (default-value bitmask)
87 (extensions
88 (list
89 ;; Add bitmask to the system profile.
90 (service-extension profile-service-type list)
91 ;; Configure polkit policy of bitmask.
92 (service-extension polkit-service-type list)))))
93
94 ;;;
95 ;;; OpenVPN.
96 ;;;
97
98 (define (uglify-field-name name)
99 (match name
100 ('verbosity "verb")
101 (_ (let ((str (symbol->string name)))
102 (if (string-suffix? "?" str)
103 (substring str 0 (1- (string-length str)))
104 str)))))
105
106 (define (serialize-field field-name val)
107 (if (eq? field-name 'pid-file)
108 (format #t "")
109 (format #t "~a ~a\n" (uglify-field-name field-name) val)))
110 (define serialize-string serialize-field)
111 (define-maybe string)
112 (define (serialize-boolean field-name val)
113 (if val
114 (serialize-field field-name "")
115 (format #t "")))
116
117 (define (ip-mask? val)
118 (and (string? val)
119 (if (string-match "^([0-9]+\\.){3}[0-9]+ ([0-9]+\\.){3}[0-9]+$" val)
120 (let ((numbers (string-tokenize val char-set:digit)))
121 (all-lte numbers (list 255 255 255 255 255 255 255 255)))
122 #f)))
123 (define serialize-ip-mask serialize-string)
124
125 (define-syntax define-enumerated-field-type
126 (lambda (x)
127 (define (id-append ctx . parts)
128 (datum->syntax ctx (apply symbol-append (map syntax->datum parts))))
129 (syntax-case x ()
130 ((_ name (option ...))
131 #`(begin
132 (define (#,(id-append #'name #'name #'?) x)
133 (memq x '(option ...)))
134 (define (#,(id-append #'name #'serialize- #'name) field-name val)
135 (serialize-field field-name val)))))))
136
137 (define-enumerated-field-type proto
138 (udp tcp udp6 tcp6))
139 (define-enumerated-field-type dev
140 (tun tap))
141
142 (define key-usage? boolean?)
143 (define (serialize-key-usage field-name value)
144 (if value
145 (format #t "remote-cert-tls server\n")
146 #f))
147
148 (define bind? boolean?)
149 (define (serialize-bind field-name value)
150 (if value
151 #f
152 (format #t "nobind\n")))
153
154 (define resolv-retry? boolean?)
155 (define (serialize-resolv-retry field-name value)
156 (if value
157 (format #t "resolv-retry infinite\n")
158 #f))
159
160 (define (serialize-tls-auth role location)
161 (if location
162 (serialize-field 'tls-auth
163 (string-append location " " (match role
164 ('server "0")
165 ('client "1"))))
166 #f))
167 (define (tls-auth? val)
168 (or (eq? val #f)
169 (string? val)))
170 (define (serialize-tls-auth-server field-name val)
171 (serialize-tls-auth 'server val))
172 (define (serialize-tls-auth-client field-name val)
173 (serialize-tls-auth 'client val))
174 (define tls-auth-server? tls-auth?)
175 (define tls-auth-client? tls-auth?)
176
177 (define (serialize-number field-name val)
178 (serialize-field field-name (number->string val)))
179
180 (define (all-lte left right)
181 (if (eq? left '())
182 (eq? right '())
183 (and (<= (string->number (car left)) (car right))
184 (all-lte (cdr left) (cdr right)))))
185
186 (define (cidr4? val)
187 (if (string? val)
188 (if (string-match "^([0-9]+\\.){3}[0-9]+/[0-9]+$" val)
189 (let ((numbers (string-tokenize val char-set:digit)))
190 (all-lte numbers (list 255 255 255 255 32)))
191 #f)
192 (eq? val #f)))
193
194 (define (cidr6? val)
195 (if (string? val)
196 (string-match "^([0-9a-f]{0,4}:){0,8}/[0-9]{1,3}$" val)
197 (eq? val #f)))
198
199 (define (serialize-cidr4 field-name val)
200 (if (eq? val #f) #f (serialize-field field-name val)))
201
202 (define (serialize-cidr6 field-name val)
203 (if (eq? val #f) #f (serialize-field field-name val)))
204
205 (define (ip? val)
206 (if (string? val)
207 (if (string-match "^([0-9]+\\.){3}[0-9]+$" val)
208 (let ((numbers (string-tokenize val char-set:digit)))
209 (all-lte numbers (list 255 255 255 255)))
210 #f)
211 (eq? val #f)))
212 (define (serialize-ip field-name val)
213 (if (eq? val #f) #f (serialize-field field-name val)))
214
215 (define (keepalive? val)
216 (and (list? val)
217 (and (number? (car val))
218 (number? (car (cdr val))))))
219 (define (serialize-keepalive field-name val)
220 (format #t "~a ~a ~a\n" (uglify-field-name field-name)
221 (number->string (car val)) (number->string (car (cdr val)))))
222
223 (define gateway? boolean?)
224 (define (serialize-gateway field-name val)
225 (and val
226 (format #t "push \"redirect-gateway\"\n")))
227
228
229 (define-configuration openvpn-remote-configuration
230 (name
231 (string "my-server")
232 "Server name.")
233 (port
234 (number 1194)
235 "Port number the server listens to."))
236
237 (define-configuration openvpn-ccd-configuration
238 (name
239 (string "client")
240 "Client name.")
241 (iroute
242 (ip-mask #f)
243 "Client own network")
244 (ifconfig-push
245 (ip-mask #f)
246 "Client VPN IP."))
247
248 (define (openvpn-remote-list? val)
249 (and (list? val)
250 (or (eq? val '())
251 (and (openvpn-remote-configuration? (car val))
252 (openvpn-remote-list? (cdr val))))))
253 (define (serialize-openvpn-remote-list field-name val)
254 (for-each (lambda (remote)
255 (format #t "remote ~a ~a\n" (openvpn-remote-configuration-name remote)
256 (number->string (openvpn-remote-configuration-port remote))))
257 val))
258
259 (define (openvpn-ccd-list? val)
260 (and (list? val)
261 (or (eq? val '())
262 (and (openvpn-ccd-configuration? (car val))
263 (openvpn-ccd-list? (cdr val))))))
264 (define (serialize-openvpn-ccd-list field-name val)
265 #f)
266
267 (define (create-ccd-directory val)
268 "Create a ccd directory containing files for the ccd configuration option
269 of OpenVPN. Each file in this directory represents particular settings for a
270 client. Each file is named after the name of the client."
271 (let ((files (map (lambda (ccd)
272 (list (openvpn-ccd-configuration-name ccd)
273 (with-output-to-string
274 (lambda ()
275 (serialize-configuration
276 ccd openvpn-ccd-configuration-fields)))))
277 val)))
278 (computed-file "ccd"
279 (with-imported-modules '((guix build utils))
280 #~(begin
281 (use-modules (guix build utils))
282 (use-modules (ice-9 match))
283 (mkdir-p #$output)
284 (for-each
285 (lambda (ccd)
286 (match ccd
287 ((name config-string)
288 (call-with-output-file
289 (string-append #$output "/" name)
290 (lambda (port) (display config-string port))))))
291 '#$files))))))
292
293 (define-syntax define-split-configuration
294 (lambda (x)
295 (syntax-case x ()
296 ((_ name1 name2 (common-option ...) (first-option ...) (second-option ...))
297 #`(begin
298 (define-configuration #,#'name1
299 common-option ...
300 first-option ...)
301 (define-configuration #,#'name2
302 common-option ...
303 second-option ...))))))
304
305 (define-split-configuration openvpn-client-configuration
306 openvpn-server-configuration
307 ((openvpn
308 (file-like openvpn)
309 "The OpenVPN package.")
310
311 (pid-file
312 (string "/var/run/openvpn/openvpn.pid")
313 "The OpenVPN pid file.")
314
315 (proto
316 (proto 'udp)
317 "The protocol (UDP or TCP) used to open a channel between clients and
318 servers.")
319
320 (dev
321 (dev 'tun)
322 "The device type used to represent the VPN connection.")
323
324 (ca
325 (maybe-string "/etc/openvpn/ca.crt")
326 "The certificate authority to check connections against.")
327
328 (cert
329 (maybe-string "/etc/openvpn/client.crt")
330 "The certificate of the machine the daemon is running on. It should be signed
331 by the authority given in @code{ca}.")
332
333 (key
334 (maybe-string "/etc/openvpn/client.key")
335 "The key of the machine the daemon is running on. It must be the key whose
336 certificate is @code{cert}.")
337
338 (comp-lzo?
339 (boolean #t)
340 "Whether to use the lzo compression algorithm.")
341
342 (persist-key?
343 (boolean #t)
344 "Don't re-read key files across SIGUSR1 or --ping-restart.")
345
346 (persist-tun?
347 (boolean #t)
348 "Don't close and reopen TUN/TAP device or run up/down scripts across
349 SIGUSR1 or --ping-restart restarts.")
350
351 (fast-io?
352 (boolean #f)
353 "(Experimental) Optimize TUN/TAP/UDP I/O writes by avoiding a call to
354 poll/epoll/select prior to the write operation.")
355
356 (verbosity
357 (number 3)
358 "Verbosity level."))
359 ;; client-specific configuration
360 ((tls-auth
361 (tls-auth-client #f)
362 "Add an additional layer of HMAC authentication on top of the TLS control
363 channel to protect against DoS attacks.")
364
365 (auth-user-pass
366 maybe-string
367 "Authenticate with server using username/password. The option is a file
368 containing username/password on 2 lines. Do not use a file-like object as it
369 would be added to the store and readable by any user.")
370
371 (verify-key-usage?
372 (key-usage #t)
373 "Whether to check the server certificate has server usage extension.")
374
375 (bind?
376 (bind #f)
377 "Bind to a specific local port number.")
378
379 (resolv-retry?
380 (resolv-retry #t)
381 "Retry resolving server address.")
382
383 (remote
384 (openvpn-remote-list '())
385 "A list of remote servers to connect to."))
386 ;; server-specific configuration
387 ((tls-auth
388 (tls-auth-server #f)
389 "Add an additional layer of HMAC authentication on top of the TLS control
390 channel to protect against DoS attacks.")
391
392 (port
393 (number 1194)
394 "Specifies the port number on which the server listens.")
395
396 (server
397 (ip-mask "10.8.0.0 255.255.255.0")
398 "An ip and mask specifying the subnet inside the virtual network.")
399
400 (server-ipv6
401 (cidr6 #f)
402 "A CIDR notation specifying the IPv6 subnet inside the virtual network.")
403
404 (dh
405 (string "/etc/openvpn/dh2048.pem")
406 "The Diffie-Hellman parameters file.")
407
408 (ifconfig-pool-persist
409 (string "/etc/openvpn/ipp.txt")
410 "The file that records client IPs.")
411
412 (redirect-gateway?
413 (gateway #f)
414 "When true, the server will act as a gateway for its clients.")
415
416 (client-to-client?
417 (boolean #f)
418 "When true, clients are allowed to talk to each other inside the VPN.")
419
420 (keepalive
421 (keepalive '(10 120))
422 "Causes ping-like messages to be sent back and forth over the link so that
423 each side knows when the other side has gone down. @code{keepalive} requires
424 a pair. The first element is the period of the ping sending, and the second
425 element is the timeout before considering the other side down.")
426
427 (max-clients
428 (number 100)
429 "The maximum number of clients.")
430
431 (status
432 (string "/var/run/openvpn/status")
433 "The status file. This file shows a small report on current connection. It
434 is truncated and rewritten every minute.")
435
436 (client-config-dir
437 (openvpn-ccd-list '())
438 "The list of configuration for some clients.")))
439
440 (define (openvpn-config-file role config)
441 (let ((config-str
442 (with-output-to-string
443 (lambda ()
444 (serialize-configuration config
445 (match role
446 ('server
447 openvpn-server-configuration-fields)
448 ('client
449 openvpn-client-configuration-fields))))))
450 (ccd-dir (match role
451 ('server (create-ccd-directory
452 (openvpn-server-configuration-client-config-dir
453 config)))
454 ('client #f))))
455 (computed-file "openvpn.conf"
456 #~(begin
457 (use-modules (ice-9 match))
458 (call-with-output-file #$output
459 (lambda (port)
460 (match '#$role
461 ('server (display "" port))
462 ('client (display "client\n" port)))
463 (display #$config-str port)
464 (match '#$role
465 ('server (display
466 (string-append "client-config-dir "
467 #$ccd-dir "\n") port))
468 ('client (display "" port)))))))))
469
470 (define (openvpn-shepherd-service role)
471 (lambda (config)
472 (let* ((config-file (openvpn-config-file role config))
473 (pid-file ((match role
474 ('server openvpn-server-configuration-pid-file)
475 ('client openvpn-client-configuration-pid-file))
476 config))
477 (openvpn ((match role
478 ('server openvpn-server-configuration-openvpn)
479 ('client openvpn-client-configuration-openvpn))
480 config))
481 (log-file (match role
482 ('server "/var/log/openvpn-server.log")
483 ('client "/var/log/openvpn-client.log"))))
484 (list (shepherd-service
485 (documentation (string-append "Run the OpenVPN "
486 (match role
487 ('server "server")
488 ('client "client"))
489 " daemon."))
490 (provision (match role
491 ('server '(vpn-server))
492 ('client '(vpn-client))))
493 (requirement '(networking))
494 (start #~(make-forkexec-constructor
495 (list (string-append #$openvpn "/sbin/openvpn")
496 "--writepid" #$pid-file "--config" #$config-file
497 "--daemon")
498 #:pid-file #$pid-file
499 #:log-file #$log-file))
500 (stop #~(make-kill-destructor)))))))
501
502 (define %openvpn-accounts
503 (list (user-group (name "openvpn") (system? #t))
504 (user-account
505 (name "openvpn")
506 (group "openvpn")
507 (system? #t)
508 (comment "Openvpn daemon user")
509 (home-directory "/var/empty")
510 (shell (file-append shadow "/sbin/nologin")))))
511
512 (define %openvpn-activation
513 #~(begin
514 (use-modules (guix build utils))
515 (mkdir-p "/var/run/openvpn")))
516
517 (define openvpn-server-service-type
518 (service-type (name 'openvpn-server)
519 (extensions
520 (list (service-extension shepherd-root-service-type
521 (openvpn-shepherd-service 'server))
522 (service-extension account-service-type
523 (const %openvpn-accounts))
524 (service-extension activation-service-type
525 (const %openvpn-activation))))
526 (description "Run the OpenVPN server, which allows you to
527 @emph{host} a @acronym{VPN, virtual private network}.")))
528
529 (define openvpn-client-service-type
530 (service-type (name 'openvpn-client)
531 (extensions
532 (list (service-extension shepherd-root-service-type
533 (openvpn-shepherd-service 'client))
534 (service-extension account-service-type
535 (const %openvpn-accounts))
536 (service-extension activation-service-type
537 (const %openvpn-activation))))
538 (description
539 "Run the OpenVPN client service, which allows you to connect
540 to an existing @acronym{VPN, virtual private network}.")))
541
542 (define* (openvpn-client-service #:key (config (openvpn-client-configuration)))
543 (service openvpn-client-service-type config))
544
545 (define* (openvpn-server-service #:key (config (openvpn-server-configuration)))
546 (service openvpn-server-service-type config))
547
548 (define (generate-openvpn-server-documentation)
549 (generate-documentation
550 `((openvpn-server-configuration
551 ,openvpn-server-configuration-fields
552 (ccd openvpn-ccd-configuration))
553 (openvpn-ccd-configuration ,openvpn-ccd-configuration-fields))
554 'openvpn-server-configuration))
555
556 (define (generate-openvpn-client-documentation)
557 (generate-documentation
558 `((openvpn-client-configuration
559 ,openvpn-client-configuration-fields
560 (remote openvpn-remote-configuration))
561 (openvpn-remote-configuration ,openvpn-remote-configuration-fields))
562 'openvpn-client-configuration))
563
564 ;;;
565 ;;; Strongswan.
566 ;;;
567
568 (define-record-type* <strongswan-configuration>
569 strongswan-configuration make-strongswan-configuration
570 strongswan-configuration?
571 (strongswan strongswan-configuration-strongswan ;file-like
572 (default strongswan))
573 (ipsec-conf strongswan-configuration-ipsec-conf ;string|#f
574 (default #f))
575 (ipsec-secrets strongswan-configuration-ipsec-secrets ;string|#f
576 (default #f)))
577
578 ;; In the future, it might be worth implementing a record type to configure
579 ;; all of the plugins, but for *most* basic use cases, simply creating the
580 ;; files will be sufficient. Same is true of charon-plugins.
581 (define strongswand-configuration-files
582 (list "charon" "charon-logging" "pki" "pool" "scepclient"
583 "swanctl" "tnc"))
584
585 ;; Plugins to load. All of these plugins end up as configuration files in
586 ;; strongswan.d/charon/.
587 (define charon-plugins
588 (list "aes" "aesni" "attr" "attr-sql" "chapoly" "cmac" "constraints"
589 "counters" "curl" "curve25519" "dhcp" "dnskey" "drbg" "eap-aka-3gpp"
590 "eap-aka" "eap-dynamic" "eap-identity" "eap-md5" "eap-mschapv2"
591 "eap-peap" "eap-radius" "eap-simaka-pseudonym" "eap-simaka-reauth"
592 "eap-simaka-sql" "eap-sim" "eap-sim-file" "eap-tls" "eap-tnc"
593 "eap-ttls" "ext-auth" "farp" "fips-prf" "gmp" "ha" "hmac"
594 "kernel-netlink" "led" "md4" "md5" "mgf1" "nonce" "openssl" "pem"
595 "pgp" "pkcs12" "pkcs1" "pkcs7" "pkcs8" "pubkey" "random" "rc2"
596 "resolve" "revocation" "sha1" "sha2" "socket-default" "soup" "sql"
597 "sqlite" "sshkey" "tnc-tnccs" "vici" "x509" "xauth-eap" "xauth-generic"
598 "xauth-noauth" "xauth-pam" "xcbc"))
599
600 (define (strongswan-configuration-file config)
601 (match-record config <strongswan-configuration>
602 (strongswan ipsec-conf ipsec-secrets)
603 (if (eq? (string? ipsec-conf) (string? ipsec-secrets))
604 (let* ((strongswan-dir
605 (computed-file
606 "strongswan.d"
607 #~(begin
608 (mkdir #$output)
609 ;; Create all of the configuration files strongswan.d/.
610 (map (lambda (conf-file)
611 (let* ((filename (string-append
612 #$output "/"
613 conf-file ".conf")))
614 (call-with-output-file filename
615 (lambda (port)
616 (display
617 "# Created by 'strongswan-service'\n"
618 port)))))
619 (list #$@strongswand-configuration-files))
620 (mkdir (string-append #$output "/charon"))
621 ;; Create all of the plugin configuration files.
622 (map (lambda (plugin)
623 (let* ((filename (string-append
624 #$output "/charon/"
625 plugin ".conf")))
626 (call-with-output-file filename
627 (lambda (port)
628 (format port "~a {
629 load = yes
630 }"
631 plugin)))))
632 (list #$@charon-plugins))))))
633 ;; Generate our strongswan.conf to reflect the user configuration.
634 (computed-file
635 "strongswan.conf"
636 #~(begin
637 (call-with-output-file #$output
638 (lambda (port)
639 (display "# Generated by 'strongswan-service'.\n" port)
640 (format port "charon {
641 load_modular = yes
642 plugins {
643 include ~a/charon/*.conf"
644 #$strongswan-dir)
645 (if #$ipsec-conf
646 (format port "
647 stroke {
648 load = yes
649 secrets_file = ~a
650 }
651 }
652 }
653
654 starter {
655 config_file = ~a
656 }
657
658 include ~a/*.conf"
659 #$ipsec-secrets
660 #$ipsec-conf
661 #$strongswan-dir)
662 (format port "
663 }
664 }
665 include ~a/*.conf"
666 #$strongswan-dir)))))))
667 (throw 'error
668 (G_ "strongSwan ipsec-conf and ipsec-secrets must both be (un)set")))))
669
670 (define (strongswan-shepherd-service config)
671 (let* ((ipsec (file-append strongswan "/sbin/ipsec"))
672 (strongswan-conf-path (strongswan-configuration-file config)))
673 (list (shepherd-service
674 (requirement '(networking))
675 (provision '(ipsec))
676 (start #~(make-forkexec-constructor
677 (list #$ipsec "start" "--nofork")
678 #:environment-variables
679 (list (string-append "STRONGSWAN_CONF="
680 #$strongswan-conf-path))))
681 (stop #~(make-kill-destructor))
682 (documentation
683 "strongSwan's charon IKE keying daemon for IPsec VPN.")))))
684
685 (define strongswan-service-type
686 (service-type
687 (name 'strongswan)
688 (extensions
689 (list (service-extension shepherd-root-service-type
690 strongswan-shepherd-service)))
691 (default-value (strongswan-configuration))
692 (description
693 "Connect to an IPsec @acronym{VPN, Virtual Private Network} with
694 strongSwan.")))
695
696 ;;;
697 ;;; Wireguard.
698 ;;;
699
700 (define-record-type* <wireguard-peer>
701 wireguard-peer make-wireguard-peer
702 wireguard-peer?
703 (name wireguard-peer-name)
704 (endpoint wireguard-peer-endpoint
705 (default #f)) ;string
706 (public-key wireguard-peer-public-key) ;string
707 (allowed-ips wireguard-peer-allowed-ips) ;list of strings
708 (keep-alive wireguard-peer-keep-alive
709 (default #f))) ;integer
710
711 (define-record-type* <wireguard-configuration>
712 wireguard-configuration make-wireguard-configuration
713 wireguard-configuration?
714 (wireguard wireguard-configuration-wireguard ;file-like
715 (default wireguard-tools))
716 (interface wireguard-configuration-interface ;string
717 (default "wg0"))
718 (addresses wireguard-configuration-addresses ;string
719 (default '("10.0.0.1/32")))
720 (port wireguard-configuration-port ;integer
721 (default 51820))
722 (private-key wireguard-configuration-private-key ;string
723 (default "/etc/wireguard/private.key"))
724 (peers wireguard-configuration-peers ;list of <wiregard-peer>
725 (default '()))
726 (dns wireguard-configuration-dns ;list of strings
727 (default #f)))
728
729 (define (wireguard-configuration-file config)
730 (define (peer->config peer)
731 (let ((name (wireguard-peer-name peer))
732 (public-key (wireguard-peer-public-key peer))
733 (endpoint (wireguard-peer-endpoint peer))
734 (allowed-ips (wireguard-peer-allowed-ips peer))
735 (keep-alive (wireguard-peer-keep-alive peer)))
736 (format #f "[Peer] #~a
737 PublicKey = ~a
738 AllowedIPs = ~a
739 ~a~a"
740 name
741 public-key
742 (string-join allowed-ips ",")
743 (if endpoint
744 (format #f "Endpoint = ~a\n" endpoint)
745 "")
746 (if keep-alive
747 (format #f "PersistentKeepalive = ~a\n" keep-alive)
748 "\n"))))
749
750 (match-record config <wireguard-configuration>
751 (wireguard interface addresses port private-key peers dns)
752 (let* ((config-file (string-append interface ".conf"))
753 (peers (map peer->config peers))
754 (config
755 (computed-file
756 "wireguard-config"
757 #~(begin
758 (mkdir #$output)
759 (chdir #$output)
760 (call-with-output-file #$config-file
761 (lambda (port)
762 (let ((format (@ (ice-9 format) format)))
763 (format port "[Interface]
764 Address = ~a
765 PostUp = ~a set %i private-key ~a
766 ~a
767 ~a
768 ~{~a~^~%~}"
769 #$(string-join addresses ",")
770 #$(file-append wireguard "/bin/wg")
771 #$private-key
772 #$(if port
773 (format #f "ListenPort = ~a" port)
774 "")
775 #$(if dns
776 (format #f "DNS = ~a"
777 (string-join dns ","))
778 "")
779 (list #$@peers)))))))))
780 (file-append config "/" config-file))))
781
782 (define (wireguard-activation config)
783 (match-record config <wireguard-configuration>
784 (private-key)
785 #~(begin
786 (use-modules (guix build utils)
787 (ice-9 popen)
788 (ice-9 rdelim))
789 (mkdir-p (dirname #$private-key))
790 (unless (file-exists? #$private-key)
791 (let* ((pipe
792 (open-input-pipe (string-append
793 #$(file-append wireguard-tools "/bin/wg")
794 " genkey")))
795 (key (read-line pipe)))
796 (call-with-output-file #$private-key
797 (lambda (port)
798 (display key port)))
799 (chmod #$private-key #o400)
800 (close-pipe pipe))))))
801
802 (define (wireguard-shepherd-service config)
803 (match-record config <wireguard-configuration>
804 (wireguard interface)
805 (let ((wg-quick (file-append wireguard "/bin/wg-quick"))
806 (config (wireguard-configuration-file config)))
807 (list (shepherd-service
808 (requirement '(networking))
809 (provision (list
810 (symbol-append 'wireguard-
811 (string->symbol interface))))
812 (start #~(lambda _
813 (invoke #$wg-quick "up" #$config)))
814 (stop #~(lambda _
815 (invoke #$wg-quick "down" #$config)
816 #f)) ;stopped!
817 (documentation "Run the Wireguard VPN tunnel"))))))
818
819 (define wireguard-service-type
820 (service-type
821 (name 'wireguard)
822 (extensions
823 (list (service-extension shepherd-root-service-type
824 wireguard-shepherd-service)
825 (service-extension activation-service-type
826 wireguard-activation)))
827 (description "Set up Wireguard @acronym{VPN, Virtual Private Network}
828 tunnels.")))