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