1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.openvpn;
8
9 inherit (pkgs) openvpn;
10
11 makeOpenVPNJob = cfg: name:
12 let
13
14 path = (getAttr "openvpn-${name}" config.systemd.services).path;
15
16 upScript = ''
17 #! /bin/sh
18 export PATH=${path}
19
20 # For convenience in client scripts, extract the remote domain
21 # name and name server.
22 for var in ''${!foreign_option_*}; do
23 x=(''${!var})
24 if [ "''${x[0]}" = dhcp-option ]; then
25 if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
26 elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
27 fi
28 fi
29 done
30
31 ${cfg.up}
32 ${optionalString cfg.updateResolvConf
33 "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
34 '';
35
36 downScript = ''
37 #! /bin/sh
38 export PATH=${path}
39 ${optionalString cfg.updateResolvConf
40 "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
41 ${cfg.down}
42 '';
43
44 configFile = pkgs.writeText "openvpn-config-${name}"
45 ''
46 errors-to-stderr
47 ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
48 ${cfg.config}
49 ${optionalString (cfg.up != "" || cfg.updateResolvConf)
50 "up ${pkgs.writeScript "openvpn-${name}-up" upScript}"}
51 ${optionalString (cfg.down != "" || cfg.updateResolvConf)
52 "down ${pkgs.writeScript "openvpn-${name}-down" downScript}"}
53 ${optionalString (cfg.authUserPass != null)
54 "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
55 ${cfg.authUserPass.username}
56 ${cfg.authUserPass.password}
57 ''}"}
58 '';
59
60 in {
61 description = "OpenVPN instance ‘${name}’";
62
63 wantedBy = optional cfg.autoStart "multi-user.target";
64 after = [ "network.target" ];
65
66 path = [ pkgs.iptables pkgs.iproute pkgs.nettools ];
67
68 serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --config ${configFile}";
69 serviceConfig.Restart = "always";
70 serviceConfig.Type = "notify";
71 };
72
73in
74
75{
76
77 ###### interface
78
79 options = {
80
81 services.openvpn.servers = mkOption {
82 default = {};
83
84 example = literalExample ''
85 {
86 server = {
87 config = '''
88 # Simplest server configuration: http://openvpn.net/index.php/documentation/miscellaneous/static-key-mini-howto.html.
89 # server :
90 dev tun
91 ifconfig 10.8.0.1 10.8.0.2
92 secret /root/static.key
93 ''';
94 up = "ip route add ...";
95 down = "ip route del ...";
96 };
97
98 client = {
99 config = '''
100 client
101 remote vpn.example.org
102 dev tun
103 proto tcp-client
104 port 8080
105 ca /root/.vpn/ca.crt
106 cert /root/.vpn/alice.crt
107 key /root/.vpn/alice.key
108 ''';
109 up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
110 down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
111 };
112 }
113 '';
114
115 description = ''
116 Each attribute of this option defines a systemd service that
117 runs an OpenVPN instance. These can be OpenVPN servers or
118 clients. The name of each systemd service is
119 <literal>openvpn-<replaceable>name</replaceable>.service</literal>,
120 where <replaceable>name</replaceable> is the corresponding
121 attribute name.
122 '';
123
124 type = with types; attrsOf (submodule {
125
126 options = {
127
128 config = mkOption {
129 type = types.lines;
130 description = ''
131 Configuration of this OpenVPN instance. See
132 <citerefentry><refentrytitle>openvpn</refentrytitle><manvolnum>8</manvolnum></citerefentry>
133 for details.
134 '';
135 };
136
137 up = mkOption {
138 default = "";
139 type = types.lines;
140 description = ''
141 Shell commands executed when the instance is starting.
142 '';
143 };
144
145 down = mkOption {
146 default = "";
147 type = types.lines;
148 description = ''
149 Shell commands executed when the instance is shutting down.
150 '';
151 };
152
153 autoStart = mkOption {
154 default = true;
155 type = types.bool;
156 description = "Whether this OpenVPN instance should be started automatically.";
157 };
158
159 updateResolvConf = mkOption {
160 default = false;
161 type = types.bool;
162 description = ''
163 Use the script from the update-resolv-conf package to automatically
164 update resolv.conf with the DNS information provided by openvpn. The
165 script will be run after the "up" commands and before the "down" commands.
166 '';
167 };
168
169 authUserPass = mkOption {
170 default = null;
171 description = ''
172 This option can be used to store the username / password credentials
173 with the "auth-user-pass" authentication method.
174
175 WARNING: Using this option will put the credentials WORLD-READABLE in the Nix store!
176 '';
177 type = types.nullOr (types.submodule {
178
179 options = {
180 username = mkOption {
181 description = "The username to store inside the credentials file.";
182 type = types.string;
183 };
184
185 password = mkOption {
186 description = "The password to store inside the credentials file.";
187 type = types.string;
188 };
189 };
190 });
191 };
192 };
193
194 });
195
196 };
197
198 };
199
200
201 ###### implementation
202
203 config = mkIf (cfg.servers != {}) {
204
205 systemd.services = listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers);
206
207 environment.systemPackages = [ openvpn ];
208
209 boot.kernelModules = [ "tun" ];
210
211 };
212
213}