summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--doc/guix.texi73
-rw-r--r--gnu/services/vpn.scm138
2 files changed, 207 insertions, 4 deletions
diff --git a/doc/guix.texi b/doc/guix.texi
index 535c98a453..942d5f93df 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -26336,9 +26336,12 @@ Defaults to @samp{()}.
 @cindex virtual private network (VPN)
 
 The @code{(gnu services vpn)} module provides services related to
-@dfn{virtual private networks} (VPNs).  It provides a @emph{client} service for
-your machine to connect to a VPN, and a @emph{server} service for your machine
-to host a VPN@.  Both services use @uref{https://openvpn.net/, OpenVPN}.
+@dfn{virtual private networks} (VPNs).
+
+@subsubheading OpenVPN
+
+It provides a @emph{client} service for your machine to connect to a
+VPN, and a @emph{server} service for your machine to host a VPN@.
 
 @deffn {Scheme Procedure} openvpn-client-service @
        [#:config (openvpn-client-configuration)]
@@ -26717,6 +26720,70 @@ Defaults to @samp{#f}.
 
 @c %end of automatic openvpn-server documentation
 
+@subsubheading Wireguard
+
+@defvr {Scheme Variable} wireguard-service-type
+A service type for a Wireguard tunnel interface.  Its value must be a
+@code{wireguard-configuration} record as in this example:
+
+@lisp
+(service wireguard-service-type
+         (wireguard-configuration
+          (peers
+           (list
+            (wireguard-peer
+             (name "my-peer")
+             (endpoint "my.wireguard.com:51820")
+             (public-key "hzpKg9X1yqu1axN6iJp0mWf6BZGo8m1wteKwtTmDGF4=")
+             (allowed-ips '("10.0.0.2/32")))))))
+@end lisp
+
+@end defvr
+
+@deftp {Data Type} wireguard-configuration
+Data type representing the configuration of the Wireguard service.
+
+@table @asis
+@item @code{wireguard}
+The wireguard package to use for this service.
+
+@item @code{interface} (default: @code{"wg0"})
+The interface name for the VPN.
+
+@item @code{addresses} (default: @code{'("10.0.0.1/32")})
+The IP addresses to be assigned to the above interface.
+
+@item @code{private-key} (default: @code{"/etc/wireguard/private.key"})
+The private key file for the interface.  It is automatically generated if
+the file does not exist.
+
+@item @code{peers} (default: @code{'()})
+The authorized peers on this interface.  This is a list of
+@var{wireguard-peer} records.
+
+@end table
+@end deftp
+
+@deftp {Data Type} wireguard-peer
+Data type representing a Wireguard peer attached to a given interface.
+
+@table @asis
+@item @code{name}
+The peer name.
+
+@item @code{endpoint} (default: @code{#f})
+The optional endpoint for the peer, such as
+@code{"demo.wireguard.com:51820"}.
+
+@item @code{public-key}
+The peer public-key represented as a base64 string.
+
+@item @code{allowed-ips}
+A list of IP addresses from which incoming traffic for this peer is
+allowed and to which incoming traffic for this peer is directed.
+
+@end table
+@end deftp
 
 @node Network File System
 @subsection Network File System
diff --git a/gnu/services/vpn.scm b/gnu/services/vpn.scm
index 70f2617c7e..3e315a6df2 100644
--- a/gnu/services/vpn.scm
+++ b/gnu/services/vpn.scm
@@ -40,7 +40,24 @@
             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.
@@ -507,3 +524,122 @@ is truncated and rewritten every minute.")
       (remote openvpn-remote-configuration))
      (openvpn-remote-configuration ,openvpn-remote-configuration-fields))
    'openvpn-client-configuration))
+
+
+;;;
+;;; 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)))))