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