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