Merge branch 'gnome-updates'
[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 ;;;
4 ;;; This file is part of GNU Guix.
5 ;;;
6 ;;; GNU Guix is free software; you can redistribute it and/or modify it
7 ;;; under the terms of the GNU General Public License as published by
8 ;;; the Free Software Foundation; either version 3 of the License, or (at
9 ;;; your option) any later version.
10 ;;;
11 ;;; GNU Guix is distributed in the hope that it will be useful, but
12 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;;; GNU General Public License for more details.
15 ;;;
16 ;;; You should have received a copy of the GNU General Public License
17 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19 (define-module (gnu services vpn)
20 #:use-module (gnu services)
21 #:use-module (gnu services configuration)
22 #:use-module (gnu services shepherd)
23 #:use-module (gnu system shadow)
24 #:use-module (gnu packages admin)
25 #:use-module (gnu packages vpn)
26 #:use-module (guix packages)
27 #:use-module (guix records)
28 #:use-module (guix gexp)
29 #:use-module (srfi srfi-1)
30 #:use-module (ice-9 match)
31 #:use-module (ice-9 regex)
32 #:export (openvpn-client-service
33 openvpn-server-service
34 openvpn-client-service-type
35 openvpn-server-service-type
36 openvpn-client-configuration
37 openvpn-server-configuration
38 openvpn-remote-configuration
39 openvpn-ccd-configuration
40 generate-openvpn-client-documentation
41 generate-openvpn-server-documentation))
42
43 ;;;
44 ;;; OpenVPN.
45 ;;;
46
47 (define (uglify-field-name name)
48 (match name
49 ('verbosity "verb")
50 (_ (let ((str (symbol->string name)))
51 (if (string-suffix? "?" str)
52 (substring str 0 (1- (string-length str)))
53 str)))))
54
55 (define (serialize-field field-name val)
56 (if (eq? field-name 'pid-file)
57 (format #t "")
58 (format #t "~a ~a\n" (uglify-field-name field-name) val)))
59 (define serialize-string serialize-field)
60 (define (serialize-boolean field-name val)
61 (if val
62 (serialize-field field-name val)
63 (format #t "")))
64
65 (define (ip-mask? val)
66 (and (string? val)
67 (if (string-match "^([0-9]+\\.){3}[0-9]+ ([0-9]+\\.){3}[0-9]+$" val)
68 (let ((numbers (string-tokenize val char-set:digit)))
69 (all-lte numbers (list 255 255 255 255 255 255 255 255)))
70 #f)))
71 (define serialize-ip-mask serialize-string)
72
73 (define-syntax define-enumerated-field-type
74 (lambda (x)
75 (define (id-append ctx . parts)
76 (datum->syntax ctx (apply symbol-append (map syntax->datum parts))))
77 (syntax-case x ()
78 ((_ name (option ...))
79 #`(begin
80 (define (#,(id-append #'name #'name #'?) x)
81 (memq x '(option ...)))
82 (define (#,(id-append #'name #'serialize- #'name) field-name val)
83 (serialize-field field-name val)))))))
84
85 (define-enumerated-field-type proto
86 (udp tcp udp6 tcp6))
87 (define-enumerated-field-type dev
88 (tun tap))
89
90 (define key-usage? boolean?)
91 (define (serialize-key-usage field-name value)
92 (if value
93 (format #t "remote-cert-tls server\n")
94 #f))
95
96 (define bind? boolean?)
97 (define (serialize-bind field-name value)
98 (if value
99 #f
100 (format #t "nobind\n")))
101
102 (define resolv-retry? boolean?)
103 (define (serialize-resolv-retry field-name value)
104 (if value
105 (format #t "resolv-retry infinite\n")
106 #f))
107
108 (define (serialize-tls-auth role location)
109 (serialize-field 'tls-auth
110 (string-append location " " (match role
111 ('server "0")
112 ('client "1")))))
113 (define (tls-auth? val)
114 (or (eq? val #f)
115 (string? val)))
116 (define (serialize-tls-auth-server field-name val)
117 (serialize-tls-auth 'server val))
118 (define (serialize-tls-auth-client field-name val)
119 (serialize-tls-auth 'client val))
120 (define tls-auth-server? tls-auth?)
121 (define tls-auth-client? tls-auth?)
122
123 (define (serialize-number field-name val)
124 (serialize-field field-name (number->string val)))
125
126 (define (all-lte left right)
127 (if (eq? left '())
128 (eq? right '())
129 (and (<= (string->number (car left)) (car right))
130 (all-lte (cdr left) (cdr right)))))
131
132 (define (cidr4? val)
133 (if (string? val)
134 (if (string-match "^([0-9]+\\.){3}[0-9]+/[0-9]+$" val)
135 (let ((numbers (string-tokenize val char-set:digit)))
136 (all-lte numbers (list 255 255 255 255 32)))
137 #f)
138 (eq? val #f)))
139
140 (define (cidr6? val)
141 (if (string? val)
142 (string-match "^([0-9a-f]{0,4}:){0,8}/[0-9]{1,3}$" val)
143 (eq? val #f)))
144
145 (define (serialize-cidr4 field-name val)
146 (if (eq? val #f) #f (serialize-field field-name val)))
147
148 (define (serialize-cidr6 field-name val)
149 (if (eq? val #f) #f (serialize-field field-name val)))
150
151 (define (ip? val)
152 (if (string? val)
153 (if (string-match "^([0-9]+\\.){3}[0-9]+$" val)
154 (let ((numbers (string-tokenize val char-set:digit)))
155 (all-lte numbers (list 255 255 255 255)))
156 #f)
157 (eq? val #f)))
158 (define (serialize-ip field-name val)
159 (if (eq? val #f) #f (serialize-field field-name val)))
160
161 (define (keepalive? val)
162 (and (list? val)
163 (and (number? (car val))
164 (number? (car (cdr val))))))
165 (define (serialize-keepalive field-name val)
166 (format #t "~a ~a ~a\n" (uglify-field-name field-name)
167 (number->string (car val)) (number->string (car (cdr val)))))
168
169 (define gateway? boolean?)
170 (define (serialize-gateway field-name val)
171 (and val
172 (format #t "push \"redirect-gateway\"\n")))
173
174
175 (define-configuration openvpn-remote-configuration
176 (name
177 (string "my-server")
178 "Server name.")
179 (port
180 (number 1194)
181 "Port number the server listens to."))
182
183 (define-configuration openvpn-ccd-configuration
184 (name
185 (string "client")
186 "Client name.")
187 (iroute
188 (ip-mask #f)
189 "Client own network")
190 (ifconfig-push
191 (ip-mask #f)
192 "Client VPN IP."))
193
194 (define (openvpn-remote-list? val)
195 (and (list? val)
196 (or (eq? val '())
197 (and (openvpn-remote-configuration? (car val))
198 (openvpn-remote-list? (cdr val))))))
199 (define (serialize-openvpn-remote-list field-name val)
200 (for-each (lambda (remote)
201 (format #t "remote ~a ~a\n" (openvpn-remote-configuration-name remote)
202 (number->string (openvpn-remote-configuration-port remote))))
203 val))
204
205 (define (openvpn-ccd-list? val)
206 (and (list? val)
207 (or (eq? val '())
208 (and (openvpn-ccd-configuration? (car val))
209 (openvpn-ccd-list? (cdr val))))))
210 (define (serialize-openvpn-ccd-list field-name val)
211 #f)
212
213 (define (create-ccd-directory val)
214 "Create a ccd directory containing files for the ccd configuration option
215 of OpenVPN. Each file in this directory represents particular settings for a
216 client. Each file is named after the name of the client."
217 (let ((files (map (lambda (ccd)
218 (list (openvpn-ccd-configuration-name ccd)
219 (with-output-to-string
220 (lambda ()
221 (serialize-configuration
222 ccd openvpn-ccd-configuration-fields)))))
223 val)))
224 (computed-file "ccd"
225 (with-imported-modules '((guix build utils))
226 #~(begin
227 (use-modules (guix build utils))
228 (use-modules (ice-9 match))
229 (mkdir-p #$output)
230 (for-each
231 (lambda (ccd)
232 (match ccd
233 ((name config-string)
234 (call-with-output-file
235 (string-append #$output "/" name)
236 (lambda (port) (display config-string port))))))
237 '#$files))))))
238
239 (define-syntax define-split-configuration
240 (lambda (x)
241 (syntax-case x ()
242 ((_ name1 name2 (common-option ...) (first-option ...) (second-option ...))
243 #`(begin
244 (define-configuration #,#'name1
245 common-option ...
246 first-option ...)
247 (define-configuration #,#'name2
248 common-option ...
249 second-option ...))))))
250
251 (define-split-configuration openvpn-client-configuration
252 openvpn-server-configuration
253 ((openvpn
254 (package openvpn)
255 "The OpenVPN package.")
256
257 (pid-file
258 (string "/var/run/openvpn/openvpn.pid")
259 "The OpenVPN pid file.")
260
261 (proto
262 (proto 'udp)
263 "The protocol (UDP or TCP) used to open a channel between clients and
264 servers.")
265
266 (dev
267 (dev 'tun)
268 "The device type used to represent the VPN connection.")
269
270 (ca
271 (string "/etc/openvpn/ca.crt")
272 "The certificate authority to check connections against.")
273
274 (cert
275 (string "/etc/openvpn/client.crt")
276 "The certificate of the machine the daemon is running on. It should be signed
277 by the authority given in @code{ca}.")
278
279 (key
280 (string "/etc/openvpn/client.key")
281 "The key of the machine the daemon is running on. It must be the whose
282 certificate is @code{cert}.")
283
284 (comp-lzo?
285 (boolean #t)
286 "Whether to use the lzo compression algorithm.")
287
288 (persist-key?
289 (boolean #t)
290 "Don't re-read key files across SIGUSR1 or --ping-restart.")
291
292 (persist-tun?
293 (boolean #t)
294 "Don't close and reopen TUN/TAP device or run up/down scripts across
295 SIGUSR1 or --ping-restart restarts.")
296
297 (verbosity
298 (number 3)
299 "Verbosity level."))
300 ;; client-specific configuration
301 ((tls-auth
302 (tls-auth-client #f)
303 "Add an additional layer of HMAC authentication on top of the TLS control
304 channel to protect against DoS attacks.")
305
306 (verify-key-usage?
307 (key-usage #t)
308 "Whether to check the server certificate has server usage extension.")
309
310 (bind?
311 (bind #f)
312 "Bind to a specific local port number.")
313
314 (resolv-retry?
315 (resolv-retry #t)
316 "Retry resolving server address.")
317
318 (remote
319 (openvpn-remote-list '())
320 "A list of remote servers to connect to."))
321 ;; server-specific configuration
322 ((tls-auth
323 (tls-auth-server #f)
324 "Add an additional layer of HMAC authentication on top of the TLS control
325 channel to protect against DoS attacks.")
326
327 (port
328 (number 1194)
329 "Specifies the port number on which the server listens.")
330
331 (server
332 (ip-mask "10.8.0.0 255.255.255.0")
333 "An ip and mask specifying the subnet inside the virtual network.")
334
335 (server-ipv6
336 (cidr6 #f)
337 "A CIDR notation specifying the IPv6 subnet inside the virtual network.")
338
339 (dh
340 (string "/etc/openvpn/dh2048.pem")
341 "The Diffie-Hellman parameters file.")
342
343 (ifconfig-pool-persist
344 (string "/etc/openvpn/ipp.txt")
345 "The file that records client IPs.")
346
347 (redirect-gateway?
348 (gateway #f)
349 "When true, the server will act as a gateway for its clients.")
350
351 (client-to-client?
352 (boolean #f)
353 "When true, clients are alowed to talk to each other inside the VPN.")
354
355 (keepalive
356 (keepalive '(10 120))
357 "Causes ping-like messages to be sent back and forth over the link so that
358 each side knows when the other side has gone down. @code{keepalive} requires
359 a pair. The first element is the period of the ping sending, and the second
360 element is the timeout before considering the other side down.")
361
362 (max-clients
363 (number 100)
364 "The maximum number of clients.")
365
366 (status
367 (string "/var/run/openvpn/status")
368 "The status file. This file shows a small report on current connection. It
369 is trunkated and rewritten every minute.")
370
371 (client-config-dir
372 (openvpn-ccd-list '())
373 "The list of configuration for some clients.")))
374
375 (define (openvpn-config-file role config)
376 (let ((config-str
377 (with-output-to-string
378 (lambda ()
379 (serialize-configuration config
380 (match role
381 ('server
382 openvpn-server-configuration-fields)
383 ('client
384 openvpn-client-configuration-fields))))))
385 (ccd-dir (match role
386 ('server (create-ccd-directory
387 (openvpn-server-configuration-client-config-dir
388 config)))
389 ('client #f))))
390 (computed-file "openvpn.conf"
391 #~(begin
392 (use-modules (ice-9 match))
393 (call-with-output-file #$output
394 (lambda (port)
395 (match '#$role
396 ('server (display "" port))
397 ('client (display "client\n" port)))
398 (display #$config-str port)
399 (match '#$role
400 ('server (display
401 (string-append "client-config-dir "
402 #$ccd-dir "\n") port))
403 ('client (display "" port)))))))))
404
405 (define (openvpn-shepherd-service role)
406 (lambda (config)
407 (let* ((config-file (openvpn-config-file role config))
408 (pid-file ((match role
409 ('server openvpn-server-configuration-pid-file)
410 ('client openvpn-client-configuration-pid-file))
411 config))
412 (openvpn ((match role
413 ('server openvpn-server-configuration-openvpn)
414 ('client openvpn-client-configuration-openvpn))
415 config))
416 (log-file (match role
417 ('server "/var/log/openvpn-server.log")
418 ('client "/var/log/openvpn-client.log"))))
419 (list (shepherd-service
420 (documentation (string-append "Run the OpenVPN "
421 (match role
422 ('server "server")
423 ('client "client"))
424 " daemon."))
425 (provision (match role
426 ('server '(vpn-server))
427 ('client '(vpn-client))))
428 (requirement '(networking))
429 (start #~(make-forkexec-constructor
430 (list (string-append #$openvpn "/sbin/openvpn")
431 "--writepid" #$pid-file "--config" #$config-file
432 "--daemon")
433 #:pid-file #$pid-file))
434 (stop #~(make-kill-destructor)))))))
435
436 (define %openvpn-accounts
437 (list (user-group (name "openvpn") (system? #t))
438 (user-account
439 (name "openvpn")
440 (group "openvpn")
441 (system? #t)
442 (comment "Openvpn daemon user")
443 (home-directory "/var/empty")
444 (shell (file-append shadow "/sbin/nologin")))))
445
446 (define %openvpn-activation
447 #~(mkdir-p "/var/run/openvpn"))
448
449 (define openvpn-server-service-type
450 (service-type (name 'openvpn-server)
451 (extensions
452 (list (service-extension shepherd-root-service-type
453 (openvpn-shepherd-service 'server))
454 (service-extension account-service-type
455 (const %openvpn-accounts))
456 (service-extension activation-service-type
457 (const %openvpn-activation))))))
458
459 (define openvpn-client-service-type
460 (service-type (name 'openvpn-client)
461 (extensions
462 (list (service-extension shepherd-root-service-type
463 (openvpn-shepherd-service 'client))
464 (service-extension account-service-type
465 (const %openvpn-accounts))
466 (service-extension activation-service-type
467 (const %openvpn-activation))))))
468
469 (define* (openvpn-client-service #:key (config (openvpn-client-configuration)))
470 (validate-configuration config openvpn-client-configuration-fields)
471 (service openvpn-client-service-type config))
472
473 (define* (openvpn-server-service #:key (config (openvpn-server-configuration)))
474 (validate-configuration config openvpn-server-configuration-fields)
475 (service openvpn-server-service-type config))
476
477 (define (generate-openvpn-server-documentation)
478 (generate-documentation
479 `((openvpn-server-configuration
480 ,openvpn-server-configuration-fields
481 (ccd openvpn-ccd-configuration))
482 (openvpn-ccd-configuration ,openvpn-ccd-configuration-fields))
483 'openvpn-server-configuration))
484
485 (define (generate-openvpn-client-documentation)
486 (generate-documentation
487 `((openvpn-client-configuration
488 ,openvpn-client-configuration-fields
489 (remote openvpn-remote-configuration))
490 (openvpn-remote-configuration ,openvpn-remote-configuration-fields))
491 'openvpn-client-configuration))