services: guix-publish: Add zstd compression by default.
[jackhill/guix/guix.git] / gnu / services / vpn.scm
index c219954..3e315a6 100644 (file)
@@ -1,5 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu>
+;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
+;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
             openvpn-remote-configuration
             openvpn-ccd-configuration
             generate-openvpn-client-documentation
-            generate-openvpn-server-documentation))
+            generate-openvpn-server-documentation
+
+            wireguard-peer
+            wireguard-peer?
+            wireguard-peer-name
+            wireguard-peer-endpoint
+            wireguard-peer-allowed-ips
+
+            wireguard-configuration
+            wireguard-configuration?
+            wireguard-configuration-wireguard
+            wireguard-configuration-interface
+            wireguard-configuration-addresses
+            wireguard-configuration-port
+            wireguard-configuration-private-key
+            wireguard-configuration-peers
+
+            wireguard-service-type))
 
 ;;;
 ;;; OpenVPN.
 
 (define (uglify-field-name name)
   (match name
-         ('verbosity "verb")
-         (_ (let ((str (symbol->string name)))
-              (if (string-suffix? "?" str)
-                  (substring str 0 (1- (string-length str)))
-                  str)))))
+    ('verbosity "verb")
+    (_ (let ((str (symbol->string name)))
+         (if (string-suffix? "?" str)
+             (substring str 0 (1- (string-length str)))
+             str)))))
 
 (define (serialize-field field-name val)
   (if (eq? field-name 'pid-file)
       (format #t "")
       (format #t "~a ~a\n" (uglify-field-name field-name) val)))
 (define serialize-string serialize-field)
+(define-maybe string)
 (define (serialize-boolean field-name val)
   (if val
-      (serialize-field field-name val)
+      (serialize-field field-name "")
       (format #t "")))
 
 (define (ip-mask? val)
       #f))
 
 (define (serialize-tls-auth role location)
-  (serialize-field 'tls-auth
-                   (string-append location " " (match role
-                                                      ('server "0")
-                                                      ('client "1")))))
+  (if location
+      (serialize-field 'tls-auth
+                       (string-append location " " (match role
+                                                     ('server "0")
+                                                     ('client "1"))))
+      #f))
 (define (tls-auth? val)
   (or (eq? val #f)
       (string? val)))
@@ -230,10 +252,10 @@ client.  Each file is named after the name of the client."
                          (for-each
                           (lambda (ccd)
                             (match ccd
-                                   ((name config-string)
-                                    (call-with-output-file
-                                        (string-append #$output "/" name)
-                                      (lambda (port) (display config-string port))))))
+                              ((name config-string)
+                               (call-with-output-file
+                                   (string-append #$output "/" name)
+                                 (lambda (port) (display config-string port))))))
                           '#$files))))))
 
 (define-syntax define-split-configuration
@@ -268,17 +290,17 @@ servers.")
     "The device type used to represent the VPN connection.")
 
    (ca
-    (string "/etc/openvpn/ca.crt")
+    (maybe-string "/etc/openvpn/ca.crt")
     "The certificate authority to check connections against.")
 
    (cert
-    (string "/etc/openvpn/client.crt")
+    (maybe-string "/etc/openvpn/client.crt")
     "The certificate of the machine the daemon is running on. It should be signed
 by the authority given in @code{ca}.")
 
    (key
-    (string "/etc/openvpn/client.key")
-    "The key of the machine the daemon is running on. It must be the whose
+    (maybe-string "/etc/openvpn/client.key")
+    "The key of the machine the daemon is running on. It must be the key whose
 certificate is @code{cert}.")
 
    (comp-lzo?
@@ -294,6 +316,11 @@ certificate is @code{cert}.")
     "Don't close and reopen TUN/TAP device or run up/down scripts across
 SIGUSR1 or --ping-restart restarts.")
 
+   (fast-io?
+     (boolean #f)
+     "(Experimental) Optimize TUN/TAP/UDP I/O writes by avoiding a call to
+poll/epoll/select prior to the write operation.")
+
    (verbosity
     (number 3)
     "Verbosity level."))
@@ -303,6 +330,12 @@ SIGUSR1 or --ping-restart restarts.")
     "Add an additional layer of HMAC authentication on top of the TLS control
 channel to protect against DoS attacks.")
 
+   (auth-user-pass
+     (maybe-string 'disabled)
+     "Authenticate with server using username/password.  The option is a file
+containing username/password on 2 lines.  Do not use a file-like object as it
+would be added to the store and readable by any user.")
+
    (verify-key-usage?
     (key-usage #t)
     "Whether to check the server certificate has server usage extension.")
@@ -350,7 +383,7 @@ channel to protect against DoS attacks.")
 
    (client-to-client?
     (boolean #f)
-    "When true, clients are alowed to talk to each other inside the VPN.")
+    "When true, clients are allowed to talk to each other inside the VPN.")
 
    (keepalive
     (keepalive '(10 120))
@@ -366,7 +399,7 @@ element is the timeout before considering the other side down.")
    (status
     (string "/var/run/openvpn/status")
     "The status file. This file shows a small report on current connection. It
-is trunkated and rewritten every minute.")
+is truncated and rewritten every minute.")
 
    (client-config-dir
     (openvpn-ccd-list '())
@@ -378,53 +411,53 @@ is trunkated and rewritten every minute.")
            (lambda ()
              (serialize-configuration config
                                       (match role
-                                             ('server
-                                              openvpn-server-configuration-fields)
-                                             ('client
-                                              openvpn-client-configuration-fields))))))
+                                        ('server
+                                         openvpn-server-configuration-fields)
+                                        ('client
+                                         openvpn-client-configuration-fields))))))
         (ccd-dir (match role
-                        ('server (create-ccd-directory
-                                  (openvpn-server-configuration-client-config-dir
-                                   config)))
-                        ('client #f))))
+                   ('server (create-ccd-directory
+                             (openvpn-server-configuration-client-config-dir
+                              config)))
+                   ('client #f))))
     (computed-file "openvpn.conf"
                    #~(begin
                        (use-modules (ice-9 match))
                        (call-with-output-file #$output
                          (lambda (port)
                            (match '#$role
-                                  ('server (display "" port))
-                                  ('client (display "client\n" port)))
+                             ('server (display "" port))
+                             ('client (display "client\n" port)))
                            (display #$config-str port)
                            (match '#$role
-                                  ('server (display
-                                            (string-append "client-config-dir "
-                                                           #$ccd-dir "\n") port))
-                                  ('client (display "" port)))))))))
+                             ('server (display
+                                       (string-append "client-config-dir "
+                                                      #$ccd-dir "\n") port))
+                             ('client (display "" port)))))))))
 
 (define (openvpn-shepherd-service role)
   (lambda (config)
     (let* ((config-file (openvpn-config-file role config))
            (pid-file ((match role
-                             ('server openvpn-server-configuration-pid-file)
-                             ('client openvpn-client-configuration-pid-file))
+                        ('server openvpn-server-configuration-pid-file)
+                        ('client openvpn-client-configuration-pid-file))
                       config))
            (openvpn ((match role
-                            ('server openvpn-server-configuration-openvpn)
-                            ('client openvpn-client-configuration-openvpn))
+                       ('server openvpn-server-configuration-openvpn)
+                       ('client openvpn-client-configuration-openvpn))
                      config))
            (log-file (match role
-                            ('server "/var/log/openvpn-server.log")
-                            ('client "/var/log/openvpn-client.log"))))
+                       ('server "/var/log/openvpn-server.log")
+                       ('client "/var/log/openvpn-client.log"))))
       (list (shepherd-service
              (documentation (string-append "Run the OpenVPN "
                                            (match role
-                                                  ('server "server")
-                                                  ('client "client"))
+                                             ('server "server")
+                                             ('client "client"))
                                            " daemon."))
              (provision (match role
-                               ('server '(vpn-server))
-                               ('client '(vpn-client))))
+                          ('server '(vpn-server))
+                          ('client '(vpn-client))))
              (requirement '(networking))
              (start #~(make-forkexec-constructor
                        (list (string-append #$openvpn "/sbin/openvpn")
@@ -444,7 +477,9 @@ is trunkated and rewritten every minute.")
          (shell (file-append shadow "/sbin/nologin")))))
 
 (define %openvpn-activation
-  #~(mkdir-p "/var/run/openvpn"))
+  #~(begin
+      (use-modules (guix build utils))
+      (mkdir-p "/var/run/openvpn")))
 
 (define openvpn-server-service-type
   (service-type (name 'openvpn-server)
@@ -489,3 +524,122 @@ is trunkated and rewritten every minute.")
       (remote openvpn-remote-configuration))
      (openvpn-remote-configuration ,openvpn-remote-configuration-fields))
    'openvpn-client-configuration))
+
+\f
+;;;
+;;; Wireguard.
+;;;
+
+(define-record-type* <wireguard-peer>
+  wireguard-peer make-wireguard-peer
+  wireguard-peer?
+  (name              wireguard-peer-name)
+  (endpoint          wireguard-peer-endpoint
+                     (default #f))     ;string
+  (public-key        wireguard-peer-public-key)   ;string
+  (allowed-ips       wireguard-peer-allowed-ips)) ;list of strings
+
+(define-record-type* <wireguard-configuration>
+  wireguard-configuration make-wireguard-configuration
+  wireguard-configuration?
+  (wireguard          wireguard-configuration-wireguard ;<package>
+                      (default wireguard-tools))
+  (interface          wireguard-configuration-interface ;string
+                      (default "wg0"))
+  (addresses          wireguard-configuration-addresses ;string
+                      (default '("10.0.0.1/32")))
+  (port               wireguard-configuration-port ;integer
+                      (default 51820))
+  (private-key        wireguard-configuration-private-key ;string
+                      (default "/etc/wireguard/private.key"))
+  (peers              wireguard-configuration-peers ;list of <wiregard-peer>
+                      (default '())))
+
+(define (wireguard-configuration-file config)
+  (define (peer->config peer)
+    (let ((name (wireguard-peer-name peer))
+          (public-key (wireguard-peer-public-key peer))
+          (endpoint (wireguard-peer-endpoint peer))
+          (allowed-ips (wireguard-peer-allowed-ips peer)))
+      (format #f "[Peer] #~a
+PublicKey = ~a
+AllowedIPs = ~a
+~a"
+              name
+              public-key
+              (string-join allowed-ips ",")
+              (if endpoint
+                  (format #f "Endpoint = ~a\n" endpoint)
+                  "\n"))))
+
+  (match-record config <wireguard-configuration>
+    (wireguard interface addresses port private-key peers)
+    (let* ((config-file (string-append interface ".conf"))
+           (peers (map peer->config peers))
+           (config
+            (computed-file
+             "wireguard-config"
+             #~(begin
+                 (mkdir #$output)
+                 (chdir #$output)
+                 (call-with-output-file #$config-file
+                   (lambda (port)
+                     (let ((format (@ (ice-9 format) format)))
+                       (format port "[Interface]
+Address = ~a
+PostUp = ~a set %i private-key ~a
+~a
+~{~a~^~%~}"
+                               #$(string-join addresses ",")
+                               #$(file-append wireguard "/bin/wg")
+                               #$private-key
+                               #$(if port
+                                     (format #f "ListenPort = ~a" port)
+                                     "")
+                               (list #$@peers)))))))))
+      (file-append config "/" config-file))))
+
+(define (wireguard-activation config)
+  (match-record config <wireguard-configuration>
+    (private-key)
+    #~(begin
+        (use-modules (guix build utils)
+                     (ice-9 popen)
+                     (ice-9 rdelim))
+        (mkdir-p (dirname #$private-key))
+        (unless (file-exists? #$private-key)
+          (let* ((pipe
+                  (open-input-pipe (string-append
+                                    #$(file-append wireguard-tools "/bin/wg")
+                                    " genkey")))
+                 (key (read-line pipe)))
+            (call-with-output-file #$private-key
+              (lambda (port)
+                (display key port)))
+            (chmod #$private-key #o400)
+            (close-pipe pipe))))))
+
+(define (wireguard-shepherd-service config)
+  (match-record config <wireguard-configuration>
+    (wireguard interface)
+    (let ((wg-quick (file-append wireguard "/bin/wg-quick"))
+          (config (wireguard-configuration-file config)))
+      (list (shepherd-service
+             (requirement '(networking))
+             (provision (list
+                         (symbol-append 'wireguard-
+                                        (string->symbol interface))))
+             (start #~(lambda _
+                       (invoke #$wg-quick "up" #$config)))
+             (stop #~(lambda _
+                       (invoke #$wg-quick "down" #$config)))
+             (documentation "Run the Wireguard VPN tunnel"))))))
+
+(define wireguard-service-type
+  (service-type
+   (name 'wireguard)
+   (extensions
+    (list (service-extension shepherd-root-service-type
+                             wireguard-shepherd-service)
+          (service-extension activation-service-type
+                             wireguard-activation)))))