diff mbox

workin on openvpn service

Message ID 20161019224153.7be605f1@lepiller.eu
State New
Headers show

Commit Message

Julien Lepiller Oct. 19, 2016, 8:41 p.m. UTC
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)?

Comments

Ludovic Courtès Oct. 26, 2016, 11:46 a.m. UTC | #1
Hello!

Julien Lepiller <tyreunom@lepiller.eu> skribis:

> 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).

Sounds useful!

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

You could use macros to simplify this.  In (gnu services dovecot) and
(gnu services cups), Andy added tools that allow you to describe typed
configuration parameters, and then generate a serialization procedure
that produces the right config file.

The goal is to extract this support code to remove duplication and
possibly use it elsewhere:

  https://lists.gnu.org/archive/html/guix-devel/2016-10/msg00490.html

So I’d suggest looking into it and see whether it would work for you
and/or what would be needed to make it work for you.

> 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?

I would suggest writing a separate validation procedure for
<openvpn-client-ccd> records, similar to the suggestion at the bottom of
<https://lists.gnu.org/archive/html/guix-devel/2016-10/msg00303.html>.

> Is this the good approach? Also, how would you create multiple ccd
> files for different clients in the same directory (preferably
> in /gnu/store)?

You can use ‘computed-file’ to create a directory containing the files.

> From 272939aad601f7a0c736449edcfcc64dffe0a370 Mon Sep 17 00:00:00 2001
> From: Julien Lepiller <julien@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

[...]

> +(define (get-openvpn-shepherd-service role)
> +  (lambda (config)
> +          (define pid-file
             ^
Should be aligned with the ‘a’ of ‘lambda’.

HTH!

Ludo’.
diff mbox

Patch

From 272939aad601f7a0c736449edcfcc64dffe0a370 Mon Sep 17 00:00:00 2001
From: Julien Lepiller <julien@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 <julien@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