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 --suppress-timestamps --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 To import an external config file, use the following definition:
136 <literal>config = "config /path/to/config.ovpn"</literal>
137 '';
138 };
139
140 up = mkOption {
141 default = "";
142 type = types.lines;
143 description = ''
144 Shell commands executed when the instance is starting.
145 '';
146 };
147
148 down = mkOption {
149 default = "";
150 type = types.lines;
151 description = ''
152 Shell commands executed when the instance is shutting down.
153 '';
154 };
155
156 autoStart = mkOption {
157 default = true;
158 type = types.bool;
159 description = "Whether this OpenVPN instance should be started automatically.";
160 };
161
162 updateResolvConf = mkOption {
163 default = false;
164 type = types.bool;
165 description = ''
166 Use the script from the update-resolv-conf package to automatically
167 update resolv.conf with the DNS information provided by openvpn. The
168 script will be run after the "up" commands and before the "down" commands.
169 '';
170 };
171
172 authUserPass = mkOption {
173 default = null;
174 description = ''
175 This option can be used to store the username / password credentials
176 with the "auth-user-pass" authentication method.
177
178 WARNING: Using this option will put the credentials WORLD-READABLE in the Nix store!
179 '';
180 type = types.nullOr (types.submodule {
181
182 options = {
183 username = mkOption {
184 description = "The username to store inside the credentials file.";
185 type = types.string;
186 };
187
188 password = mkOption {
189 description = "The password to store inside the credentials file.";
190 type = types.string;
191 };
192 };
193 });
194 };
195 };
196
197 });
198
199 };
200
201 };
202
203
204 ###### implementation
205
206 config = mkIf (cfg.servers != {}) {
207
208 systemd.services = listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers);
209
210 environment.systemPackages = [ openvpn ];
211
212 boot.kernelModules = [ "tun" ];
213
214 };
215
216}