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 = makeBinPath (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.iproute2 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 imports = [
77 (mkRemovedOptionModule [ "services" "openvpn" "enable" ] "")
78 ];
79
80 ###### interface
81
82 options = {
83
84 services.openvpn.servers = mkOption {
85 default = {};
86
87 example = literalExample ''
88 {
89 server = {
90 config = '''
91 # Simplest server configuration: https://community.openvpn.net/openvpn/wiki/StaticKeyMiniHowto
92 # server :
93 dev tun
94 ifconfig 10.8.0.1 10.8.0.2
95 secret /root/static.key
96 ''';
97 up = "ip route add ...";
98 down = "ip route del ...";
99 };
100
101 client = {
102 config = '''
103 client
104 remote vpn.example.org
105 dev tun
106 proto tcp-client
107 port 8080
108 ca /root/.vpn/ca.crt
109 cert /root/.vpn/alice.crt
110 key /root/.vpn/alice.key
111 ''';
112 up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
113 down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
114 };
115 }
116 '';
117
118 description = ''
119 Each attribute of this option defines a systemd service that
120 runs an OpenVPN instance. These can be OpenVPN servers or
121 clients. The name of each systemd service is
122 <literal>openvpn-<replaceable>name</replaceable>.service</literal>,
123 where <replaceable>name</replaceable> is the corresponding
124 attribute name.
125 '';
126
127 type = with types; attrsOf (submodule {
128
129 options = {
130
131 config = mkOption {
132 type = types.lines;
133 description = ''
134 Configuration of this OpenVPN instance. See
135 <citerefentry><refentrytitle>openvpn</refentrytitle><manvolnum>8</manvolnum></citerefentry>
136 for details.
137
138 To import an external config file, use the following definition:
139 <literal>config = "config /path/to/config.ovpn"</literal>
140 '';
141 };
142
143 up = mkOption {
144 default = "";
145 type = types.lines;
146 description = ''
147 Shell commands executed when the instance is starting.
148 '';
149 };
150
151 down = mkOption {
152 default = "";
153 type = types.lines;
154 description = ''
155 Shell commands executed when the instance is shutting down.
156 '';
157 };
158
159 autoStart = mkOption {
160 default = true;
161 type = types.bool;
162 description = "Whether this OpenVPN instance should be started automatically.";
163 };
164
165 updateResolvConf = mkOption {
166 default = false;
167 type = types.bool;
168 description = ''
169 Use the script from the update-resolv-conf package to automatically
170 update resolv.conf with the DNS information provided by openvpn. The
171 script will be run after the "up" commands and before the "down" commands.
172 '';
173 };
174
175 authUserPass = mkOption {
176 default = null;
177 description = ''
178 This option can be used to store the username / password credentials
179 with the "auth-user-pass" authentication method.
180
181 WARNING: Using this option will put the credentials WORLD-READABLE in the Nix store!
182 '';
183 type = types.nullOr (types.submodule {
184
185 options = {
186 username = mkOption {
187 description = "The username to store inside the credentials file.";
188 type = types.str;
189 };
190
191 password = mkOption {
192 description = "The password to store inside the credentials file.";
193 type = types.str;
194 };
195 };
196 });
197 };
198 };
199
200 });
201
202 };
203
204 };
205
206
207 ###### implementation
208
209 config = mkIf (cfg.servers != {}) {
210
211 systemd.services = listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers);
212
213 environment.systemPackages = [ openvpn ];
214
215 boot.kernelModules = [ "tun" ];
216
217 };
218
219}