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