Hi,

I'm currently writing an openvpn service. Here is the patch (wip). It
works for the client part, I didn't test the server part yet (but it
generates a configuration file).

First, how could I make openvpn-config-file look better?

Also, I need to document all of this patch.

When running as a server, the configuration may specify a ccd directory
that contains files with information about clients (one client per
file, named after the CN of the client's certificate). This file can be
used to assign a specific IP to the client, or allow its subnet to
access the VPN. They are tightly coupled with the configuration file.
For instance, to allow "Alice"'s subnet to access the VPN, you would:

create a ccd/Alice file with content:

> iroute 192.168.1.0/24

and add this configuration in the main server's configuration file:

> route 192.168.1.0/24

and optionally (in conjunction with client-to-client):

> push "route 192.168.1.0/24"

So I would like to create a record to contain information for each
client, something like <openvpn-client-ccd>, that would contain a
name, a subnet and a specific VPN IP address ("ifconfig-push" in the ccd
file). The VPN IP has some restrictions on its last byte (should be one
of 1, 5, 9, 13, 17, ..., 253). How would you verify that?

Is this the good approach? Also, how would you create multiple ccd
files for different clients in the same directory (preferably
in /gnu/store)?
From 272939aad601f7a0c736449edcfcc64dffe0a370 Mon Sep 17 00:00:00 2001
From: Julien Lepiller <jul...@lepiller.eu>
Date: Tue, 18 Oct 2016 23:16:31 +0200
Subject: [PATCH] gnu: Add openvpn services

* gnu/services/vpn.scm: new file.
* gnu/local.mk(GNU_SYSTEM_MODULES): add it
---
 gnu/local.mk         |   1 +
 gnu/services/vpn.scm | 289 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 290 insertions(+)
 create mode 100644 gnu/services/vpn.scm

diff --git a/gnu/local.mk b/gnu/local.mk
index 18ba0c2..5c7ab97 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -406,6 +406,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/services/sddm.scm				\
   %D%/services/spice.scm				\
   %D%/services/ssh.scm				\
+  %D%/services/vpn.scm				\
   %D%/services/web.scm				\
   %D%/services/xorg.scm				\
 						\
diff --git a/gnu/services/vpn.scm b/gnu/services/vpn.scm
new file mode 100644
index 0000000..8c6b720
--- /dev/null
+++ b/gnu/services/vpn.scm
@@ -0,0 +1,289 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2016 Julien Lepiller <jul...@lepiller.eu>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu services vpn)
+  #:use-module (gnu packages vpn)
+  #:use-module (gnu packages admin)
+  #:use-module (gnu services)
+  #:use-module (gnu services shepherd)
+  #:use-module (gnu system pam)
+  #:use-module (gnu system shadow)
+  #:use-module (guix gexp)
+  #:use-module (guix records)
+  #:use-module (srfi srfi-26)
+  #:use-module (ice-9 match)
+  #:export (openvpn-configuration
+            openvpn-configuration?
+            openvpn-client-service-type
+            openvpn-client-service
+            openvpn-server-service-type
+            openvpn-server-service
+
+            openvpn-remote-server))
+
+;;;
+;;; OpenVPN.
+;;;
+
+(define-record-type* <openvpn-remote-server>
+  openvpn-remote-server make-openvpn-remote-server
+  openvpn-remote-server?
+  (name  openvpn-remote-server-name
+        (default "my-server")) ; string (domain name or ip)
+  (port openvpn-remote-server-port
+        (default 1194))) ; int
+
+;; TODO: actually use this structure or something similar
+(define-record-type* <openvpn-client-ccd>
+  openvpn-client-ccd make-openvpn-client-ccd
+  openvpn-client-ccd?
+  (name openvpn-client-cdd-name
+	(default "")) ; string
+  (subnet openvpn-client-ccd-subnet
+	  (default #f)) ; string
+  (ip openvpn-client-ccd-ip
+      (default #f))) ; string
+
+;; openvpn can be started as server or client. Part of the configuration
+;; is common to the two modes of operation, and some are specific to
+;; the server.
+;; TODO: add a list of clients to the server configuration that are
+;; of a specific record type, so we can push their route (if required)
+;; or add entries in ccd.
+;; TODO: add a ccd configuration for the server
+(define-record-type* <openvpn-configuration>
+  openvpn-configuration make-openvpn-configuration
+  openvpn-configuration?
+  ;; common configuration options
+  (pid-file             openvpn-configuration-pid-file
+                        (default "/var/run/openvpn/openvpn.pid"))
+  (proto                openvpn-configuration-proto
+                        (default 'udp)) ; 'udp | 'tcp
+  (dev                  openvpn-configuration-dev
+                        (default 'tun)) ; 'tun | 'tap
+  (ca                   openvpn-configuration-ca
+                        (default "/etc/openvpn/ca.crt")) ; string
+  (cert                 openvpn-configuration-cert
+                        (default "/etc/openvpn/client.crt")) ; string
+  (key                  openvpn-configuration-key
+                        (default "/etc/openvpn/client.key")) ; string
+  (tls-auth             openvpn-configuration-tls-auth
+                        (default #f)) ; boolean | string
+  (comp-lzo?            openvpn-configuration-comp-lzo?
+                        (default #t)) ; boolean
+  (persist-key?         openvpn-configuration-persist-key?
+                        (default #t)) ; boolean
+  (persist-tun?         openvpn-configuration-persist-tun?
+                        (default #t)) ; boolean
+  (verbosity            openvpn-configuration-verbosity
+                        (default 3))
+  ;; client-only
+  (verify-key-usage?    openvpn-configuration-verify-key-usage?
+                        (default #t)) ; boolean
+  (bind?                openvpn-configuration-bind
+                        (default #f)) ; boolean
+  (resolv-retry?        openvpn-configuration-resolv-retry?
+                        (default #t)) ; boolean
+  (remote               openvpn-configuration-remote
+                        (default (list (openvpn-remote-server)))) ; list
+  ;; server-only
+  (port                 openvpn-configuration-port
+                        (default 1194)) ; number
+  (subnet               openvpn-configuration-subnet
+                        (default "10.8.0.0/24")) ; CIDR notation (string) | #f
+  (subnet6              openvpn-configuration-subnet6
+                        (default #f)) ; CIDR notation (string) | #f
+  (ifconfig-pool-persist openvpn-configuration-ifconfig-pool-persist
+                        (default "/etc/openvpn/ipp.txt")) ; string
+  (redirect-gateway?    openvpn-configuration-redirect-gateway?
+                        (default #f)) ; boolean
+  (client-to-client?    openvpn-configuration-client-to-client?
+                        (default #f)) ; boolean
+  (keep-alive-period    openvpn-configuration-keep-alive-period
+                        (default 10)) ; number
+  (keep-alive-timeout   openvpn-configuration-keep-alive-timeout
+                        (default 120)) ; number
+  (max-clients          openvpn-configuration-max-clients
+                        (default 100)) ; number
+  (status-file          openvpn-configuration-status-file
+                        (default "/var/run/openvpn/status"))) ; string
+
+(define %openvpn-accounts
+  (list (user-group (name "openvpn") (system? #t))
+        (user-account
+          (name "openvpn")
+          (group "openvpn")
+          (system? #t)
+          (comment "openvpn privilege separation user")
+          (home-directory "/var/run/openvpn")
+          (shell #~(string-append #$shadow "/sbin/nologin")))))
+
+(define (openvpn-activation config)
+  "Return the activation GEXP for CONFIG."
+  #~(begin
+      (mkdir-p (dirname #$(openvpn-configuration-pid-file config)))))
+
+
+(define (openvpn-config-file config role)
+  "Return the openvpn configuration file corresponding to CONFIG."
+  (computed-file
+   (string-append "openvpn-"
+		  (match role ('client "client") ('server "server"))
+		  ".conf")
+   #~(call-with-output-file #$output
+       (lambda (port)
+         (display "# Generated by 'openvpn-service'.\n" port)
+         (display "user openvpn\n" port)
+         (display "group openvpn\n" port)
+         (format port "~a\n" #$(match role ('client "client") (_ "")))
+         (format port "proto ~a\n"
+                 #$(match (openvpn-configuration-proto config)
+                          ('udp "udp")
+                          ('tcp "tcp")))
+         (format port "dev ~a\n"
+                 #$(match (openvpn-configuration-dev config)
+                          ('tun "tun")
+                          ('tap "tap")))
+         (format port "ca ~a\n"
+                 #$(openvpn-configuration-ca config))
+         (format port "cert ~a\n"
+                 #$(openvpn-configuration-cert config))
+         (format port "key ~a\n"
+                 #$(openvpn-configuration-key config))
+         #$(match (openvpn-configuration-tls-auth config)
+                (#f "")
+                (str #~(format port "tls-auth ~a ~a\n"
+                               #$(openvpn-configuration-tls-auth config)
+                               #$(match role ('client "1") ('server "0")))))
+         #$(if (openvpn-configuration-persist-key? config)
+             #~(format port "persist-key\n"))
+         #$(if (openvpn-configuration-persist-tun? config)
+             #~(format port "persist-tun\n"))
+         #$(if (openvpn-configuration-comp-lzo? config)
+             #~(format port "comp-lzo\n"))
+         (format port "verb ~a\n"
+		 #$(number->string (openvpn-configuration-verbosity config)))
+	 ;; client-specific configuration
+	 (format port "~a"
+		 #$(if (eq? role 'client)
+                       (do ((remaining 
+                             (openvpn-configuration-remote config)
+                             (cdr remaining))
+                            (str "" (string-append str "remote "
+                                     (openvpn-remote-server-name
+                                      (car remaining))
+                                     " "
+                                     (number->string
+                                      (openvpn-remote-server-port
+                                       (car remaining))) "\n")))
+                           ((null? remaining) str))
+		       ""))
+         #$(if (and (eq? role 'client)
+		    (openvpn-configuration-verify-key-usage? config))
+               #~(format port "remote-cert-tls server\n")
+	       #~(display "" port))
+         ;; server-specific configuration
+         #$(if (eq? role 'server)
+	       #~(format port "port ~a\n"
+                          #$(number->string
+                             (openvpn-configuration-port config)))
+               #~(display "" port))
+         #$(if (and (eq? role 'server)
+		    (openvpn-configuration-subnet config))
+               #~(format port "server ~a\n"
+                         #$(openvpn-configuration-subnet config))
+	       #~(display "" port))
+         #$(if (and (eq? role 'server)
+		    (openvpn-configuration-subnet6 config))
+               #~(format port "server-ipv6 ~a\n"
+                         #$(openvpn-configuration-subnet6 config))
+	       #~(display "" port))
+         #$(if (and (eq? role 'server)
+		    (openvpn-configuration-ifconfig-pool-persist config))
+               #~(format port "ifconfig-pool-persist ~a\n"
+                         #$(openvpn-configuration-ifconfig-pool-persist config))
+	       #~(display "" port))
+         #$(if (and (eq? role 'server)
+		    (openvpn-configuration-redirect-gateway? config))
+               #~(display "push \"redirect-gateway\"\n" port)
+	       #~(display "" port))
+         #$(if (and (eq? role 'server)
+		    (openvpn-configuration-client-to-client? config))
+               #~(display "client-to-client\n" port)
+	       #~(display "" port))
+         #$(if (eq? role 'server)
+	       #~(format port "keep-alive ~a ~a\n"
+                         #$(openvpn-configuration-keep-alive-period config)
+                         #$(openvpn-configuration-keep-alive-timeout config))
+	       #~(display "" port))
+         #$(if (eq? role 'server)
+               #~(format port "max-clients ~a\n"
+                         #$(openvpn-configuration-max-clients config))
+	       #~(display "" port))
+         #$(if (eq? role 'server)
+               #~(format port "status ~a\n"
+                         #$(openvpn-configuration-status-file config))
+	       #~(display "" port))
+         #t))))
+
+(define (get-openvpn-shepherd-service role)
+  (lambda (config)
+          (define pid-file
+                  (openvpn-configuration-pid-file config))
+
+          (define openvpn-command
+                  #~(list (string-append #$openvpn "/sbin/openvpn")
+                                         "--writepid" #$pid-file
+					 "--config"
+					 #$(openvpn-config-file config role)))
+
+          (list (shepherd-service
+           (documentation (string-append "OpenVPN "
+					 (match role
+						('client "client")
+						('server "server"))
+					 "."))
+           (requirement '(networking syslogd))
+           (provision (match role
+			     ('client '(vpn-client-daemon))
+			     ('server '(vpn-server-daemon))))
+           (start #~(make-forkexec-constructor #$openvpn-command
+                                               #:pid-file #$pid-file))
+           (stop #~(make-kill-destructor))))))
+
+(define openvpn-client-service-type
+  (service-type (name 'openvpn-client)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          (get-openvpn-shepherd-service 'client))
+                       (service-extension activation-service-type
+                                          openvpn-activation)
+                       (service-extension account-service-type
+                                          (const %openvpn-accounts))))))
+
+(define openvpn-server-service-type
+  (service-type (name 'openvpn-server)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          (get-openvpn-shepherd-service 'server))
+                       (service-extension activation-service-type
+                                          openvpn-activation)
+                       (service-extension account-service-type
+                                          (const %openvpn-accounts))))))
+
+;;; vpn.scm ends here
-- 
2.10.1

Reply via email to