gnu: libnma: Depend on GTK 4.x only on supported platforms.
[jackhill/guix/guix.git] / gnu / services / vpn.scm
CommitLineData
2be1b471
JL
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu>
e57bd0be 3;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
85ac401a 4;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
3313f61e 5;;; Copyright © 2021 Guillaume Le Vaillant <glv@posteo.net>
ac956092 6;;; Copyright © 2021 Solene Rapenne <solene@perso.pw>
66be80fa
DS
7;;; Copyright © 2021 Domagoj Stolfa <ds815@gmx.com>
8;;; Copyright © 2021 Tobias Geerinckx-Rice <me@tobias.gr>
02562e2f
RG
9;;; Copyright © 2021 Raghav Gururajan <rg@raghavgururajan.name>
10;;; Copyright © 2021 jgart <jgart@dismail.de>
6fb5459e 11;;; Copyright © 2021 Nathan Dehnel <ncdehnel@gmail.com>
48bd8b40 12;;; Copyright © 2022 Cameron V Chaparro <cameron@cameronchaparro.com>
2be1b471
JL
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)
02562e2f 32 #:use-module (gnu services dbus)
2be1b471
JL
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)
66be80fa 40 #:use-module (guix i18n)
2be1b471
JL
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
43b2e440
MO
53 generate-openvpn-server-documentation
54
66be80fa
DS
55 strongswan-configuration
56 strongswan-service-type
57
43b2e440
MO
58 wireguard-peer
59 wireguard-peer?
60 wireguard-peer-name
61 wireguard-peer-endpoint
62 wireguard-peer-allowed-ips
ac956092
SR
63 wireguard-peer-public-key
64 wireguard-peer-keep-alive
43b2e440
MO
65
66 wireguard-configuration
67 wireguard-configuration?
68 wireguard-configuration-wireguard
69 wireguard-configuration-interface
70 wireguard-configuration-addresses
71 wireguard-configuration-port
6fb5459e 72 wireguard-configuration-dns
43b2e440
MO
73 wireguard-configuration-private-key
74 wireguard-configuration-peers
75
76 wireguard-service-type))
2be1b471 77
02562e2f
RG
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
2be1b471
JL
94;;;
95;;; OpenVPN.
96;;;
97
98(define (uglify-field-name name)
99 (match name
be051880
LC
100 ('verbosity "verb")
101 (_ (let ((str (symbol->string name)))
102 (if (string-suffix? "?" str)
103 (substring str 0 (1- (string-length str)))
104 str)))))
2be1b471
JL
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)
c6c44770 111(define-maybe string)
2be1b471
JL
112(define (serialize-boolean field-name val)
113 (if val
0372dd1a 114 (serialize-field field-name "")
2be1b471
JL
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)
4b8b4418
JL
161 (if location
162 (serialize-field 'tls-auth
163 (string-append location " " (match role
164 ('server "0")
165 ('client "1"))))
166 #f))
2be1b471
JL
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
269of OpenVPN. Each file in this directory represents particular settings for a
270client. 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
be051880
LC
287 ((name config-string)
288 (call-with-output-file
289 (string-append #$output "/" name)
290 (lambda (port) (display config-string port))))))
2be1b471
JL
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
892f1b72 308 (file-like openvpn)
2be1b471
JL
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
318servers.")
319
320 (dev
321 (dev 'tun)
322 "The device type used to represent the VPN connection.")
323
324 (ca
5221df34 325 (maybe-string "/etc/openvpn/ca.crt")
2be1b471
JL
326 "The certificate authority to check connections against.")
327
328 (cert
5221df34 329 (maybe-string "/etc/openvpn/client.crt")
2be1b471
JL
330 "The certificate of the machine the daemon is running on. It should be signed
331by the authority given in @code{ca}.")
332
333 (key
5221df34 334 (maybe-string "/etc/openvpn/client.key")
85ac401a 335 "The key of the machine the daemon is running on. It must be the key whose
2be1b471
JL
336certificate 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
349SIGUSR1 or --ping-restart restarts.")
350
c6c44770
JL
351 (fast-io?
352 (boolean #f)
353 "(Experimental) Optimize TUN/TAP/UDP I/O writes by avoiding a call to
354poll/epoll/select prior to the write operation.")
355
2be1b471
JL
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
363channel to protect against DoS attacks.")
364
c6c44770 365 (auth-user-pass
8cb1a49a 366 maybe-string
c6c44770
JL
367 "Authenticate with server using username/password. The option is a file
368containing username/password on 2 lines. Do not use a file-like object as it
369would be added to the store and readable by any user.")
370
2be1b471
JL
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
390channel 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)
9fc221b5 418 "When true, clients are allowed to talk to each other inside the VPN.")
2be1b471
JL
419
420 (keepalive
421 (keepalive '(10 120))
422 "Causes ping-like messages to be sent back and forth over the link so that
423each side knows when the other side has gone down. @code{keepalive} requires
424a pair. The first element is the period of the ping sending, and the second
425element 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
9fc221b5 434is truncated and rewritten every minute.")
2be1b471
JL
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
be051880
LC
446 ('server
447 openvpn-server-configuration-fields)
448 ('client
449 openvpn-client-configuration-fields))))))
2be1b471 450 (ccd-dir (match role
be051880
LC
451 ('server (create-ccd-directory
452 (openvpn-server-configuration-client-config-dir
453 config)))
454 ('client #f))))
2be1b471
JL
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
be051880
LC
461 ('server (display "" port))
462 ('client (display "client\n" port)))
2be1b471
JL
463 (display #$config-str port)
464 (match '#$role
be051880
LC
465 ('server (display
466 (string-append "client-config-dir "
467 #$ccd-dir "\n") port))
468 ('client (display "" port)))))))))
2be1b471
JL
469
470(define (openvpn-shepherd-service role)
471 (lambda (config)
472 (let* ((config-file (openvpn-config-file role config))
473 (pid-file ((match role
be051880
LC
474 ('server openvpn-server-configuration-pid-file)
475 ('client openvpn-client-configuration-pid-file))
2be1b471
JL
476 config))
477 (openvpn ((match role
be051880
LC
478 ('server openvpn-server-configuration-openvpn)
479 ('client openvpn-client-configuration-openvpn))
2be1b471
JL
480 config))
481 (log-file (match role
be051880
LC
482 ('server "/var/log/openvpn-server.log")
483 ('client "/var/log/openvpn-client.log"))))
2be1b471
JL
484 (list (shepherd-service
485 (documentation (string-append "Run the OpenVPN "
486 (match role
be051880
LC
487 ('server "server")
488 ('client "client"))
2be1b471
JL
489 " daemon."))
490 (provision (match role
be051880
LC
491 ('server '(vpn-server))
492 ('client '(vpn-client))))
2be1b471
JL
493 (requirement '(networking))
494 (start #~(make-forkexec-constructor
495 (list (string-append #$openvpn "/sbin/openvpn")
496 "--writepid" #$pid-file "--config" #$config-file
497 "--daemon")
48bd8b40
CC
498 #:pid-file #$pid-file
499 #:log-file #$log-file))
2be1b471
JL
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
e57bd0be
CL
513 #~(begin
514 (use-modules (guix build utils))
515 (mkdir-p "/var/run/openvpn")))
2be1b471
JL
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
9d7248cd
LC
525 (const %openvpn-activation))))
526 (description "Run the OpenVPN server, which allows you to
527@emph{host} a @acronym{VPN, virtual private network}.")))
2be1b471
JL
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
9d7248cd
LC
537 (const %openvpn-activation))))
538 (description
539 "Run the OpenVPN client service, which allows you to connect
540to an existing @acronym{VPN, virtual private network}.")))
2be1b471
JL
541
542(define* (openvpn-client-service #:key (config (openvpn-client-configuration)))
2be1b471
JL
543 (service openvpn-client-service-type config))
544
545(define* (openvpn-server-service #:key (config (openvpn-server-configuration)))
2be1b471
JL
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))
43b2e440 563
66be80fa
DS
564;;;
565;;; Strongswan.
566;;;
567
568(define-record-type* <strongswan-configuration>
569 strongswan-configuration make-strongswan-configuration
570 strongswan-configuration?
892f1b72 571 (strongswan strongswan-configuration-strongswan ;file-like
66be80fa
DS
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
654starter {
655 config_file = ~a
656}
657
658include ~a/*.conf"
659 #$ipsec-secrets
660 #$ipsec-conf
661 #$strongswan-dir)
662 (format port "
663 }
664}
665include ~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
694strongSwan.")))
695
43b2e440
MO
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
3313f61e
GLV
707 (allowed-ips wireguard-peer-allowed-ips) ;list of strings
708 (keep-alive wireguard-peer-keep-alive
709 (default #f))) ;integer
43b2e440
MO
710
711(define-record-type* <wireguard-configuration>
712 wireguard-configuration make-wireguard-configuration
713 wireguard-configuration?
892f1b72 714 (wireguard wireguard-configuration-wireguard ;file-like
43b2e440
MO
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>
6fb5459e
ND
725 (default '()))
726 (dns wireguard-configuration-dns ;list of strings
727 (default #f)))
43b2e440
MO
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))
3313f61e
GLV
734 (allowed-ips (wireguard-peer-allowed-ips peer))
735 (keep-alive (wireguard-peer-keep-alive peer)))
43b2e440
MO
736 (format #f "[Peer] #~a
737PublicKey = ~a
738AllowedIPs = ~a
3313f61e 739~a~a"
43b2e440
MO
740 name
741 public-key
742 (string-join allowed-ips ",")
743 (if endpoint
744 (format #f "Endpoint = ~a\n" endpoint)
3313f61e
GLV
745 "")
746 (if keep-alive
747 (format #f "PersistentKeepalive = ~a\n" keep-alive)
43b2e440
MO
748 "\n"))))
749
750 (match-record config <wireguard-configuration>
6fb5459e 751 (wireguard interface addresses port private-key peers dns)
43b2e440
MO
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]
764Address = ~a
765PostUp = ~a set %i private-key ~a
766~a
6fb5459e 767~a
43b2e440
MO
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 "")
6fb5459e
ND
775 #$(if dns
776 (format #f "DNS = ~a"
777 (string-join dns ","))
778 "")
43b2e440
MO
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 _
3c0c6ee5
LC
815 (invoke #$wg-quick "down" #$config)
816 #f)) ;stopped!
43b2e440
MO
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
9d7248cd
LC
826 wireguard-activation)))
827 (description "Set up Wireguard @acronym{VPN, Virtual Private Network}
828tunnels.")))